Skip to content
Open
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
cd0a3d3
Initial CLI argument parsing for RR
arjunr2 Jun 4, 2025
016f6b4
Validate RR args with clap
arjunr2 Jun 5, 2025
bd6859f
Setup `RRConfig` for runtime access
arjunr2 Jun 5, 2025
2533f7e
Add rr buffers to `Store`
arjunr2 Jun 5, 2025
21c9e98
Determinism config enforcement during RR
arjunr2 Jun 10, 2025
1dbaa03
Added RR event buffers
arjunr2 Jun 13, 2025
63f96cf
Initial RR serde support
arjunr2 Jun 16, 2025
b832a44
Support types for `RREvent`
arjunr2 Jun 17, 2025
94364b0
Refactor RR infrastructure
arjunr2 Jun 18, 2025
34e78a1
Add compressed validation/no-validation for Record
arjunr2 Jun 20, 2025
a0012df
Added event callback closures
arjunr2 Jun 23, 2025
aeb3c0b
Add replay injection with function call stubbing on trampoline
arjunr2 Jun 23, 2025
89dec1e
Added RR buffer test
arjunr2 Jun 24, 2025
0ed8b80
Clarify docs for RR cli
arjunr2 Jun 24, 2025
e2591d9
Refactor event interface from enum to typed event structs
arjunr2 Jul 1, 2025
b6866f7
Refactor events to indepedent module
arjunr2 Jul 1, 2025
c5d6b62
Added component host function entry/exit event recording
arjunr2 Jul 2, 2025
586ba3a
Added component host function entry/exit (shallow) replay support
arjunr2 Jul 2, 2025
89ea484
fixup! Added component host function entry/exit (shallow) replay support
arjunr2 Jul 3, 2025
f21361b
Support dynamic entrypoints and defining linker imports as trap durin…
arjunr2 Jul 7, 2025
3f187d7
Ensure replay completion and restructure events directory
arjunr2 Jul 8, 2025
73d2a1c
Add recording for lowering, lowering-stores, and reallocations
arjunr2 Jul 9, 2025
8226774
Tighten mutability references acquision points for lowering contexts
arjunr2 Jul 9, 2025
478d4ce
Support event action and iterator on replayer
arjunr2 Jul 11, 2025
8a1347f
fixup! Support event action and iterator on replayer
arjunr2 Jul 11, 2025
7169109
Support recording of memory slice bytes with `MemorySliceCell`
arjunr2 Jul 11, 2025
3a93791
MVP for RR component model complete
arjunr2 Jul 15, 2025
7a1f754
Change interface names for buffers and store
arjunr2 Jul 15, 2025
2f6716b
Added macro wrappers for record/replay stubs
arjunr2 Jul 15, 2025
836ab80
Add `RecordMetadata` to the trace to support optional replay validation
arjunr2 Jul 15, 2025
d2df9f4
Move unsafe for valraw transmute out of rr module
arjunr2 Jul 15, 2025
2c803f9
Added enum in host call rr stubs for readability
arjunr2 Jul 16, 2025
423b19e
fixup! Added enum in host call rr stubs for readability
arjunr2 Jul 16, 2025
0cb5ce1
Switch `rr_event` macro syntax
arjunr2 Jul 16, 2025
42101ea
Initial feature gating of RR for type info; fix doc inclusion for com…
arjunr2 Jul 16, 2025
7c7a60e
Enum and rr type gating for core wasm; bugfix in component lowering type
arjunr2 Jul 16, 2025
0cac3b8
Change all prints to log interface
arjunr2 Jul 18, 2025
d0346fb
Remove `ValRawSer` struct abstraction
arjunr2 Jul 18, 2025
81f8bae
Add wasmtime version to recorded trace
arjunr2 Jul 18, 2025
fdd8b45
Initial support for generic RR readers/writers
arjunr2 Jul 24, 2025
65118cf
Move RR buffer sanity checks into `Drop` implementations
arjunr2 Jul 24, 2025
ceadca3
Added `InstantiationEvent` for checksumming components; also fix repl…
arjunr2 Jul 24, 2025
6cc4147
Added buffered reader/writers for RR
arjunr2 Jul 25, 2025
c15be6e
Fix some defaults and docs
arjunr2 Jul 25, 2025
d819287
Added internal RR event buffering with configurable window size
arjunr2 Jul 28, 2025
4e8e2f8
Prevent memory export during RR for core wasm
arjunr2 Jul 28, 2025
9a056c6
Remove dead code attributes
arjunr2 Jul 28, 2025
217ddd2
Added `replay` command to CLI; refactor config api
arjunr2 Jul 29, 2025
8d3d374
fixup! Added `replay` command to CLI; refactor config api
arjunr2 Jul 29, 2025
03421d6
Added `no_std` support for RR
arjunr2 Jul 29, 2025
2918403
Added RR feature gating and style nits
arjunr2 Jul 30, 2025
8ae8f84
Add notes about `rr` feature configurability
arjunr2 Jul 31, 2025
fdc6887
Refactor type validation across the RR stack
arjunr2 Aug 1, 2025
b500f07
Transition away from macros in component RR
arjunr2 Aug 1, 2025
a468a11
Cleanup core RR funcs and validation flows
arjunr2 Aug 5, 2025
f55bbff
Refactor validation API
arjunr2 Aug 5, 2025
c852b12
Move to typefunc for host function entry validation
arjunr2 Aug 5, 2025
b0dfb0f
Add validation tests and gating across whole project
arjunr2 Aug 5, 2025
205f436
Added configuration flag for deserialization buffer
arjunr2 Aug 6, 2025
a2beaef
Doc comment style fix
arjunr2 Aug 6, 2025
6afb9c1
Added panic stubs for rr in libcalls
arjunr2 Aug 6, 2025
0025593
fixup! Added panic stubs for rr in libcalls
arjunr2 Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

