Section 07: Salsa Integration & Transition
Status: Not Started
Goal: Make the bytecode VM actually usable in the Ori compiler. Sections 04-05 build the VM in isolation; this section wires it into the real runtime entry points so ori run and the interpreter-backed ori test path can use it, while ori check and type-driven const-eval continue to rely on the preserved tree-walker where appropriate. Without this section, the VM is a standalone module that can’t run programs through the compiler’s real execution surfaces.
Context: Ori has two runtime-facing interpreter entry points today, and both must be integrated:
ori runusesevaluated()/run_evaluation()incompiler/oric/src/query/mod.rsori testusesTestRunner::run_file_with_interner()incompiler/oric/src/test/runner/mod.rs, which builds anEvaluatordirectly and callsload_module(...)- Import/module wiring lives in
Evaluator::load_module()(compiler/oric/src/eval/evaluator/module_loading.rs), not only at the top-level query boundary
The bytecode VM must plug into this same pipeline. The tree-walker cannot simply be deleted because:
ConstEvalmode is used by the type checker for compile-time evaluation of$nameconstants and#cfgconditions. It needs budget limiting, no I/O, and deterministic behavior. The VM can serve this role, but only after the VM supports budget tracking.TestRunmode is part of the evaluator contract for test execution. The VM must preserve the runtime semantics needed by the test runner, but the runner itself still owns test enumeration, filtering, and pass/fail bookkeeping.PatternExecutortrait —InterpreterimplementsPatternExecutorfromori_patterns, used for pattern system integration. The VM must either implement this trait or the pattern system must be updated.
Reference implementations:
- Rust
const fnevaluation uses MIR interpreter (separate from codegen). Tree-walker is the const-eval engine; LLVM is the runtime engine. - Zig
comptimeuses the same Sema interpreter for both compile-time and runtime, with a mode flag. - CPython caches compiled
.pycbytecode; thecompile()step is separate fromexec().
Depends on: Section 05 (working bytecode VM).
07.1 Salsa Query Integration
File(s): compiler/oric/src/query/mod.rs, compiler/oric/src/test/runner/mod.rs, compiler/oric/src/eval/evaluator/module_loading.rs, compiler/ori_eval/src/lib.rs
Wire the bytecode VM into the real execution entry points so programs can execute through the full runtime pipeline.
Current pipeline (query/mod.rs:392-448):
evaluated() → parsed() → typed() → canonicalize_cached() → Evaluator::builder().build() → load + run
Target runtime shape (with VM):
ori run:
evaluated() / run_evaluation() → canonicalize_cached() →
if VM_ENABLED: compile chunk → VM::execute() → ModuleEvalResult
else: Evaluator(tree-walker) → ModuleEvalResult
ori test:
TestRunner → Evaluator::load_module(...) / backend dispatch →
if VM backend enabled: compile chunk(s) + execute test body through VM
else: existing tree-walker path
-
Add a
bytecode_compiled()Salsa query or side cache for theori runpath that compilesSharedCanonResulttoChunk. The cache choice (#[salsa::tracked]vs session-scoped side cache) is owned here, after the Section 04 IR shape is real. -
Create
run_evaluation_vm()— parallel torun_evaluation()inquery/mod.rs— that takes a compiledChunkand executes it via the VM, producing aModuleEvalResultforori run. -
Add the corresponding VM execution seam to the test runner:
ori test --vmcannot be implemented by togglingevaluated()alone because the test runner builds anEvaluatordirectly. -
Handle
ModuleEvalResultcompatibility: The VM must produce the sameModuleEvalResultstructure as the tree-walker. This includes: success/failure status, captured output (forTestRun), error messages with spans and backtraces. -
Handle imported modules at the module-loading seam: the current evaluator resolves imports via
imports::resolve_imports(...)andEvaluator::load_module(...), canonicalizing imported modules and registering their functions before execution. The VM path must plug into that same module-loading contract rather than assuming imports are driven only by recursiveevaluated()calls. -
Verify
decision_treeTODOs are resolved:compiler/ori_eval/src/exec/decision_tree/mod.rslines 266 and 276 have TODOs referencing “section-07” abouttest_kindusage and numeric discriminants. These should have been resolved during Section 04.3 (DecisionTree compilation). Verify the bytecode compiler’s handling is correct and update or remove the TODOs. If 04.3 deferred them, resolve here: either the VM handlesTestKinddispatch and numeric discriminants natively, or the tree-walker decision_tree code is shared via a common helper. -
Resolve
can_evalTODO:compiler/ori_eval/src/interpreter/can_eval/mod.rsline 119 has a TODO about type reference resolution during canonicalization. Verify this is not a VM blocker — if TypeRef resolution is already handled in canonicalization (it is, per theCanExpr::TypeRefcomment), update the TODO to reflect current state. -
Matrix tests (in
compiler/oric/tests/phases/eval/and spec tests):- Dimensions: pipeline path (ori run, ori run —vm) x program shape (simple @main, @main with args, imports, type errors) x eval mode (Interpret, ConstEval, TestRun)
- Semantic pin:
ori run --vm hello_world.oriproduces identical stdout toori run hello_world.ori - Negative pin: program with type errors does NOT reach the VM (caught at type-check phase, produces same diagnostic)
- TDD ordering: wire up
run_evaluation_vm()FIRST with a minimal program, verify it producesModuleEvalResult, then expand to imports and test runner
-
/tpr-reviewpassed — independent review found no critical or major issues (or all findings triaged) -
/impl-hygiene-reviewpassed — hygiene review clean. MUST run AFTER/tpr-reviewis clean. -
Subsection close-out (07.1) — MANDATORY before starting the next subsection. Run
/improve-toolingretrospectively on THIS subsection’s debugging journey (per.claude/skills/improve-tooling/SKILL.md“Per-Subsection Workflow”): whichdiagnostics/scripts you ran, where you addeddbg!/tracingcalls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE/commit-pushusing a valid conventional-commit type (build(diagnostics): ... — surfaced by section-07.1 retrospective—build/test/chore/ci/docsare valid;tools(...)is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 07.1: no tooling gaps”. Update this subsection’sstatusin section frontmatter tocomplete. -
/sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW. -
Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
07.2 Const-Eval and Test-Mode Preservation
File(s): compiler/ori_eval/src/eval_mode/mod.rs, compiler/ori_eval/src/bytecode/vm.rs
The tree-walker serves three EvalModes. The VM must either replace all three or the tree-walker must be preserved for modes the VM cannot handle.
Current modes (eval_mode/mod.rs):
-
EvalMode::Interpret— full I/O, forori run. VM target. -
EvalMode::ConstEval { budget }— no I/O, budget-limited, deterministic. Used by type checker. Must work. -
EvalMode::TestRun { only_attached }— captures output and provides the evaluator-side runtime contract used by the test runner. Must work. -
Add
EvalModesupport to the VM: The VM must respect the same mode policies:ConstEval: check budget on everyCallopcode (equivalent tomode_state.check_budget()in the tree-walker). ReturnBudgetExceedederror if exceeded. Forbid all capability lookups. No I/O.TestRun: inject print handler (buffer capture) and preserve evaluator semantics expected by the test runner. Test enumeration,only_attached, and result bookkeeping remain runner-owned unless that contract is explicitly redesigned.Interpret: full capabilities, no budget, stdout print handler.
-
Handle
PatternExecutortrait: The tree-walkerInterpreterimplementsPatternExecutorfromori_patterns. The VM must either: (a) implementPatternExecutor(requires the VM to expose amatch_pattern()method), or (b) use a thin adapter that creates a temporary tree-walkerInterpreterfor pattern execution. Option (b) is pragmatic for the initial cut; option (a) is correct long-term. -
Handle ModeState and performance counters: The VM must increment
count_expression(),count_function_call(),count_method_call(),count_pattern_match()when profiling is enabled (via--profile). These are inlined no-ops when profiling is off. -
Decision: VM for ConstEval or preserve tree-walker? The tree-walker is simple and correct for
ConstEval(small programs, tight budget). The VM adds overhead (bytecode compilation) for programs that execute <100 operations. Recommended: use VM forInterpretandTestRun; keep tree-walker forConstEval. This avoids the bytecode compilation cost for tiny const-eval programs and preserves a known-correct fallback. -
Matrix tests (in
compiler/ori_eval/src/bytecode/vm/tests.rsandcompiler/oric/tests/phases/eval/):- Dimensions: eval mode (Interpret, ConstEval, TestRun) x boundary condition (budget exceeded, budget OK, output captured, profiling on/off)
- Semantic pin:
ConstEvalof$x = 2 + 3producesValue::Int(5)via tree-walker (not VM), with budget decremented - Semantic pin:
TestRunvia VM capturesprint(msg: "hello")output in buffer (identical to tree-walker capture) - Negative pin:
ConstEvalwith budget=1 on a 2-call program returnsBudgetExceedederror - TDD ordering: write mode-dispatch tests FIRST (verify tree-walker used for ConstEval, VM used for Interpret), then mode-specific behavior tests
-
/tpr-reviewpassed — independent review found no critical or major issues (or all findings triaged) -
/impl-hygiene-reviewpassed — hygiene review clean. MUST run AFTER/tpr-reviewis clean. -
Subsection close-out (07.2) — MANDATORY before starting the next subsection. Run
/improve-toolingretrospectively on THIS subsection’s debugging journey (per.claude/skills/improve-tooling/SKILL.md“Per-Subsection Workflow”): whichdiagnostics/scripts you ran, where you addeddbg!/tracingcalls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE/commit-pushusing a valid conventional-commit type (build(diagnostics): ... — surfaced by section-07.2 retrospective—build/test/chore/ci/docsare valid;tools(...)is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 07.2: no tooling gaps”. Update this subsection’sstatusin section frontmatter tocomplete. -
/sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW. -
Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
07.3 Feature Flag and Switchover
File(s): compiler/oric/src/query/mod.rs, compiler/oric/src/test/runner/mod.rs, compiler/oric/src/commands/run/mod.rs, compiler/oric/src/commands/test.rs, compiler/oric/src/main.rs
Provide a way to gradually switch from tree-walker to VM without a big-bang cutover.
-
Add
ORI_USE_BYTECODE_VMenvironment variable: When set to1,evaluated()routes through the VM pipeline. Default:0(tree-walker). This allows testing the VM in production-like scenarios without committing to it. -
Add
--vmCLI flag toori run:ori run --vm file.oriforces VM execution for a single invocation. Useful for A/B comparison. -
Add
--vmflag toori test: Runs the interpreter-backed test suite through a VM-capable backend/switch inTestRunner, not just via theevaluated()query path. This is the primary acceptance criterion — ifori test --vmpasses all spec tests, the VM is ready to become default. -
Dual-execution mode:
ori run --dual file.oriruns both tree-walker and VM, compares results, reports mismatches. This is the transition-period safety net. Integrate withdiagnostics/bytecode-verify.shfrom Section 06. -
Switchover sequence:
ORI_USE_BYTECODE_VM=1behind env var (opt-in, testing)- All spec tests pass with
--vm(Section 06 gate) - Default flips to VM (tree-walker becomes fallback)
- Tree-walker retained only for
ConstEvalmode (see 07.4)
-
Matrix tests (in
compiler/oric/tests/integration tests):- Dimensions: CLI flag (no flag, —vm, —dual) x command (run, test) x program category (simple, closures, imports, error-propagation)
- Semantic pin:
ori run --dual file.orireports “0 mismatches” for a representative program - Negative pin:
ori run --dualwith a program that triggers a known tree-walker/VM divergence (if any) reports the mismatch clearly - TDD ordering: implement env var and CLI flag parsing FIRST, then
--vmrouting, then--dualcomparison
-
/tpr-reviewpassed — independent review found no critical or major issues (or all findings triaged) -
/impl-hygiene-reviewpassed — hygiene review clean. MUST run AFTER/tpr-reviewis clean. -
Subsection close-out (07.3) — MANDATORY before starting the next subsection. Run
/improve-toolingretrospectively on THIS subsection’s debugging journey (per.claude/skills/improve-tooling/SKILL.md“Per-Subsection Workflow”): whichdiagnostics/scripts you ran, where you addeddbg!/tracingcalls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE/commit-pushusing a valid conventional-commit type (build(diagnostics): ... — surfaced by section-07.3 retrospective—build/test/chore/ci/docsare valid;tools(...)is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 07.3: no tooling gaps”. Update this subsection’sstatusin section frontmatter tocomplete. -
/sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW. -
Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
07.4 Tree-Walker Retirement Strategy
File(s): compiler/ori_eval/src/interpreter/mod.rs, compiler/ori_eval/src/lib.rs
Define what happens to the tree-walker after the VM is the default.
-
Do NOT delete the tree-walker. It must be preserved for:
ConstEvalmode — used by the type checker for compile-time constant evaluation. Small programs, tight budget, no I/O. The tree-walker is simpler and correct for this use case.PatternExecutortrait implementation — used byori_patternsfor the pattern matching system.- Fallback/debugging — if the VM produces wrong results, developers need the tree-walker as a reference oracle.
-
Mark the tree-walker as “const-eval engine”: Rename or reorganize to clarify its new role. It is no longer the primary runtime interpreter; it is the compile-time evaluator.
-
Remove only genuinely runtime-only code from the tree-walker: after the VM handles
InterpretandTestRun, trim pieces that are provably unused by const-eval/pattern execution, but do not assume generic function-call machinery is removable just because const-eval has a tighter recursion limit.create_function_interpreter()/ scope cleanup may still be required for compile-time function calls unless a separate const-eval call path exists first. -
Gate the cleanup on dual-execution parity: Only remove tree-walker runtime code AFTER Section 06 proves the VM and tree-walker produce identical results for all programs.
-
Matrix tests (in
compiler/oric/tests/phases/eval/andcompiler/ori_eval/src/interpreter/mod.rstests):- Dimensions: preserved path (ConstEval, PatternExecutor) x program complexity (simple const, const with function call, pattern with guard)
- Semantic pin:
$ANSWER = 6 * 7evaluates toValue::Int(42)via tree-walker ConstEval after VM is default for runtime - Semantic pin:
PatternExecutor::call()on tree-walker Interpreter still works (used byori_patterns) - Negative pin: importing a deleted tree-walker module produces compile error (if any code was incorrectly removed)
- TDD ordering: write preservation tests FIRST (verify ConstEval and PatternExecutor work), then clean up unused tree-walker code, verify preservation tests still pass
-
/tpr-reviewpassed — independent review found no critical or major issues (or all findings triaged) -
/impl-hygiene-reviewpassed — hygiene review clean. MUST run AFTER/tpr-reviewis clean. -
Subsection close-out (07.4) — MANDATORY before starting the next subsection. Run
/improve-toolingretrospectively on THIS subsection’s debugging journey (per.claude/skills/improve-tooling/SKILL.md“Per-Subsection Workflow”): whichdiagnostics/scripts you ran, where you addeddbg!/tracingcalls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE/commit-pushusing a valid conventional-commit type (build(diagnostics): ... — surfaced by section-07.4 retrospective—build/test/chore/ci/docsare valid;tools(...)is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 07.4: no tooling gaps”. Update this subsection’sstatusin section frontmatter tocomplete. -
/sync-claudesection-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW. -
Repo hygiene check — run
diagnostics/repo-hygiene.sh --checkand clean any detected temp files.
07.R Third Party Review Findings
- None.
07.N Completion Checklist
-
ori run file.oriexecutes through the VM via the full Salsa pipeline (parse -> type-check -> canonicalize -> compile bytecode -> VM execute) -
ori run --vm file.oriflag works and uses the VM -
ori test --vmruns spec tests through the VM -
ORI_USE_BYTECODE_VM=1env var works -
ConstEvalmode continues to use the tree-walker and works correctly -
TestRunmode works through the VM with output capture - Imported modules resolve correctly through the VM pipeline
-
--dualmode works and detects mismatches - Tree-walker preserved for const-eval and pattern execution
-
./test-all.shgreen - Plan annotation cleanup:
bash .claude/skills/impl-hygiene-review/plan-annotations.sh --plan 07returns 0 annotations -
/tpr-reviewpassed -
/impl-hygiene-reviewpassed -
/improve-toolingretrospective completed — MANDATORY at section close, after both reviews are clean. Reflect on the section’s debugging journey (whichdiagnostics/scripts you ran, which command sequences you repeated, where you added ad-hocdbg!/tracingcalls, where output was hard to interpret) and identify any tool/log/diagnostic improvement that would have made this section materially easier OR that would help the next section touching this area. Implement every accepted improvement NOW (zero deferral) and commit each via SEPARATE/commit-push. The retrospective is mandatory even when nothing felt painful — that is exactly when blind spots accumulate. See.claude/skills/improve-tooling/SKILL.md“Retrospective Mode” for the full protocol.
Exit Criteria: ori run --vm can execute any Ori program through the full Salsa pipeline. ori test --vm passes all 5,800+ spec tests. ConstEval mode unaffected. A feature flag controls the switchover. The tree-walker is preserved for compile-time evaluation.