Skip to content

♻️ refactor(mq-lang): remove tarn feature flag and tree-walking evaluator - #2307

Open
harehare wants to merge 20 commits into
mainfrom
remove-tarn-feature-tree-walker
Open

♻️ refactor(mq-lang): remove tarn feature flag and tree-walking evaluator#2307
harehare wants to merge 20 commits into
mainfrom
remove-tarn-feature-tree-walker

Conversation

@harehare

@harehare harehare commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Summary

Make the Tarn bytecode VM the sole, unconditional execution engine. Deletes the tree-walking evaluator (eval.rs) and its Env module, collapses the dual-backend split in Engine/RuntimeValue/DebugContext/builtins, drops the deprecated set_variable/get_variable builtins, removes the now-pointless tarn feature from every crate that passed it through, and deletes the tree-walker-vs-VM differential fuzz harness.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • ♻️ Refactor
  • 📝 Documentation
  • ⚡ Performance
  • ✅ Test
  • 📦 Build / dependencies
  • 👷 CI

Checklist

  • I ran cargo fmt and cargo clippy and addressed any warnings
  • I ran just test-all and all tests pass
  • I added or updated tests covering this change
  • I updated relevant documentation (/docs, crate README.md) if needed
  • I added a changelog entry if this is a user-facing change

Additional Context

@codspeed-hq

codspeed-hq Bot commented Sep 8, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 8.18%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
❌ 6 regressed benchmarks
✅ 8 untouched benchmarks
🆕 6 new benchmarks
⏩ 36 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
eval_compiled_nested_function_calls 772.6 µs 1,000.6 µs -22.79%
eval_compiled_array_chained_operations 2.8 ms 3.4 ms -17.78%
eval_compiled_array_filter 3.2 ms 3.9 ms -17.21%
eval_compiled_array_fold 369.4 µs 435.6 µs -15.21%
eval_compiled_array_map 1.8 ms 2 ms -12.27%
eval_compiled_reused_single_input_with_globals 21 µs 23.8 µs -11.75%
eval_compiled_owned_markdown_tree 3.8 ms 2.4 ms +59.42%
🆕 eval_compiled_dynamic_builtin_call N/A 1.7 ms N/A
🆕 eval_compiled_dict_field_access N/A 6 ms N/A
🆕 eval_compiled_direct_builtin_calls N/A 6.9 ms N/A
🆕 eval_compiled_large_dict_field_access N/A 7.2 ms N/A
🆕 eval_compiled_owned_markdown_tree_without_matches N/A 2.6 ms N/A
🆕 eval_compiled_while N/A 9.5 ms N/A

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing remove-tarn-feature-tree-walker (25f15ff) with main (e9d12c0)

Open in CodSpeed

Footnotes

  1. 36 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@harehare
harehare force-pushed the remove-tarn-feature-tree-walker branch 3 times, most recently from 3d2bc94 to a6138f4 Compare September 10, 2026 10:47
@harehare
harehare added this pull request to stack #2323 September 10, 2026 12:52
@harehare
harehare force-pushed the remove-tarn-feature-tree-walker branch from 25f15ff to fe604ec Compare September 10, 2026 13:18
…ator

Make the Tarn bytecode VM the sole, unconditional execution engine. Deletes
the tree-walking evaluator (`eval.rs`) and its `Env` module, collapses the
dual-backend split in Engine/RuntimeValue/DebugContext/builtins, drops the
deprecated set_variable/get_variable builtins, removes the now-pointless
`tarn` feature from every crate that passed it through, and deletes the
tree-walker-vs-VM differential fuzz harness.
- Replace recursive run_chunk_inner_impl/run_chunk calls with an iterative
  frame-stack trampoline (run_frames/run_frame_slice), decoupling mq call
  depth from Rust's native stack.
- Add CallStatic/CallSelf/CallUpvalue opcodes to call known-static,
  self-recursive, and captured-immutable functions directly, skipping
  closure materialization/loading.
- Share closure upvalues via Option<Shared<Vec<Cell>>> instead of cloning a
  Vec<Cell> on every call; drop RefCell from non-capturing Locals::Flat in
  favor of direct &mut access.
Adds owning update_value/update_children_value variants
(into_with_value/into_with_children_value) so the VM's markdown
update path clones the tree once instead of cloning again for the
mutated child, while keeping the existing COW value() API for
callers that still need an unmodified copy.

Also consolidates the benchmark suite to keep only steady-state
eval_compiled_* benchmarks (dropping cold/duplicate eval_* variants)
and fixes a test added in 862b7eb1 that called a soft (mq-defined)
builtin through compile_program's BuiltinPrelude::None path, where
it can never resolve — swapped for a native builtin so the test
actually exercises the arg-order guarantee it documents.
Adds a bytecode-optimizer peephole that recognizes a BinaryLocalLocal
or BinaryLocalConst comparison immediately followed by JumpIfFalse —
the exact shape every `if`/`while`/`until` condition compiles to —
and fuses it into a single JumpIfFalseLocalLocal/JumpIfFalseLocalConst
instruction. The comparison is evaluated and branched on directly,
without ever pushing the boolean result onto the operand stack, cutting
one dispatch iteration (and a push+pop) from every loop/conditional
check. The fusion runs entirely as a post-hoc rewrite in
optimize_chunk, so the compiler's AST-to-bytecode lowering (and its
identifier-shadowing rules) is untouched.

eval_compiled_while median: 450.9µs -> 373.6µs (~17%, --sample-count 50).
eval_compiled_fibonacci improves similarly since its recursion is
dominated by a single comparison-guarded branch. No other benchmark
regressed. Full workspace test suite (8746 tests) and clippy pass.
RuntimeValue::Dict was a BTreeMap<Ident, RuntimeValue>, ordered by
each key's interned symbol id rather than its text or insertion
order — an accidental, fragile ordering that could change based on
unrelated code elsewhere in a program (e.g. an earlier `let apple =
1` reordering a later JSON object's "apple" key). Switches to
IndexMap<Ident, RuntimeValue, FxBuildHasher> (re-exported as
mq_lang::DictMap), which preserves true insertion order and replaces
per-key tree comparisons with FxHash-based lookup. FxHash is safe
here since keys are opaque interned symbol ids, not raw untrusted
strings, matching how the compiler already hashes Ident elsewhere
(FxHashMap<Ident, _> in tarn/compiler.rs).

Benchmarked (--sample-count 30): a 100-key dict with varying-key
get() lookups improves ~10% (433.3us -> 391.4us median); a tiny
fixed 5-key dict accessed in a loop is a wash, since builtin-call
dispatch and Ident interning dominate that shape's per-call cost.

Updates every downstream Dict call site across mq-lang, mq-run,
mq-web-api, and mq-test to the new type, changes del()'s dict removal
to shift_remove (preserving order), and regenerates snapshot/test
fixtures that had baked in the old accidental key order (output
content is unchanged; only key order in the fixtures moved to match
true insertion order).
node_values() matched on self.clone() to extract one field, deep-cloning
the matched variant's other fields (e.g. position) for nothing. Match on
&self and clone only the values field that's actually returned.
A failed frame (e.g. mid-construction of an array/dict literal) could
leave partial operands above its own stack_base. unwind() never
truncated them, so a subsequent catch frame's stack_base captured
that leftover garbage, and the parent's own accumulator got
overwritten once the catch's result was pushed. Truncate to the
failed frame's stack_base right after popping it, matching what
run_frame_slice already does on the success path.
@harehare
harehare force-pushed the remove-tarn-feature-tree-walker branch from f7ea221 to 3d32c82 Compare September 10, 2026 13:59
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