Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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.toml
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ component-model-async = [
"wasmtime-wasi-http?/p3",
"dep:futures",
]
rr = ["wasmtime/rr", "component-model", "wasmtime-cli-flags/rr", "run"]

# This feature, when enabled, will statically compile out all logging statements
# throughout Wasmtime and its dependencies.
Expand Down
1 change: 1 addition & 0 deletions crates/cli-flags/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ memory-protection-keys = ["wasmtime/memory-protection-keys"]
pulley = ["wasmtime/pulley"]
stack-switching = ["wasmtime/stack-switching"]
debug = ["wasmtime/debug"]
rr = ["wasmtime/rr", "component-model"]
61 changes: 61 additions & 0 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,25 @@ wasmtime_option_group! {
}
}

wasmtime_option_group! {
#[derive(PartialEq, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct RecordOptions {
/// Filename for the recorded execution trace (or empty string to skip writing a file).
pub path: Option<String>,
/// Include (optional) signatures to facilitate validation checks during replay
/// (see `wasmtime replay` for details).
pub validation_metadata: Option<bool>,
/// Window size of internal buffering for record events (large windows offer more opportunities
/// for coalescing events at the cost of memory usage).
pub event_window_size: Option<usize>,
Comment thread
arjunr2 marked this conversation as resolved.
}

enum Record {
...
}
}

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

/// Options to enable and configure execution recording, `-R help` to see all.
///
/// Generates 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 `wasmtime replay` command).
///
/// Note: Minimal configuration options 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>>,

// 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 @@ -580,6 +611,10 @@ pub struct CommonOptions {
#[serde(rename = "wasi", default)]
pub wasi: WasiOptions,

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

/// The target triple; default is the host triple
#[arg(long, value_name = "TARGET")]
#[serde(skip)]
Expand Down Expand Up @@ -626,12 +661,14 @@ impl CommonOptions {
debug_raw: Vec::new(),
wasm_raw: Vec::new(),
wasi_raw: Vec::new(),
record_raw: Vec::new(),
configured: true,
opts: Default::default(),
codegen: Default::default(),
debug: Default::default(),
wasm: Default::default(),
wasi: Default::default(),
record: Default::default(),
target: None,
config: None,
}
Expand All @@ -649,12 +686,14 @@ impl CommonOptions {
self.debug = toml_options.debug;
self.wasm = toml_options.wasm;
self.wasi = toml_options.wasi;
self.record = toml_options.record;
}
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);
Ok(())
}

Expand Down Expand Up @@ -1017,6 +1056,15 @@ impl CommonOptions {
config.shared_memory(enable);
}

let record = &self.record;
match_feature! {
["rr" : &record.path]
_path => {
bail!("recording configuration for `rr` feature is not supported yet");
},
_ => err,
}

Ok(config)
}

