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
10 changes: 10 additions & 0 deletions Cargo.lock

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

72 changes: 72 additions & 0 deletions crates/wasmtime/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ pub struct Config {
pub(crate) coredump_on_trap: bool,
pub(crate) macos_use_mach_ports: bool,
pub(crate) detect_host_feature: Option<fn(&str) -> Option<bool>>,
pub(crate) x86_float_abi_ok: Option<bool>,
}

/// User-provided configuration for the compiler.
Expand Down Expand Up @@ -271,6 +272,7 @@ impl Config {
detect_host_feature: Some(detect_host_feature),
#[cfg(not(feature = "std"))]
detect_host_feature: None,
x86_float_abi_ok: None,
};
#[cfg(any(feature = "cranelift", feature = "winch"))]
{
Expand Down Expand Up @@ -2701,6 +2703,76 @@ impl Config {
pub fn gc_support(&mut self, enable: bool) -> &mut Self {
self.wasm_feature(WasmFeatures::GC_TYPES, enable)
}

/// Explicitly indicate or not whether the host is using a hardware float
/// ABI on x86 targets.
///
/// This configuration option is only applicable on the
/// `x86_64-unknown-none` Rust target and has no effect on other host
/// targets. The `x86_64-unknown-none` Rust target does not support hardware
/// floats by default and uses a "soft float" implementation and ABI. This
/// means that `f32`, for example, is passed in a general-purpose register
/// between functions instead of a floating-point register. This does not
/// match Cranelift's ABI for `f32` where it's passed in floating-point
/// registers. Cranelift does not have support for a "soft float"
/// implementation where all floating-point operations are lowered to
/// libcalls.
///
/// This means that for the `x86_64-unknown-none` target the ABI between
/// Wasmtime's libcalls and the host is incompatible when floats are used.
/// This further means that, by default, Wasmtime is unable to load native
/// code when compiled to the `x86_64-unknown-none` target. The purpose of
/// this option is to explicitly allow loading code and bypass this check.
///
/// Setting this configuration option to `true` indicates that either:
/// (a) the Rust target is compiled with the hard-float ABI manually via
/// `-Zbuild-std` and a custom target JSON configuration, or (b) sufficient
/// x86 features have been enabled in the compiler such that float libcalls
/// will not be used in Wasmtime. For (a) there is no way in Rust at this
/// time to detect whether a hard-float or soft-float ABI is in use on
/// stable Rust, so this manual opt-in is required. For (b) the only
/// instance where Wasmtime passes a floating-point value in a register
/// between the host and compiled wasm code is with libcalls.
///
/// Float-based libcalls are only used when the compilation target for a
/// wasm module has insufficient target features enabled for native
/// support. For example SSE4.1 is required for the `f32.ceil` WebAssembly
/// instruction to be compiled to a native instruction. If SSE4.1 is not
/// enabled then `f32.ceil` is translated to a "libcall" which is
/// implemented on the host. Float-based libcalls can be avoided with
/// sufficient target features enabled, for example:
///
/// * `self.cranelift_flag_enable("has_sse3")`
/// * `self.cranelift_flag_enable("has_ssse3")`
/// * `self.cranelift_flag_enable("has_sse41")`
/// * `self.cranelift_flag_enable("has_sse42")`
/// * `self.cranelift_flag_enable("has_fma")`
///
/// Note that when these features are enabled Wasmtime will perform a
/// runtime check to determine that the host actually has the feature
/// present.
///
/// For some more discussion see [#11506].
///
/// [#11506]: https://github.com/bytecodealliance/wasmtime/issues/11506
///
/// # Safety
///
/// This method is not safe because it cannot be detected in Rust right now
/// whether the host is compiled with a soft or hard float ABI. Additionally
/// if the host is compiled with a soft float ABI disabling this check does
/// not ensure that the wasm module in question has zero usage of floats
/// in the boundary to the host.
///
/// Safely using this method requires one of:
///
/// * The host target is compiled to use hardware floats.
/// * Wasm modules loaded are compiled with enough x86 Cranelift features
/// enabled to avoid float-related hostcalls.
pub unsafe fn x86_float_abi_ok(&mut self, enable: bool) -> &mut Self {
self.x86_float_abi_ok = Some(enable);
self
}
}

impl Default for Config {
Expand Down
50 changes: 34 additions & 16 deletions crates/wasmtime/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ struct EngineInner {

/// One-time check of whether the compiler's settings, if present, are
/// compatible with the native host.
#[cfg(any(feature = "cranelift", feature = "winch"))]
compatible_with_native_host: crate::sync::OnceLock<Result<(), String>>,
}

Expand Down Expand Up @@ -141,7 +140,6 @@ impl Engine {
signatures: TypeRegistry::new(),
#[cfg(all(feature = "runtime", target_has_atomic = "64"))]
epoch: AtomicU64::new(0),
#[cfg(any(feature = "cranelift", feature = "winch"))]
compatible_with_native_host: Default::default(),
config,
tunables,
Expand Down Expand Up @@ -293,7 +291,6 @@ impl Engine {
/// engine can indeed load modules for the configured compiler (if any).
/// Note that if cranelift is disabled this trivially returns `Ok` because
/// loaded serialized modules are checked separately.
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub(crate) fn check_compatible_with_native_host(&self) -> Result<()> {
self.inner
.compatible_with_native_host
Expand All @@ -302,18 +299,16 @@ impl Engine {
.map_err(anyhow::Error::msg)
}

#[cfg(any(feature = "cranelift", feature = "winch"))]
fn _check_compatible_with_native_host(&self) -> Result<(), String> {
use target_lexicon::Triple;

let compiler = self.compiler();

let target = compiler.triple();
let host = Triple::host();
let target = self.config().compiler_target();

let target_matches_host = || {
// If the host target and target triple match, then it's valid
// to run results of compilation on this host.
if host == *target {
if host == target {
return true;
}

Expand All @@ -337,12 +332,16 @@ impl Engine {
));
}

// Also double-check all compiler settings
for (key, value) in compiler.flags().iter() {
self.check_compatible_with_shared_flag(key, value)?;
}
for (key, value) in compiler.isa_flags().iter() {
self.check_compatible_with_isa_flag(key, value)?;
#[cfg(any(feature = "cranelift", feature = "winch"))]
{
let compiler = self.compiler();
// Also double-check all compiler settings
for (key, value) in compiler.flags().iter() {
self.check_compatible_with_shared_flag(key, value)?;
}
for (key, value) in compiler.isa_flags().iter() {
self.check_compatible_with_isa_flag(key, value)?;
}
}

// Double-check that this configuration isn't requesting capabilities
Expand All @@ -356,6 +355,22 @@ impl Engine {
if !cfg!(target_has_atomic = "64") && self.tunables().epoch_interruption {
return Err("epochs currently require 64-bit atomics".into());
}

// Double-check that the host's float ABI matches Cranelift's float ABI.
// See `Config::x86_float_abi_ok` for some more
// information.
if target == target_lexicon::triple!("x86_64-unknown-none")
&& self.config().x86_float_abi_ok != Some(true)
{
return Err("\
the x86_64-unknown-none target by default uses a soft-float ABI that is \
incompatible with Cranelift and Wasmtime -- use \
`Config::x86_float_abi_ok` to disable this check and see more \
information about this check\
"
.into());
}

Ok(())
}

Expand Down Expand Up @@ -601,8 +616,8 @@ impl Engine {
available on the host",
)),
None => Err(format!(
"failed to detect if target-specific flag {flag:?} is \
available at runtime"
"failed to detect if target-specific flag {host_feature:?} is \
available at runtime (compile setting {flag:?})"
)),
}
}
Expand Down Expand Up @@ -876,6 +891,9 @@ impl Engine {
mmap: crate::runtime::vm::MmapVec,
expected: ObjectKind,
) -> Result<Arc<crate::CodeMemory>> {
self.check_compatible_with_native_host()
.context("compilation settings are not compatible with the native host")?;

serialization::check_compatible(self, &mmap, expected)?;
let mut code = crate::CodeMemory::new(self, mmap)?;
code.publish()?;
Expand Down
3 changes: 3 additions & 0 deletions examples/min-platform/embedding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ wasmtime-wasi-io = { workspace = true, optional = true }
# Memory allocator used in this example (not required, however)
dlmalloc = "0.2.4"

[target.'cfg(target_arch = "x86_64")'.dependencies]
raw-cpuid = "11.5.0"

[lib]
crate-type = ['staticlib']
test = false
Expand Down
75 changes: 67 additions & 8 deletions examples/min-platform/embedding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
extern crate alloc;

use alloc::string::ToString;
use anyhow::Result;
use anyhow::{Result, ensure};
use core::ptr;
use wasmtime::{Engine, Instance, Linker, Module, Store};
use wasmtime::{Config, Engine, Instance, Linker, Module, Store};

mod allocator;
mod panic;
Expand All @@ -29,14 +29,17 @@ pub unsafe extern "C" fn run(
simple_add_size: usize,
simple_host_fn_module: *const u8,
simple_host_fn_size: usize,
simple_floats_module: *const u8,
simple_floats_size: usize,
) -> usize {
unsafe {
let buf = core::slice::from_raw_parts_mut(error_buf, error_size);
let smoke = core::slice::from_raw_parts(smoke_module, smoke_size);
let simple_add = core::slice::from_raw_parts(simple_add_module, simple_add_size);
let simple_host_fn =
core::slice::from_raw_parts(simple_host_fn_module, simple_host_fn_size);
match run_result(smoke, simple_add, simple_host_fn) {
let simple_floats = core::slice::from_raw_parts(simple_floats_module, simple_floats_size);
match run_result(smoke, simple_add, simple_host_fn, simple_floats) {
Ok(()) => 0,
Err(e) => {
let msg = format!("{e:?}");
Expand All @@ -52,15 +55,58 @@ fn run_result(
smoke_module: &[u8],
simple_add_module: &[u8],
simple_host_fn_module: &[u8],
simple_floats_module: &[u8],
) -> Result<()> {
smoke(smoke_module)?;
simple_add(simple_add_module)?;
simple_host_fn(simple_host_fn_module)?;
simple_floats(simple_floats_module)?;
Ok(())
}

fn config() -> Config {
let mut config = Config::new();
let _ = &mut config;

#[cfg(target_arch = "x86_64")]
{
// This example runs in a Linux process where it's valid to use
// floating point registers. Additionally sufficient x86 features are
// enabled during compilation to avoid float-related libcalls. Thus
// despite the host being configured for "soft float" it should be
// valid to turn this on.
unsafe {
config.x86_float_abi_ok(true);
}

// To make the float ABI above OK it requires CPU features above
// baseline to be enabled. Wasmtime needs to be able to check to ensure
// that the feature is actually supplied at runtime, but a default check
// isn't possible in no_std. For x86_64 we can use the cpuid instruction
// bound through an external crate.
//
// Note that CPU support for these features has existend since 2013
// (Haswell) on Intel chips and 2012 (Piledriver) on AMD chips.
unsafe {
config.detect_host_feature(move |feature| {
let id = raw_cpuid::CpuId::new();
match feature {
"sse3" => Some(id.get_feature_info()?.has_sse3()),
"ssse3" => Some(id.get_feature_info()?.has_sse3()),
"sse4.1" => Some(id.get_feature_info()?.has_sse41()),
"sse4.2" => Some(id.get_feature_info()?.has_sse42()),
"fma" => Some(id.get_feature_info()?.has_fma()),
Comment on lines +94 to +98
Copy link
Contributor

Choose a reason for hiding this comment

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

All of these features are required to bypass the libcalls or just a subset?

Copy link
Member Author

Choose a reason for hiding this comment

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

SE4.1 is sufficient if you're not using relaxed-simd, but with relaxed-simd you'll also need FMA.

_ => None,
}
});
}
}

config
}

fn smoke(module: &[u8]) -> Result<()> {
let engine = Engine::default();
let engine = Engine::new(&config())?;
let module = match deserialize(&engine, module)? {
Some(module) => module,
None => return Ok(()),
Expand All @@ -70,20 +116,20 @@ fn smoke(module: &[u8]) -> Result<()> {
}

fn simple_add(module: &[u8]) -> Result<()> {
let engine = Engine::default();
let engine = Engine::new(&config())?;
let module = match deserialize(&engine, module)? {
Some(module) => module,
None => return Ok(()),
};
let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &module)?;
let func = instance.get_typed_func::<(u32, u32), u32>(&mut store, "add")?;
assert_eq!(func.call(&mut store, (2, 3))?, 5);
ensure!(func.call(&mut store, (2, 3))? == 5);
Ok(())
}

fn simple_host_fn(module: &[u8]) -> Result<()> {
let engine = Engine::default();
let engine = Engine::new(&config())?;
let module = match deserialize(&engine, module)? {
Some(module) => module,
None => return Ok(()),
Expand All @@ -93,7 +139,20 @@ fn simple_host_fn(module: &[u8]) -> Result<()> {
let mut store = Store::new(&engine, ());
let instance = linker.instantiate(&mut store, &module)?;
let func = instance.get_typed_func::<(u32, u32, u32), u32>(&mut store, "add_and_mul")?;
assert_eq!(func.call(&mut store, (2, 3, 4))?, 10);
ensure!(func.call(&mut store, (2, 3, 4))? == 10);
Ok(())
}

fn simple_floats(module: &[u8]) -> Result<()> {
let engine = Engine::new(&config())?;
let module = match deserialize(&engine, module)? {
Some(module) => module,
None => return Ok(()),
};
let mut store = Store::new(&engine, ());
let instance = Linker::new(&engine).instantiate(&mut store, &module)?;
let func = instance.get_typed_func::<(f32, f32), f32>(&mut store, "frob")?;
ensure!(func.call(&mut store, (1.4, 3.2))? == 5.);
Ok(())
}

Expand Down
4 changes: 2 additions & 2 deletions examples/min-platform/embedding/src/wasi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
use wasmtime::component::{Component, Linker, Resource, ResourceTable};
use wasmtime::{Config, Engine, Store};
use wasmtime::{Engine, Store};
use wasmtime_wasi_io::{
IoView,
bytes::Bytes,
Expand Down Expand Up @@ -78,7 +78,7 @@ fn run(wasi_component: &[u8]) -> Result<String> {
// interface will poll as Pending while execution is suspended and it is
// waiting for a Pollable to become Ready. This example provides a very
// small async executor which is entered below with `block_on`.
let mut config = Config::default();
let mut config = super::config();
config.async_support(true);
// For future: we could consider turning on fuel in the Config to meter
// how long a wasm guest could execute for.
Expand Down
Loading