Skip to content

Commit

Permalink
Port wasmtime-fiber to no_std and allow async feature in no_std
Browse files Browse the repository at this point in the history
… Wasmtime. (#9689)

This PR allows a `no_std` Wasmtime build to be configured with the
`async` feature. (Previously, a minimal `no_std` configuration could
only run with sync entry points, without suspending of stacks.)

The main hurdle to this support was the `wasmtime-fiber` crate.
Fortunately, the "unix" variant of fibers was almost entirely portable
to a `no_std` environment, owing to the fact that it implements
stack-switching manually in assembly itself. I moved the per-ISA
implementations to a shared submodule and built the nostd platform
backend for `wasmtime-fiber` with a stripped-down version of the unix
backend.

The nostd backend does not support mmap'd stacks, does not support
custom stack allocators, and does not propagate panics.

prtest:full
  • Loading branch information
cfallin authored Dec 4, 2024
1 parent 7ef8f2e commit abcd6ac
Show file tree
Hide file tree
Showing 17 changed files with 320 additions and 66 deletions.
22 changes: 21 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,12 @@ jobs:
-p wasmtime --no-default-features --features profiling
-p wasmtime --no-default-features --features cache
-p wasmtime --no-default-features --features async
-p wasmtime --no-default-features --features std
-p wasmtime --no-default-features --features pooling-allocator
-p wasmtime --no-default-features --features cranelift
-p wasmtime --no-default-features --features component-model
-p wasmtime --no-default-features --features runtime,component-model
-p wasmtime --no-default-features --features cranelift,wat,async,cache
-p wasmtime --no-default-features --features cranelift,wat,async,std,cache
-p wasmtime --no-default-features --features winch
-p wasmtime --no-default-features --features wmemcheck
-p wasmtime --no-default-features --features wmemcheck,cranelift,runtime
Expand All @@ -384,6 +385,12 @@ jobs:
-p wasmtime --features incremental-cache
-p wasmtime --all-features
- name: wasmtime-fiber
checks: |
-p wasmtime-fiber --no-default-features
-p wasmtime-fiber --no-default-features --features std
-p wasmtime-fiber --all-features
- name: wasmtime-cli
checks: |
-p wasmtime-cli --no-default-features
Expand Down Expand Up @@ -432,6 +439,18 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}

fiber_tests:
name: wasmtime-fiber tests
runs-on: ubuntu-latest
env:
CARGO_NDK_VERSION: 2.12.2
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: ./.github/actions/install-rust
- run: cargo test -p wasmtime-fiber --no-default-features

