Skip to content

JIT nil-sweep batch 4: text + format helpers#287

Merged
danieljohnmorris merged 3 commits into
mainfrom
fix/jit-nil-sweep-batch4
May 15, 2026
Merged

JIT nil-sweep batch 4: text + format helpers#287
danieljohnmorris merged 3 commits into
mainfrom
fix/jit-nil-sweep-batch4

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

Batch 4 of the JIT-helper permissive-nil sweep, picking up Group B from where #282 (batch 3) left off: the text helpers and fmt2.

Twelve helpers (jit_fmt2, jit_trm, jit_upr, jit_lwr, jit_cap, jit_padl, jit_padr, jit_ord, jit_chr, jit_chars, jit_unq, jit_frq) all silently returned TAG_NIL on type-error, empty-string, invalid-codepoint, and non-finite-element paths where tree and VM both raise an ILO-R009 runtime error. Each helper now carries a packed span_bits immediate and routes its failure paths through jit_set_runtime_error_with_span (the TLS primitive from #254), so the JIT raises the same diagnostic as tree/VM with a caret matching the source span.

(jit_len / jit_str / jit_num from the original batch-4 plan were already routed in batch 3 and intentionally not in scope here.)

Repro before/after

$ ilo 'f>t;trm 1' --run-cranelift f   # before: prints "nil", exits 0
$ ilo 'f>t;trm 1' --run-cranelift f   # after:  ILO-R009 trm requires text, exits 1

$ ilo 'f>n;ord ""' --run-cranelift f  # before: prints "nil", exits 0
$ ilo 'f>n;ord ""' --run-cranelift f  # after:  ILO-R009 ord requires a non-empty string, exits 1

What's in the diff

  1. route text + format JIT helpers through JIT_RUNTIME_ERROR — helper bodies in src/vm/mod.rs grow a span_bits parameter and route every error path that previously fell through to TAG_NIL via jit_set_runtime_error_with_span. Both helper registries (the AOT one in compile_cranelift.rs and the in-process JIT one in jit_cranelift.rs) bump the declared arity and pack the source span at every call site. The second registry is the load-bearing bit: without it the JIT would call the helper with one fewer arg and read garbage from an uninit register on the error path.

  2. add cross-engine regression suite for batch-4 helpers — new tests/regression_jit_nil_sweep_batch4.rs pins happy-path output of every batch-4 helper across tree, VM, and Cranelift. Per-helper error-path coverage lives next to the helpers as unit tests in src/vm/mod.rs (driving them directly, since the verifier rejects mixed-type programs that would surface these errors via the CLI). tests/regression_ord_chr.rs is consolidated: the previous test that pinned silent-nil-on-empty for Cranelift while tree/VM errored is folded into a single ord_empty_string_errors_on_every_engine now that all three engines agree.

  3. add example pinning batch-4 helper happy paths through examples_enginesexamples/text-helpers-jit-parity.ilo exercises every helper through the existing tests/examples_engines.rs harness, which runs each -- run: / -- out: pair through every available engine. Doubles as in-context learning material for future agents.

Test plan

  • cargo build --release --features cranelift
  • cargo test --release --features cranelift — 123 test binaries, 0 failures
  • cargo fmt --all -- --check
  • cargo clippy --release --features cranelift --all-targets -- -D warnings
  • New cross-engine regression suite (14 tests) passes on tree, VM, and Cranelift
  • New unit tests for each helper's error paths (22 tests) pass
  • examples/text-helpers-jit-parity.ilo runs identically on every engine via examples_engines.rs

Follow-ups

Group B of the JIT nil-sweep: jit_fmt2, jit_trm, jit_upr, jit_lwr,
jit_cap, jit_padl, jit_padr, jit_ord, jit_chr, jit_chars, jit_unq,
jit_frq. Each helper grows a packed span_bits parameter and routes its
type-error paths (and the empty-string / invalid-codepoint / non-finite
element edge cases for ord/chr/frq) through jit_set_runtime_error_with_span
so the JIT raises an ILO-R009-shaped runtime error matching tree and VM.

Both helper registries (AOT in compile_cranelift.rs and in-process JIT
in jit_cranelift.rs) bump the arity to match the new helper signatures
and pack the source span at every call site. Without the second
registry the JIT would call the helper with one fewer argument and read
a garbage span_bits from an uninitialised register, which only matters
on the error path but is still UB.

The jit_fmt2 digits-clamping branches (non-finite / negative / >20)
stay correct-by-design: they coerce to 0 / 20 to mirror tree, not
error. Likewise jit_chr's char::from_u32 returning None now errors
rather than silently nil-ing, again matching tree.
Pins the happy-path output of jit_fmt2, jit_trm, jit_upr, jit_lwr,
jit_cap, jit_padl, jit_padr, jit_ord, jit_chr, jit_chars, jit_unq, and
jit_frq across tree, VM, and Cranelift JIT. The verifier rejects
programs that statically mix types so most of the error paths are not
reachable from a single CLI program; per-helper error-path coverage
already lives as unit tests next to the helpers in src/vm/mod.rs.

The padl/padr happy-path assertions use a sibling helper that strips
just the trailing newline, since the default check_stdout would
trim() the very pad whitespace we want to verify.

Also updates regression_ord_chr.rs: the existing test pinned the
silent-nil behaviour on empty input for Cranelift specifically while
tree and VM errored. With the JIT now joining tree/VM, that split goes
away. The test is consolidated into ord_empty_string_errors_on_every_engine
and the obsolete cranelift-nil pin is removed.
A single-file ilo example that exercises trm, upr, lwr, cap, padl,
padr, ord, chr, chars, unq, fmt2, and unq-on-list. The harness in
tests/examples_engines.rs runs every -- run: / -- out: pair through
every available engine, so dropping the file here gives us a
higher-level regression test on top of the unit + integration tests.

It also serves as an in-context learning example for agents: a future
agent reading this file sees the now-correct behaviour without having
to chase the helper through three different engines.
@danieljohnmorris danieljohnmorris force-pushed the fix/jit-nil-sweep-batch4 branch from 6f8b8a4 to 8ed1be8 Compare May 15, 2026 16:44
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

❌ Patch coverage is 79.81651% with 66 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/vm/mod.rs 83.11% 39 Missing ⚠️
src/vm/compile_cranelift.rs 43.75% 27 Missing ⚠️

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris merged commit 959c53d into main May 15, 2026
4 of 5 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/jit-nil-sweep-batch4 branch May 15, 2026 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant