#![cfg(not(miri))] use crate::async_functions::{execute_across_threads, PollOnce}; use anyhow::Result; use wasmtime::component::*; use wasmtime::{Engine, Store, StoreContextMut, Trap}; use wasmtime_component_util::REALLOC_AND_FREE; /// This is super::func::thunks, except with an async store. #[tokio::test] async fn smoke() -> Result<()> { let component = r#" (component (core module $m (func (export "thunk")) (func (export "thunk-trap") unreachable) ) (core instance $i (instantiate $m)) (func (export "thunk") (canon lift (core func $i "thunk")) ) (func (export "thunk-trap") (canon lift (core func $i "thunk-trap")) ) ) "#; let engine = super::async_engine(); let component = Component::new(&engine, component)?; let mut store = Store::new(&engine, ()); let instance = Linker::new(&engine) .instantiate_async(&mut store, &component) .await?; let thunk = instance.get_typed_func::<(), ()>(&mut store, "thunk")?; thunk.call_async(&mut store, ()).await?; thunk.post_return_async(&mut store).await?; let err = instance .get_typed_func::<(), ()>(&mut store, "thunk-trap")? .call_async(&mut store, ()) .await .unwrap_err(); assert_eq!(err.downcast::()?, Trap::UnreachableCodeReached); Ok(()) } /// Handle an import function, created using component::Linker::func_wrap_async. #[tokio::test] async fn smoke_func_wrap() -> Result<()> { let component = r#" (component (type $f (func)) (import "i" (func $f)) (core module $m (import "imports" "i" (func $i)) (func (export "thunk") call $i) ) (core func $f (canon lower (func $f))) (core instance $i (instantiate $m (with "imports" (instance (export "i" (func $f)) )) )) (func (export "thunk") (canon lift (core func $i "thunk")) ) ) "#; let engine = super::async_engine(); let component = Component::new(&engine, component)?; let mut store = Store::new(&engine, ()); let mut linker = Linker::new(&engine); let mut root = linker.root(); root.func_wrap_async("i", |_: StoreContextMut<()>, _: ()| { Box::new(async { Ok(()) }) })?; let instance = linker.instantiate_async(&mut store, &component).await?; let thunk = instance.get_typed_func::<(), ()>(&mut store, "thunk")?; thunk.call_async(&mut store, ()).await?; thunk.post_return_async(&mut store).await?; Ok(()) } // This test stresses TLS management in combination with the `realloc` option // for imported functions. This will create an async computation which invokes a // component that invokes an imported function. The imported function returns a // list which will require invoking malloc. // // As an added stressor all polls are sprinkled across threads through // `execute_across_threads`. Yields are injected liberally by configuring 1 // fuel consumption to trigger a yield. // // Overall a yield should happen during malloc which should be an "interesting // situation" with respect to the runtime. #[tokio::test] async fn resume_separate_thread() -> Result<()> { let mut config = component_test_util::config(); config.async_support(true); config.consume_fuel(true); let engine = Engine::new(&config)?; let component = format!( r#" (component (import "yield" (func $yield (result (list u8)))) (core module $libc (memory (export "memory") 1) {REALLOC_AND_FREE} ) (core instance $libc (instantiate $libc)) (core func $yield (canon lower (func $yield) (memory $libc "memory") (realloc (func $libc "realloc")) ) ) (core module $m (import "" "yield" (func $yield (param i32))) (import "libc" "memory" (memory 0)) (func $start i32.const 8 call $yield ) (start $start) ) (core instance (instantiate $m (with "" (instance (export "yield" (func $yield)))) (with "libc" (instance $libc)) )) ) "# ); let component = Component::new(&engine, component)?; let mut linker = Linker::new(&engine); linker .root() .func_wrap_async("yield", |_: StoreContextMut<()>, _: ()| { Box::new(async { tokio::task::yield_now().await; Ok((vec![1u8, 2u8],)) }) })?; execute_across_threads(async move { let mut store = Store::new(&engine, ()); store.out_of_fuel_async_yield(u64::MAX, 1); linker.instantiate_async(&mut store, &component).await?; Ok::<_, anyhow::Error>(()) }) .await?; Ok(()) } // This test is intended to stress TLS management in the component model around // the management of the `realloc` function. This creates an async computation // representing the execution of a component model function where entry into the // component uses `realloc` and then the component runs. This async computation // is then polled iteratively with another "wasm activation" (in this case a // core wasm function) on the stack. The poll-per-call should work and nothing // should in theory have problems here. // // As an added stressor all polls are sprinkled across threads through // `execute_across_threads`. Yields are injected liberally by configuring 1 // fuel consumption to trigger a yield. // // Overall a yield should happen during malloc which should be an "interesting // situation" with respect to the runtime. #[tokio::test] async fn poll_through_wasm_activation() -> Result<()> { let mut config = component_test_util::config(); config.async_support(true); config.consume_fuel(true); let engine = Engine::new(&config)?; let component = format!( r#" (component (core module $m {REALLOC_AND_FREE} (memory (export "memory") 1) (func (export "run") (param i32 i32) ) ) (core instance $i (instantiate $m)) (func (export "run") (param "x" (list u8)) (canon lift (core func $i "run") (memory $i "memory") (realloc (func $i "realloc")))) ) "# ); let component = Component::new(&engine, component)?; let linker = Linker::new(&engine); let invoke_component = { let engine = engine.clone(); async move { let mut store = Store::new(&engine, ()); store.out_of_fuel_async_yield(u64::MAX, 1); let instance = linker.instantiate_async(&mut store, &component).await?; let func = instance.get_typed_func::<(Vec,), ()>(&mut store, "run")?; func.call_async(&mut store, (vec![1, 2, 3],)).await?; Ok::<_, anyhow::Error>(()) } }; execute_across_threads(async move { let mut store = Store::new(&engine, Some(Box::pin(invoke_component))); let poll_once = wasmtime::Func::wrap0_async(&mut store, |mut cx| { let invoke_component = cx.data_mut().take().unwrap(); Box::new(async move { match PollOnce::new(invoke_component).await { Ok(result) => { result?; Ok(1) } Err(future) => { *cx.data_mut() = Some(future); Ok(0) } } }) }); let poll_once = poll_once.typed::<(), i32>(&mut store)?; while poll_once.call_async(&mut store, ()).await? != 1 { // loop around to call again } Ok::<_, anyhow::Error>(()) }) .await?; Ok(()) }