Skip to content

Engines — tree-walker vs VM


Both engines consume the same input and must agree to the byte; they are two implementations of one semantics, not two features.

Input & value layer

InputThe compiler's post-semcheck typed AIF (.s.aif) — the exact artifact the native backend consumes. No separate parser, no separate type system.
seq/string/TableLibrary types built on raw alloc in native nimony. aowli intercepts the procs that implement them as "natives" and substitutes its own boxed value model (seq, string, array, set, object) instead of running the pointer code.
MemoryThe interpreter never touches raw memory — every aggregate is a boxed value the engines share.
I/OShared primitive layer for stdout/stdin so both engines produce identical program output, not just identical control flow.

The two engines

EngineBinaryMechanismRole
Tree-walkerbin/aowli-interpWalks the typed AIF directly, node by nodeCorrectness oracle: simple, source-line accurate, easy to reason about
Bytecode VMbin/aowli-vmCompiles the AIF into a register/stack instruction chunk, then executes that chunkSpeed path; already supports dynamic method dispatch

Work in progress: retargeting the VM onto a "partial-hexer" lowering to gain custom iterators, closures, and exceptions.

Differential testing

tests/crosscheck.sh runs both aowli engines and the native nimony compiler on the same program, then classifies the result:

VerdictMeaning
AGREE-PASSBoth engines match native. Everything is correct.
AGREE-FAILThe engines agree with each other but not with native — a shared gap in aowli.
DIVERGEThe two engines disagree with each other — a real bug in one of them.

Splitting "the engines disagree" from "the engines agree but miss native" catches a bug class a native-only harness hides: if a single interpreter is wrong, there is nothing to compare it against.

Corpus parity

As of aowli v0.3.0 (correctness-complete), both engines agree with each other and with native nimony across a 423-program differential corpus, with zero in-scope divergence. aowli also runs a real pure-nimony program end-to-end — the MDN CSS validator (css) — byte-identical to native.

The one boundary the pure value model can't cross by itself is literal-C: {.emit.} and C FFI have no C to execute inside the value world. That's hybrid-native territory — interpret most modules, run selected ones as native code through the real C toolchain as a shared object via the provider layer.

As of 2026-07-25 hybrid-native runs real foreign C: a header-backed {.importc.} proc is offloaded to a compiled shim (--app:lib), dlopen'd, and called for real, its result marshaled back into the interpreter byte-identical to native. This is interpreter-development progress (not part of the public v0.3.0 binary). The remaining piece — top-level {.emit.} / importc-var in the main module (the shape configtest2 uses) — is the last design item, not a correctness gap in the interpreter.

The run rung — a run is an AIF

--emit-run (env AIFI_EMIT_RUN=PATH) serializes an execution back into AIF: a run rung token stream recording every binding, loop iteration, and value the program produced, each atom carrying an (at …) back-pointer to the .s.aif node it evaluated.

source AIF (.p.aif) → typed AIF (.s.aif) → the run (run rung)

The value walker underneath walks each runtime value off its cell/object structure rather than stringifying it — aggregates keep their real fields (nimony's $ renders every object as the dead string "(object)") and ref/ptr identity is deduped, so sharing is explicit and cycles terminate. Emission is gated behind an off-by-default flag; a normal run's stdout stays byte-identical. The browser playground surfaces this in its Run tab, alongside the Parsed (.p.aif) and Typed (.s.aif) rungs.

Tracing

--trace / --trace-full renders the whole call tree with arguments and return values at each node — see Debugging for the full flag reference and the aowlcode plugin surface.

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