Skip to content

Commit

Permalink
Fix handling of Tunables on cross-compiles (#7844)
Browse files Browse the repository at this point in the history
* Fix handling of `Tunables` on cross-compiles

This commit fixes how Wasmtime handles `Tunables` when targetting
non-host platforms (or namely platforms with different pointer widths).
Previously the host's `Tunables` would always be used instead of the
target's tunables which meant that modules couldn't be loaded on the
other platform due to the host having differing tunables by default.

This commit updates tunables in `wasmtime::Config` to all be optional
and loading the actual `Tunables` is deferred until the target is known
during `Engine`-creation time.

* Fix warning
  • Loading branch information
alexcrichton authored Feb 2, 2024
1 parent b6a8abc commit 787e966
Show file tree
Hide file tree
Showing 17 changed files with 227 additions and 131 deletions.
11 changes: 7 additions & 4 deletions crates/cranelift/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use wasmtime_cranelift_shared::isa_builder::IsaBuilder;
use wasmtime_environ::{CacheStore, CompilerBuilder, Setting, Tunables};

struct Builder {
tunables: Tunables,
tunables: Option<Tunables>,
inner: IsaBuilder<CodegenResult<OwnedTargetIsa>>,
linkopts: LinkOptions,
cache_store: Option<Arc<dyn CacheStore>>,
Expand All @@ -39,7 +39,7 @@ pub struct LinkOptions {

pub fn builder(triple: Option<Triple>) -> Result<Box<dyn CompilerBuilder>> {
Ok(Box::new(Builder {
tunables: Tunables::default(),
tunables: None,
inner: IsaBuilder::new(triple, |triple| isa::lookup(triple).map_err(|e| e.into()))?,
linkopts: LinkOptions::default(),
cache_store: None,
Expand Down Expand Up @@ -82,14 +82,17 @@ impl CompilerBuilder for Builder {
}

fn set_tunables(&mut self, tunables: Tunables) -> Result<()> {
self.tunables = tunables;
self.tunables = Some(tunables);
Ok(())
}

fn build(&self) -> Result<Box<dyn wasmtime_environ::Compiler>> {
let isa = self.inner.build()?;
Ok(Box::new(crate::compiler::Compiler::new(
self.tunables.clone(),
self.tunables
.as_ref()
.expect("set_tunables not called")
.clone(),
isa,
self.cache_store.clone(),
self.linkopts.clone(),
Expand Down
2 changes: 1 addition & 1 deletion crates/environ/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub use crate::ref_bits::*;
pub use crate::scopevec::ScopeVec;
pub use crate::stack_map::StackMap;
pub use crate::trap_encoding::*;
pub use crate::tunables::Tunables;
pub use crate::tunables::*;
pub use crate::vmoffsets::*;
pub use object;

Expand Down
99 changes: 61 additions & 38 deletions crates/environ/src/tunables.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serde_derive::{Deserialize, Serialize};

/// Tunable parameters for WebAssembly compilation.
#[derive(Clone, Hash, Serialize, Deserialize)]
#[derive(Clone, Hash, Serialize, Deserialize, Debug)]
pub struct Tunables {
/// For static heaps, the size in wasm pages of the heap protected by bounds
/// checking.
Expand Down Expand Up @@ -56,62 +56,85 @@ pub struct Tunables {
pub tail_callable: bool,
}

impl Default for Tunables {
fn default() -> Self {
let (static_memory_bound, static_memory_offset_guard_size) = if cfg!(miri) {
impl Tunables {
/// Returns a `Tunables` configuration assumed for running code on the host.
pub fn default_host() -> Self {
if cfg!(miri) {
Tunables::default_miri()
} else if cfg!(target_pointer_width = "32") {
Tunables::default_u32()
} else if cfg!(target_pointer_width = "64") {
Tunables::default_u64()
} else {
panic!("unsupported target_pointer_width");
}
}

/// Returns the default set of tunables for running under MIRI.
pub fn default_miri() -> Tunables {
Tunables {
// No virtual memory tricks are available on miri so make these
// limits quite conservative.
((1 << 20) / crate::WASM_PAGE_SIZE as u64, 0)
} else if cfg!(target_pointer_width = "64") {
static_memory_bound: (1 << 20) / crate::WASM_PAGE_SIZE as u64,
static_memory_offset_guard_size: 0,
dynamic_memory_offset_guard_size: 0,
dynamic_memory_growth_reserve: 0,

// General options which have the same defaults regardless of
// architecture.
generate_native_debuginfo: false,
parse_wasm_debuginfo: true,
consume_fuel: false,
epoch_interruption: false,
static_memory_bound_is_maximum: false,
guard_before_linear_memory: true,
generate_address_map: true,
debug_adapter_modules: false,
relaxed_simd_deterministic: false,
tail_callable: false,
}
}

/// Returns the default set of tunables for running under a 32-bit host.
pub fn default_u32() -> Tunables {
Tunables {
// For 32-bit we scale way down to 10MB of reserved memory. This
// impacts performance severely but allows us to have more than a
// few instances running around.
static_memory_bound: (10 * (1 << 20)) / crate::WASM_PAGE_SIZE as u64,
static_memory_offset_guard_size: 0x1_0000,
dynamic_memory_offset_guard_size: 0x1_0000,
dynamic_memory_growth_reserve: 1 << 20, // 1MB

..Tunables::default_miri()
}
}

/// Returns the default set of tunables for running under a 64-bit host.
pub fn default_u64() -> Tunables {
Tunables {
// 64-bit has tons of address space to static memories can have 4gb
// address space reservations liberally by default, allowing us to
// help eliminate bounds checks.
//
// Coupled with a 2 GiB address space guard it lets us translate
// wasm offsets into x86 offsets as aggressively as we can.
(0x1_0000, 0x8000_0000)
} else if cfg!(target_pointer_width = "32") {
// For 32-bit we scale way down to 10MB of reserved memory. This
// impacts performance severely but allows us to have more than a
// few instances running around.
((10 * (1 << 20)) / crate::WASM_PAGE_SIZE as u64, 0x1_0000)
} else {
panic!("unsupported target_pointer_width");
};
Self {
static_memory_bound,
static_memory_offset_guard_size,
static_memory_bound: 0x1_0000,
static_memory_offset_guard_size: 0x8000_0000,

// Size in bytes of the offset guard for dynamic memories.
//
// Allocate a small guard to optimize common cases but without
// wasting too much memory.
dynamic_memory_offset_guard_size: if cfg!(miri) { 0 } else { 0x1_0000 },
dynamic_memory_offset_guard_size: 0x1_0000,

// We've got lots of address space on 64-bit so use a larger
// grow-into-this area, but on 32-bit we aren't as lucky. Miri is
// not exactly fast so reduce memory consumption instead of trying
// to avoid memory movement.
dynamic_memory_growth_reserve: if cfg!(miri) {
0
} else if cfg!(target_pointer_width = "64") {
2 << 30 // 2GB
} else if cfg!(target_pointer_width = "32") {
1 << 20 // 1MB
} else {
panic!("unsupported target_pointer_width");
},
dynamic_memory_growth_reserve: 2 << 30, // 2GB

generate_native_debuginfo: false,
parse_wasm_debuginfo: true,
consume_fuel: false,
epoch_interruption: false,
static_memory_bound_is_maximum: false,
guard_before_linear_memory: true,
generate_address_map: true,
debug_adapter_modules: false,
relaxed_simd_deterministic: false,
tail_callable: false,
..Tunables::default_miri()
}
}
}
6 changes: 3 additions & 3 deletions crates/runtime/src/instance/allocator/pooling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ mod test {
&config,
&Tunables {
static_memory_bound: 1,
..Tunables::default()
..Tunables::default_host()
},
)
.map_err(|e| e.to_string())
Expand All @@ -603,7 +603,7 @@ mod test {
async_stack_zeroing: true,
..PoolingInstanceAllocatorConfig::default()
};
let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default())?;
let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default_host())?;

unsafe {
for _ in 0..255 {
Expand Down Expand Up @@ -637,7 +637,7 @@ mod test {
async_stack_zeroing: false,
..PoolingInstanceAllocatorConfig::default()
};
let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default())?;
let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default_host())?;

unsafe {
for i in 0..255 {
Expand Down
6 changes: 3 additions & 3 deletions crates/runtime/src/instance/allocator/pooling/memory_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ mod tests {
&Tunables {
static_memory_bound: 1,
static_memory_offset_guard_size: 0,
..Tunables::default()
..Tunables::default_host()
},
)?;

Expand Down Expand Up @@ -808,7 +808,7 @@ mod tests {
&Tunables {
static_memory_bound: 1,
static_memory_offset_guard_size: 0,
..Tunables::default()
..Tunables::default_host()
},
)
.unwrap();
Expand All @@ -828,7 +828,7 @@ mod tests {
memory_protection_keys: MpkEnabled::Enable,
..PoolingInstanceAllocatorConfig::default()
};
let pool = MemoryPool::new(&config, &Tunables::default()).unwrap();
let pool = MemoryPool::new(&config, &Tunables::default_host()).unwrap();
assert!(pool.stripes.len() >= 2);

let max_memory_slots = config.limits.total_memories;
Expand Down
6 changes: 3 additions & 3 deletions crates/wasmtime/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub(crate) fn build_artifacts<T: FinishedObject>(
engine: &Engine,
wasm: &[u8],
) -> Result<(T, Option<(CompiledModuleInfo, ModuleTypes)>)> {
let tunables = &engine.config().tunables;
let tunables = engine.tunables();

// First a `ModuleEnvironment` is created which records type information
// about the wasm module. This is where the WebAssembly is parsed and
Expand Down Expand Up @@ -117,7 +117,7 @@ pub(crate) fn build_component_artifacts<T: FinishedObject>(
use wasmtime_environ::component::{CompiledComponentInfo, ComponentArtifacts};
use wasmtime_environ::ScopeVec;

let tunables = &engine.config().tunables;
let tunables = engine.tunables();
let compiler = engine.compiler();

let scope = ScopeVec::new();
Expand Down Expand Up @@ -610,7 +610,7 @@ impl FunctionIndices {
// `symbol_ids_and_locs[i]` is the symbol ID and function location of
// `compiled_funcs[i]`.
let compiler = engine.compiler();
let tunables = &engine.config().tunables;
let tunables = engine.tunables();
let symbol_ids_and_locs = compiler.append_code(
&mut obj,
&compiled_funcs,
Expand Down
Loading

0 comments on commit 787e966

Please sign in to comment.