From 18a15c2fe756a6dbec390589f9a3fd872623fe15 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Jun 2025 01:04:54 +0200 Subject: [PATCH 1/5] miri: Fix a new code pattern with stacked borrows (#11019) Joel and I went down a deep rabbit hole today trying to figure out what's going on with a new code pattern which was being flagged as violating stacked borrows. At the end of the day I'm honestly not 100% sure what this doing or how to explain all the behavior we were seeing, but this seems to be basically equivalent and we're otherwise able to get a bit further so I figured I'd commit this. Along the way I updated the preexisting `table-intrinsics` test to correctly use the table it was supposed to be using instead of using the previous table by accident. --- crates/wasmtime/src/runtime/vm/table.rs | 12 +++--------- tests/all/pulley.rs | 3 +++ tests/all/pulley_provenance_test.wat | 25 ++++++++++++++++--------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/wasmtime/src/runtime/vm/table.rs b/crates/wasmtime/src/runtime/vm/table.rs index d31de31749..35d3ed5e0a 100644 --- a/crates/wasmtime/src/runtime/vm/table.rs +++ b/crates/wasmtime/src/runtime/vm/table.rs @@ -957,25 +957,19 @@ impl Table { }, Table::Dynamic(DynamicTable::Func(DynamicFuncTable { elements, .. })) => { VMTableDefinition { - base: NonNull::<[FuncTableElem]>::from(&mut elements[..]) - .cast() - .into(), + base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(), current_elements: elements.len(), } } Table::Dynamic(DynamicTable::GcRef(DynamicGcRefTable { elements, .. })) => { VMTableDefinition { - base: NonNull::<[Option]>::from(&mut elements[..]) - .cast() - .into(), + base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(), current_elements: elements.len(), } } Table::Dynamic(DynamicTable::Cont(DynamicContTable { elements, .. })) => { VMTableDefinition { - base: NonNull::<[Option]>::from(&mut elements[..]) - .cast() - .into(), + base: NonNull::new(elements.as_mut_ptr()).unwrap().cast().into(), current_elements: elements.len(), } } diff --git a/tests/all/pulley.rs b/tests/all/pulley.rs index dfe7922939..2f47f5efd2 100644 --- a/tests/all/pulley.rs +++ b/tests/all/pulley.rs @@ -181,6 +181,9 @@ fn pulley_provenance_test() -> Result<()> { instance .get_typed_func::<(), ()>(&mut store, "table-intrinsics")? .call(&mut store, ())?; + instance + .get_typed_func::<(), ()>(&mut store, "table-intrinsics2")? + .call(&mut store, ())?; let funcref = Func::wrap(&mut store, move |mut caller: Caller<'_, ()>| { let func = instance.get_typed_func::<(), (i32, i32, i32)>(&mut caller, "call-wasm")?; diff --git a/tests/all/pulley_provenance_test.wat b/tests/all/pulley_provenance_test.wat index ae52f7bdc7..82ef755ade 100644 --- a/tests/all/pulley_provenance_test.wat +++ b/tests/all/pulley_provenance_test.wat @@ -67,20 +67,27 @@ ) (data $d "abcd") - (table 1 funcref) + (table $t 1 funcref) (func (export "table-intrinsics") - (drop (table.get (i32.const 0))) - (table.set (i32.const 0) (table.get (i32.const 0))) + (drop (table.get $t (i32.const 0))) + (table.set $t (i32.const 0) (table.get $t (i32.const 0))) - (drop (table.grow (ref.null func) (i32.const 100))) + (drop (table.grow $t (ref.null func) (i32.const 100))) - (drop (table.get (i32.const 1))) - (table.set (i32.const 1) (table.get (i32.const 1))) + (drop (table.get $t (i32.const 1))) + (table.set $t (i32.const 1) (table.get $t (i32.const 1))) - (table.copy (i32.const 0) (i32.const 1) (i32.const 10)) - (table.init $e (i32.const 0) (i32.const 1) (i32.const 3)) - (table.fill (i32.const 0) (ref.func $empty) (i32.const 10)) + (table.copy $t $t (i32.const 0) (i32.const 1) (i32.const 10)) + (table.init $t $e (i32.const 0) (i32.const 1) (i32.const 3)) + (table.fill $t (i32.const 0) (ref.func $empty) (i32.const 10)) ) (elem $e func $empty $empty $empty $empty) (func $empty) + + (table $t2 2 funcref) + (elem (table $t2) (i32.const 0) func $empty) + (func (export "table-intrinsics2") + (drop (table.get $t2 (i32.const 1))) + (drop (table.get $t2 (i32.const 0))) + ) ) From ca7757e2e2788722a492c02804c2107ccaf08e7b Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 16 Jun 2025 12:41:12 -0600 Subject: [PATCH 2/5] fix provenance for `TaggedFuncRef` Signed-off-by: Joel Dice --- crates/wasmtime/src/runtime/vm/table.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/wasmtime/src/runtime/vm/table.rs b/crates/wasmtime/src/runtime/vm/table.rs index 35d3ed5e0a..c1a857bc71 100644 --- a/crates/wasmtime/src/runtime/vm/table.rs +++ b/crates/wasmtime/src/runtime/vm/table.rs @@ -7,7 +7,7 @@ use crate::prelude::*; use crate::runtime::vm::stack_switching::VMContObj; use crate::runtime::vm::vmcontext::{VMFuncRef, VMTableDefinition}; -use crate::runtime::vm::{GcStore, SendSyncPtr, VMGcRef, VMStore}; +use crate::runtime::vm::{GcStore, SendSyncPtr, VMGcRef, VMStore, VmPtr}; use core::alloc::Layout; use core::mem; use core::ops::Range; @@ -134,19 +134,26 @@ impl From for TableElement { #[derive(Copy, Clone)] #[repr(transparent)] -struct TaggedFuncRef(*mut VMFuncRef); +struct TaggedFuncRef(Option>); impl TaggedFuncRef { - const UNINIT: TaggedFuncRef = TaggedFuncRef(ptr::null_mut()); + const UNINIT: TaggedFuncRef = TaggedFuncRef(None); /// Converts the given `ptr`, a valid funcref pointer, into a tagged pointer /// by adding in the `FUNCREF_INIT_BIT`. fn from(ptr: Option>, lazy_init: bool) -> Self { - let ptr = ptr.map(|p| p.as_ptr()).unwrap_or(ptr::null_mut()); if lazy_init { - let masked = ptr.map_addr(|a| a | FUNCREF_INIT_BIT); + let masked = match ptr { + Some(ptr) => Some(ptr.map_addr(|a| a | FUNCREF_INIT_BIT).into()), + None => Some( + NonNull::new(core::ptr::without_provenance_mut(FUNCREF_INIT_BIT)) + .unwrap() + .into(), + ), + }; TaggedFuncRef(masked) } else { + let ptr = ptr.map(|p| p.into()); TaggedFuncRef(ptr) } } @@ -155,13 +162,14 @@ impl TaggedFuncRef { /// for null (not a tagged value) or `FuncRef` for otherwise tagged values. fn into_table_element(self, lazy_init: bool) -> TableElement { let ptr = self.0; - if lazy_init && ptr.is_null() { + if lazy_init && ptr.is_none() { TableElement::UninitFunc } else { // Masking off the tag bit is harmless whether the table uses lazy // init or not. - let unmasked = ptr.map_addr(|a| a & FUNCREF_MASK); - TableElement::FuncRef(NonNull::new(unmasked)) + let unmasked = + ptr.and_then(|ptr| NonNull::new(ptr.as_ptr().map_addr(|a| a & FUNCREF_MASK))); + TableElement::FuncRef(unmasked) } } } From d5a2987389ffbd4501c689d5ecd91ad5f4c274b5 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 16 Jun 2025 15:23:11 -0600 Subject: [PATCH 3/5] fix another provenance/lazy-init issue Signed-off-by: Joel Dice --- crates/wasmtime/src/runtime/func.rs | 3 ++- crates/wasmtime/src/runtime/func/typed.rs | 9 ++++++--- crates/wasmtime/src/runtime/instance.rs | 3 ++- crates/wasmtime/src/runtime/vm/vmcontext.rs | 22 ++++++++++----------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs index 3c4b07a5dd..825c762730 100644 --- a/crates/wasmtime/src/runtime/func.rs +++ b/crates/wasmtime/src/runtime/func.rs @@ -1044,7 +1044,8 @@ impl Func { params_and_returns: NonNull<[ValRaw]>, ) -> Result<()> { invoke_wasm_and_catch_traps(store, |caller, vm| { - func_ref.as_ref().array_call( + VMFuncRef::array_call( + func_ref, vm, VMOpaqueContext::from_vmcontext(caller), params_and_returns, diff --git a/crates/wasmtime/src/runtime/func/typed.rs b/crates/wasmtime/src/runtime/func/typed.rs index 893168065c..e9106a9e27 100644 --- a/crates/wasmtime/src/runtime/func/typed.rs +++ b/crates/wasmtime/src/runtime/func/typed.rs @@ -235,9 +235,12 @@ where let storage = storage.cast::(); let storage = core::ptr::slice_from_raw_parts_mut(storage, storage_len); let storage = NonNull::new(storage).unwrap(); - func_ref - .as_ref() - .array_call(vm, VMOpaqueContext::from_vmcontext(caller), storage) + VMFuncRef::array_call( + *func_ref, + vm, + VMOpaqueContext::from_vmcontext(caller), + storage, + ) }); let (_, storage) = captures; diff --git a/crates/wasmtime/src/runtime/instance.rs b/crates/wasmtime/src/runtime/instance.rs index 4b43d0cdcf..65798779c6 100644 --- a/crates/wasmtime/src/runtime/instance.rs +++ b/crates/wasmtime/src/runtime/instance.rs @@ -341,7 +341,8 @@ impl Instance { let caller_vmctx = instance.vmctx(); unsafe { super::func::invoke_wasm_and_catch_traps(store, |_default_caller, vm| { - f.func_ref.as_ref().array_call( + VMFuncRef::array_call( + f.func_ref, vm, VMOpaqueContext::from_vmcontext(caller_vmctx), NonNull::from(&mut []), diff --git a/crates/wasmtime/src/runtime/vm/vmcontext.rs b/crates/wasmtime/src/runtime/vm/vmcontext.rs index 0a0f07a90b..964558aec2 100644 --- a/crates/wasmtime/src/runtime/vm/vmcontext.rs +++ b/crates/wasmtime/src/runtime/vm/vmcontext.rs @@ -869,19 +869,19 @@ impl VMFuncRef { /// exhaustively documented. #[inline] pub unsafe fn array_call( - &self, + me: NonNull, pulley: Option>, caller: NonNull, args_and_results: NonNull<[ValRaw]>, ) -> bool { match pulley { - Some(vm) => self.array_call_interpreted(vm, caller, args_and_results), - None => self.array_call_native(caller, args_and_results), + Some(vm) => Self::array_call_interpreted(me, vm, caller, args_and_results), + None => Self::array_call_native(me, caller, args_and_results), } } unsafe fn array_call_interpreted( - &self, + me: NonNull, vm: InterpreterRef<'_>, caller: NonNull, args_and_results: NonNull<[ValRaw]>, @@ -889,14 +889,14 @@ impl VMFuncRef { // If `caller` is actually a `VMArrayCallHostFuncContext` then skip the // interpreter, even though it's available, as `array_call` will be // native code. - if self.vmctx.as_non_null().as_ref().magic + if me.as_ref().vmctx.as_non_null().as_ref().magic == wasmtime_environ::VM_ARRAY_CALL_HOST_FUNC_MAGIC { - return self.array_call_native(caller, args_and_results); + return Self::array_call_native(me, caller, args_and_results); } vm.call( - self.array_call.as_non_null().cast(), - self.vmctx.as_non_null(), + me.as_ref().array_call.as_non_null().cast(), + me.as_ref().vmctx.as_non_null(), caller, args_and_results, ) @@ -904,7 +904,7 @@ impl VMFuncRef { #[inline] unsafe fn array_call_native( - &self, + me: NonNull, caller: NonNull, args_and_results: NonNull<[ValRaw]>, ) -> bool { @@ -913,11 +913,11 @@ impl VMFuncRef { ptr: NonNull, } let native = GetNativePointer { - ptr: self.array_call.as_non_null(), + ptr: me.as_ref().array_call.as_non_null(), } .native; native( - self.vmctx.as_non_null(), + me.as_ref().vmctx.as_non_null(), caller, args_and_results.cast(), args_and_results.len(), From 33062f23931d644701bb7109055122536b13f2fb Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Mon, 16 Jun 2025 16:41:09 -0600 Subject: [PATCH 4/5] fix issues running `component-async-tests` under miri In my previous commit, I had missed making a few test suites miri-compatible. In addition, some of the suites (e.g. `round_trip`) run each test multiple times using different host APIs, which is intolerably slow on miri, so we now only use one call style per test when miri is enabled. Relatedly, we now build the test programs using `--release` when the `MIRI_TEST_CWASM_DIR` environment variable is set. Finally, miri's willingness to allocate `[u8]` arrays which are _not_ 4-byte aligned exposed an issue in `error_context_new`, which I've fixed. Signed-off-by: Joel Dice --- .../component-async-tests/src/round_trip.rs | 2 +- .../src/round_trip_direct.rs | 2 +- .../src/round_trip_many.rs | 2 +- .../misc/component-async-tests/src/sleep.rs | 2 +- crates/misc/component-async-tests/src/util.rs | 73 ++++- .../tests/scenario/borrowing.rs | 20 +- .../tests/scenario/common.rs | 19 +- .../tests/scenario/error_context.rs | 10 +- .../tests/scenario/round_trip.rs | 293 ++++++++++-------- .../tests/scenario/round_trip_direct.rs | 29 +- .../tests/scenario/round_trip_many.rs | 178 ++++++----- .../tests/scenario/streams.rs | 22 +- .../tests/scenario/transmit.rs | 14 +- .../misc/component-async-tests/wit/test.wit | 2 +- crates/test-programs/artifacts/build.rs | 12 +- .../src/bin/async_cancel_caller.rs | 18 +- .../concurrent/futures_and_streams.rs | 18 +- .../src/runtime/component/func/typed.rs | 2 +- .../wasmtime/src/runtime/component/store.rs | 4 +- crates/wasmtime/src/runtime/store.rs | 4 +- 20 files changed, 436 insertions(+), 290 deletions(-) diff --git a/crates/misc/component-async-tests/src/round_trip.rs b/crates/misc/component-async-tests/src/round_trip.rs index 2b700ba904..0045aa6184 100644 --- a/crates/misc/component-async-tests/src/round_trip.rs +++ b/crates/misc/component-async-tests/src/round_trip.rs @@ -25,7 +25,7 @@ pub mod non_concurrent_export_bindings { impl bindings::local::local::baz::HostConcurrent for Ctx { async fn foo(_: &mut Accessor, s: String) -> wasmtime::Result { - tokio::time::sleep(Duration::from_millis(10)).await; + crate::util::sleep(Duration::from_millis(10)).await; Ok(format!("{s} - entered host - exited host")) } } diff --git a/crates/misc/component-async-tests/src/round_trip_direct.rs b/crates/misc/component-async-tests/src/round_trip_direct.rs index 48dee5efe8..85950828ec 100644 --- a/crates/misc/component-async-tests/src/round_trip_direct.rs +++ b/crates/misc/component-async-tests/src/round_trip_direct.rs @@ -15,7 +15,7 @@ pub mod bindings { impl bindings::RoundTripDirectImportsConcurrent for Ctx { async fn foo(_: &mut Accessor, s: String) -> wasmtime::Result { - tokio::time::sleep(Duration::from_millis(10)).await; + crate::util::sleep(Duration::from_millis(10)).await; Ok(format!("{s} - entered host - exited host")) } } diff --git a/crates/misc/component-async-tests/src/round_trip_many.rs b/crates/misc/component-async-tests/src/round_trip_many.rs index ed4338f14a..13f4a3eaeb 100644 --- a/crates/misc/component-async-tests/src/round_trip_many.rs +++ b/crates/misc/component-async-tests/src/round_trip_many.rs @@ -47,7 +47,7 @@ impl bindings::local::local::many::HostConcurrent for Ctx { Option, Result, )> { - tokio::time::sleep(Duration::from_millis(10)).await; + crate::util::sleep(Duration::from_millis(10)).await; Ok(( format!("{a} - entered host - exited host"), b, diff --git a/crates/misc/component-async-tests/src/sleep.rs b/crates/misc/component-async-tests/src/sleep.rs index 6adee974a8..7fde90b399 100644 --- a/crates/misc/component-async-tests/src/sleep.rs +++ b/crates/misc/component-async-tests/src/sleep.rs @@ -16,7 +16,7 @@ wasmtime::component::bindgen!({ impl local::local::sleep::HostConcurrent for Ctx { async fn sleep_millis(_: &mut Accessor, time_in_millis: u64) { - tokio::time::sleep(Duration::from_millis(time_in_millis)).await; + crate::util::sleep(Duration::from_millis(time_in_millis)).await; } } diff --git a/crates/misc/component-async-tests/src/util.rs b/crates/misc/component-async-tests/src/util.rs index e64f9e40de..0f52ecf80f 100644 --- a/crates/misc/component-async-tests/src/util.rs +++ b/crates/misc/component-async-tests/src/util.rs @@ -42,6 +42,60 @@ pub fn config() -> Config { config } +pub async fn sleep(duration: std::time::Duration) { + if cfg!(miri) { + // TODO: We should be able to use `tokio::time::sleep` here, but as of + // this writing the miri-compatible version of `wasmtime-fiber` uses + // threads behind the scenes, which means thread-local storage is not + // preserved when we switch fibers, and that confuses Tokio. If we ever + // fix that we can stop using our own, special version of `sleep` and + // switch back to the Tokio version. + + use std::{ + future, + sync::{ + Arc, Mutex, + atomic::{AtomicU32, Ordering::SeqCst}, + }, + task::Poll, + thread, + }; + + let state = Arc::new(AtomicU32::new(0)); + let waker = Arc::new(Mutex::new(None)); + let mut join_handle = None; + future::poll_fn(move |cx| match state.load(SeqCst) { + 0 => { + state.store(1, SeqCst); + let state = state.clone(); + *waker.lock().unwrap() = Some(cx.waker().clone()); + let waker = waker.clone(); + join_handle = Some(thread::spawn(move || { + thread::sleep(duration); + state.store(2, SeqCst); + let waker = waker.lock().unwrap().clone().unwrap(); + waker.wake(); + })); + Poll::Pending + } + 1 => { + *waker.lock().unwrap() = Some(cx.waker().clone()); + Poll::Pending + } + 2 => { + if let Some(handle) = join_handle.take() { + _ = handle.join(); + } + Poll::Ready(()) + } + _ => unreachable!(), + }) + .await; + } else { + tokio::time::sleep(duration).await; + } +} + /// Compose two components /// /// a is the "root" component, and b is composed into it @@ -166,7 +220,11 @@ pub async fn test_run(components: &[&str]) -> Result<()> { pub async fn test_run_with_count(components: &[&str], count: usize) -> Result<()> { let mut config = config(); - config.epoch_interruption(true); + // As of this writing, miri/pulley/epochs is a problematic combination, so + // we don't test it. + if env::var_os("MIRI_TEST_CWASM_DIR").is_none() { + config.epoch_interruption(true); + } let engine = Engine::new(&config)?; @@ -198,12 +256,15 @@ pub async fn test_run_with_count(components: &[&str], count: usize) -> Result<() wakers: Arc::new(std::sync::Mutex::new(None)), }, ); - store.set_epoch_deadline(1); - std::thread::spawn(move || { - std::thread::sleep(Duration::from_secs(10)); - engine.increment_epoch(); - }); + if env::var_os("MIRI_TEST_CWASM_DIR").is_none() { + store.set_epoch_deadline(1); + + std::thread::spawn(move || { + std::thread::sleep(Duration::from_secs(10)); + engine.increment_epoch(); + }); + } let instance = linker.instantiate_async(&mut store, &component).await?; let yield_host = super::yield_host::bindings::YieldHost::new(&mut store, &instance)?; diff --git a/crates/misc/component-async-tests/tests/scenario/borrowing.rs b/crates/misc/component-async-tests/tests/scenario/borrowing.rs index 6404747ce6..9bc86fa447 100644 --- a/crates/misc/component-async-tests/tests/scenario/borrowing.rs +++ b/crates/misc/component-async-tests/tests/scenario/borrowing.rs @@ -1,3 +1,4 @@ +use std::env; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -65,7 +66,11 @@ pub async fn async_borrowing_callee() -> Result<()> { pub async fn test_run_bool(components: &[&str], v: bool) -> Result<()> { let mut config = config(); - config.epoch_interruption(true); + // As of this writing, miri/pulley/epochs is a problematic combination, so + // we don't test it. + if env::var_os("MIRI_TEST_CWASM_DIR").is_none() { + config.epoch_interruption(true); + } let engine = Engine::new(&config)?; @@ -88,12 +93,15 @@ pub async fn test_run_bool(components: &[&str], v: bool) -> Result<()> { wakers: Arc::new(Mutex::new(None)), }, ); - store.set_epoch_deadline(1); - std::thread::spawn(move || { - std::thread::sleep(Duration::from_secs(10)); - engine.increment_epoch(); - }); + if env::var_os("MIRI_TEST_CWASM_DIR").is_none() { + store.set_epoch_deadline(1); + + std::thread::spawn(move || { + std::thread::sleep(Duration::from_secs(10)); + engine.increment_epoch(); + }); + } let instance = linker.instantiate_async(&mut store, &component).await?; let borrowing_host = diff --git a/crates/misc/component-async-tests/tests/scenario/common.rs b/crates/misc/component-async-tests/tests/scenario/common.rs index b39ed1aa00..669097ce9d 100644 --- a/crates/misc/component-async-tests/tests/scenario/common.rs +++ b/crates/misc/component-async-tests/tests/scenario/common.rs @@ -49,7 +49,11 @@ pub async fn compose(a: &[u8], b: &[u8]) -> Result> { #[allow(unused)] pub async fn test_run(component: &[u8]) -> Result<()> { let mut config = config(); - config.epoch_interruption(true); + // As of this writing, miri/pulley/epochs is a problematic combination, so + // we don't test it. + if env::var_os("MIRI_TEST_CWASM_DIR").is_none() { + config.epoch_interruption(true); + } let engine = Engine::new(&config)?; @@ -80,12 +84,15 @@ pub async fn test_run(component: &[u8]) -> Result<()> { wakers: Arc::new(Mutex::new(None)), }, ); - store.set_epoch_deadline(1); - std::thread::spawn(move || { - std::thread::sleep(Duration::from_secs(10)); - engine.increment_epoch(); - }); + if env::var_os("MIRI_TEST_CWASM_DIR").is_none() { + store.set_epoch_deadline(1); + + std::thread::spawn(move || { + std::thread::sleep(Duration::from_secs(10)); + engine.increment_epoch(); + }); + } let yield_host = component_async_tests::yield_host::bindings::YieldHost::instantiate_async( &mut store, &component, &linker, diff --git a/crates/misc/component-async-tests/tests/scenario/error_context.rs b/crates/misc/component-async-tests/tests/scenario/error_context.rs index a80b82d460..e27db6de12 100644 --- a/crates/misc/component-async-tests/tests/scenario/error_context.rs +++ b/crates/misc/component-async-tests/tests/scenario/error_context.rs @@ -7,10 +7,12 @@ pub async fn async_error_context() -> Result<()> { test_run(&[test_programs_artifacts::ASYNC_ERROR_CONTEXT_COMPONENT]).await } -#[tokio::test] -pub async fn async_error_context_callee() -> Result<()> { - test_run(&[test_programs_artifacts::ASYNC_ERROR_CONTEXT_COMPONENT]).await -} +// No-op function; we only test this by composing it in `async_error_context_caller` +#[allow( + dead_code, + reason = "here only to make the `assert_test_exists` macro happy" +)] +pub fn async_error_context_callee() {} #[tokio::test] pub async fn async_error_context_caller() -> Result<()> { diff --git a/crates/misc/component-async-tests/tests/scenario/round_trip.rs b/crates/misc/component-async-tests/tests/scenario/round_trip.rs index f337147a7b..a0beb6ee1c 100644 --- a/crates/misc/component-async-tests/tests/scenario/round_trip.rs +++ b/crates/misc/component-async-tests/tests/scenario/round_trip.rs @@ -1,6 +1,9 @@ use std::future::Future; use std::pin::pin; -use std::sync::{Arc, Mutex}; +use std::sync::{ + Arc, Mutex, + atomic::{AtomicU32, Ordering::Relaxed}, +}; use std::task::{Context, Wake, Waker}; use std::time::Duration; @@ -17,7 +20,7 @@ use wasmtime_wasi::p2::WasiCtxBuilder; use component_async_tests::Ctx; -pub use component_async_tests::util::{config, make_component}; +pub use component_async_tests::util::{config, make_component, sleep}; #[tokio::test] pub async fn async_round_trip_stackful() -> Result<()> { @@ -216,6 +219,11 @@ pub async fn test_round_trip( let component = make_component(&engine, components).await?; + // On miri, we only use one call style per test since they take so long to + // run. On non-miri, we use every call style for each test. + static CALL_STYLE_COUNTER: AtomicU32 = AtomicU32::new(0); + let call_style = CALL_STYLE_COUNTER.fetch_add(1, Relaxed) % 6; + // First, test the `wasmtime-wit-bindgen` static API: { let mut linker = Linker::new(&engine); @@ -232,125 +240,136 @@ pub async fn test_round_trip( let round_trip = component_async_tests::round_trip::bindings::RoundTrip::new(&mut store, &instance)?; - // Start concurrent calls and then join them all: - let mut futures = FuturesUnordered::new(); - for (input, output) in inputs_and_outputs { - let output = (*output).to_owned(); - futures.push( - round_trip - .local_local_baz() - .call_foo(&mut store, (*input).to_owned()) - .map(move |v| v.map(move |v| (v, output))), - ); - } + if call_style == 0 || !cfg!(miri) { + // Start concurrent calls and then join them all: + let mut futures = FuturesUnordered::new(); + for (input, output) in inputs_and_outputs { + let output = (*output).to_owned(); + futures.push( + round_trip + .local_local_baz() + .call_foo(&mut store, (*input).to_owned()) + .map(move |v| v.map(move |v| (v, output))), + ); + } - while let Some((actual, expected)) = instance.run(&mut store, futures.try_next()).await?? { - assert_eq!(expected, actual); + while let Some((actual, expected)) = + instance.run(&mut store, futures.try_next()).await?? + { + assert_eq!(expected, actual); + } } - // Now do it again using `Instance::run_with`: - instance - .run_with(&mut store, { - let inputs_and_outputs = inputs_and_outputs - .iter() - .map(|(a, b)| (String::from(*a), String::from(*b))) - .collect::>(); - - move |accessor| { - Box::pin(async move { - let mut futures = FuturesUnordered::new(); - accessor.with(|mut store| { - for (input, output) in &inputs_and_outputs { - let output = output.clone(); - futures.push( - round_trip - .local_local_baz() - .call_foo(&mut store, input.clone()) - .map(move |v| v.map(move |v| (v, output))) - .boxed(), - ); + if call_style == 1 || !cfg!(miri) { + // Now do it again using `Instance::run_with`: + instance + .run_with(&mut store, { + let inputs_and_outputs = inputs_and_outputs + .iter() + .map(|(a, b)| (String::from(*a), String::from(*b))) + .collect::>(); + + move |accessor| { + Box::pin(async move { + let mut futures = FuturesUnordered::new(); + accessor.with(|mut store| { + for (input, output) in &inputs_and_outputs { + let output = output.clone(); + futures.push( + round_trip + .local_local_baz() + .call_foo(&mut store, input.clone()) + .map(move |v| v.map(move |v| (v, output))) + .boxed(), + ); + } + }); + + while let Some((actual, expected)) = futures.try_next().await? { + assert_eq!(expected, actual); } - }); - while let Some((actual, expected)) = futures.try_next().await? { - assert_eq!(expected, actual); + Ok::<_, wasmtime::Error>(()) + }) + } + }) + .await??; + } + + if call_style == 2 || !cfg!(miri) { + // And again using `Instance::spawn`: + struct Task { + instance: Instance, + inputs_and_outputs: Vec<(String, String)>, + tx: oneshot::Sender<()>, + } + + impl AccessorTask, Result<()>> for Task { + async fn run(self, accessor: &mut Accessor) -> Result<()> { + let mut futures = FuturesUnordered::new(); + accessor.with(|mut store| { + let round_trip = + component_async_tests::round_trip::bindings::RoundTrip::new( + &mut store, + &self.instance, + )?; + + for (input, output) in &self.inputs_and_outputs { + let output = output.clone(); + futures.push( + round_trip + .local_local_baz() + .call_foo(&mut store, input.clone()) + .map(move |v| v.map(move |v| (v, output))) + .boxed(), + ); } Ok::<_, wasmtime::Error>(()) - }) - } - }) - .await??; - - // And again using `Instance::spawn`: - struct Task { - instance: Instance, - inputs_and_outputs: Vec<(String, String)>, - tx: oneshot::Sender<()>, - } + })?; - impl AccessorTask, Result<()>> for Task { - async fn run(self, accessor: &mut Accessor) -> Result<()> { - let mut futures = FuturesUnordered::new(); - accessor.with(|mut store| { - let round_trip = component_async_tests::round_trip::bindings::RoundTrip::new( - &mut store, - &self.instance, - )?; - - for (input, output) in &self.inputs_and_outputs { - let output = output.clone(); - futures.push( - round_trip - .local_local_baz() - .call_foo(&mut store, input.clone()) - .map(move |v| v.map(move |v| (v, output))) - .boxed(), - ); + while let Some((actual, expected)) = futures.try_next().await? { + assert_eq!(expected, actual); } - Ok::<_, wasmtime::Error>(()) - })?; + _ = self.tx.send(()); - while let Some((actual, expected)) = futures.try_next().await? { - assert_eq!(expected, actual); + Ok(()) } - - _ = self.tx.send(()); - - Ok(()) } - } - let (tx, rx) = oneshot::channel(); - instance.spawn( - &mut store, - Task { - instance, - inputs_and_outputs: inputs_and_outputs - .iter() - .map(|(a, b)| (String::from(*a), String::from(*b))) - .collect::>(), - tx, - }, - ); + let (tx, rx) = oneshot::channel(); + instance.spawn( + &mut store, + Task { + instance, + inputs_and_outputs: inputs_and_outputs + .iter() + .map(|(a, b)| (String::from(*a), String::from(*b))) + .collect::>(), + tx, + }, + ); - instance.run(&mut store, rx).await??; + instance.run(&mut store, rx).await??; + } - // And again using `TypedFunc::call_async`-based bindings: - let round_trip = - component_async_tests::round_trip::non_concurrent_export_bindings::RoundTrip::new( - &mut store, &instance, - )?; - - for (input, expected) in inputs_and_outputs { - assert_eq!( - *expected, - &round_trip - .local_local_baz() - .call_foo(&mut store, (*input).to_owned()) - .await? - ); + if call_style == 3 || !cfg!(miri) { + // And again using `TypedFunc::call_async`-based bindings: + let round_trip = + component_async_tests::round_trip::non_concurrent_export_bindings::RoundTrip::new( + &mut store, &instance, + )?; + + for (input, expected) in inputs_and_outputs { + assert_eq!( + *expected, + &round_trip + .local_local_baz() + .call_foo(&mut store, (*input).to_owned()) + .await? + ); + } } } @@ -364,7 +383,7 @@ pub async fn test_round_trip( .instance("local:local/baz")? .func_new_concurrent("[async]foo", |_, params| { Box::pin(async move { - tokio::time::sleep(Duration::from_millis(10)).await; + sleep(Duration::from_millis(10)).await; let Some(Val::String(s)) = params.into_iter().next() else { unreachable!() }; @@ -387,38 +406,44 @@ pub async fn test_round_trip( .get_func(&mut store, foo_function) .ok_or_else(|| anyhow!("can't find `foo` in instance"))?; - // Start three concurrent calls and then join them all: - let mut futures = FuturesUnordered::new(); - for (input, output) in inputs_and_outputs { - let output = (*output).to_owned(); - futures.push( - foo_function - .call_concurrent(&mut store, vec![Val::String((*input).to_owned())]) - .map(move |v| v.map(move |v| (v, output))), - ); - } + if call_style == 4 || !cfg!(miri) { + // Start three concurrent calls and then join them all: + let mut futures = FuturesUnordered::new(); + for (input, output) in inputs_and_outputs { + let output = (*output).to_owned(); + futures.push( + foo_function + .call_concurrent(&mut store, vec![Val::String((*input).to_owned())]) + .map(move |v| v.map(move |v| (v, output))), + ); + } - while let Some((actual, expected)) = instance.run(&mut store, futures.try_next()).await?? { - let Some(Val::String(actual)) = actual.into_iter().next() else { - unreachable!() - }; - assert_eq!(expected, actual); + while let Some((actual, expected)) = + instance.run(&mut store, futures.try_next()).await?? + { + let Some(Val::String(actual)) = actual.into_iter().next() else { + unreachable!() + }; + assert_eq!(expected, actual); + } } - // Now do it again using `Func::call_async`: - for (input, expected) in inputs_and_outputs { - let mut results = [Val::Bool(false)]; - foo_function - .call_async( - &mut store, - &[Val::String((*input).to_owned())], - &mut results, - ) - .await?; - let Val::String(actual) = &results[0] else { - unreachable!() - }; - assert_eq!(*expected, actual); + if call_style == 5 || !cfg!(miri) { + // Now do it again using `Func::call_async`: + for (input, expected) in inputs_and_outputs { + let mut results = [Val::Bool(false)]; + foo_function + .call_async( + &mut store, + &[Val::String((*input).to_owned())], + &mut results, + ) + .await?; + let Val::String(actual) = &results[0] else { + unreachable!() + }; + assert_eq!(*expected, actual); + } } } diff --git a/crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs b/crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs index 1be4bd326a..196189a286 100644 --- a/crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs +++ b/crates/misc/component-async-tests/tests/scenario/round_trip_direct.rs @@ -3,31 +3,24 @@ use std::time::Duration; use anyhow::{Result, anyhow}; use futures::stream::{FuturesUnordered, TryStreamExt}; -use tokio::fs; -use wasmtime::component::{Component, Linker, ResourceTable, Val}; +use wasmtime::component::{Linker, ResourceTable, Val}; use wasmtime::{Engine, Store}; use wasmtime_wasi::p2::WasiCtxBuilder; use component_async_tests::Ctx; -use component_async_tests::util::config; - -#[tokio::test] -pub async fn async_direct_stackless() -> Result<()> { - let stackless = - &fs::read(test_programs_artifacts::ASYNC_ROUND_TRIP_DIRECT_STACKLESS_COMPONENT).await?; - test_round_trip_direct_uncomposed(stackless).await -} +use component_async_tests::util::{config, make_component, sleep}; #[tokio::test] pub async fn async_round_trip_direct_stackless() -> Result<()> { - let stackless = - &fs::read(test_programs_artifacts::ASYNC_ROUND_TRIP_DIRECT_STACKLESS_COMPONENT).await?; - test_round_trip_direct_uncomposed(stackless).await + test_round_trip_direct_uncomposed( + test_programs_artifacts::ASYNC_ROUND_TRIP_DIRECT_STACKLESS_COMPONENT, + ) + .await } -async fn test_round_trip_direct_uncomposed(component: &[u8]) -> Result<()> { +async fn test_round_trip_direct_uncomposed(component: &str) -> Result<()> { test_round_trip_direct( - component, + &[component], "hello, world!", "hello, world! - entered guest - entered host - exited host - exited guest", ) @@ -35,7 +28,7 @@ async fn test_round_trip_direct_uncomposed(component: &[u8]) -> Result<()> { } async fn test_round_trip_direct( - component: &[u8], + components: &[&str], input: &str, expected_output: &str, ) -> Result<()> { @@ -53,7 +46,7 @@ async fn test_round_trip_direct( ) }; - let component = Component::new(&engine, component)?; + let component = make_component(&engine, components).await?; // First, test the `wasmtime-wit-bindgen` static API: { @@ -90,7 +83,7 @@ async fn test_round_trip_direct( wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; linker.root().func_new_concurrent("foo", |_, params| { Box::pin(async move { - tokio::time::sleep(Duration::from_millis(10)).await; + sleep(Duration::from_millis(10)).await; let Some(Val::String(s)) = params.into_iter().next() else { unreachable!() }; diff --git a/crates/misc/component-async-tests/tests/scenario/round_trip_many.rs b/crates/misc/component-async-tests/tests/scenario/round_trip_many.rs index 30abb42bef..bc1415e8dd 100644 --- a/crates/misc/component-async-tests/tests/scenario/round_trip_many.rs +++ b/crates/misc/component-async-tests/tests/scenario/round_trip_many.rs @@ -1,5 +1,8 @@ use std::iter; -use std::sync::{Arc, Mutex}; +use std::sync::{ + Arc, Mutex, + atomic::{AtomicU32, Ordering::Relaxed}, +}; use std::time::Duration; use anyhow::{Result, anyhow}; @@ -12,7 +15,7 @@ use wasmtime::{Engine, Store}; use wasmtime_wasi::p2::WasiCtxBuilder; use component_async_tests::Ctx; -use component_async_tests::util::{config, make_component}; +use component_async_tests::util::{config, make_component, sleep}; #[tokio::test] pub async fn async_round_trip_many_stackless() -> Result<()> { @@ -224,6 +227,11 @@ async fn test_round_trip_many( let f = Some(e.clone()); let g = Err(()); + // On miri, we only use one call style per test since they take so long to + // run. On non-miri, we use every call style for each test. + static CALL_STYLE_COUNTER: AtomicU32 = AtomicU32::new(0); + let call_style = CALL_STYLE_COUNTER.fetch_add(1, Relaxed) % 4; + // First, test the `wasmtime-wit-bindgen` static API: { let mut linker = Linker::new(&engine); @@ -241,73 +249,79 @@ async fn test_round_trip_many( &mut store, &instance, )?; - // Start concurrent calls and then join them all: - let mut futures = FuturesUnordered::new(); - for (input, output) in inputs_and_outputs { - let output = (*output).to_owned(); - futures.push( - round_trip_many - .local_local_many() - .call_foo( - &mut store, - (*input).to_owned(), - b, - c.clone(), - d, - e.clone(), - f.clone(), - g.clone(), - ) - .map(move |v| v.map(move |v| (v, output))), - ); + if call_style == 0 { + // Start concurrent calls and then join them all: + let mut futures = FuturesUnordered::new(); + for (input, output) in inputs_and_outputs { + let output = (*output).to_owned(); + futures.push( + round_trip_many + .local_local_many() + .call_foo( + &mut store, + (*input).to_owned(), + b, + c.clone(), + d, + e.clone(), + f.clone(), + g.clone(), + ) + .map(move |v| v.map(move |v| (v, output))), + ); + } + + while let Some((actual, expected)) = + instance.run(&mut store, futures.try_next()).await?? + { + assert_eq!( + (expected, b, c.clone(), d, e.clone(), f.clone(), g.clone()), + actual + ); + } } - while let Some((actual, expected)) = instance.run(&mut store, futures.try_next()).await?? { - assert_eq!( - (expected, b, c.clone(), d, e.clone(), f.clone(), g.clone()), - actual - ); - } - - // Now do it again using `TypedFunc::call_async`-based bindings: - let e = component_async_tests::round_trip_many::non_concurrent_export_bindings::exports::local::local::many::Stuff { + if call_style == 1 { + // Now do it again using `TypedFunc::call_async`-based bindings: + let e = component_async_tests::round_trip_many::non_concurrent_export_bindings::exports::local::local::many::Stuff { a: vec![42i32; 42], b: true, c: 424242, }; - let f = Some(e.clone()); - let g = Err(()); + let f = Some(e.clone()); + let g = Err(()); - let round_trip_many = component_async_tests::round_trip_many::non_concurrent_export_bindings::RoundTripMany::instantiate_async( + let round_trip_many = component_async_tests::round_trip_many::non_concurrent_export_bindings::RoundTripMany::instantiate_async( &mut store, &component, &linker, ) .await?; - for (input, expected) in inputs_and_outputs { - assert_eq!( - ( - (*expected).to_owned(), - b, - c.clone(), - d, - e.clone(), - f.clone(), - g.clone() - ), - round_trip_many - .local_local_many() - .call_foo( - &mut store, - (*input).to_owned(), + for (input, expected) in inputs_and_outputs { + assert_eq!( + ( + (*expected).to_owned(), b, c.clone(), d, e.clone(), f.clone(), g.clone() - ) - .await? - ); + ), + round_trip_many + .local_local_many() + .call_foo( + &mut store, + (*input).to_owned(), + b, + c.clone(), + d, + e.clone(), + f.clone(), + g.clone() + ) + .await? + ); + } } } @@ -321,7 +335,7 @@ async fn test_round_trip_many( .instance("local:local/many")? .func_new_concurrent("[async]foo", |_, params| { Box::pin(async move { - tokio::time::sleep(Duration::from_millis(10)).await; + sleep(Duration::from_millis(10)).await; let mut params = params.into_iter(); let Some(Val::String(s)) = params.next() else { unreachable!() @@ -367,34 +381,40 @@ async fn test_round_trip_many( ] }; - // Start three concurrent calls and then join them all: - let mut futures = FuturesUnordered::new(); - for (input, output) in inputs_and_outputs { - let output = (*output).to_owned(); - futures.push( - foo_function - .call_concurrent(&mut store, make(input)) - .map(move |v| v.map(move |v| (v, output))), - ); + if call_style == 2 { + // Start three concurrent calls and then join them all: + let mut futures = FuturesUnordered::new(); + for (input, output) in inputs_and_outputs { + let output = (*output).to_owned(); + futures.push( + foo_function + .call_concurrent(&mut store, make(input)) + .map(move |v| v.map(move |v| (v, output))), + ); + } + + while let Some((actual, expected)) = + instance.run(&mut store, futures.try_next()).await?? + { + let Some(Val::Tuple(actual)) = actual.into_iter().next() else { + unreachable!() + }; + assert_eq!(make(&expected), actual); + } } - while let Some((actual, expected)) = instance.run(&mut store, futures.try_next()).await?? { - let Some(Val::Tuple(actual)) = actual.into_iter().next() else { - unreachable!() - }; - assert_eq!(make(&expected), actual); - } - - // Now do it again using `Func::call_async`: - for (input, expected) in inputs_and_outputs { - let mut results = [Val::Bool(false)]; - foo_function - .call_async(&mut store, &make(input), &mut results) - .await?; - let Val::Tuple(actual) = &results[0] else { - unreachable!() - }; - assert_eq!(&make(expected), actual); + if call_style == 3 { + // Now do it again using `Func::call_async`: + for (input, expected) in inputs_and_outputs { + let mut results = [Val::Bool(false)]; + foo_function + .call_async(&mut store, &make(input), &mut results) + .await?; + let Val::Tuple(actual) = &results[0] else { + unreachable!() + }; + assert_eq!(&make(expected), actual); + } } } diff --git a/crates/misc/component-async-tests/tests/scenario/streams.rs b/crates/misc/component-async-tests/tests/scenario/streams.rs index b98454a536..7064c6f7bd 100644 --- a/crates/misc/component-async-tests/tests/scenario/streams.rs +++ b/crates/misc/component-async-tests/tests/scenario/streams.rs @@ -1,6 +1,9 @@ use { anyhow::Result, - component_async_tests::{Ctx, closed_streams, util::config}, + component_async_tests::{ + Ctx, closed_streams, + util::{config, make_component}, + }, futures::{ future::{self, FutureExt}, stream::{FuturesUnordered, StreamExt, TryStreamExt}, @@ -9,10 +12,9 @@ use { future::Future, sync::{Arc, Mutex}, }, - tokio::fs, wasmtime::{ Engine, Store, - component::{Component, Linker, ResourceTable, StreamReader, StreamWriter, VecBuffer}, + component::{Linker, ResourceTable, StreamReader, StreamWriter, VecBuffer}, }, wasmtime_wasi::p2::WasiCtxBuilder, }; @@ -35,10 +37,11 @@ pub async fn async_watch_streams() -> Result<()> { wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; - let component = Component::new( + let component = make_component( &engine, - &fs::read(test_programs_artifacts::ASYNC_CLOSED_STREAMS_COMPONENT).await?, - )?; + &[test_programs_artifacts::ASYNC_CLOSED_STREAMS_COMPONENT], + ) + .await?; let instance = linker.instantiate_async(&mut store, &component).await?; @@ -161,10 +164,11 @@ pub async fn test_closed_streams(watch: bool) -> Result<()> { wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; - let component = Component::new( + let component = make_component( &engine, - &fs::read(test_programs_artifacts::ASYNC_CLOSED_STREAMS_COMPONENT).await?, - )?; + &[test_programs_artifacts::ASYNC_CLOSED_STREAMS_COMPONENT], + ) + .await?; let instance = linker.instantiate_async(&mut store, &component).await?; diff --git a/crates/misc/component-async-tests/tests/scenario/transmit.rs b/crates/misc/component-async-tests/tests/scenario/transmit.rs index 9fad7fffab..83c62b46b1 100644 --- a/crates/misc/component-async-tests/tests/scenario/transmit.rs +++ b/crates/misc/component-async-tests/tests/scenario/transmit.rs @@ -79,6 +79,16 @@ pub async fn async_trap_cancel_host_after_return() -> Result<()> { test_cancel_trap(Mode::TrapCancelHostAfterReturn).await } +fn cancel_delay() -> u64 { + // Miri-based builds are much slower to run, so we delay longer in that case + // to ensure that async calls which the test expects to return `BLOCKED` + // actually do so. + // + // TODO: Make this test (more) deterministic so that such tuning is not + // necessary. + if cfg!(miri) { 1000 } else { 10 } +} + async fn test_cancel_trap(mode: Mode) -> Result<()> { let message = "`subtask.cancel` called after terminal status delivered"; let trap = test_cancel(mode).await.unwrap_err(); @@ -118,7 +128,9 @@ async fn test_cancel(mode: Mode) -> Result<()> { let instance = linker.instantiate_async(&mut store, &component).await?; let cancel_host = cancel::CancelHost::new(&mut store, &instance)?; - let run = cancel_host.local_local_cancel().call_run(&mut store, mode); + let run = cancel_host + .local_local_cancel() + .call_run(&mut store, mode, cancel_delay()); instance.run(&mut store, run).await??; Ok(()) diff --git a/crates/misc/component-async-tests/wit/test.wit b/crates/misc/component-async-tests/wit/test.wit index 0e88bcf6d9..78a7099898 100644 --- a/crates/misc/component-async-tests/wit/test.wit +++ b/crates/misc/component-async-tests/wit/test.wit @@ -153,7 +153,7 @@ interface cancel { trap-cancel-host-after-return, } - run: func(mode: mode); + run: func(mode: mode, cancel-delay-millis: u64); } interface intertask { diff --git a/crates/test-programs/artifacts/build.rs b/crates/test-programs/artifacts/build.rs index 79ce508566..50ba3c48ee 100644 --- a/crates/test-programs/artifacts/build.rs +++ b/crates/test-programs/artifacts/build.rs @@ -129,9 +129,15 @@ impl Artifacts { } fn build_rust_tests(&mut self, tests: &mut Vec) { + println!("cargo:rerun-if-env-changed=MIRI_TEST_CWASM_DIR"); + let release_mode = env::var_os("MIRI_TEST_CWASM_DIR").is_some(); + let mut cmd = cargo(); - cmd.arg("build") - .arg("--target=wasm32-wasip1") + cmd.arg("build"); + if release_mode { + cmd.arg("--release"); + } + cmd.arg("--target=wasm32-wasip1") .arg("--package=test-programs") .env("CARGO_TARGET_DIR", &self.out_dir) .env("CARGO_PROFILE_DEV_DEBUG", "2") @@ -157,7 +163,7 @@ impl Artifacts { let wasm = self .out_dir .join("wasm32-wasip1") - .join("debug") + .join(if release_mode { "release" } else { "debug" }) .join(format!("{target}.wasm")); self.read_deps_of(&wasm); tests.push(Test { diff --git a/crates/test-programs/src/bin/async_cancel_caller.rs b/crates/test-programs/src/bin/async_cancel_caller.rs index 5f013839c2..1b11f00052 100644 --- a/crates/test-programs/src/bin/async_cancel_caller.rs +++ b/crates/test-programs/src/bin/async_cancel_caller.rs @@ -82,6 +82,7 @@ struct SleepParams { enum State { S0 { mode: u8, + cancel_delay_millis: u64, }, S1 { mode: u8, @@ -108,9 +109,15 @@ enum State { } #[unsafe(export_name = "[async-lift]local:local/cancel#run")] -unsafe extern "C" fn export_run(mode: u8) -> u32 { +unsafe extern "C" fn export_run(mode: u8, cancel_delay_millis: u64) -> u32 { unsafe { - context_set(u32::try_from(Box::into_raw(Box::new(State::S0 { mode })) as usize).unwrap()); + context_set( + u32::try_from(Box::into_raw(Box::new(State::S0 { + mode, + cancel_delay_millis, + })) as usize) + .unwrap(), + ); callback_run(EVENT_NONE, 0, 0) } } @@ -120,7 +127,10 @@ unsafe extern "C" fn callback_run(event0: u32, event1: u32, event2: u32) -> u32 unsafe { let state = &mut *(usize::try_from(context_get()).unwrap() as *mut State); match state { - State::S0 { mode } => { + State::S0 { + mode, + cancel_delay_millis, + } => { assert_eq!(event0, EVENT_NONE); // First, call and cancel `sleep_with_options::sleep_millis` @@ -187,7 +197,7 @@ unsafe extern "C" fn callback_run(event0: u32, event1: u32, event2: u32) -> u32 // a non-zero cancel delay. Cancelling _should_ block this // time. - (*params).on_cancel_delay_millis = 10; + (*params).on_cancel_delay_millis = *cancel_delay_millis; let status = sleep_with_options::sleep_millis(params.cast()); diff --git a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs index 8b52290981..fb78c98686 100644 --- a/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs +++ b/crates/wasmtime/src/runtime/component/concurrent/futures_and_streams.rs @@ -9,7 +9,7 @@ use { component::{ Instance, Lower, Val, WasmList, WasmStr, concurrent::tls, - func::{self, Lift, LiftContext, LowerContext, Options}, + func::{self, LiftContext, LowerContext, Options}, matching::InstanceType, values::{ErrorContextAny, FutureAny, StreamAny}, }, @@ -2717,16 +2717,12 @@ impl Instance { // Read string from guest memory let address = usize::try_from(debug_msg_address)?; let len = usize::try_from(debug_msg_len)?; - let message = WasmStr::load( - lift_ctx, - InterfaceType::String, - &lift_ctx - .memory() - .get(address..) - .and_then(|b| b.get(..len)) - .map(|_| [debug_msg_address.to_le_bytes(), debug_msg_len.to_le_bytes()].concat()) - .ok_or_else(|| anyhow::anyhow!("invalid debug message pointer: out of bounds"))?, - )?; + lift_ctx + .memory() + .get(address..) + .and_then(|b| b.get(..len)) + .ok_or_else(|| anyhow::anyhow!("invalid debug message pointer: out of bounds"))?; + let message = WasmStr::new(address, len, lift_ctx)?; // Create a new ErrorContext that is tracked along with other concurrent state let err_ctx = ErrorContextState { diff --git a/crates/wasmtime/src/runtime/component/func/typed.rs b/crates/wasmtime/src/runtime/component/func/typed.rs index 1684023e1d..f8b1e73cd7 100644 --- a/crates/wasmtime/src/runtime/component/func/typed.rs +++ b/crates/wasmtime/src/runtime/component/func/typed.rs @@ -1644,7 +1644,7 @@ pub struct WasmStr { } impl WasmStr { - fn new(ptr: usize, len: usize, cx: &mut LiftContext<'_>) -> Result { + pub(crate) fn new(ptr: usize, len: usize, cx: &mut LiftContext<'_>) -> Result { let byte_len = match cx.options.string_encoding() { StringEncoding::Utf8 => Some(len), StringEncoding::Utf16 => len.checked_mul(2), diff --git a/crates/wasmtime/src/runtime/component/store.rs b/crates/wasmtime/src/runtime/component/store.rs index 09622e2fa5..725e0f4a69 100644 --- a/crates/wasmtime/src/runtime/component/store.rs +++ b/crates/wasmtime/src/runtime/component/store.rs @@ -31,8 +31,8 @@ impl ComponentStoreData { } #[cfg(feature = "component-model-async")] - pub(crate) unsafe fn drop_fibers(&mut self) { - for (_, instance) in self.instances.iter_mut() { + pub(crate) unsafe fn drop_fibers(store: &mut StoreOpaque) { + for (_, instance) in store.store_data_mut().components.instances.iter_mut() { let Some(instance) = instance.as_mut() else { continue; }; diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs index 8cd7946477..41d3207a96 100644 --- a/crates/wasmtime/src/runtime/store.rs +++ b/crates/wasmtime/src/runtime/store.rs @@ -78,6 +78,8 @@ use crate::RootSet; #[cfg(feature = "component-model-async")] +use crate::component::ComponentStoreData; +#[cfg(feature = "component-model-async")] use crate::component::concurrent; use crate::module::RegisteredModuleId; use crate::prelude::*; @@ -2337,7 +2339,7 @@ impl Drop for Store { // need to be resumed and allowed to exit cleanly before we yank the // state out from under them. #[cfg(feature = "component-model-async")] - self.inner.store_data.components.drop_fibers(); + ComponentStoreData::drop_fibers(&mut self.inner); ManuallyDrop::drop(&mut self.inner.data); ManuallyDrop::drop(&mut self.inner); From 2d6312b6d0780bbdff1e7a2146a855f0d37f4350 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Tue, 17 Jun 2025 10:02:41 -0600 Subject: [PATCH 5/5] fix stack switching compilation error Signed-off-by: Joel Dice --- .../wasmtime/src/runtime/vm/stack_switching/stack/unix.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/wasmtime/src/runtime/vm/stack_switching/stack/unix.rs b/crates/wasmtime/src/runtime/vm/stack_switching/stack/unix.rs index 0a3aaadc9c..ea9a4d2478 100644 --- a/crates/wasmtime/src/runtime/vm/stack_switching/stack/unix.rs +++ b/crates/wasmtime/src/runtime/vm/stack_switching/stack/unix.rs @@ -310,7 +310,6 @@ unsafe extern "C" fn fiber_start( return_value_count: u32, ) { unsafe { - let func_ref = func_ref.as_ref().expect("Non-null function reference"); let caller_vmxtx = VMOpaqueContext::from_vmcontext(NonNull::new_unchecked(caller_vmctx)); let args = &mut *args; let params_and_returns: NonNull<[ValRaw]> = if args.capacity == 0 { @@ -333,7 +332,12 @@ unsafe extern "C" fn fiber_start( // // TODO(dhil): we are ignoring the boolean return value // here... we probably shouldn't. - func_ref.array_call(None, caller_vmxtx, params_and_returns); + VMFuncRef::array_call( + NonNull::new(func_ref as *mut _).unwrap(), + None, + caller_vmxtx, + params_and_returns, + ); // The array call trampoline should have just written // `return_value_count` values to the `args` buffer. Let's reflect that