NURLNURL registrynurl-lang.org →

← wasmbuilder

wasmbuilder 0.1.3 API

build.nu

packages/wasmbuilder/src/build.nu — the wasm build driver (library API).

The pipeline, end to end and fully local:

.nu ──nurlc──▶ LLVM IR ──wb_prepare_ir_for_wasi──▶ wasm32 IR ──[zig] cc --target=wasm32-wasi + runtime.wasm.o──▶ .wasm

Link flags mirror nurlapi's production /build_wasm handler: -Wl,--no-gc-sections NURL closures store function-table indices; gc-sections renumbers the table → call_indirect traps at scale (proven on nurlc.wasm >150 fns) (nurlapi's -Wl,--allow-undefined is replaced by per-declare "wasm-import-module"="env" IR attributes — zig cc's driver rejects the linker flag; see wasi_ir.nu. Same semantics: undefined symbols become wasm imports the host runtime resolves.)

Library entry points (embed these — no subprocess, no HTTP): ( wb_build_file nu_path out_wasm opts ) → !v String ( wb_build_source source filename opts ) → !( Vec u ) String

Other packages import src/build.nu (which pulls wasi_ir.nu + toolchain.nu); the CLI in main.nu is a thin wrapper over the same calls.

API

: WbOpts

: WbOpts {
    s opt  // clang opt level: `-O0`..`-O3`, `-Oz` (nurlapi uses -O1)
    b host_imports  // pass --ffi-host-imports (auto-dropped on old nurlc)
    b asyncify  // wasm-opt asyncify wrap for canvas programs (needs binaryen)
    b keep_ll  // leave the rewritten .ll next to the .wasm
    b quiet  // suppress progress lines on stderr
    s extra_obj  // extra object(s), space-separated, linked in (`` = none) — e.g.
    // a generated kernels_static.o so `& c` symbols resolve
    // statically instead of becoming env imports
    s extra_cflags  // extra compile/link flags, space-separated (`` = none)
    // — e.g. `-msimd128` for wasm SIMD
    s asyncify_imports  // comma-separated async import names (`` = none);
    // e.g. `env.wgpu_download` for the WebGPU backend's async readback
}

Build options. opt is a BORROWED view (literal or string_data of a caller-owned String) — WbOpts is passed by value, so it must not own heap fields.

@ string_split_borrow s flags → ( Vec String )

Split a space-separated flag string into owned Strings (caller frees). NOTE: the Vec Strings LEAK by design at the single link call site (a handful of tiny strings once per build) to keep the argv s views alive through process_run.

@ wb_opts_default → WbOpts

@ wb_build_file s nu_path s out_wasm WbOpts opts → !v String

Compile one .nu file to out_wasm. The heavy lifting shared by the CLI and wb_build_source.

@ wb_build_source s source s filename WbOpts opts → !( Vec u ) String

Compile NURL source (a string) to wasm bytes — the embedding API swarm-mcp's kernel path uses. Single-file sources only (imports resolve against the installed stdlib; a multi-file package should go through wb_build_file on its entry point instead).


toolchain.nu

packages/wasmbuilder/src/toolchain.nu — locate (or provision) everything a local wasm32-wasi build needs, using only what the installed NURL toolchain already ships.

The wasm pipeline needs three things beyond nurlc itself:

  1. a compiler+linker that lowers LLVM IR to a wasm32-wasi module.

The toolchain's bundled zig cc (at $NURL_HOME/zig/zig, preferred by nurl.sh for native builds too) does this out of the box — zig carries its own wasi-libc and wasm-ld, so NO wasi-sdk is needed.

  1. the NURL runtime compiled for wasm32 (runtime.wasm.o). The installed

stdlib ships runtime.c, so we compile it on first use and cache the object keyed by the SOURCE HASH — a toolchain upgrade that changes runtime.c automatically invalidates the cache. No version skew.

  1. (optional) canvas/audio host-import objects, same on-demand scheme.

Resolution order for the wasm compiler: $NURL_ZIG → $NURL_HOME/zig/zig → zig on PATH → $WASI_CLANG (a wasi-sdk clang, for setups that already have one) → download a pinned, sha256-verified zig release into $NURL_HOME/zig (opt out with NURL_WASM_NO_DOWNLOAD=1).

Everything is namespaced wb_/__wb_ — NURL has one flat function namespace and this module is meant to be imported by other packages (swarm-mcp, nurl-mcp).

API

@ wb_is_windows → b

@ wb_nurl_home → String

$NURL_HOME, defaulting to <home>/.nurl — the same layout tools/install-toolchain.sh and get-nurl.{sh,ps1} produce.

@ wb_stdlib_dir → String

The directory holding the stdlib C sources (runtime.c & friends). $NURL_STDLIB is the toolchain PREFIX (import strings like stdlib/core/string.nu resolve against it), so the C sources live at $NURL_STDLIB/stdlib/. Fall back to $NURL_HOME/stdlib for direct runs.

@ wb_find_nurlc → !String String

Locate nurlc: $NURLC → $NURL_HOME/bin/nurlc → nurlc on PATH. The $NURL_HOME/bin shim is preferred over PATH because it exports NURL_STDLIB, keeping stdlib import resolution correct even when wasmbuilder itself was started without the shims.

: WbCompiler

: WbCompiler {
    String cmd
    b is_zig
}

WbCompiler.cmd is the executable; is_zig=T means invoke as cmd cc … (zig's clang driver), F means a wasi-sdk clang invoked as cmd ….

@ wb_compiler_free WbCompiler c → v

@ wb_find_compiler b allow_download → !WbCompiler String

@ wb_cache_dir → String

Cache root: $NURL_HOME/build/wasmbuilder (created on demand).

@ wb_ensure_wasm_obj WbCompiler cc s src_c → !String String

Compile <stdlib>/<src_c> for wasm32-wasi and cache the object keyed by the source's content hash: runtime.c changes with the toolchain, so an upgrade automatically produces a fresh object that matches it. Returns the cached object path.


main.nu

wasmbuilder — compile NURL to wasm32-wasi locally, with zero setup beyond the installed toolchain.

wasmbuilder program.nu # → program.wasm wasmbuilder program.nu -o out/app.wasm # explicit output wasmbuilder program.nu -O z # size-optimised link wasmbuilder program.nu --emit-ll # keep the rewritten .ll too wasmbuilder --doctor # explain what is resolved

Run the result with any wasm runtime — the reference wasmtime, or the pure-NURL one: nurlpkg install wasmtime && wt run program.wasm.

The first build on a machine without a bundled zig downloads a pinned, sha256-verified zig 0.13.0 into $NURL_HOME/zig (set NURL_WASM_NO_DOWNLOAD=1 to forbid that and fail instead).

API

@ main → i


wasi_ir.nu

packages/wasmbuilder/src/wasi_ir.nu — LLVM-IR → wasm32-wasi rewriter.

nurlc emits IR targeting the host (x86_64/arm64, 64-bit pointers, glibc FFI declares). This module retargets that IR for wasm32-wasi:

wasm32 ABI uses i32 (malloc/strlen/memcpy/… — trunc/zext wrappers)

so the stdlib's unconditional declares link; the stubs return error sentinels and are unreachable behind posix_const runtime gates

itself EMITS IR (nurlc.wasm!) doesn't get its data corrupted

Extracted verbatim from nurlapi/main.nu (the /build_wasm handler), which is the production-proven pipeline behind play.nurl-lang.org — only the function names are prefixed (wb_/__wb_) for library use.

API

@ wb_prepare_ir_for_wasi String ir → String