fix(aot): strip top-level ~/^ wrappers in compiled binaries#281
Merged
Conversation
Mirrors print_value's top-level Result-wrapper treatment used by the in-process runners after PR #275. A top-level Value::Ok(inner) prints inner bare on stdout (exit 0); Value::Err goes to stderr with exit 1; anything else is Display on stdout, exit 0. Returns the exit code so generate_main can route it out of main(). The in-program prnt builtin keeps jit_prt (which always shows the wrapper) because those callers are inside a program and genuinely want to see ~v / ^e.
generate_main was calling jit_prt directly on the user function's return value, which always prints with the ~/^ wrapper visible and ignores Value::Err for the process exit code. That diverged from the in-process runners after PR #275 split top-level Result handling. Switch to the new jit_prt_main_result helper and truncate its u64 return to i32 for main()'s exit code. Result: AOT binaries now match tree, VM, and Cranelift JIT byte-for-byte on stdout, stderr, and exit code.
Compiles three Result-shaped programs to real AOT binaries and compares their stdout, stderr, and exit code byte-for-byte against tree, VM, and Cranelift JIT in-process. Cases: ~"hello" → stdout hello exit 0, ^"err" → stderr ^err exit 1, bare 42 → stdout 42 exit 0, plus a number-typed ~7 variant so a future regression that strips only ~"text" doesn't slip through. Gated on the cranelift feature because both AOT compile and the --run-cranelift baseline require it. The example pins the happy path as an in-context learning artefact for agents. The examples harness only drives the in-process runners, so the AOT contract lives in the dedicated regression test.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
6 tasks
danieljohnmorris
added a commit
that referenced
this pull request
May 15, 2026
Group C of the JIT-helper permissive-nil sweep (batches 1/2/3/4 landed in PRs #264, #259, #282, #281). Helpers in this batch: jit_spl, jit_cat, jit_has, jit_range, jit_window, jit_zip, jit_chunks, jit_enumerate, jit_setunion, jit_setinter, jit_setdiff, jit_rev, jit_srt, jit_rsrt, jit_cumsum. Before: every helper above silently returned TAG_NIL on the type-error path (TAG_FALSE for the two jit_has type-mismatch arms), diverging from the tree walker and the bytecode VM, which both raise VmError::Type with a specific message. Agents hitting srt on a mixed-type list, or cat on a list of numbers, got nil where they expected an error to retry off. After: each helper signals via jit_set_runtime_error_with_span using the same wording the VM dispatcher uses ("spl requires two strings", "cat: list items must be text", "setop: elements must be text, number, or bool", "srt: list must contain all numbers or all text", etc.), so the JIT entry point synthesises a VmRuntimeError that renders with a caret matching tree/VM diagnostics. Mechanics: - extern "C" signatures grow a span_bits: u64 immediate (packed (start << 32) | end). 2-arg helpers become 3-arg; 1-arg helpers become 2-arg. - jit_srt and jit_rsrt now distinguish mixed-list (raises "...must contain all numbers or all text") from non-list/non-text (raises "...requires a list or text") instead of falling through to a single nil sentinel. - jit_setop_impl threads span_bits and surfaces both the per-arg "setop arg N requires a list" messages and the per-element "setop: elements must be text, number, or bool" message that the VM dispatcher uses. Unit tests inside vm::tests::jit_helpers updated: the *_returns_nil and *_returns_false assertions now assert the runtime-error cell is set and carries the expected VmError variant. New mixed-list test for jit_srt pins the previously-silent diverging path.
5 tasks
danieljohnmorris
added a commit
that referenced
this pull request
May 15, 2026
- slc / take / drop accept negative indices counting from end (bounds clamp), matching at xs i. Closes the quant-trader fencepost and the slc xs -np 1 np ergonomics gap (#266). - Map keys are typed: text or integer. mset m 7 v and mget m 7 work directly, no str conversion. Int(1) and Text("1") are distinct. Float keys floor to i64; jdmp stringifies numeric keys for JSON (#267). - Add map / flt / fld to the builtin reference. All HOFs (map, flt, fld, srt, grp, uniqby, partition, flatmap) now work cross-engine on tree, VM, Cranelift JIT, and AOT (#274 #277 #278 #279 #280 #283). - New Inline lambdas subsection: Phase 1 literals are cross-engine, Phase 2 closure capture is tree-only with automatic fallthrough surfacing ILO-R012 on VM and Cranelift (#265 #284). - AOT-compiled binaries from ilo compile now strip the top-level ~/^ wrapper byte-for-byte the same as in-process runners (#281).
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR #275 made the in-process runners (tree, VM, Cranelift JIT) treat top-level Result returns specially:
~vfrom main printsvbare on stdout (exit 0),^eprints^eon stderr (exit 1). The AOT path (ilo compile main.ilo -o ./main && ./main) was left calling the in-programjit_prthelper fromgenerate_main, so AOT binaries kept printing the visible wrapper and always exited 0, even for^e. That's a real cost for shell callers piping a compiled ilo program: they have to strip a leading~and have no way to detect failure without parsing stdout.This PR adds a dedicated
jit_prt_main_resulthelper that mirrorsprint_value's top-level treatment and returns the desired exit code;generate_maincalls it and truncates the result to i32 formain(). The in-programprntbuiltin keeps usingjit_prt(which always shows the wrapper), because those callers are inside a program and genuinely want to see~v/^e.Repro before/after
And
~"hello"now prints barehelloon stdout from an AOT binary, same as the three in-process runners.What's in the diff
aot runtime: add jit_prt_main_result helper(src/vm/mod.rs,src/vm/jit_cranelift.rs) — new extern "C" helper that dispatches onTAG_OK/TAG_ERRand returns the exit code packed in u64; registered in the JIT symbol table so the helper resolves at link time for AOT.cranelift aot: route generate_main result through jit_prt_main_result(src/vm/compile_cranelift.rs) — addsprt_main: FuncIdtoHelperFuncs, declares the import, and switchesgenerate_mainto call it and ireduce the u64 result into the i32mainreturn.test + example: cross-engine AOT wrapper-strip coverage(tests/regression_aot_wrapper_strip.rs,examples/aot-wrapper-strip.ilo) — compiles each case to a real AOT binary and asserts byte-for-byte parity against tree, VM, and Cranelift JIT in-process; covers~"hello",^"err", bare42, and~7.Test plan
cargo build --release --features craneliftregenerateslibilo.awith the new helpercargo test --release --features cranelift --test regression_aot_wrapper_strip— 4 passedcargo test --release --features cranelift— all 118 test binaries pass, no failurescargo fmt -- --checkcleancargo clippy --release --features cranelift --all-targets -- -D warningscleanFollow-ups
numand the program printsnilinstead of the parsed number (visible if you AOT-compileexamples/main-err-exit-code.iloand run./bin 42). Unrelated to this fix; the in-process runners get it right. Will log it separately in the assessment doc.