Skip to content

Commit

Permalink
Add a signals-based-traps Cargo compile-time feature (#9614)
Browse files Browse the repository at this point in the history
* Gate signal handlers behind a new Cargo feature

This commit adds a new on-by-default Cargo feature to the `wasmtime`
crate named `signals-based-traps`. This is modeled after the
`Config::signals_based_traps` configuration at runtime and can be used
to statically disable the use of signal handlers in Wasmtime. This
notably reduces the number of platform dependencies that Wasmtime has
and provides a mode of avoiding relying on signals altogether.

This introduces a new `MallocMemory` which is a linear memory backed by
the system allocator. This new type of memory is enabled when virtual
memory guards are disabled and signals-based-traps are disabled. This
means that this new type of memory will be candidate for fuzzing for
example.

prtest:full

* Fix rebase conflict

* Refactor `MmapVec` documentation and representation

* Remove no-longer-needed `Arc`
* Document it may be backed by `Vec<u8>`
  • Loading branch information
alexcrichton authored Nov 19, 2024
1 parent dab9dc8 commit d3132c9
Show file tree
Hide file tree
Showing 58 changed files with 1,239 additions and 715 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ jobs:
-p wasmtime --no-default-features --features threads
-p wasmtime --no-default-features --features runtime,threads
-p wasmtime --no-default-features --features cranelift,threads
-p wasmtime --no-default-features --features runtime,signals-based-traps
-p wasmtime --no-default-features --features runtime,gc,component-model,signals-based-traps
-p wasmtime --features incremental-cache
-p wasmtime --all-features
Expand Down Expand Up @@ -443,7 +445,9 @@ jobs:
- uses: ./.github/actions/install-rust

- run: rustup target add x86_64-unknown-none
- run: cargo check --target x86_64-unknown-none -p wasmtime --no-default-features --features runtime,component-model
- run: cargo check --target x86_64-unknown-none -p wasmtime --no-default-features --features runtime,gc,component-model
- run: cargo check --target x86_64-unknown-none -p wasmtime --no-default-features --features runtime,gc,component-model,signals-based-traps
- run: cargo check --target x86_64-unknown-none -p cranelift-control --no-default-features
- run: cargo check --target x86_64-unknown-none -p pulley-interpreter --features encode,decode,disas,interp

Expand Down Expand Up @@ -952,6 +956,12 @@ jobs:
# that the regeneration process didn't change anything in-tree.
- run: git diff --exit-code

# Test some other feature combinations
- run: ./build.sh x86_64-unknown-none
working-directory: ./examples/min-platform
env:
WASMTIME_SIGNALS_BASED_TRAPS: 1

# Add the `wasmtime-platform.h` file as a release artifact
- uses: actions/upload-artifact@v4
with:
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ rustix = { workspace = true, features = ["mm", "param", "process"] }

[dev-dependencies]
# depend again on wasmtime to activate its default features for tests
wasmtime = { workspace = true, features = ['default', 'winch', 'pulley', 'all-arch', 'call-hook', 'memory-protection-keys'] }
wasmtime = { workspace = true, features = ['default', 'winch', 'pulley', 'all-arch', 'call-hook', 'memory-protection-keys', 'signals-based-traps'] }
env_logger = { workspace = true }
log = { workspace = true }
filecheck = { workspace = true }
Expand Down Expand Up @@ -392,6 +392,7 @@ default = [
"addr2line",
"debug-builtins",
"component-model",
"signals-based-traps",
"threads",
"gc",
"gc-drc",
Expand Down Expand Up @@ -450,6 +451,7 @@ threads = ["wasmtime-cli-flags/threads"]
gc = ["wasmtime-cli-flags/gc", "wasmtime/gc"]
gc-drc = ["gc", "wasmtime/gc-drc", "wasmtime-cli-flags/gc-drc"]
gc-null = ["gc", "wasmtime/gc-null", "wasmtime-cli-flags/gc-null"]
signals-based-traps = ["wasmtime/signals-based-traps", "wasmtime-cli-flags/signals-based-traps"]

# CLI subcommands for the `wasmtime` executable. See `wasmtime $cmd --help`
# for more information on each subcommand.
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 @@ -35,3 +35,4 @@ gc-drc = ["gc", "wasmtime/gc-drc"]
gc-null = ["gc", "wasmtime/gc-null"]
threads = ["wasmtime/threads"]
memory-protection-keys = ["wasmtime/memory-protection-keys"]
signals-based-traps = ["wasmtime/signals-based-traps"]
54 changes: 34 additions & 20 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,39 +642,51 @@ impl CommonOptions {
true => err,
}

if let Some(max) = self
let memory_reservation = self
.opts
.memory_reservation
.or(self.opts.static_memory_maximum_size)
{
config.memory_reservation(max);
.or(self.opts.static_memory_maximum_size);
match_feature! {
["signals-based-traps" : memory_reservation]
size => config.memory_reservation(size),
_ => err,
}

if let Some(enable) = self.opts.static_memory_forced {
config.memory_may_move(!enable);
match_feature! {
["signals-based-traps" : self.opts.static_memory_forced]
enable => config.memory_may_move(!enable),
_ => err,
}
if let Some(enable) = self.opts.memory_may_move {
config.memory_may_move(enable);
match_feature! {
["signals-based-traps" : self.opts.memory_may_move]
enable => config.memory_may_move(enable),
_ => err,
}

if let Some(size) = self
let memory_guard_size = self
.opts
.static_memory_guard_size
.or(self.opts.dynamic_memory_guard_size)
.or(self.opts.memory_guard_size)
{
config.memory_guard_size(size);
.or(self.opts.memory_guard_size);
match_feature! {
["signals-based-traps" : memory_guard_size]
size => config.memory_guard_size(size),
_ => err,
}

if let Some(size) = self
let mem_for_growth = self
.opts
.memory_reservation_for_growth
.or(self.opts.dynamic_memory_reserved_for_growth)
{
config.memory_reservation_for_growth(size);
.or(self.opts.dynamic_memory_reserved_for_growth);
match_feature! {
["signals-based-traps" : mem_for_growth]
size => config.memory_reservation_for_growth(size),
_ => err,
}
if let Some(enable) = self.opts.guard_before_linear_memory {
config.guard_before_linear_memory(enable);
match_feature! {
["signals-based-traps" : self.opts.guard_before_linear_memory]
enable => config.guard_before_linear_memory(enable),
_ => err,
}
if let Some(enable) = self.opts.table_lazy_init {
config.table_lazy_init(enable);
Expand All @@ -694,8 +706,10 @@ impl CommonOptions {
if let Some(enable) = self.opts.memory_init_cow {
config.memory_init_cow(enable);
}
if let Some(enable) = self.opts.signals_based_traps {
config.signals_based_traps(enable);
match_feature! {
["signals-based-traps" : self.opts.signals_based_traps]
enable => config.signals_based_traps(enable),
_ => err,
}
if let Some(enable) = self.codegen.native_unwind_info {
config.native_unwind_info(enable);
Expand Down
6 changes: 6 additions & 0 deletions crates/environ/src/obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ pub const EF_WASMTIME_MODULE: u32 = 1 << 0;
/// component.
pub const EF_WASMTIME_COMPONENT: u32 = 1 << 1;

/// Flag for the `sh_flags` field in the ELF text section that indicates that
/// the text section does not itself need to be executable. This is used for the
/// Pulley target, for example, to indicate that it does not need to be made
/// natively executable as it does not contain actual native code.
pub const SH_WASMTIME_NOT_EXECUTED: u64 = 1 << 0;

/// A custom Wasmtime-specific section of our compilation image which stores
/// mapping data from offsets in the image to offset in the original wasm
/// binary.
Expand Down
2 changes: 1 addition & 1 deletion crates/fuzzing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ target-lexicon = { workspace = true }
tempfile = "3.3.0"
wasmparser = { workspace = true }
wasmprinter = { workspace = true }
wasmtime = { workspace = true, features = ['default', 'winch', 'gc', 'memory-protection-keys'] }
wasmtime = { workspace = true, features = ['default', 'winch', 'gc', 'memory-protection-keys', 'signals-based-traps'] }
wasmtime-wast = { workspace = true, features = ['component-model'] }
wasm-encoder = { workspace = true }
wasm-smith = { workspace = true }
Expand Down
5 changes: 4 additions & 1 deletion crates/fuzzing/src/oracles/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ pub fn check_memory_accesses(input: MemoryAccesses) {
Ok(x) => x,
Err(e) => {
log::info!("Failed to instantiate: {e:?}");
assert!(format!("{e:?}").contains("Cannot allocate memory"));
assert!(
format!("{e:?}").contains("Cannot allocate memory"),
"bad error: {e:?}",
);
return;
}
};
Expand Down
2 changes: 1 addition & 1 deletion crates/wasi-nn/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ cap-std = { workspace = true }
libtest-mimic = { workspace = true }
test-programs-artifacts = { workspace = true }
wasmtime-wasi = { workspace = true, features = ["preview1"] }
wasmtime = { workspace = true, features = ["cranelift"] }
wasmtime = { workspace = true, features = ["cranelift", 'signals-based-traps'] }
tracing-subscriber = { workspace = true }

[features]
Expand Down
2 changes: 1 addition & 1 deletion crates/wasi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ test-log = { workspace = true }
tracing-subscriber = { workspace = true }
test-programs-artifacts = { workspace = true }
tempfile = { workspace = true }
wasmtime = { workspace = true, features = ['cranelift', 'incremental-cache'] }
wasmtime = { workspace = true, features = ['cranelift', 'incremental-cache', 'signals-based-traps'] }

[target.'cfg(unix)'.dependencies]
rustix = { workspace = true, features = ["event", "fs", "net"] }
Expand Down
39 changes: 35 additions & 4 deletions crates/wasmtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ default = [
'component-model',
'threads',
'std',
'signals-based-traps',
]

# An on-by-default feature enabling runtime compilation of WebAssembly modules
Expand Down Expand Up @@ -188,7 +189,11 @@ async = [
]

# Enables support for the pooling instance allocation strategy
pooling-allocator = ["runtime", "std"]
pooling-allocator = [
"runtime",
"std", # not ported to no_std yet
"signals-based-traps", # pooling allocation always uses mmap at this time
]

# Enables support for all architectures in Cranelift, allowing
# cross-compilation using the `wasmtime` crate's API, notably the
Expand Down Expand Up @@ -236,7 +241,6 @@ runtime = [
"dep:mach2",
"dep:memfd",
"dep:wasmtime-asm-macros",
"dep:wasmtime-jit-icache-coherence",
"dep:wasmtime-slab",
"dep:wasmtime-versioned-export-macros",
"dep:windows-sys",
Expand All @@ -263,7 +267,11 @@ runtime = [
#
# You can additionally configure which GC implementations are enabled via the
# `gc-drc` and `gc-null` features.
gc = ["wasmtime-environ/gc", "wasmtime-cranelift?/gc"]
gc = [
"wasmtime-environ/gc",
"wasmtime-cranelift?/gc",
"signals-based-traps", # not ported to non-mmap schemes yet
]

# Enable the deferred reference counting garbage collector.
gc-drc = ["gc", "wasmtime-environ/gc-drc", "wasmtime-cranelift?/gc-drc"]
Expand All @@ -272,7 +280,11 @@ gc-drc = ["gc", "wasmtime-environ/gc-drc", "wasmtime-cranelift?/gc-drc"]
gc-null = ["gc", "wasmtime-environ/gc-null", "wasmtime-cranelift?/gc-null"]

# Enable runtime support for the WebAssembly threads proposal.
threads = ["wasmtime-cranelift?/threads", "std"]
threads = [
"wasmtime-cranelift?/threads",
"std",
"signals-based-traps",
]

# Controls whether backtraces will attempt to parse DWARF information in
# WebAssembly modules and components to provide filenames and line numbers in
Expand All @@ -290,6 +302,13 @@ std = [
'wasmtime-environ/std',
'object/std',
'once_cell',
# technically this isn't necessary but once you have the standard library you
# probably want things to go fast in which case you've probably got signal
# handlers and such so implicitly enable this. This also helps reduce the
# verbosity of others depending on `wasmtime` with `default-features = false`
# where frequently `std` is enabled and this feature will typically want to be
# enabled by default as well.
'signals-based-traps',
]

# Enables support for the `Store::call_hook` API which enables injecting custom
Expand Down Expand Up @@ -320,3 +339,15 @@ reexport-wasmparser = []
# Enables instances of the traits defined in the wasm-wave crate, which
# provides a human-readable text format for component values.
wave = ["dep:wasm-wave"]

# Gates compile-time support for host signals-based-traps.
#
# Traps based on signals, such as SIGSEGV, are useful for accelerating
# WebAssembly by removing explicit checks and letting the hardware deliver
# signals instead. This feature is enabled by default and gates a number
# of implementations within Wasmtime that may rely on virtual memory, for
# example. Embedded systems or smaller systems may wish to disable this feature
# to reduce the runtime requirements of Wasmtime.
signals-based-traps = [
"dep:wasmtime-jit-icache-coherence",
]
6 changes: 5 additions & 1 deletion crates/wasmtime/src/compile/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ impl FinishedObject for MmapVecWrapper {

fn write_bytes(&mut self, val: &[u8]) {
let mmap = self.mmap.as_mut().expect("write before reserve");
mmap[self.len..][..val.len()].copy_from_slice(val);
// SAFETY: the `mmap` has not be made readonly yet so it should
// be safe to mutate it.
unsafe {
mmap.as_mut_slice()[self.len..][..val.len()].copy_from_slice(val);
}
self.len += val.len();
}
}
Expand Down
27 changes: 26 additions & 1 deletion crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ impl Config {
async_support: false,
module_version: ModuleVersionStrategy::default(),
parallel_compilation: !cfg!(miri),
memory_init_cow: true,
// If signals are disabled then virtual memory is probably mostly
// disabled so also disable the use of CoW by default.
memory_init_cow: cfg!(feature = "signals-based-traps"),
memory_guaranteed_dense_image_size: 16 << 20,
force_memory_init_memfd: false,
wmemcheck: false,
Expand Down Expand Up @@ -1496,6 +1498,7 @@ impl Config {
///
/// For 32-bit platforms this value defaults to 10MiB. This means that
/// bounds checks will be required on 32-bit platforms.
#[cfg(feature = "signals-based-traps")]
pub fn memory_reservation(&mut self, bytes: u64) -> &mut Self {
self.tunables.memory_reservation = Some(bytes);
self
Expand Down Expand Up @@ -1531,6 +1534,7 @@ impl Config {
/// the memory configuration works at runtime.
///
/// The default value for this option is `true`.
#[cfg(feature = "signals-based-traps")]
pub fn memory_may_move(&mut self, enable: bool) -> &mut Self {
self.tunables.memory_may_move = Some(enable);
self
Expand Down Expand Up @@ -1579,6 +1583,7 @@ impl Config {
/// allows eliminating almost all bounds checks on loads/stores with an
/// immediate offset of less than 32MiB. On 32-bit platforms this defaults
/// to 64KiB.
#[cfg(feature = "signals-based-traps")]
pub fn memory_guard_size(&mut self, bytes: u64) -> &mut Self {
self.tunables.memory_guard_size = Some(bytes);
self
Expand Down Expand Up @@ -1668,6 +1673,7 @@ impl Config {
/// ## Default
///
/// This value defaults to `true`.
#[cfg(feature = "signals-based-traps")]
pub fn guard_before_linear_memory(&mut self, enable: bool) -> &mut Self {
self.tunables.guard_before_linear_memory = Some(enable);
self
Expand Down Expand Up @@ -1783,6 +1789,7 @@ impl Config {
/// [`Module::deserialize_file`]: crate::Module::deserialize_file
/// [`Module`]: crate::Module
/// [IPI]: https://en.wikipedia.org/wiki/Inter-processor_interrupt
#[cfg(feature = "signals-based-traps")]
pub fn memory_init_cow(&mut self, enable: bool) -> &mut Self {
self.memory_init_cow = enable;
self
Expand Down Expand Up @@ -2036,6 +2043,16 @@ impl Config {
None => Tunables::default_host(),
};

// When signals-based traps are disabled use slightly different defaults
// for tunables to be more amenable to `MallocMemory`. Note that these
// can still be overridden by config options.
if !cfg!(feature = "signals-based-traps") {
tunables.signals_based_traps = false;
tunables.memory_reservation = 0;
tunables.memory_guard_size = 0;
tunables.memory_reservation_for_growth = 1 << 20; // 1MB
}

self.tunables.configure(&mut tunables);

// If we're going to compile with winch, we must use the winch calling convention.
Expand All @@ -2060,6 +2077,13 @@ impl Config {
None
};

// These `Config` accessors are disabled at compile time so double-check
// the defaults here.
if !cfg!(feature = "signals-based-traps") {
assert!(!tunables.signals_based_traps);
assert!(!self.memory_init_cow);
}

Ok((tunables, features))
}

Expand Down Expand Up @@ -2394,6 +2418,7 @@ impl Config {
/// are enabled by default.
///
/// **Note** Disabling this option is not compatible with the Winch compiler.
#[cfg(feature = "signals-based-traps")]
pub fn signals_based_traps(&mut self, enable: bool) -> &mut Self {
self.tunables.signals_based_traps = Some(enable);
self
Expand Down
Loading

0 comments on commit d3132c9

Please sign in to comment.