Tail-Call Interpreters in Rust
8 points by nemin
8 points by nemin
One minor variation I have tinkered with that cleans things up syntactically without semantic impact: write the implementations of each individual operation as simple functions without any dispatch-next bits, and then as a separate thing use macros or a code generator to generate functions that call those. Because the simple functions get inlined, it's equivalent to the examples in the blog, but lets you separate the implementation logic from the interpreter threading.
E.g.
// simple direct implementation of add, unaware of interpreter
// threading, possibly even lives in a different module
fn add(machine: &mut Machine, instr: Instr) {
let (rd, imm, r1v, rdv) = decode(instr);
machine.regs[rd] = rdv.wrapping_add(r1v.wrapping_add(imm));
machine.ip += 1;
// no dispatch next instr logic here
}
// main dispatch function wraps each impl with a macro call
fn dispatch(machine: &mut Machine, instr: Instr) {
match instr.op() {
Op::Add => my_tailcall!(add),
...
}
}
And then the my_tailcall macro generates a function with the become bits and the next-instruction decoding as in the blog post.