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/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/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); 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 diff --git a/crates/wasmtime/src/runtime/vm/table.rs b/crates/wasmtime/src/runtime/vm/table.rs index d31de31749..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) } } } @@ -957,25 +965,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/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(), 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))) + ) )