Skip to content

Commit abcd6ac

Browse files
authored
Port wasmtime-fiber to no_std and allow async feature in no_std 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
1 parent 7ef8f2e commit abcd6ac

File tree

17 files changed

+320
-66
lines changed

17 files changed

+320
-66
lines changed

.github/workflows/main.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,12 @@ jobs:
356356
-p wasmtime --no-default-features --features profiling
357357
-p wasmtime --no-default-features --features cache
358358
-p wasmtime --no-default-features --features async
359+
-p wasmtime --no-default-features --features std
359360
-p wasmtime --no-default-features --features pooling-allocator
360361
-p wasmtime --no-default-features --features cranelift
361362
-p wasmtime --no-default-features --features component-model
362363
-p wasmtime --no-default-features --features runtime,component-model
363-
-p wasmtime --no-default-features --features cranelift,wat,async,cache
364+
-p wasmtime --no-default-features --features cranelift,wat,async,std,cache
364365
-p wasmtime --no-default-features --features winch
365366
-p wasmtime --no-default-features --features wmemcheck
366367
-p wasmtime --no-default-features --features wmemcheck,cranelift,runtime
@@ -384,6 +385,12 @@ jobs:
384385
-p wasmtime --features incremental-cache
385386
-p wasmtime --all-features
386387
388+
- name: wasmtime-fiber
389+
checks: |
390+
-p wasmtime-fiber --no-default-features
391+
-p wasmtime-fiber --no-default-features --features std
392+
-p wasmtime-fiber --all-features
393+
387394
- name: wasmtime-cli
388395
checks: |
389396
-p wasmtime-cli --no-default-features
@@ -432,6 +439,18 @@ jobs:
432439
env:
433440
GH_TOKEN: ${{ github.token }}
434441

442+
fiber_tests:
443+
name: wasmtime-fiber tests
444+
runs-on: ubuntu-latest
445+
env:
446+
CARGO_NDK_VERSION: 2.12.2
447+
steps:
448+
- uses: actions/checkout@v4
449+
with:
450+
submodules: true
451+
- uses: ./.github/actions/install-rust
452+
- run: cargo test -p wasmtime-fiber --no-default-features
453+
435454
# Checks for no_std support, ensure that crates can build on a no_std target
436455
no_std_checks:
437456
name: no_std checks
@@ -1192,6 +1211,7 @@ jobs:
11921211
- cargo_vet
11931212
- doc
11941213
- micro_checks
1214+
- fiber_tests
11951215
- no_std_checks
11961216
- clippy
11971217
- monolith_checks

