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 singleLoadHot(i)/StoreHot(i)opcodes that hit the array slot directly — no name match, no Value cloning. - q-channels (
q1..q64) useLoadQ(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 — noFunction::calldispatch, noValue::Tupleallocation per call. - Cold reads/writes (custom vars, per-frame builtins like
bass) fall back toMilkContext::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’seval_processed_with_loopsrewrites 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§
- Compiled
Bytecode - A bytecode-compiled per_point / per_pixel block. Constructed by
CompiledBytecode::try_compileand replayed byCompiledBytecode::run. - Compiler 🔒
Enums§
- Compile
Error - A reason the bytecode compiler couldn’t lower a given AST node.
The caller falls back to evalexpr’s
Nodeevaluation in that case. - Op
- Stack-machine opcodes. The
u8/u32payloads stay inline so theVec<Op>is a flat array the interpreter scans linearly.
Constants§
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.