Module bytecode

Module bytecode 

Source
Expand description

Stack-machine bytecode for MD2 per-point / per-pixel hot loops.

§Why

evalexpr evaluates a compiled Node by recursively walking the operator tree. Each visit allocates a Vec<Value> to gather child results, clones Values through the call stack, and looks up identifiers by string. On a per-point block iterated 512 times per wave times 4 waves per frame, that overhead dominates the actual arithmetic.

This module translates the AST once at preset load into a flat Vec<Op> and runs it through a tight interpreter loop:

  • Hot vars (x, y, r, g, b, a, …) become single LoadHot(i) / StoreHot(i) opcodes that hit the array slot directly — no name match, no Value cloning.
  • q-channels (q1..q64) use LoadQ(i) / StoreQ(i) for the same reason.
  • Math functions (sin, sqrt, pow, if, …) lower to dedicated opcodes that pop arguments off the f64 stack and push the result — no Function::call dispatch, no Value::Tuple allocation per call.
  • Cold reads/writes (custom vars, per-frame builtins like bass) fall back to MilkContext::get / set, which still beats evalexpr’s clone-and-dispatch by skipping the tree walk.

§Scope

The compiler bails out (Err(CompileError::Unsupported)) on any operator or function it doesn’t recognise. Callers must keep the original evalexpr Vec<Node> around and use it as the fallback. What’s supported today covers the bulk of corpus per_point / per_pixel blocks: arithmetic, comparisons, conditionals, the standard math library, and reads/writes of hot + q channels.

Explicitly NOT supported (caller must fall back to evalexpr):

  • loop() / while() / exec2() / exec3() — the evaluator’s eval_processed_with_loops rewrites these structurally; they never reach this compiler.
  • gmegabuf / megabuf / gmegabuf_set / megabuf_set — thread-local persistent state we don’t expose in the VM.
  • rand — relies on a thread-local RNG; sample-order dependent.
  • String values and tuples (other than as function-argument wrappers).

Structs§

CompiledBytecode
A bytecode-compiled per_point / per_pixel block. Constructed by CompiledBytecode::try_compile and replayed by CompiledBytecode::run.
Compiler 🔒

Enums§

CompileError
A reason the bytecode compiler couldn’t lower a given AST node. The caller falls back to evalexpr’s Node evaluation in that case.
Op
Stack-machine opcodes. The u8 / u32 payloads stay inline so the Vec<Op> is a flat array the interpreter scans linearly.

Constants§

STACK_SIZE 🔒

Functions§

bin_op 🔒
unary 🔒
unwrap_root 🔒
Strip evalexpr’s parenthesis-wrapper RootNodes. Every group like (expr) becomes a single-child RootNode in the tree, so descending into function args means peeling them one layer at a time until we reach a real operator.