Skip to content

Add a bytecode VM as a third execution path - #3

Open
erichanwang wants to merge 1 commit into
feature/arraysfrom
feature/bytecode-vm
Open

Add a bytecode VM as a third execution path#3
erichanwang wants to merge 1 commit into
feature/arraysfrom
feature/bytecode-vm

Conversation

@erichanwang

Copy link
Copy Markdown
Owner

Adds a stack-based bytecode VM alongside the tree-walking interpreter and the
x86-64 codegen, so the differential suite compares three independent
implementations instead of two.

The VM compiles the AST once into a flat instruction stream per function.
if/while lower to jump targets and break/continue to plain JMPs resolved at
compile time, rather than the Flow value the interpreter threads through
recursive calls. Arithmetic, comparison, truthiness, and array indexing are
each re-derived independently instead of shared with Interpreter or
runtime.c, so a bug has to survive three unrelated implementations to pass.

run_tests.sh now diffs interpreter/VM/x86-64 pairwise on every .lang program;
all fifteen agree. benchmark.sh times the VM alongside the other two paths.

Measured on bench/workload.lang, best of 9 runs:

Backend Time
Tree-walking interpreter 89 ms
Bytecode VM 88 ms
Compiled x86-64 30 ms

The VM lands close to the interpreter rather than clearly ahead of it: both
resolve every variable through a map<string, Value> frame lookup, and that
is what this fib(21)-heavy workload spends most of its time on. Trading AST
recursion for instruction dispatch doesn't help until variables are slots
instead of names -- noted in the README as the next step, not implemented
here.

The interpreter and the x86-64 codegen already diff against each other on
every corpus program, but that only catches a bug if it shows up as a
disagreement between exactly two implementations. A third, structurally
different one raises the bar: the VM compiles the AST once into a flat
instruction stream and resolves if/while to jump targets and break/continue
to plain JMPs at compile time, instead of threading a Flow value through
recursive C++ calls the way the interpreter does. Arithmetic, comparison,
truthiness, and array indexing are each re-derived independently rather than
shared with Interpreter or runtime.c, so the same bug has to exist in all
three to slip through.

run_tests.sh now diffs interpreter/VM/x86-64 output pairwise on the whole
corpus; all fifteen programs agree. benchmark.sh times the VM alongside the
existing two paths on bench/workload.lang: interpreter 89ms, VM 88ms, x86-64
30ms (best of 9). The VM lands close to the interpreter rather than clearly
ahead of it, because both resolve every variable through a map<string,
Value> frame lookup, and that is what a fib(21)-heavy workload spends most
of its time on -- trading AST recursion for instruction dispatch does not
help until variables are slots instead of names.
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