Skip to content

Commit

Permalink
Improve support for completely unknown architectures (#10107)
Browse files Browse the repository at this point in the history
* Improve support for completely unknown architectures

This commit is a step in the direction of trying to make Wasmtime more
portable by default. The goal here is to enable Wasmtime to compile for
architectures that it has no prior knowledge of. There's a few
miscellaneous locations through Wasmtime where we need
architecture-specific intrinsics and such but that's all in service of
Cranelift itself. Without Cranelift support none of them are necessary.

This commit plumbs a custom `#[cfg]` from Wasmtime's `build.rs` script
into the crate about whether there's a supported Cranelift backend. If
this isn't available some architecture-specific intrinsics are turned
off and not included. An example is that `vm::arch` entirely disappears
which is only in service of `UnwindHost`, which also disappears.
Furthermore the `helpers.c` file also entirely disappears as it's not
necessary on unknown architectures.

To help keep this working I've added CI to build Wasmtime for
`powerpc64le-unknown-linux-gnu`. Wasmtime currently has no support for
this architecture, although if it grows such support in the future
this'll need to be changed to some other unsupported architecture.

* Review feedback

* Fix powerpc build

* Refactor windows trap handling to look like Unix

Shuffle some files around to be more amenable to #[cfg]

* Move signal-handling tests to wasmtime crate

That way it's got easy access to the #[cfg]'s from the build script

* Disable signals support without a host compiler

Even if custom signals are enabled, don't compile it in.

prtest:full

* Fix windows unused imports

* Fix unused imports on Windows

* Remove untested stubs for arch intrinsics

These aren't needed any more to compile Pulley

* Defer tunables validation to loading modules

Instead of validating at `Engine` config time instead validate at
`Module` config time to enable cross-compilation.

* Skip `Tunables` auto-configuration if cross-compiling

This commit

* Tweak some Tunables based on Pulley

Ensures that specific `--target pulleyNN` works most of the time.

* Update host_segfault.rs to handle new 32-bit defaults

No signal handlers are used at all with Pulley so when the async stack
overflows there's no message printed any more.

* Disable Tunables::signals_based_traps on miri
  • Loading branch information
alexcrichton authored Jan 28, 2025
1 parent 263308e commit 4afa86b
Show file tree
Hide file tree
Showing 33 changed files with 474 additions and 318 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,18 @@ jobs:
test: cargo build
env:
IPHONEOS_DEPLOYMENT_TARGET: 13.0
# Test that when Cranelift has no support for an architecture, even a
# 64-bit one, that Wasmtime still compiles. Note that this is also
# intended to test various fallbacks in the codebase where we have no
# support at all for a particular architecture. In theory if someone
# adds powerpc64 support to Wasmtime this should get switched to some
# other architecture.
- target: powerpc64le-unknown-linux-gnu
os: ubuntu-latest
test: cargo build
apt_packages: gcc-powerpc64le-linux-gnu
env:
CARGO_TARGET_POWERPC64LE_UNKNOWN_LINUX_GNU_LINKER: powerpc64le-linux-gnu-gcc
env: ${{ matrix.env || fromJSON('{}') }}
steps:
- uses: actions/checkout@v4
Expand All @@ -570,6 +582,9 @@ jobs:
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall --no-confirm cross
if: ${{ matrix.cross }}
- name: Install apt packages
if: ${{ matrix.apt_packages }}
run: sudo apt-get install -y ${{ matrix.apt_packages }}
- run: ${{ matrix.test }}
env:
CARGO_BUILD_TARGET: ${{ matrix.target }}
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

4 changes: 0 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -506,10 +506,6 @@ run = [
]
completion = ["dep:clap_complete"]

[[test]]
name = "host_segfault"
harness = false

[[test]]
name = "disas"
harness = false
Expand Down
19 changes: 15 additions & 4 deletions crates/environ/src/tunables.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::TripleExt;
use anyhow::{anyhow, bail, Result};
use core::fmt;
use serde_derive::{Deserialize, Serialize};
Expand Down Expand Up @@ -146,14 +147,22 @@ impl Tunables {
if cfg!(miri) {
return Ok(Tunables::default_miri());
}
match target
let mut ret = match target
.pointer_width()
.map_err(|_| anyhow!("failed to retrieve target pointer width"))?
{
PointerWidth::U32 => Ok(Tunables::default_u32()),
PointerWidth::U64 => Ok(Tunables::default_u64()),
PointerWidth::U32 => Tunables::default_u32(),
PointerWidth::U64 => Tunables::default_u64(),
_ => bail!("unsupported target pointer width"),
};

// Pulley targets never use signals-based-traps and also can't benefit
// from guard pages, so disable them.
if target.is_pulley() {
ret.signals_based_traps = false;
ret.memory_guard_size = 0;
}
Ok(ret)
}

/// Returns the default set of tunables for running under MIRI.
Expand All @@ -180,7 +189,7 @@ impl Tunables {
debug_adapter_modules: false,
relaxed_simd_deterministic: false,
winch_callable: false,
signals_based_traps: true,
signals_based_traps: false,
memory_init_cow: true,
}
}
Expand All @@ -194,6 +203,7 @@ impl Tunables {
memory_reservation: 10 * (1 << 20),
memory_guard_size: 0x1_0000,
memory_reservation_for_growth: 1 << 20, // 1MB
signals_based_traps: true,

..Tunables::default_miri()
}
Expand All @@ -220,6 +230,7 @@ impl Tunables {
// to avoid memory movement.
memory_reservation_for_growth: 2 << 30, // 2GB

signals_based_traps: true,
..Tunables::default_miri()
}
}
Expand Down
6 changes: 6 additions & 0 deletions crates/fiber/src/nostd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ impl Fiber {
where
F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,
{
// On unsupported platforms `wasmtime_fiber_init` is a panicking shim so
// return an error saying the host architecture isn't supported instead.
if !SUPPORTED_ARCH {
anyhow::bail!("fibers unsupported on this host architecture");
}
unsafe {
let data = Box::into_raw(Box::new(func)).cast();
wasmtime_fiber_init(stack.top().unwrap(), fiber_start::<F, A, B, C>, data);
Expand All @@ -147,6 +152,7 @@ impl Fiber {
let addr = stack.top().unwrap().cast::<usize>().offset(-1);
addr.write(result as *const _ as usize);

assert!(SUPPORTED_ARCH);
wasmtime_fiber_switch(stack.top().unwrap());

// null this out to help catch use-after-free
Expand Down
68 changes: 55 additions & 13 deletions crates/fiber/src/stackswitch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,74 @@
cfg_if::cfg_if! {
if #[cfg(target_arch = "aarch64")] {
mod aarch64;
pub(crate) use supported::*;
} else if #[cfg(target_arch = "x86_64")] {
mod x86_64;
pub(crate) use supported::*;
} else if #[cfg(target_arch = "x86")] {
mod x86;
pub(crate) use supported::*;
} else if #[cfg(target_arch = "arm")] {
mod arm;
pub(crate) use supported::*;
} else if #[cfg(target_arch = "s390x")] {
// currently `global_asm!` isn't stable on s390x so this is an external
// assembler file built with the `build.rs`.
pub(crate) use supported::*;
} else if #[cfg(target_arch = "riscv64")] {
mod riscv64;
pub(crate) use supported::*;
} else {
compile_error!("fibers are not supported on this CPU architecture");
// No support for this platform. Don't fail compilation though and
// instead defer the error to happen at runtime when a fiber is created.
// Should help keep compiles working and narrows the failure to only
// situations that need fibers on unsupported platforms.
pub(crate) use unsupported::*;
}
}

