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

aowli reproduces 100% of the runnable nimony test corpus byte-for-byte on both engines, and runs a real pure-nimony program end-to-end — the MDN CSS validator (css) — byte-identical to native.

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.