crates/asm-macros/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ cfg_if::cfg_if! {
1313
#[macro_export]
1414
macro_rules! asm_func {
1515
($name:expr, $body:expr $(, $($args:tt)*)?) => {
16-
std::arch::global_asm!(
16+
core::arch::global_asm!(
1717
concat!(
1818
".p2align 4\n",
1919
".private_extern _", $name, "\n",
@@ -29,7 +29,7 @@ cfg_if::cfg_if! {
2929
#[macro_export]
3030
macro_rules! asm_func {
3131
($name:expr, $body:expr $(, $($args:tt)*)?) => {
32-
std::arch::global_asm!(
32+
core::arch::global_asm!(
3333
concat!(
3434
".def ", $name, "\n",
3535
".scl 2\n",
@@ -65,7 +65,7 @@ cfg_if::cfg_if! {
6565
#[macro_export]
6666
macro_rules! asm_func {
6767
($name:expr, $body:expr $(, $($args:tt)*)?) => {
68-
std::arch::global_asm!(
68+
core::arch::global_asm!(
6969
concat!(
7070
".p2align 4\n",
7171
".hidden ", $name, "\n",

crates/fiber/Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ workspace = true
1515
anyhow = { workspace = true }
1616
cfg-if = { workspace = true }
1717
wasmtime-versioned-export-macros = { workspace = true }
18+
wasmtime-asm-macros = { workspace = true }
1819

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

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

3434
[dev-dependencies]
3535
backtrace = "0.3.68"
36+
37+
[features]
38+
39+
# Assume presence of the standard library. Allows propagating
40+
# panic-unwinds across fiber invocations.
41+
std = []

crates/fiber/build.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ fn main() {
2323
build.file("src/windows.c");
2424
build.define("VERSIONED_SUFFIX", Some(versioned_suffix!()));
2525
} else if arch == "s390x" {
26-
println!("cargo:rerun-if-changed=src/unix/s390x.S");
27-
build.file("src/unix/s390x.S");
26+
println!("cargo:rerun-if-changed=src/stackswitch/s390x.S");
27+
build.file("src/stackswitch/s390x.S");
2828
build.define("VERSIONED_SUFFIX", Some(versioned_suffix!()));
2929
} else {
3030
// assume that this is included via inline assembly in the crate itself,

crates/fiber/src/lib.rs

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
#![expect(clippy::allow_attributes, reason = "crate not migrated yet")]
2+
#![no_std]
23

4+
#[cfg(any(feature = "std", unix, windows))]
5+
#[macro_use]
6+
extern crate std;
7+
extern crate alloc;
8+
9+
use alloc::boxed::Box;
310
use anyhow::Error;
4-
use std::any::Any;
5-
use std::cell::Cell;
6-
use std::io;
7-
use std::marker::PhantomData;
8-
use std::ops::Range;
9-
use std::panic::{self, AssertUnwindSafe};
11+
use core::cell::Cell;
12+
use core::marker::PhantomData;
13+
use core::ops::Range;
1014

1115
cfg_if::cfg_if! {
12-
if #[cfg(windows)] {
16+
if #[cfg(not(feature = "std"))] {
17+
mod nostd;
18+
use nostd as imp;
19+
} else if #[cfg(windows)] {
1320
mod windows;
1421
use windows as imp;
1522
} else if #[cfg(unix)] {
@@ -20,6 +27,11 @@ cfg_if::cfg_if! {
2027
}
2128
}
2229

30+
// Our own stack switcher routines are used on Unix and no_std
31+
// platforms, but not on Windows (it has its own fiber API).
32+
#[cfg(any(unix, not(feature = "std")))]
33+
pub(crate) mod stackswitch;
34+
2335
/// Represents an execution stack to use for a fiber.
2436
pub struct FiberStack(imp::FiberStack);
2537

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

46+
pub type Result<T, E = imp::Error> = core::result::Result<T, E>;
47+
3448
impl FiberStack {
3549
/// Creates a new fiber stack of the given size.
36-
pub fn new(size: usize) -> io::Result<Self> {
50+
pub fn new(size: usize) -> Result<Self> {
3751
Ok(Self(imp::FiberStack::new(size)?))
3852
}
3953

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

@@ -55,11 +69,7 @@ impl FiberStack {
5569
///
5670
/// The caller must properly allocate the stack space with a guard page and
5771
/// make the pages accessible for correct behavior.
58-
pub unsafe fn from_raw_parts(
59-
bottom: *mut u8,
60-
guard_size: usize,
61-
len: usize,
62-
) -> io::Result<Self> {
72+
pub unsafe fn from_raw_parts(bottom: *mut u8, guard_size: usize, len: usize) -> Result<Self> {
6373
Ok(Self(imp::FiberStack::from_raw_parts(
6474
bottom, guard_size, len,
6575
)?))
@@ -128,7 +138,8 @@ enum RunResult<Resume, Yield, Return> {
128138
Resuming(Resume),
129139
Yield(Yield),
130140
Returned(Return),
131-
Panicked(Box<dyn Any + Send>),
141+
#[cfg(feature = "std")]
142+
Panicked(Box<dyn core::any::Any + Send>),
132143
}
133144

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

146157
Ok(Self {
@@ -177,7 +188,11 @@ impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> {
177188
Err(y)
178189
}
179190
RunResult::Returned(r) => Ok(r),
180-
RunResult::Panicked(payload) => std::panic::resume_unwind(payload),
191+
#[cfg(feature = "std")]
192+
RunResult::Panicked(_payload) => {
193+
use std::panic;
194+
panic::resume_unwind(_payload);
195+
}
181196
}
182197
}
183198

@@ -222,11 +237,27 @@ impl<Resume, Yield, Return> Suspend<Resume, Yield, Return> {
222237
inner,
223238
_phantom: PhantomData,
224239
};
225-
let result = panic::catch_unwind(AssertUnwindSafe(|| (func)(initial, &mut suspend)));
226-
suspend.inner.switch::<Resume, Yield, Return>(match result {
227-
Ok(result) => RunResult::Returned(result),
228-
Err(panic) => RunResult::Panicked(panic),
229-
});
240+
241+
#[cfg(feature = "std")]
242+
{
243+
use std::panic::{self, AssertUnwindSafe};
244+
let result = panic::catch_unwind(AssertUnwindSafe(|| (func)(initial, &mut suspend)));
245+
suspend.inner.switch::<Resume, Yield, Return>(match result {
246+
Ok(result) => RunResult::Returned(result),
247+
Err(panic) => RunResult::Panicked(panic),
248+
});
249+
}
250+
// Note that it is sound to omit the `catch_unwind` here: it
251+
// will not result in unwinding going off the top of the fiber
252+
// stack, because the code on the fiber stack is invoked via
253+
// an extern "C" boundary which will panic on unwinds.
254+
#[cfg(not(feature = "std"))]
255+
{
256+
let result = (func)(initial, &mut suspend);
257+
suspend
258+
.inner
259+
.switch::<Resume, Yield, Return>(RunResult::Returned(result));
260+
}
230261
}
231262
}
232263

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

239-
#[cfg(test)]
270+
#[cfg(all(test))]
240271
mod tests {
241272
use super::{Fiber, FiberStack};
273+
use alloc::string::ToString;
242274
use std::cell::Cell;
243-
use std::panic::{self, AssertUnwindSafe};
244275
use std::rc::Rc;
245276

246277
#[test]
@@ -332,7 +363,10 @@ mod tests {
332363
}
333364

334365
#[test]
366+
#[cfg(feature = "std")]
335367
fn panics_propagated() {
368+
use std::panic::{self, AssertUnwindSafe};
369+
336370
let a = Rc::new(Cell::new(false));
337371
let b = SetOnDrop(a.clone());
338372
let fiber =

0 commit comments

Comments
 (0)