Skip to content

aowli — the nimony interpreter

A standalone interpreter for typed nimony: it executes the compiler's post-semcheck typed AIF (.s.aif) — the exact artifact the native backend consumes — on a real runtime substrate (not a per-shape stdlib re-impl). Two independent engines run it and agree byte-for-byte.

The source above is private. A prebuilt, binary-only distribution — aowli-release (v0.3.5, obfuscated + licence-gated + stripped) — carries aowli-interp/aowli-dbg, which run typed AIF with no build step and are what the aowlcode plugin's trace/debug tools drive. Builds come from the store.


Two engines, one output

EngineBinaryHowRole
Tree-walkeraowli-interpWalks the typed AIF directlyCorrectness oracle — simple, source-line accurate
Bytecode VMaowli-vmCompiles AIF into a register/stack instruction chunk, then executes itSpeed path, held honest against the tree-walker

Both consume nimony's post-semcheck .s.aif — no separate parser, no separate type system. See Engines for the shared value layer and the differential harness that keeps the two honest.

Runtime

Six primitive layers the ordinary stdlib runs on — not one intercept per proc:

LayerProvides
Flat memorybyte-addressable load/store under the value tree: cast, copyMem/zeroMem/equalMem, alloc, UncheckedArray, {.union.} — SHA256/CRC/binary codecs run
OS / fdfd table + open/read/write/close/lseek/pipe, getEnv/putEnv — real files, hermetic in-proc env
FinalizationARC =destroy/=copy/=sink/=wasMoved in reverse-decl order; ref refcount + assertRc; RAII close-on-scope-exit
Loud dispatchevery unported foreign leaf fails named — no silent nil (one explicit no-op allowlist)
AsyncFuture[T]/await on a deterministic virtual-clock event loop
Threadscooperative run-to-completion scheduler over rawthreads / threadpool / || parfor

Flat memory / OS / finalization / loud-dispatch hold on both engines; async + threads are tree-walker-only (the VM has no coroutine frame model).

Runtime layer — one spine for every boundary crossing

Those layers used to be scattered — host natives here, flat memory there, syscalls somewhere else, a miss policy hardcoded per call site. They're now unified into one spine with three pieces:

PieceWhat it is
Provider registrywho services a crossing: interpret · host-native · syscall · hybrid-native
Codechow a value crosses: identity · flat C-ABI · JS value
Policywhat happens on a miss — never silently wrong: an unsupported crossing either fails loud or falls back to interpret, never returns a bogus value

The point is that there's no fourth outcome. Every foreign leaf is either serviced by a registered provider or refused by name; the interpreter never guesses.

Hybrid mode — interpret one file, run the rest native

Interpret only the file(s) you name; run every other module as natively-compiled code at full speed. aowli auto-generates C-callable shims for the calls that cross the boundary, marshals the arguments across using aowlabi's layout — scalars, POD objects and tuples, strings, seqs — and dispatches at the call site. The native side is byte-identical to a fully-native build; anything that can't cross honestly (refs, closures) falls back to interpret, so a hybrid run is never wrong, only sometimes slower.

sh
aowli --hybrid --interpret:mymod prog

The motivating use: debug one file slowly, with full frame-level observability, while its libraries run at native speed instead of being walked. The layout that keeps the two sides in agreement lives in aowlabi.

aowlidbg — debug without instrumenting the source

aowli-dbg adds batch breakpoints (--break:LINE, --break-func:NAME) that dump every hit's frame locals in one non-interactive pass, plus --trace/--trace-depth/--trace-profile for the call tree. See Debugging for the flag reference, and Debugging a real bug for a full session that found and fixed an actual off-by-one in a real nimony library using nothing but frame captures.

Map

PageCovers
EnginesTree-walker vs VM, the shared value/primitive/IO layer, .s.aif input, differential testing, corpus parity.
Debuggingaowlidbg reference: --break vs --break-func, --trace vs --trace-depth vs --trace-profile, when to use which.
Debugging a real bugCase study — a real off-by-one in aoughwl/css, found via --break-func frame captures, no print statements.
aowli-releasePrebuilt binaries: hardening, distribution, usage.

Status

Complete runtime across all six layers (single-thread + async + cooperative threads; real-parallelism timing is the native backend's job, not an interpreter's). Corpus parity — oracle nimony c -r, byte-identical stdout + exit:

  • Tree-walker: 432 / 469 runnable (92%) — and zero in-scope cases that run but silently return a wrong answer.
  • Both engines lockstep: 358 PASS-BOTH. The gap is VM-backend-only (async/threads are tree-walker-only) plus enumerated leaf ports — all fail loudly, all in excluded tests.
  • Non-runnable = negative .msgs tests + nimony-frontend rejects, not runtime holes.

Real end-to-end proof: the MDN CSS validator (css) runs byte-identical to native on both engines.

aoughwl — self-hosted platform for things n stuff. Contact / Support on Discord for access to the private backends.