Expand Down Expand Up @@ -1127,6 +1175,7 @@ mod tests {
[debug]
[wasm]
[wasi]
[record]
"#;
let mut common_options: CommonOptions = toml::from_str(basic_toml).unwrap();
common_options.config(None).unwrap();
Expand Down Expand Up @@ -1249,6 +1298,8 @@ impl fmt::Display for CommonOptions {
wasm,
wasi_raw,
wasi,
record_raw,
record,
configured,
target,
config,
Expand All @@ -1265,13 +1316,15 @@ impl fmt::Display for CommonOptions {
let wasi_flags;
let wasm_flags;
let debug_flags;
let record_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();
} else {
codegen_flags = codegen_raw
.iter()
Expand All @@ -1282,6 +1335,11 @@ 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();
}

for flag in codegen_flags {
Expand All @@ -1299,6 +1357,9 @@ impl fmt::Display for CommonOptions {
for flag in debug_flags {
write!(f, "-D{flag} ")?;
}
for flag in record_flags {
write!(f, "-R{flag} ")?;
}

Ok(())
}
Expand Down
3 changes: 3 additions & 0 deletions crates/wasmtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,6 @@ debug = [

# Enables support for defining compile-time builtins.
compile-time-builtins = ['anyhow', 'dep:wasm-compose', 'dep:tempfile']

# Enable support for the common base infrastructure of record/replay
rr = ["component-model"]
73 changes: 73 additions & 0 deletions crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,31 @@ impl core::hash::Hash for ModuleVersionStrategy {
}
}

impl ModuleVersionStrategy {
/// Get the string-encoding version of the module.
pub fn as_str(&self) -> &str {
match &self {
Self::WasmtimeVersion => env!("CARGO_PKG_VERSION_MAJOR"),
Self::Custom(c) => c,
Self::None => "",
}
}
}

/// Configuration for record/replay
#[derive(Clone)]
#[non_exhaustive]
pub enum RRConfig {
#[cfg(feature = "rr")]
/// Recording on store is enabled
Recording,
#[cfg(feature = "rr")]
/// Replaying on store is enabled
Replaying,
/// No record/replay is enabled
None,
}

/// Global configuration options used to create an [`Engine`](crate::Engine)
/// and customize its behavior.
///
Expand Down Expand Up @@ -164,6 +189,7 @@ pub struct Config {
pub(crate) detect_host_feature: Option<fn(&str) -> Option<bool>>,
pub(crate) x86_float_abi_ok: Option<bool>,
pub(crate) shared_memory: bool,
pub(crate) rr_config: RRConfig,
}

/// User-provided configuration for the compiler.
Expand Down Expand Up @@ -273,6 +299,7 @@ impl Config {
detect_host_feature: None,
x86_float_abi_ok: None,
shared_memory: false,
rr_config: RRConfig::None,
};
ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
ret
Expand Down Expand Up @@ -2356,6 +2383,14 @@ impl Config {
bail!("exceptions support requires garbage collection (GC) to be enabled in the build");
}

match &self.rr_config {
#[cfg(feature = "rr")]
RRConfig::Recording | RRConfig::Replaying => {
self.validate_rr_determinism_conflicts()?;
}
RRConfig::None => {}
};

let mut tunables = Tunables::default_for_target(&self.compiler_target())?;

// By default this is enabled with the Cargo feature, and if the feature
Expand Down Expand Up @@ -2977,6 +3012,44 @@ impl Config {
self.tunables.concurrency_support = Some(enable);
self
}

/// Validate if the current configuration has conflicting overrides that prevent
/// execution determinism. Returns an error if a conflict exists.
///
/// Note: Keep this in sync with [`Config::enforce_determinism`].
#[inline]
#[cfg(feature = "rr")]
pub(crate) fn validate_rr_determinism_conflicts(&self) -> Result<()> {
if let Some(v) = self.tunables.relaxed_simd_deterministic {
if v == false {
bail!("Relaxed deterministic SIMD cannot be disabled when determinism is enforced");
}
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
if let Some(v) = self
.compiler_config
.as_ref()
.and_then(|c| c.settings.get("enable_nan_canonicalization"))
{
if v != "true" {
bail!("NaN canonicalization cannot be disabled when determinism is enforced");
}
}
Ok(())
}

/// Enable execution trace recording or replaying to the configuration.
///
/// When either recording/replaying are enabled, validation fails if settings
/// that control determinism are not set appropriately. In particular, RR requires
/// doing the following:
/// * Enabling NaN canonicalization with [`Config::cranelift_nan_canonicalization`].
/// * Enabling deterministic relaxed SIMD with [`Config::relaxed_simd_deterministic`].
#[inline]
pub fn rr(&mut self, cfg: RRConfig) -> &mut Self {
self.rr_config = cfg;
self
}
}

impl Default for Config {
Expand Down
25 changes: 25 additions & 0 deletions crates/wasmtime/src/engine.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::Config;
use crate::RRConfig;
use crate::prelude::*;
#[cfg(feature = "runtime")]
pub use crate::runtime::code_memory::CustomCodeMemory;
Expand Down Expand Up @@ -264,6 +265,30 @@ impl Engine {
Arc::ptr_eq(&a.inner, &b.inner)
}

/// Returns whether the engine is configured to support execution recording
#[inline]
pub fn is_recording(&self) -> bool {
match self.config().rr_config {
#[cfg(feature = "rr")]
RRConfig::Recording => true,
#[cfg(feature = "rr")]
RRConfig::Replaying => false,
RRConfig::None => false,
}
}

/// Returns whether the engine is configured to support execution replaying
#[inline]
pub fn is_replaying(&self) -> bool {
match self.config().rr_config {
#[cfg(feature = "rr")]
RRConfig::Replaying => true,
#[cfg(feature = "rr")]
RRConfig::Recording => false,
RRConfig::None => false,
}
}

/// Detects whether the bytes provided are a precompiled object produced by
/// Wasmtime.
///
Expand Down
18 changes: 4 additions & 14 deletions crates/wasmtime/src/engine/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,13 @@ pub fn check_compatible(engine: &Engine, mmap: &[u8], expected: ObjectKind) -> R
};

match &engine.config().module_version {
ModuleVersionStrategy::WasmtimeVersion => {
let version = core::str::from_utf8(version)?;
if version != env!("CARGO_PKG_VERSION_MAJOR") {
bail!("Module was compiled with incompatible Wasmtime version '{version}'");
}
}
ModuleVersionStrategy::Custom(v) => {
ModuleVersionStrategy::None => { /* ignore the version info, accept all */ }
_ => {
let version = core::str::from_utf8(&version)?;
if version != v {
if version != engine.config().module_version.as_str() {
bail!("Module was compiled with incompatible version '{version}'");
}
}
ModuleVersionStrategy::None => { /* ignore the version info, accept all */ }
}
postcard::from_bytes::<Metadata<'_>>(data)?.check_compatible(engine)
}
Expand All @@ -121,11 +115,7 @@ pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>, metadata: &Me
);
let mut data = Vec::new();
data.push(VERSION);
let version = match &engine.config().module_version {
ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION_MAJOR"),
ModuleVersionStrategy::Custom(c) => c,
ModuleVersionStrategy::None => "",
};
let version = engine.config().module_version.as_str();
// This precondition is checked in Config::module_version:
assert!(
version.len() < 256,
Expand Down