Gentle opened PR #12633 from Gentle:feat-savestates to bytecodealliance:main:
Rationale: If you keep the instrumentation information inside the output wasm file, then you can at any time use wizer to snapshot it again, allowing you to effectively save and resume later or on another machine
- adds a
keep_instrumentationoption that preserves__wizer_*exports in the output module- adds a
parse_instrumentedmethod that re-parses a previously snapshotted module so it can be snapshotted again.- parser::parse has been adjusted to either reject or require existing
__wizer_*exports depending on new bool argparse_instrumented skips validating the wasm module, the logic behind this is that if the wasm file was successfully instrumented before, then it must be valid. From micro benchmarking, wasm_validate takes longer than instrumentation itself, so there is a significant speedup by reusing the instrumentation information
Gentle requested fitzgen for a review on PR #12633.
Gentle requested wasmtime-core-reviewers for a review on PR #12633.
Gentle updated PR #12633.
github-actions[bot] added the label wizer on PR #12633.
github-actions[bot] commented on PR #12633:
Subscribe to Label Action
cc @fitzgen
<details>
This issue or pull request has been labeled: "wizer"Thus the following users have been cc'd because of the following labels:
- fitzgen: wizer
To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.
Learn more.
</details>
bjorn3 commented on PR #12633:
parse_instrumented skips validating the wasm module, the logic behind this is that if the wasm file was successfully instrumented before, then it must be valid.
Is that safe to do when the wasm module comes from an untrusted source?
Gentle commented on PR #12633:
well no, at that point you are trusting that whoever added the __wizer_* exports did not lie and did validate the wasm before adding them
it's possible that my microbenchmarks were bad, but with a 20MB wasm file, validate, parse, then snapshot was 80% slower than just parse and snapshot. It doesn't break anything to add self.wasm_validate() in parse_instrumented, I just think it's redundant but I'd be happy to implement it differently if there are concerns
Gentle edited a comment on PR #12633:
well no, at that point you are trusting that whoever added the
__wizer_*exports did not lie and did validate the wasm before adding themit's possible that my microbenchmarks were bad, but with a 20MB wasm file, validate, parse, then snapshot was 80% slower than just parse and snapshot. It doesn't break anything to add self.wasm_validate() in parse_instrumented, I just think it's redundant but I'd be happy to implement it differently if there are concerns
Gentle edited a comment on PR #12633:
well no, at that point you are trusting that whoever added the
__wizer_*exports did not lie and did validate the wasm before adding themit's possible that my microbenchmarks were bad, but with a 20MB wasm file, validate, parse, then snapshot took roughly 6 times as long as just parse and snapshot. It doesn't break anything to add self.wasm_validate() in parse_instrumented, I just think it's redundant but I'd be happy to implement it differently if there are concerns
tschneidereit commented on PR #12633:
well no, at that point you are trusting that whoever added the
__wizer_*exports did not lie and did validate the wasm before adding themValidation is there in part for security reasons: the following steps can then assume they're handling valid wasm content. If the validation step is skipped, that's not a safe assumption anymore, so any code depending on it might do wrong things—potentially in ways that an attacker can exploit.
If you think about it, if we trusted the author of a module with these exports to create a valid module, why wouldn't we do that for all content in general?
with a 20MB wasm file, validate, parse, then snapshot took roughly 6 times as long as just parse and snapshot.
6x overhead for validation seems like a lot. Would you be able to share the module in question, by any chance?
Gentle updated PR #12633.
Gentle commented on PR #12633:
I'm sorry for the confusion, my test setup was indeed flawed, in a clean reproduction I can see that validate takes reasonable time even with large code sections. I added it back to parse_instrumented
(there was an issue in the allocator of my runtime, so I actually found a bug in the way I use wasmtime in my code, but that was entirely unrelated to wizer)
Gentle edited PR #12633:
Rationale: If you keep the instrumentation information inside the output wasm file, then you can at any time use wizer to snapshot it again, allowing you to effectively save and resume later or on another machine
- adds a
keep_instrumentationoption that preserves__wizer_*exports in the output module- adds a
parse_instrumentedmethod that re-parses a previously snapshotted module so it can be snapshotted again.- parser::parse has been adjusted to either reject or require existing
__wizer_*exports depending on new bool arg
alexcrichton commented on PR #12633:
Could you detail a bit more your use case here? I think it should work today to wizen a module/component twice, e.g. running one export on one machine and another export on another machine. The only downside I can think of to doing this is that the instrumentation takes a small amount of time to generate, but I would expect that to be negligible compared to compilation as a whole.
So, for more info, could you describe if performance is a primary concern here? Or if wizening a module twice is or isn't appropriate? Or if you're doing something else with the instrumented wizer artifact?
Gentle commented on PR #12633:
I use this for basically durable execution and stateful modules
I have an RPC system and there are complex tasks that need processing. I always keep instrumentation info in the modules. When starting a task, the fresh module is instantiated, told to load the input task it should process, then it runs the task, possibly sending out RPC requests. The guest runs eagerly either until it finishes the task or until it only has pending requests that await responses.
In the case of nothing left to do but not finished yet, I take the current state in memory and the last snapshot I made and use these to snapshot the long-running instance, then store that file and shut down the worker. When the RPC responses arrive, I wake the worker up, resolve the response, snapshot the module again and save it until the next response arrives. This can loop until the task is finished even if some outgoing requests may take days or require human intervention
Gentle commented on PR #12633:
from previous attempts, the instrumentation info has to be in the wasm file when it is instantiated or it will fail to snapshot, I tried naively just running the regular wasm file, then running the file through instrument; snapshot to learn that this won't work
so at a minimum I need --keep-instrumentation and parser::parse needs to not reject files that were already instrumented to make this feature work, but skipping instrument when possible would be optimal
Gentle edited a comment on PR #12633:
from previous attempts, the instrumentation info has to be in the wasm file when it is instantiated or it will fail to snapshot, I tried naively just running the regular wasm file, then running the file through instrument; snapshot to learn that this won't work
so at a minimum I need --keep-instrumentation and parser::parse needs to not reject files that were already instrumented to make this feature work, but skipping instrument when possible would be optimal
Edit: to clarify, my use case is
- instantiate
- at some later point load wizer only if needed
- snapshot
in that order. This is because I am actually running the wasm as V8 WebAssembly.Instance and I use wizer compiled to wasm, so effectively I can dynamically load wizer only if required, but otherwise run the wasm file regularly. But for that to work the file has to always include instrumentation exports
Gentle edited a comment on PR #12633:
from previous attempts, the instrumentation info has to be in the wasm file when it is instantiated or it will fail to snapshot, I tried naively just running the regular wasm file, then running the file through instrument; snapshot to learn that this won't work
so at a minimum I need --keep-instrumentation and parser::parse needs to not reject files that were already instrumented to make this feature work, but skipping instrument when possible would be optimal
Edit: to clarify, my use case is
- instantiate
- at some later point load wizer only if needed
- snapshot if not finished
in that order. This is because I am actually running the wasm as V8 WebAssembly.Instance and I use wizer compiled to wasm, so effectively I can dynamically load wizer only if required, but otherwise run the wasm file regularly. But for that to work the file has to always include instrumentation exports
Last updated: Feb 24 2026 at 04:36 UTC