A high-performance, deterministic WebAssembly sandbox built on Wasmtime. This host environment ensures that WASM programs (including multi-threaded ones) execute with absolute predictability by controlling time, randomness, and scheduling.
- Deterministic Multi-threading: Custom cooperative scheduler for WASI threads.
- Virtual Time:
clock_time_getreturns a deterministic virtual clock that only advances when threads sleep or yield. - Deterministic Randomness:
random_getproduces a fixed, predictable byte sequence. - Deterministic Sleep:
poll_oneoffis implemented via a virtual timeline, allowing programs to "sleep" without consuming real-world time. - Shared Memory Support: Full support for atomic operations and shared memory across deterministic threads.
The project is modularized for clarity and maintainability:
- main.rs: The entry point. Handles Wasmtime engine configuration, module loading, and initial instantiation.
- scheduler.rs: The heart of the sandbox. Implements the
Schedulerwhich managespaused_threadsanddelayed_threads. - wasi_builtins.rs: Contains the hand-rolled WASI snapshot preview1 implementation.
- memory.rs: Provides safe abstractions for WASM memory access.
- src/cpp/: C++ test cases and their deterministic expectations.
- src/go/: Go test cases compiled to WASM.
- src/wat/: Handwritten WebAssembly Text (WAT) tests.
- src/python/: Python test cases (experimental - requires Python stdlib VFS support).
The project is built and tested with Bazel for absolute reproducibility across Rust, Go, and C++.
# Build all components (Rust host, Go WASM, C++ tests)
bazel build //...
# Run all tests
bazel test //...
# Run the host binary directly
bazel run //:deterministic-wasm -- src/wat/proc_exit_test.wat
# Run a specific C++ test via Bazel
bazel test //src/cpp:threads_cpp_testcargo buildTests are now compiled using Bazel. Using compile-tests.sh is deprecated and the script has been removed.
bazel build //src/cpp:model_checker_test_wasmcargo run -- src/wat/proc_exit_test.watYou can set environment variables for the WASM simulation using the --env flag. Multiple variables can be set by repeating the flag.
cargo run -- --env KEY1=value1 --env KEY2=value2 src/wat/env_test.watThe deterministic sandbox includes a built-in model checker that can systematically explore different execution paths in multi-threaded programs. This is particularly useful for finding race conditions and other concurrency bugs.
The model checker finds bugs by exploring different thread interleavings. It achieves this by:
- WASM Transformation: Is automatically runs a
transform_wasmpass on your code to replace standard atomic instructions (memory.atomic.wait32,memory.atomic.notify) with calls to the model checker's built-in functions. - Deterministic Interleaving: These built-ins yield control back to the scheduler, allowing it to pause threads and explore different wake-up orders.
- Explicit Choices: Programs can also use
model_checker_select(num_options)to explicitly branch execution.
Programs can use the model_checker_select(num_options) WASI builtin (imported from the wasi module) to indicate a non-deterministic choice.
When run with the --model-check flag, the host will:
- Execute the program multiple times.
- Each time
model_checker_selectis called, the host chooses one of the available options. - The host maintains a queue of unexplored execution traces and continues running until all paths have been explored.
See tests/model_checker_test.cpp for a complete example. This test simulates two threads incrementing a shared variable without locking. By placing a model_checker_select call between the read and write operations, the model checker can force interleavings that reveal the race condition.
# Compile the test
bazel build //src/cpp:model_checker_test_wasm
# Run with the model checker
cargo run -- --model-check bazel-bin/src/cpp/model_checker_test.wasmIf a bug is found (e.g., the final value is incorrect), the program can abort(), which the host will report.
When running with --model-check, the host prints a reproduction command for each execution trace. You can use this command to run just that specific interleaving:
# Example reproduction command
cargo run -- bazel-bin/src/cpp/model_checker_test.wasm --trace 1,0The --trace argument takes a comma-separated list of choices for the model_checker_select calls.
The sandbox uses a unified test harness. Tests are defined in src/cpp/, src/go/, src/python/, and src/wat/. Each test consists of a source file and a corresponding .stdout file containing the expected deterministic output.
Note: Python tests are currently experimental and require additional VFS infrastructure to make the Python standard library accessible to the WASM runtime.
# Run all tests using Bazel (recommended)
bazel test //...
# Run all tests using Cargo
cargo test- Add your source file to the appropriate directory (
src/cpp/,src/go/,src/python/, orsrc/wat/). - Build the project with
bazel build //.... - Create an expected
.stdoutfile for your test. For C++ tests, use the naming convention[name]_cpp.stdout. For Python tests, use[name]_python.stdout. - The Bazel build will automatically detect and create test targets using the
wasm_testmacro.
Instead of using the standard wasmtime-wasi crate, which inherits host-system non-determinism, this project implements a custom "Linker" that maps WASI syscalls to deterministic host functions.
When a thread calls poll_oneoff (sleep) or sched_yield, it is placed into a queue. The host scheduler then picks the next available thread to run. If no threads are ready, virtual time is advanced to the next scheduled wake-up time in the delayed_threads priority queue.