unsafe extern "C" {
#[wasmtime_versioned_export_macros::versioned_link]
pub(crate) fn wasmtime_fiber_init(
top_of_stack: *mut u8,
entry: extern "C" fn(*mut u8, *mut u8),
entry_arg0: *mut u8,
);
#[wasmtime_versioned_export_macros::versioned_link]
pub(crate) fn wasmtime_fiber_switch(top_of_stack: *mut u8);
#[allow(dead_code, reason = "only used on some platforms for inline asm")]
#[wasmtime_versioned_export_macros::versioned_link]
pub(crate) fn wasmtime_fiber_start();
/// A helper module to get reeported above in each case that we actually have
/// stack-switching routines available in in line asm. The fall-through case
/// though reexports the `unsupported` module instead.
#[allow(
dead_code,
reason = "expected to have dead code in some configurations"
)]
mod supported {
pub const SUPPORTED_ARCH: bool = true;
unsafe extern "C" {
#[wasmtime_versioned_export_macros::versioned_link]
pub(crate) fn wasmtime_fiber_init(
top_of_stack: *mut u8,
entry: extern "C" fn(*mut u8, *mut u8),
entry_arg0: *mut u8,
);
#[wasmtime_versioned_export_macros::versioned_link]
pub(crate) fn wasmtime_fiber_switch(top_of_stack: *mut u8);
#[wasmtime_versioned_export_macros::versioned_link]
pub(crate) fn wasmtime_fiber_start();
}
}

