Bytecode VM - #1109
Conversation
|
There are some failed CI tests... Can you look into them? All in all, surprisingly few global impact for such a large feature! The changes are all localized and we'll packaged. |
There was a problem hiding this comment.
Pull request overview
This PR introduces an experimental, feature-gated (“grain”) bytecode VM into Rhai, including artifact (de)serialization, verification, and extensive differential/fuzz-style test harnesses. It also updates the crate to Rust 2021 edition to enable newer borrow-checker capabilities needed by the implementation.
Changes:
- Add
grainmodule behind a newgrainfeature flag, exposingCompiler,Program, andVm, plus on-the-wire artifact read/write and verification. - Add a dedicated
grainintegration test harness and fixtures, plus fuzz targets for hostile-bytecode loading and compile→write→read→run roundtrips. - Bump Rust edition to 2021 and add supporting internal API exposure (
Engine::get_indexed_muttopub(crate)).
Reviewed changes
Copilot reviewed 42 out of 46 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| .gitignore | Ignore .zed/ editor directory. |
| Cargo.toml | Switch to Rust 2021, add grain feature, wire grain-only tests, extend fuzz feature to include grain. |
| fuzz/Cargo.toml | Enable grain feature for fuzz crate; add new fuzz bins; strengthen release profile checks. |
| fuzz/fuzz_targets/generated.rs | Grammar-directed fuzz target comparing VM vs walker. |
| fuzz/fuzz_targets/load.rs | Fuzz target for hostile artifact bytes into Program::read and VM execution. |
| fuzz/fuzz_targets/roundtrip.rs | Fuzz target for script compile→write→read→run parity vs walker. |
| src/eval/chaining.rs | Make Engine::get_indexed_mut pub(crate) for grain VM use. |
| src/lib.rs | Export pub mod grain behind #[cfg(feature = "grain")]. |
| src/module/mod.rs | Whitespace cleanup in docs for with_params_info. |
| src/grain/mod.rs | New grain module root; forbids unsafe_code; exports Compiler, Program, Vm. |
| src/grain/bytecode/mod.rs | Bytecode module surface (ops, pools, verifier, assembly/disassembly). |
| src/grain/bytecode/chain.rs | Chain representation (Root/Step/Tail) for lvalue/rvalue chaining semantics. |
| src/grain/bytecode/chunk.rs | Chunk metadata (entry/end/max_stack) and ops iterator. |
| src/grain/bytecode/code.rs | Bytecode encoding/decoding, assembly/disassembly, switch target resolution. |
| src/grain/bytecode/op.rs | Opcode model and operand decoding helpers. |
| src/grain/bytecode/positions.rs | Position table representation and compact encoding/decoding integration. |
| src/grain/bytecode/strings.rs | String table representation borrowing names from artifact bytes. |
| src/grain/bytecode/switch.rs | Switch dispatch using Rhai’s hashed case semantics with seed probing. |
| src/grain/bytecode/verify.rs | Verifier ensuring structural safety before VM executes borrowed code bytes. |
| src/grain/compile/mod.rs | AST→bytecode lowering pipeline (compiler). |
| src/grain/compile/cases.rs | Range-arm splitting for switch dispatch table generation. |
| src/grain/compile/poolable.rs | Constant-pool eligibility rules for cross-process artifact safety. |
| src/grain/compile/slots.rs | Local slot assignment model aligned with Scope behavior. |
| src/grain/format/mod.rs | Artifact container format (header/sections), Cursor, varint helpers. |
| src/grain/format/abi.rs | ABI fingerprinting (widths + restriction feature flags) and mismatch reporting. |
| src/grain/format/read.rs | Artifact parsing into a Program, including safety checks + verification. |
| src/grain/format/write.rs | Artifact writer with explicit refusal modes for unsupported constructs. |
| src/grain/pos/mod.rs | No-std-friendly address→(line,col) resolver for stripped diagnostics. |
| src/grain/pos/varint.rs | LEB128/zigzag varint codec for position tables. |
| src/grain/program.rs | Program container for pools/chunks/code/positions plus helpers used across VM/compiler/format. |
| src/grain/vm/mod.rs | VM execution engine over verified bytecode, reusing host Engine for dispatch. |
| src/grain/vm/callback.rs | Runtime wrapper module enabling Rhai-native callbacks into compiled grain chunks. |
| tests/mod.rs | Grain integration test harness entry point (feature-gated via Cargo test config). |
| tests/grain/allocation.rs | Allocation/peak tracking harness (separate binary due to global allocator). |
| tests/grain/callback.rs | Tests for native-to-compiled callback behavior and known divergences. |
| tests/grain/corpus/mod.rs | Differential corpus definitions/utilities. |
| tests/grain/corpus/generate.rs | Deterministic script generator used by tests + fuzz targets. |
| tests/grain/differential.rs | Corpus-based differential testing between walker and VM. |
| tests/grain/fixtures/follow.rhai | Realistic fixture script for AST sizing/projection/allocation tests. |
| tests/grain/fixtures/golden.rgrn | Golden artifact fixture for format stability tests. |
| tests/grain/fixtures/golden.rhai | Source for the golden artifact fixture. |
| tests/grain/format.rs | Artifact format and robustness tests. |
| tests/grain/fuzz.rs | Seeded mutation/structural fuzz tests for loader+VM safety and parity checks. |
| tests/grain/limits.rs | Tests ensuring compiled code respects operation limits and progress interrupts. |
| tests/grain/projection.rs | AST-node pricing projection against planned stack encoding (internals-only). |
| tests/grain/scope.rs | Tests covering scope semantics and closure pointer differences. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Final thoughts: Do you suggest creating a new branch for it? Or just merge into Actually merging into And, as I mentioned in Discord, should we call the feature |
|
I think pushing to |
|
Sorry, I just edited my last comment but you might have already read it. Basically two questions:
|
|
I think |
|
CI caught a bug in 32-bit integers mode... |
|
A question: right now, how does the user use this thing? Something like the following? let code = engine.compile_into_grain(&ast)?;
let result = engine.eval_grain_with_scope(...)?; |
|
Example now in the |
|
OK, one final round of CI... |
|
wait need to make sure it compiles under |
|
Right now, I see that your intended usage scenario is to compile a single AST into bytecodes. Then run it on a VM separately without the source. We need to address the scenario of loaded modules. I'm not sure how you're handling the In that case, we'd want to modify the Which also means that the standard In that case, standard Rhai scripts can also load/call grain functions in a seamless manner. Also, In addition, perhaps a new command line tool |
|
The current convention is I want to get this out in experimental to library users who are interested then add the more complex stuff in the future. |
I'd suggest beginning it with Alternatively, many scripting systems name their compiled files by attaching a If we settle on a standard extension, we can write a standard header to the file to contain the bytecodes format and/or version (we'd want backwards compatibility). This way, Rhai can load the correct VM based on the header info. Or we can base64 encode and make it text based, so we can still call it |
|
Yeah the header already has versioning information so we could probably just look for the magic string at the beginning of the file. I'm pretty open to whatever since file extensions don't really matter. |
Why don't we use the first 4 bytes in the file as a 4CC to identify the bytecode type... Like |
|
Ya I see that Ok, I'll merge this in about 12 hours. Give some time to last minute changes, if any. When I get back to a desk, I'll send you an invite as an org member and give you direct commit rights. I don't think I can do it on GitHub mobile app... |
|
Awesome, sounds good! |
|
Ok you should now have commit access to |
|
Yup! I'll let you merge this PR unless you'd like me to do so. |
|
You can do the honors.:-D |
|
A few suggestions:
|
|
Also, forgot to ask. How do we handle I suspect not at all right now... As it requires a parser and variable offsets will be all wracked. Beware that custom syntax may also modify the scope file so variables offset may be wracked anyway. So the VM needs to keep a flag on this. |
|
Right now |
Yeah, I see it in the code. However, a custom syntax definition can declare itself to not touch the scope, so perhaps it should be allowed for those cases. And I see Pls do the honors of merging! |
|
Actually if we get As a normal user, I would expect the VM to import modules that must also be compiled, from |
|
@ImTheSquid I just found out that the Also, I ran the bench (without walker vm speedup floor spread walker-slow fragments
tight integer loop 106.0ms 54.4ms 1.95x 1.30x 29% 194.0ms 0
float arithmetic 537.8ms 272.4ms 1.97x 1.10x 83% 405.9ms 0
script fn calls 90.8ms 51.2ms 1.77x 1.55x 40% 106.0ms 0
switch, 4 arms 145.4ms 83.7ms 1.74x 1.40x 22% 261.3ms 0
switch, 16 arms 136.3ms 78.1ms 1.75x 1.35x 13% 283.3ms 0
branch heavy 197.8ms 107.9ms 1.83x 1.45x 12% 414.1ms 0
native callbacks 13.5ms 62.3ms 0.22x 0.25x 9% 19.8ms 0So it seems roughly 2x speedup can be expected! |
|
I see in the code that That would be a pretty severe limitation... I personally use it quite a lot, and it is very common for Is there a particular reason why it cannot be supported? |
|
That was just me not wanting to deal with how to deal with |
This PR merges my experiment
rhaigraininto the main Rhai repo under thegrainmodule and feature flag, requested as an alternative to #1108. It will be marked as experimental. This PR also updates the Rust edition to 2021 from 2018 to support some operations that were previously forbidden by the borrow checker (such as split mutable borrows). The MSRV was not changed.