NURLNURL registrynurl-lang.org →

← wasmtime

wasmtime 0.6.0 API

interp.nu

packages/wasmtime/src/interp.nu — a small WebAssembly interpreter (pure NURL).

Executes wasm: i32/i64/f32/f64 const, locals, globals, the integer and float arithmetic/comparison/bitwise ops, all int↔float conversions, structured control flow (block / loop / if / else / br / br_if / return), drop / select, direct and indirect calls, and linear memory load/store. Each value occupies one 64-bit cell; i32 wraps + sign-extends to 32 bits, and floats are held as their IEEE-754 bit pattern (reinterpreted via std/floatbits for arithmetic). Imports (the WASI surface) are the remaining milestone — so this runs self-contained compute modules today.

Control flow uses an explicit control stack. Entering a block/if computes its matching end by a one-pass immediate-skipping scan; br to a loop re-enters at the loop start, to a block jumps past its end.

API

& m @ rint f x → f

round-to-nearest-even (wasm f*.nearest) — libm rint honours the default mode.

& c @ nurl_rand_fill *u buf i n → i

OS entropy (runtime helper; getrandom/urandom under the hood).

& c @ unlink s path → i32

unlink(2): path_unlink_file must NOT remove directories (remove(3) would).

& cuda @ cuInit i32 flags → i32

── CUDA driver + NVRTC (the GPU host-import bridge) ────────────── A wasm module built from a GPU-using NURL package (packages/gpu → onnx → objdet) imports these under module "env"; wasmtime resolves them to the real libcuda/libnvrtc here, marshalling guest linear memory ↔ host. nurl.sh auto-links libcuda/libnvrtc when these symbols appear, and links stub objects on a GPU-less host (so wasmtime always builds; the guest then just sees nonzero CUresult codes). Handle types match the portable i64 model in packages/gpu/src/cuda.nu.

& cuda @ cuDeviceGetCount *u count → i32

& cuda @ cuDeviceGet *u device i32 ordinal → i32

& cuda @ cuDeviceGetName *u name i32 len i32 dev → i32

& cuda @ cuCtxCreate *u pctx i32 flags i32 dev → i32

& cuda @ cuCtxDestroy i ctx → i32

& cuda @ cuCtxSynchronize → i32

& cuda @ cuModuleLoadData *u module *u image → i32

& cuda @ cuModuleUnload i module → i32

& cuda @ cuModuleGetFunction *u hfunc i hmod s name → i32

& cuda @ cuMemAlloc *u dptr i bytesize → i32

& cuda @ cuMemFree i dptr → i32

& cuda @ cuMemcpyHtoD i dst *u src i n → i32

& cuda @ cuMemcpyDtoH *u dst i src i n → i32

& cuda @ cuLaunchKernel i f i32 gx i32 gy i32 gz i32 bx i32 by i32 bz i32 sh i stream *u params i extra → i32

& nvrtc @ nvrtcCreateProgram *u prog s src s name i32 nh *u headers *u incs → i32

& nvrtc @ nvrtcCompileProgram i prog i32 nopt *u opts → i32

& nvrtc @ nvrtcGetPTXSize i prog *u sz → i32

& nvrtc @ nvrtcGetPTX i prog *u ptx → i32

& nvrtc @ nvrtcGetProgramLogSize i prog *u sz → i32

& nvrtc @ nvrtcGetProgramLog i prog *u log → i32

& nvrtc @ nvrtcDestroyProgram *u prog → i32

: Arg { ( Vec u ) bytes }

: Ctrl { i is_loop i start_pc i end_pc i height i arity }

: WFd { i kind ( Vec u ) data i pos ( Vec u ) host ( Vec u ) name b writable b dirty b append }

A WASI file descriptor. kind: 0 closed, 1 stdio, 2 preopened dir, 3 file, 4 opened (non-preopen) directory. For a file, data holds the contents, pos the read/write offset, host the on-disk path (for open/flush), and name (a dir) the guest-visible preopen name reported by fd_prestat_*. append = O_APPEND: every write lands at the end regardless of pos.

: Interp

: Interp {
    s mod  // *Module
    ( Vec i ) vs  // value stack
    ( Vec u ) mem  // linear memory (bytes)
    i mem_pages  // current size in 64 KiB pages
    ( Vec i ) globals  // mutable global values
    ( Vec i ) table  // runtime funcref table (mutable via table.set/grow/…)
    ( Vec i ) data_dropped  // 1 per data segment once dropped / active
    ( Vec i ) elem_dropped  // 1 per element segment once dropped / active
    ( Vec s ) argv  // *Arg — WASI program arguments (argv[0] = program)
    ( Vec s ) envp  // *Arg — WASI environment entries ("NAME=VALUE")
    ( Vec s ) fds  // *WFd — file-descriptor table (0/1/2 stdio, 3 preopen, …)
    b exited  // set after proc_exit
    i exit_code
    b trap
    ( Vec u ) trapmsg
    i pending_call  // callee set by call/call_indirect for the driver (-1 none)
    i max_depth  // frame-stack depth limit (trap when exceeded)
    i fuel  // remaining instruction budget (-1 = unlimited)
    b gpu_ok  // env/CUDA host imports enabled (opt-in; default off)
}

: Frame { i fidx ( Vec i ) locals ( Vec s ) ctrl i pos i end i code_start }

One activation record on the explicit call stack: the function, its locals, its control stack, and the instruction cursor [pos, end).

@ interp_new * Module m → *Interp

@ interp_free * Interp it → v

@ interp_push_arg * Interp it s str → v

Append a program argument (copied from a NUL-terminated host string).

@ interp_push_env * Interp it s str → v

Append an environment entry ("NAME=VALUE", copied).

