Skip to content

vm/cranelift: lift grp + uniqby + srt-2arg + partition via tree-bridge (HOF dispatch PR 3b)#279

Merged
danieljohnmorris merged 3 commits into
mainfrom
fix/hof-3b-tree-bridge
May 15, 2026
Merged

vm/cranelift: lift grp + uniqby + srt-2arg + partition via tree-bridge (HOF dispatch PR 3b)#279
danieljohnmorris merged 3 commits into
mainfrom
fix/hof-3b-tree-bridge

Conversation

@danieljohnmorris
Copy link
Copy Markdown
Collaborator

Summary

Fourth in the HOF dispatch chain after PR 1 (#274, FnRef NaN-tagging), PR 2 (#277, map native), PR 3a (#278, flt/fld/flatmap native).

The remaining tree-only HOFs (grp, uniqby, partition, 2-arg srt) call back into user-defined functions per element, so the existing tree-bridge couldn't dispatch them: call_builtin_for_bridge constructs an empty Env. This PR plumbs the AST through to the bridge so it can register every Decl::Function before invoking the tree interpreter, and extends is_tree_bridge_eligible to cover the four HOFs.

Manifesto framing: every HOF that was previously tree-only now runs on every engine without a per-builtin native lift. Future PRs can graduate any specific HOF off the bridge with a native opcode (the existing PR 3a pattern) without changing observable behaviour.

Repro

Before:

$ ilo --run-vm 'grp f xs:L n>M t (L n);grp parity xs' f '[1,2,3,4]'
Compile error: undefined function: grp

After (matches --run-tree byte-for-byte):

$ ilo --run-vm 'parity n:n>t;str (mod n 2)
f xs:L n>M t (L n);grp parity xs' f '[1,2,3,4]'
{0: [2, 4]; 1: [1, 3]}

Same for uniqby, partition, and 2-arg srt on VM and Cranelift.

What is in the diff

  • interpreter: program-aware bridge entry for HOF dispatch — adds call_builtin_for_bridge_with_program(name, args, program) that registers user functions/tools into a fresh Env before dispatch.
  • vm: route grp/uniqby/srt-2arg/partition through tree-bridgeCompiledProgram now retains Arc<Program> (populated by vm::compile); VM::execute and with_active_registry publish it via a new ACTIVE_AST_PROGRAM TLS, cleared by an RAII guard that parallels the existing ACTIVE_REGISTRY/ACTIVE_FUNC_NAMES/ACTIVE_PROGRAM slots. OP_CALL_BUILTIN_TREE and jit_call_builtin_tree pick the program-aware bridge entry when the TLS is set. is_tree_bridge_eligible accepts (Grp, 2), (Uniqby, 2), (Partition, 2), (Srt, 2). Cranelift gets these for free through the existing jit_call_builtin_tree helper.
  • test + example: cross-engine coverage — new tests/regression_hof_3b.rs (13 cases, tree/VM/Cranelift), new examples/grp-basics.ilo and examples/srt-by-key.ilo, lifted vm/cranelift skips from examples/uniqby.ilo and examples/partition.ilo.

Test plan

  • cargo build --release --features cranelift clean
  • cargo test --release --features cranelift green across all 117 suites (2932 lib + 13 new regression + 1 examples_engines)
  • cargo clippy --release --features cranelift --all-targets -- -D warnings clean
  • cargo fmt clean
  • Tree/VM/Cranelift all produce identical output for the four new HOFs across the regression suite and the new example files
  • Unignored vm_grp_* and vm_srt_* dynamic-dispatch tests in src/vm/mod.rs pass
  • Re-ignored a handful of tests that need direct FnRef-variable calls (PR 3d) with an updated note

Follow-ups

  • PR 3c: closure-bind 3-arg ctx forms across all HOFs (the cb x y ctx shape) — unignores the 14 closure-bind HOF tests.
  • PR 3d: direct FnRef-variable calls (OP_CALL_DYN from Expr::Call when callee resolves to a FnRef-typed local).
  • PR 4: lift engine-skip from inline lambda examples once the lambda lift pass is in.
  • Cranelift's jit_call_builtin_tree still collapses bridge errors to Nil. Documented as a follow-up in its rustdoc; the new regression test only asserts the typed error on tree + VM.

The existing call_builtin_for_bridge constructs an empty Env, which works
for tree-only builtins that have no FnRef args (rgx, fmt, sleep, etc.)
but cannot dispatch user-fn callbacks. The next step in the HOF chain
(grp/uniqby/partition/srt-2arg) needs those callbacks to resolve, so add
a program-aware variant that registers every Decl::Function and
Decl::Tool into the Env before dispatching, mirroring the prefix of
run_with_env.
The HOFs that take a FnRef + list (grp, uniqby, partition, 2-arg srt)
can all run on the bridge once the bridge has access to the user-defined
functions in the source program. Three pieces:

- CompiledProgram retains an Arc<Program> (populated by vm::compile)
- VM::execute and with_active_registry publish it through an
  ACTIVE_AST_PROGRAM TLS, cleared by an RAII guard parallel to the
  existing ACTIVE_REGISTRY/ACTIVE_FUNC_NAMES/ACTIVE_PROGRAM slots
- is_tree_bridge_eligible accepts (Grp, 2), (Uniqby, 2), (Partition, 2),
  (Srt, 2). Cranelift gets them for free via the existing
  jit_call_builtin_tree helper, which also reads the new TLS

OP_CALL_BUILTIN_TREE and jit_call_builtin_tree now pick the
program-aware bridge entry when the TLS is set, falling back to the
no-program variant for the test-shell case where CompiledProgram is
constructed by hand without an AST.

Unignores the corresponding vm_grp_* and vm_srt_* tests in src/vm/mod.rs
(grp by string/numeric/bool/float key + empty list + key-returning-list
error path; 2-arg srt by length/numeric/text key + bool ordering +
wrong-arg). A handful of dynamic-dispatch tests that need direct
FnRef-variable calls (PR 3d) get re-ignored with an updated note.

Closure-bind 3-arg ctx forms still error here; they land in PR 3c.
…artition

tests/regression_hof_3b.rs: thirteen cases covering grp (by string,
numeric key, empty list), uniqby (keeps first per key, empty list),
partition (mixed, all-pass, all-fail), 2-arg srt (by abs, by length,
empty), chained uniqby + grp (catches stale-Env bugs across consecutive
bridge invocations), and a tree/VM error path for wrong list arg. Each
case runs on tree, VM, and Cranelift and asserts identical output.

examples/grp-basics.ilo and examples/srt-by-key.ilo: new docs-as-tests
covering the 2-arg HOF shapes for grp and srt. Both run on all three
engines via the bridge.

examples/uniqby.ilo and examples/partition.ilo: lift the vm/cranelift
engine-skip lines now that the bridge handles them. jit (LLVM) skip
stays in place pending the LLVM backend's own HOF dispatch work.

Cranelift's jit_call_builtin_tree still swallows bridge errors as Nil
(matching jit_rgxsub/jit_rd precedent); the error-path test asserts
tree + VM surface the typed runtime error and notes the Cranelift
follow-up.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

❌ Patch coverage is 84.50704% with 11 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 82.45% 10 Missing ⚠️
src/interpreter/mod.rs 92.85% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@danieljohnmorris danieljohnmorris merged commit 5fd4130 into main May 15, 2026
4 of 5 checks passed
@danieljohnmorris danieljohnmorris deleted the fix/hof-3b-tree-bridge branch May 15, 2026 13:35
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).
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