# Checks for no_std support, ensure that crates can build on a no_std target
no_std_checks:
name: no_std checks
Expand Down Expand Up @@ -1192,6 +1211,7 @@ jobs:
- cargo_vet
- doc
- micro_checks
- fiber_tests
- no_std_checks
- clippy
- monolith_checks
Expand Down
6 changes: 3 additions & 3 deletions crates/asm-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ cfg_if::cfg_if! {
#[macro_export]
macro_rules! asm_func {
($name:expr, $body:expr $(, $($args:tt)*)?) => {
std::arch::global_asm!(
core::arch::global_asm!(
concat!(
".p2align 4\n",
".private_extern _", $name, "\n",
Expand All @@ -29,7 +29,7 @@ cfg_if::cfg_if! {
#[macro_export]
macro_rules! asm_func {
($name:expr, $body:expr $(, $($args:tt)*)?) => {
std::arch::global_asm!(
core::arch::global_asm!(
concat!(
".def ", $name, "\n",
".scl 2\n",
Expand Down Expand Up @@ -65,7 +65,7 @@ cfg_if::cfg_if! {
#[macro_export]
macro_rules! asm_func {
($name:expr, $body:expr $(, $($args:tt)*)?) => {
std::arch::global_asm!(
core::arch::global_asm!(
concat!(
".p2align 4\n",
".hidden ", $name, "\n",
Expand Down
8 changes: 7 additions & 1 deletion crates/fiber/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ workspace = true
anyhow = { workspace = true }
cfg-if = { workspace = true }
wasmtime-versioned-export-macros = { workspace = true }
wasmtime-asm-macros = { workspace = true }

[target.'cfg(unix)'.dependencies]
rustix = { workspace = true, features = ["mm", "param"] }
wasmtime-asm-macros = { workspace = true }

[target.'cfg(windows)'.dependencies.windows-sys]
workspace = true
Expand All @@ -33,3 +33,9 @@ wasmtime-versioned-export-macros = { workspace = true }

[dev-dependencies]
backtrace = "0.3.68"

[features]

# Assume presence of the standard library. Allows propagating
# panic-unwinds across fiber invocations.
std = []
4 changes: 2 additions & 2 deletions crates/fiber/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ fn main() {
build.file("src/windows.c");
build.define("VERSIONED_SUFFIX", Some(versioned_suffix!()));
} else if arch == "s390x" {
println!("cargo:rerun-if-changed=src/unix/s390x.S");
build.file("src/unix/s390x.S");
println!("cargo:rerun-if-changed=src/stackswitch/s390x.S");
build.file("src/stackswitch/s390x.S");
build.define("VERSIONED_SUFFIX", Some(versioned_suffix!()));
} else {
// assume that this is included via inline assembly in the crate itself,
Expand Down
82 changes: 58 additions & 24 deletions crates/fiber/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
#![expect(clippy::allow_attributes, reason = "crate not migrated yet")]
#![no_std]

#[cfg(any(feature = "std", unix, windows))]
#[macro_use]
extern crate std;
extern crate alloc;

use alloc::boxed::Box;
use anyhow::Error;
use std::any::Any;
use std::cell::Cell;
use std::io;
use std::marker::PhantomData;
use std::ops::Range;
use std::panic::{self, AssertUnwindSafe};
use core::cell::Cell;
use core::marker::PhantomData;
use core::ops::Range;

cfg_if::cfg_if! {
if #[cfg(windows)] {
if #[cfg(not(feature = "std"))] {
mod nostd;
use nostd as imp;
} else if #[cfg(windows)] {
mod windows;
use windows as imp;
} else if #[cfg(unix)] {
Expand All @@ -20,6 +27,11 @@ cfg_if::cfg_if! {
}
}

// Our own stack switcher routines are used on Unix and no_std
// platforms, but not on Windows (it has its own fiber API).
#[cfg(any(unix, not(feature = "std")))]
pub(crate) mod stackswitch;

/// Represents an execution stack to use for a fiber.
pub struct FiberStack(imp::FiberStack);

Expand All @@ -31,14 +43,16 @@ fn _assert_send_sync() {
_assert_sync::<FiberStack>();
}

pub type Result<T, E = imp::Error> = core::result::Result<T, E>;

impl FiberStack {
/// Creates a new fiber stack of the given size.
pub fn new(size: usize) -> io::Result<Self> {
pub fn new(size: usize) -> Result<Self> {
Ok(Self(imp::FiberStack::new(size)?))
}

/// Creates a new fiber stack of the given size.
pub fn from_custom(custom: Box<dyn RuntimeFiberStack>) -> io::Result<Self> {
pub fn from_custom(custom: Box<dyn RuntimeFiberStack>) -> Result<Self> {
Ok(Self(imp::FiberStack::from_custom(custom)?))
}

Expand All @@ -55,11 +69,7 @@ impl FiberStack {
///
/// The caller must properly allocate the stack space with a guard page and
/// make the pages accessible for correct behavior.
pub unsafe fn from_raw_parts(
bottom: *mut u8,
guard_size: usize,
len: usize,
) -> io::Result<Self> {
pub unsafe fn from_raw_parts(bottom: *mut u8, guard_size: usize, len: usize) -> Result<Self> {
Ok(Self(imp::FiberStack::from_raw_parts(
bottom, guard_size, len,
)?))
Expand Down Expand Up @@ -128,7 +138,8 @@ enum RunResult<Resume, Yield, Return> {
Resuming(Resume),
Yield(Yield),
Returned(Return),
Panicked(Box<dyn Any + Send>),
#[cfg(feature = "std")]
Panicked(Box<dyn core::any::Any + Send>),
}

impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
Expand All @@ -140,7 +151,7 @@ impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
pub fn new(
stack: FiberStack,
func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return + 'a,
) -> io::Result<Self> {
) -> Result<Self> {
let inner = imp::Fiber::new(&stack.0, func)?;

Ok(Self {
Expand Down Expand Up @@ -177,7 +188,11 @@ impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
Err(y)
}
RunResult::Returned(r) => Ok(r),
RunResult::Panicked(payload) => std::panic::resume_unwind(payload),
#[cfg(feature = "std")]
RunResult::Panicked(_payload) => {
use std::panic;
panic::resume_unwind(_payload);
}
}
}

Expand Down Expand Up @@ -222,11 +237,27 @@ impl<Resume, Yield, Return> Suspend<Resume, Yield, Return> {
inner,
_phantom: PhantomData,
};
let result = panic::catch_unwind(AssertUnwindSafe(|| (func)(initial, &mut suspend)));
suspend.inner.switch::<Resume, Yield, Return>(match result {
Ok(result) => RunResult::Returned(result),
Err(panic) => RunResult::Panicked(panic),
});

#[cfg(feature = "std")]
{
use std::panic::{self, AssertUnwindSafe};
let result = panic::catch_unwind(AssertUnwindSafe(|| (func)(initial, &mut suspend)));
suspend.inner.switch::<Resume, Yield, Return>(match result {
Ok(result) => RunResult::Returned(result),
Err(panic) => RunResult::Panicked(panic),
});
}
// Note that it is sound to omit the `catch_unwind` here: it
// will not result in unwinding going off the top of the fiber
// stack, because the code on the fiber stack is invoked via
// an extern "C" boundary which will panic on unwinds.
#[cfg(not(feature = "std"))]
{
let result = (func)(initial, &mut suspend);
suspend
.inner
.switch::<Resume, Yield, Return>(RunResult::Returned(result));
}
}
}

Expand All @@ -236,11 +267,11 @@ impl<A, B, C> Drop for Fiber<'_, A, B, C> {
}
}

#[cfg(test)]
#[cfg(all(test))]
mod tests {
use super::{Fiber, FiberStack};
use alloc::string::ToString;
use std::cell::Cell;
use std::panic::{self, AssertUnwindSafe};
use std::rc::Rc;

#[test]
Expand Down Expand Up @@ -332,7 +363,10 @@ mod tests {
}

#[test]
#[cfg(feature = "std")]
fn panics_propagated() {
use std::panic::{self, AssertUnwindSafe};

let a = Rc::new(Cell::new(false));
let b = SetOnDrop(a.clone());
let fiber =
Expand Down
Loading

0 comments on commit abcd6ac

Please sign in to comment.