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.
& m @ rint f x → fround-to-nearest-even (wasm f*.nearest) — libm rint honours the default mode.
& c @ nurl_rand_fill *u buf i n → iOS entropy (runtime helper; getrandom/urandom under the hood).
& c @ unlink s path → i32unlink(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 → vAppend a program argument (copied from a NUL-terminated host string).
@ interp_push_env * Interp it s str → vAppend an environment entry ("NAME=VALUE", copied).
@ interp_allow_gpu * Interp it → vEnable 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 → vGrant 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 → vRun the module's start-section function, if any — the final instantiation step, before any export is invoked.
@ exec_func * Interp it i fidx → vExecute 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 → vFlush every open dirty file — called on proc_exit and when start returns, so buffered writes are never lost to a missing fdclose.
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.
: Wc { ( Vec u ) buf i pos i len }@ wc_new ( Vec u ) buf → *Wc@ wc_free * Wc c → vWc 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 → iUnsigned LEB128 → i (NURL i is 64-bit, covers u32/u64).
@ wc_sleb * Wc c → iSigned LEB128 → i (sign-extended).
@ wc_skip * Wc c i n → vSkip n bytes.
@ wc_avail * Wc c → iBytes 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 → *ModuleDecode a whole module. On error, .ok is F and .err carries a message.
@ module_export_func * Module m s name → iFind an exported function index by name (-1 if absent).
@ module_func_type * Module m i fidx → sThe *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 → bStructural function-type equality (the call_indirect runtime check): same parameter and result valtypes, in order.
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.
@ 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 → iWASI command: run the module's _start with argv = [module, prog args…], the given preopened directories and environment entries.
@ main → i