@ interp_allow_gpu * Interp it → v

Enable the module "env" GPU/CUDA host-import bridge. Off by default: those imports hand the guest raw host pointers into linear memory and forward them to libcuda, so they are only safe for trusted compute — the embedder must opt in explicitly (the CLI does so with --allow-gpu).

@ interp_set_preopen * Interp it s host_path s guest_name → v

Grant the module one preopened host directory, visible to it as guest_name (the path it resolves opens against). Installed as fd 3.

@ interp_run_start * Interp it → v

Run the module's start-section function, if any — the final instantiation step, before any export is invoked.

@ exec_func * Interp it i fidx → v

Execute function fidx to completion on an EXPLICIT frame stack — guest recursion depth is bounded by max_depth, not by the host's native stack. Arguments are already on the value stack; results are left on top.

@ interp_flush * Interp it → v

Flush every open dirty file — called on proc_exit and when start returns, so buffered writes are never lost to a missing fdclose.


module.nu

packages/wasmtime/src/module.nu — WebAssembly binary decoder (pure NURL).

Decodes a wasm32 module's structure: the magic/version header and the sections this runtime understands (type, function, table, memory, global, export, element, code, data). Imports and the custom/start sections are skipped. The byte cursor + LEB128 readers here are reused by the interpreter (interp.nu) to walk instruction streams.

API

: Wc { ( Vec u ) buf i pos i len }

@ wc_new ( Vec u ) buf → *Wc

@ wc_free * Wc c → v

Wc does not own buf (the module bytes outlive it); free only the struct.

@ wc_eof * Wc c → b

@ wc_u8 * Wc c → i

@ wc_peek * Wc c → i

@ wc_uleb * Wc c → i

Unsigned LEB128 → i (NURL i is 64-bit, covers u32/u64).

@ wc_sleb * Wc c → i

Signed LEB128 → i (sign-extended).

@ wc_skip * Wc c i n → v

Skip n bytes.

@ wc_avail * Wc c → i

Bytes physically remaining in the input (never negative).

: FuncType { ( Vec i ) params ( Vec i ) results }

: WFunc { i typeidx ( Vec i ) locals i code_start i code_end }

A defined function: its type index, its declared (non-param) local valtypes expanded flat, and the [start,end) byte range of its instruction stream.

: WExport { ( Vec u ) name i kind i index }

: DataSeg { i offset ( Vec u ) bytes i passive }

A data segment. Active (passive=0): copied to memory at offset during instantiation. Passive (passive=1): source material for memory.init only.

: ElemSeg { ( Vec i ) funcs i passive }

An element segment: function indices (−1 = null ref). Active segments are applied to the table at decode; passive ones feed table.init; declared (and applied active) ones count as dropped at instantiation.

: NameBuf { ( Vec u ) bytes }

An owned byte buffer behind an opaque pointer (name-section entries).

: WImport { ( Vec u ) module ( Vec u ) field i typeidx }

An imported function (the only import kind this runtime resolves): module + field name select the host (WASI) implementation; typeidx gives its signature. Non-function imports are a decode error (nothing satisfies them).

: Module

: Module {
    ( Vec s ) types  // *FuncType
    ( Vec i ) functypes  // type index per defined function
    ( Vec s ) funcs  // *WFunc
    ( Vec s ) exports  // *WExport
    ( Vec u ) code  // the whole module byte image (functions index into it)
    i has_mem
    i mem_min  // initial pages (64 KiB each)
    i mem_max  // 0 if unbounded
    ( Vec s ) datas  // *DataSeg
    ( Vec i ) global_init  // initial value per global
    ( Vec i ) global_mut  // 1 if mutable
    i has_table
    ( Vec i ) table  // initial table image: function indices (−1 = null)
    i table_max  // declared table maximum (0 = none)
    ( Vec s ) elems  // *ElemSeg — element segments, in order
    ( Vec s ) imports  // *WImport (imported functions, in index order)
    i num_import_funcs  // imported funcs occupy func indices 0..n-1
    i start_func  // start-section function index (-1 = none)
    ( Vec i ) name_idx  // function indices with a "name"-section entry…
    ( Vec s ) name_str  // …and their names (*( Vec u )), parallel (sparse)
    b ok
    ( Vec u ) err
}

@ module_free * Module m → v

@ module_func_name * Module m i fidx → ( Vec u )

The name-section name of function fidx as a fresh byte vector (empty if unknown). Cold path — linear scan is fine (used only for trap backtraces).

@ module_decode ( Vec u ) bytes → *Module

Decode a whole module. On error, .ok is F and .err carries a message.

@ module_export_func * Module m s name → i

Find an exported function index by name (-1 if absent).

@ module_func_type * Module m i fidx → s

The *FuncType of any function index — imported (low indices) or defined — as an opaque pointer; #s 0 if out of range.

@ functype_eq * FuncType a * FuncType b → b

Structural function-type equality (the call_indirect runtime check): same parameter and result valtypes, in order.


main.nu

packages/wasmtime/src/main.nu — wasmtime: a WebAssembly runtime in pure NURL.

wasmtime run --invoke <export> <module.wasm> [int args…]

Loads a wasm module, invokes an exported function with integer arguments, and prints the integer result. This is the foundation — the integer interpreter core (module.nu + interp.nu). Linear memory, floats, and the WASI surface (incl. --dir) build on top in later milestones.

API

@ usage → v

@ run_invoke s export s path i first_arg i argc i allow_gpu → i

@ run_command s path i prog_start i argc ( Vec String ) dirs ( Vec String ) envs i fuel i allow_gpu → i

WASI command: run the module's _start with argv = [module, prog args…], the given preopened directories and environment entries.

@ main → i