/// Helper module reexported in the fallback case above when the current host
/// architecture is not supported for stack switching. The `SUPPORTED_ARCH`
/// boolean here is set to `false` which causes `Fiber::new` to return `false`.
#[allow(
dead_code,
reason = "expected to have dead code in some configurations"
)]
mod unsupported {
pub const SUPPORTED_ARCH: bool = false;

pub(crate) unsafe fn wasmtime_fiber_init(
_top_of_stack: *mut u8,
_entry: extern "C" fn(*mut u8, *mut u8),
_entry_arg0: *mut u8,
) {
unreachable!();
}

pub(crate) unsafe fn wasmtime_fiber_switch(_top_of_stack: *mut u8) {
unreachable!();
}
}
10 changes: 10 additions & 0 deletions crates/fiber/src/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ impl Fiber {
where
F: FnOnce(A, &mut super::Suspend<A, B, C>) -> C,
{
// On unsupported platforms `wasmtime_fiber_init` is a panicking shim so
// return an error saying the host architecture isn't supported instead.
if !SUPPORTED_ARCH {
return Err(io::Error::new(
io::ErrorKind::Other,
"fibers not supported on this host architecture",
));
}
unsafe {
let data = Box::into_raw(Box::new(func)).cast();
wasmtime_fiber_init(stack.top().unwrap(), fiber_start::<F, A, B, C>, data);
Expand Down Expand Up @@ -344,6 +352,7 @@ mod asan {
is_finishing: bool,
prev: &mut PreviousStack,
) {
assert!(super::SUPPORTED_ARCH);
let mut private_asan_pointer = std::ptr::null_mut();

// If this fiber is finishing then NULL is passed to asan to let it know
Expand Down Expand Up @@ -475,6 +484,7 @@ mod asan_disabled {
_is_finishing: bool,
_prev: &mut PreviousStack,
) {
assert!(super::SUPPORTED_ARCH);
super::wasmtime_fiber_switch(top_of_stack);
}

Expand Down
6 changes: 6 additions & 0 deletions crates/wasmtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,17 @@ proptest = { workspace = true }
rand = { workspace = true }
tempfile = { workspace = true }
wasi-common = { path = "../wasi-common", default-features = true }
libtest-mimic = { workspace = true }
cranelift-native = { workspace = true }

[build-dependencies]
cc = { workspace = true, optional = true }
wasmtime-versioned-export-macros = { workspace = true, optional = true }

[[test]]
name = "host_segfault"
harness = false

# =============================================================================
#
# Features for the Wasmtime crate.
Expand Down
93 changes: 44 additions & 49 deletions crates/wasmtime/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,51 +7,49 @@ fn main() {
enable_features_based_on_rustc_version();

// NB: duplicating a workaround in the wasmtime-fiber build script.
println!("cargo:rustc-check-cfg=cfg(asan)");
if cfg_is("sanitize", "address") {
println!("cargo:rustc-cfg=asan");
}
custom_cfg("asan", cfg_is("sanitize", "address"));

let unix = cfg("unix");
let windows = cfg("windows");
let miri = cfg("miri");
let supported_platform = unix || windows;

let has_native_signals =
!miri && (supported_platform || cfg!(feature = "custom-native-signals"));
let has_virtual_memory = supported_platform || cfg!(feature = "custom-virtual-memory");

println!("cargo:rustc-check-cfg=cfg(has_native_signals, has_virtual_memory)");
if has_native_signals {
println!("cargo:rustc-cfg=has_native_signals");
}
if has_virtual_memory {
println!("cargo:rustc-cfg=has_virtual_memory");
let supported_os = unix || windows;

// Determine if the current host architecture is supported by Cranelift
// meaning that we might be executing native code.
let has_host_compiler_backend = match std::env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() {
"x86_64" | "riscv64" | "s390x" | "aarch64" => true,
_ => false,
};

let has_native_signals = !miri
&& (supported_os || cfg!(feature = "custom-native-signals"))
&& has_host_compiler_backend;
let has_virtual_memory = supported_os || cfg!(feature = "custom-virtual-memory");

custom_cfg("has_native_signals", has_native_signals);
custom_cfg("has_virtual_memory", has_virtual_memory);
custom_cfg("has_host_compiler_backend", has_host_compiler_backend);

// If this OS isn't supported or if Cranelift doesn't support the host then
// there's no need to build these helpers.
#[cfg(feature = "runtime")]
if supported_os && has_host_compiler_backend {
build_c_helpers();
}

#[cfg(feature = "runtime")]
build_c_helpers();

// Determine whether Pulley will be enabled and used for this target.
match std::env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() {
// These targets use Cranelift by default as they have backends in
// Cranelift. Pulley can still be used on an opt-in basis, but it's not
// otherwise explicitly enabled here.
"x86_64" | "riscv64" | "s390x" | "aarch64" => {}

// All other targets at this time use Pulley by default. That means
// that the pulley feature is "enabled" here and the default target is
// pulley. Note that by enabling the feature here it doesn't actually
// enable the Cargo feature, it just passes a cfg to rustc. That means
// that conditional dependencies enabled in `Cargo.toml` (or other
// features) by `pulley` aren't activated, which is why the `pulley`
// feature of this crate depends on nothing else.
_ => {
println!("cargo:rustc-cfg=feature=\"pulley\"");
println!("cargo:rustc-cfg=default_target_pulley");
}
// Figure out what to do about Pulley.
//
// If the target platform does not have any Cranelift support then Pulley
// will be used by default. That means that the pulley feature is "enabled"
// here and the default target is pulley. Note that by enabling the feature
// here it doesn't actually enable the Cargo feature, it just passes a cfg
// to rustc. That means that conditional dependencies enabled in
// `Cargo.toml` (or other features) by `pulley` aren't activated, which is
// why the `pulley` feature of this crate depends on nothing else.
custom_cfg("default_target_pulley", !has_host_compiler_backend);
if !has_host_compiler_backend {
println!("cargo:rustc-cfg=feature=\"pulley\"");
}
println!("cargo:rustc-check-cfg=cfg(default_target_pulley)");
}

fn cfg(key: &str) -> bool {
Expand All @@ -65,17 +63,17 @@ fn cfg_is(key: &str, val: &str) -> bool {
== Some(val)
}

fn custom_cfg(key: &str, enabled: bool) {
println!("cargo:rustc-check-cfg=cfg({key})");
if enabled {
println!("cargo:rustc-cfg={key}");
}
}

#[cfg(feature = "runtime")]
fn build_c_helpers() {
use wasmtime_versioned_export_macros::versioned_suffix;

// If this platform is neither unix nor windows then there's no default need
// for a C helper library since `helpers.c` is tailored for just these
// platforms currently.
if !cfg("unix") && !cfg("windows") {
return;
}

let mut build = cc::Build::new();
build.warnings(true);
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
Expand Down Expand Up @@ -104,10 +102,7 @@ fn enable_features_based_on_rustc_version() {
// in the future the MSRV of this crate will be beyond 1.84 in which case
// this build script can be deleted.
let minor = rustc_minor_version().unwrap_or(0);
if minor >= 84 {
println!("cargo:rustc-cfg=has_provenance_apis");
}
println!("cargo:rustc-check-cfg=cfg(has_provenance_apis)");
custom_cfg("has_provenance_apis", minor >= 84);
}

fn rustc_minor_version() -> Option<u32> {
Expand Down
Loading

0 comments on commit 4afa86b

Please sign in to comment.