121 changes: 120 additions & 1 deletion crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use serde::Deserialize;
use std::{
fmt, fs,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use wasmtime::Config;
use wasmtime::{Config, RRConfig, RecordConfig, RecordMetadata, ReplayConfig, ReplayMetadata};

pub mod opt;

Expand Down Expand Up @@ -478,6 +479,38 @@ wasmtime_option_group! {
}
}

wasmtime_option_group! {
#[derive(PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct RecordOptions {
/// Filesystem endpoint to store the recorded execution trace
pub path: Option<String>,
/// Include (optional) signatures to facilitate validation checks during replay
/// (see `validate` in replay options).
pub validation_metadata: Option<bool>,
}

enum Record {
...
}
}

wasmtime_option_group! {
#[derive(PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct ReplayOptions {
/// Filesystem endpoint to read the recorded execution trace from
pub path: Option<String>,
/// Dynamic validation checks of record signatures to guarantee faithful replay.
/// Requires record traces to be generated with `validation_metadata` enabled.
pub validate: Option<bool>,
}

enum Replay {
...
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct WasiNnGraph {
pub format: String,
Expand Down Expand Up @@ -528,6 +561,29 @@ pub struct CommonOptions {
#[serde(skip)]
wasi_raw: Vec<opt::CommaSeparated<Wasi>>,

/// Options to enable and configure execution recording, `-R help` to see all.
///
/// Generates of a serialized trace of the Wasm module execution that captures all
/// non-determinism observable by the module. This trace can subsequently be
/// re-executed in a determinstic, embedding-agnostic manner (see the `--replay` option).
///
/// Note: Minimal configs for deterministic Wasm semantics will be
/// enforced during recording by default (NaN canonicalization, deterministic relaxed SIMD)
#[arg(short = 'R', long = "record", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
record_raw: Vec<opt::CommaSeparated<Record>>,

/// Options to enable and configure execution replay, `-P help` to see all.
///
/// Run a determinstic, embedding-agnostic replay execution of the Wasm module
/// according to a prior recorded execution trace (see the `--record` option).
///
/// Note: Minimal configs for deterministic Wasm semantics will be
/// enforced during replay by default (NaN canonicalization, deterministic relaxed SIMD)
#[arg(short = 'P', long = "replay", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
replay_raw: Vec<opt::CommaSeparated<Replay>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High-level thoughts on command-line options:

  • It makes sense to me that record is a general option that the user should be able to set on any Wasmtime subcommand that executes WebAssembly; this includes wasmtime run, wasmtime serve, and maybe others. (Curious: have you tried it on an HTTP server component?)
  • However, replay is a specific and separate thing: for example, if I am replaying an HTTP server execution, I should not be spawning an actual HTTP server on the host side. Basically, the idea of replay is that it is replacing the "real" host with a trace, so it should be properly seen (I think) as a separate top-level driver.

With that in mind, what do you think about turning replay into a command, rather than an option?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to what @cfallin said about replay being a command, not an option.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replay in practice operates very similar to the run command and shares a lot of the top level command line options with it as well.

The replay command as a result would be just a minor super-set of the run setup. It sort of makes sense to do one of two things:

  • Have a top-level replay command, that just adds additional replay options, and calls run under the hood with it
  • Add replay options perhaps just for the run command (as opposed to general options)

Leaning towards the former since maybe in the future replay could support different modes of operation

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replay in practice operates very similar to the run command and shares a lot of the top level command line options with it as well.

Shouldn't all the top-level options pretty much be fixed and effectively identical to whatever they were when the trace was recorded? When does it make sense to specify different options than were used when recording? If the answer is "it doesn't make sense" then I don't think we want to force users to manually provide the same options from before in the replay command.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that's true for most options. I think the ones that might be usable are perhaps just profiling

Copy link
Member

@fitzgen fitzgen Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah great point, very cool to profile an execution after the fact and ignore I/O and the external world.

I think we can probably add CLI options one at a time as they make sense to the replay command, and have the default1 be "whatever was configured during the recording".

Footnotes

  1. I think this is implicit/automatic in that the trace will reflect whatever configuration settings were in effect during recording? At least that is true for anything that affects execution.


// These fields are filled in by the `configure` method below via the
// options parsed from the CLI above. This is what the CLI should use.
#[arg(skip)]
Expand All @@ -554,6 +610,14 @@ pub struct CommonOptions {
#[serde(rename = "wasi", default)]
pub wasi: WasiOptions,

#[arg(skip)]
#[serde(rename = "record", default)]
pub record: RecordOptions,

#[arg(skip)]
#[serde(rename = "replay", default)]
pub replay: ReplayOptions,

/// The target triple; default is the host triple
#[arg(long, value_name = "TARGET")]
#[serde(skip)]
Expand Down Expand Up @@ -600,12 +664,16 @@ impl CommonOptions {
debug_raw: Vec::new(),
wasm_raw: Vec::new(),
wasi_raw: Vec::new(),
record_raw: Vec::new(),
replay_raw: Vec::new(),
configured: true,
opts: Default::default(),
codegen: Default::default(),
debug: Default::default(),
wasm: Default::default(),
wasi: Default::default(),
record: Default::default(),
replay: Default::default(),
target: None,
config: None,
}
Expand All @@ -623,12 +691,16 @@ impl CommonOptions {
self.debug = toml_options.debug;
self.wasm = toml_options.wasm;
self.wasi = toml_options.wasi;
self.record = toml_options.record;
self.replay = toml_options.replay;
}
self.opts.configure_with(&self.opts_raw);
self.codegen.configure_with(&self.codegen_raw);
self.debug.configure_with(&self.debug_raw);
self.wasm.configure_with(&self.wasm_raw);
self.wasi.configure_with(&self.wasi_raw);
self.record.configure_with(&self.record_raw);
self.replay.configure_with(&self.replay_raw);
Ok(())
}

Expand Down Expand Up @@ -970,6 +1042,27 @@ impl CommonOptions {
true => err,
}

let record = &self.record;
let replay = &self.replay;
let rr_cfg = if let Some(path) = record.path.clone() {
Some(RRConfig::from(RecordConfig {
writer_initializer: Arc::new(move || Box::new(fs::File::create(&path).unwrap())),
metadata: RecordMetadata {
add_validation: record.validation_metadata.unwrap_or(false),
},
}))
} else if let Some(path) = replay.path.clone() {
Some(RRConfig::from(ReplayConfig {
reader_initializer: Arc::new(move || Box::new(fs::File::open(&path).unwrap())),
metadata: ReplayMetadata {
validate: replay.validate.unwrap_or(false),
},
}))
} else {
None
};
config.rr(rr_cfg);

Ok(config)
}

Expand Down Expand Up @@ -1074,6 +1167,8 @@ mod tests {
[debug]
[wasm]
[wasi]
[record]
[replay]
"#;
let mut common_options: CommonOptions = toml::from_str(basic_toml).unwrap();
common_options.config(None).unwrap();
Expand Down Expand Up @@ -1195,6 +1290,10 @@ impl fmt::Display for CommonOptions {
wasm,
wasi_raw,
wasi,
record_raw,
record,
replay_raw,
replay,
configured,
target,
config,
Expand All @@ -1211,13 +1310,17 @@ impl fmt::Display for CommonOptions {
let wasi_flags;
let wasm_flags;
let debug_flags;
let record_flags;
let replay_flags;

if *configured {
codegen_flags = codegen.to_options();
debug_flags = debug.to_options();
wasi_flags = wasi.to_options();
wasm_flags = wasm.to_options();
opts_flags = opts.to_options();
record_flags = record.to_options();
replay_flags = replay.to_options();
} else {
codegen_flags = codegen_raw
.iter()
Expand All @@ -1228,6 +1331,16 @@ impl fmt::Display for CommonOptions {
wasi_flags = wasi_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
wasm_flags = wasm_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
opts_flags = opts_raw.iter().flat_map(|t| t.0.iter()).cloned().collect();
record_flags = record_raw
.iter()
.flat_map(|t| t.0.iter())
.cloned()
.collect();
replay_flags = replay_raw
.iter()
.flat_map(|t| t.0.iter())
.cloned()
.collect();
}

for flag in codegen_flags {
Expand All @@ -1245,6 +1358,12 @@ impl fmt::Display for CommonOptions {
for flag in debug_flags {
write!(f, "-D{flag} ")?;
}
for flag in record_flags {
write!(f, "-R{flag} ")?;
}
for flag in replay_flags {
write!(f, "-P{flag} ")?;
}

Ok(())
}
Expand Down
2 changes: 2 additions & 0 deletions crates/environ/src/component/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub struct ComponentArtifacts {
pub types: ComponentTypes,
/// Serialized metadata about all included core wasm modules.
pub static_modules: PrimaryMap<StaticModuleIndex, CompiledModuleInfo>,
/// A SHA-256 checksum of the source Wasm binary from which the component was compiled
pub checksum: [u8; 32],
}

/// Runtime state that a component retains to support its operation.
Expand Down
20 changes: 19 additions & 1 deletion crates/wasmtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ wat = { workspace = true, optional = true }
serde = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true, optional = true }
postcard = { workspace = true }
postcard = { workspace = true, features = ["use-std"] }
indexmap = { workspace = true }
once_cell = { version = "1.12.0", optional = true }
rayon = { version = "1.0", optional = true }
Expand All @@ -62,6 +62,7 @@ smallvec = { workspace = true, optional = true }
hashbrown = { workspace = true, features = ["default-hasher"] }
bitflags = { workspace = true }
futures = { workspace = true, features = ["alloc"], optional = true }
sha2 = "0.10.2"

[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
workspace = true
Expand Down Expand Up @@ -393,3 +394,20 @@ component-model-async = [
"wasmtime-component-macro?/component-model-async",
"dep:futures"
]

# Enables support for record/replay
rr = ["rr-component", "rr-core"]

# Component model RR support
rr-component = ["component-model", "std"]

# Core wasm RR support
rr-core = ["std"]

# Support for type information of recorded events for replay validation
rr-type-validation = []

# Support for input values to recorded events for replay validation.
#
# This can be used to check whether the entire module is truly deterministic
rr-args-validation = []
2 changes: 2 additions & 0 deletions crates/wasmtime/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::Engine;
use crate::hash_map::HashMap;
use crate::hash_set::HashSet;
use crate::prelude::*;
use sha2::{Digest, Sha256};
use std::{
any::Any,
borrow::Cow,
Expand Down Expand Up @@ -198,6 +199,7 @@ pub(crate) fn build_component_artifacts<T: FinishedObject>(
ty,
types,
static_modules: compilation_artifacts.modules,
checksum: Sha256::digest(binary).into(),
};
object.serialize_info(&artifacts);

Expand Down
Loading