1 #![cfg(not(miri))] 2 3 use crate::async_functions::{execute_across_threads, PollOnce}; 4 use anyhow::Result; 5 use wasmtime::component::*; 6 use wasmtime::{Engine, Store, StoreContextMut, Trap}; 7 use wasmtime_component_util::REALLOC_AND_FREE; 8 9 /// This is super::func::thunks, except with an async store. 10 #[tokio::test] 11 async fn smoke() -> Result<()> { 12 let component = r#" 13 (component 14 (core module $m 15 (func (export "thunk")) 16 (func (export "thunk-trap") unreachable) 17 ) 18 (core instance $i (instantiate $m)) 19 (func (export "thunk") 20 (canon lift (core func $i "thunk")) 21 ) 22 (func (export "thunk-trap") 23 (canon lift (core func $i "thunk-trap")) 24 ) 25 ) 26 "#; 27 28 let engine = super::async_engine(); 29 let component = Component::new(&engine, component)?; 30 let mut store = Store::new(&engine, ()); 31 let instance = Linker::new(&engine) 32 .instantiate_async(&mut store, &component) 33 .await?; 34 35 let thunk = instance.get_typed_func::<(), ()>(&mut store, "thunk")?; 36 37 thunk.call_async(&mut store, ()).await?; 38 thunk.post_return_async(&mut store).await?; 39 40 let err = instance 41 .get_typed_func::<(), ()>(&mut store, "thunk-trap")? 42 .call_async(&mut store, ()) 43 .await 44 .unwrap_err(); 45 assert_eq!(err.downcast::<Trap>()?, Trap::UnreachableCodeReached); 46 47 Ok(()) 48 } 49 50 /// Handle an import function, created using component::Linker::func_wrap_async. 51 #[tokio::test] 52 async fn smoke_func_wrap() -> Result<()> { 53 let component = r#" 54 (component 55 (type $f (func)) 56 (import "i" (func $f)) 57 58 (core module $m 59 (import "imports" "i" (func $i)) 60 (func (export "thunk") call $i) 61 ) 62 63 (core func $f (canon lower (func $f))) 64 (core instance $i (instantiate $m 65 (with "imports" (instance 66 (export "i" (func $f)) 67 )) 68 )) 69 (func (export "thunk") 70 (canon lift (core func $i "thunk")) 71 ) 72 ) 73 "#; 74 75 let engine = super::async_engine(); 76 let component = Component::new(&engine, component)?; 77 let mut store = Store::new(&engine, ()); 78 let mut linker = Linker::new(&engine); 79 let mut root = linker.root(); 80 root.func_wrap_async("i", |_: StoreContextMut<()>, _: ()| { 81 Box::new(async { Ok(()) }) 82 })?; 83 84 let instance = linker.instantiate_async(&mut store, &component).await?; 85 86 let thunk = instance.get_typed_func::<(), ()>(&mut store, "thunk")?; 87 88 thunk.call_async(&mut store, ()).await?; 89 thunk.post_return_async(&mut store).await?; 90 91 Ok(()) 92 } 93 94 // This test stresses TLS management in combination with the `realloc` option 95 // for imported functions. This will create an async computation which invokes a 96 // component that invokes an imported function. The imported function returns a 97 // list which will require invoking malloc. 98 // 99 // As an added stressor all polls are sprinkled across threads through 100 // `execute_across_threads`. Yields are injected liberally by configuring 1 101 // fuel consumption to trigger a yield. 102 // 103 // Overall a yield should happen during malloc which should be an "interesting 104 // situation" with respect to the runtime. 105 #[tokio::test] 106 async fn resume_separate_thread() -> Result<()> { 107 let mut config = component_test_util::config(); 108 config.async_support(true); 109 config.consume_fuel(true); 110 let engine = Engine::new(&config)?; 111 let component = format!( 112 r#" 113 (component 114 (import "yield" (func $yield (result (list u8)))) 115 (core module $libc 116 (memory (export "memory") 1) 117 {REALLOC_AND_FREE} 118 ) 119 (core instance $libc (instantiate $libc)) 120 121 (core func $yield 122 (canon lower 123 (func $yield) 124 (memory $libc "memory") 125 (realloc (func $libc "realloc")) 126 ) 127 ) 128 129 (core module $m 130 (import "" "yield" (func $yield (param i32))) 131 (import "libc" "memory" (memory 0)) 132 (func $start 133 i32.const 8 134 call $yield 135 ) 136 (start $start) 137 ) 138 (core instance (instantiate $m 139 (with "" (instance (export "yield" (func $yield)))) 140 (with "libc" (instance $libc)) 141 )) 142 ) 143 "# 144 ); 145 let component = Component::new(&engine, component)?; 146 let mut linker = Linker::new(&engine); 147 linker 148 .root() 149 .func_wrap_async("yield", |_: StoreContextMut<()>, _: ()| { 150 Box::new(async { 151 tokio::task::yield_now().await; 152 Ok((vec![1u8, 2u8],)) 153 }) 154 })?; 155 156 execute_across_threads(async move { 157 let mut store = Store::new(&engine, ()); 158 store.set_fuel(u64::MAX).unwrap(); 159 store.fuel_async_yield_interval(Some(1)).unwrap(); 160 linker.instantiate_async(&mut store, &component).await?; 161 Ok::<_, anyhow::Error>(()) 162 }) 163 .await?; 164 Ok(()) 165 } 166 167 // This test is intended to stress TLS management in the component model around 168 // the management of the `realloc` function. This creates an async computation 169 // representing the execution of a component model function where entry into the 170 // component uses `realloc` and then the component runs. This async computation 171 // is then polled iteratively with another "wasm activation" (in this case a 172 // core wasm function) on the stack. The poll-per-call should work and nothing 173 // should in theory have problems here. 174 // 175 // As an added stressor all polls are sprinkled across threads through 176 // `execute_across_threads`. Yields are injected liberally by configuring 1 177 // fuel consumption to trigger a yield. 178 // 179 // Overall a yield should happen during malloc which should be an "interesting 180 // situation" with respect to the runtime. 181 #[tokio::test] 182 async fn poll_through_wasm_activation() -> Result<()> { 183 let mut config = component_test_util::config(); 184 config.async_support(true); 185 config.consume_fuel(true); 186 let engine = Engine::new(&config)?; 187 let component = format!( 188 r#" 189 (component 190 (core module $m 191 {REALLOC_AND_FREE} 192 (memory (export "memory") 1) 193 (func (export "run") (param i32 i32) 194 ) 195 ) 196 (core instance $i (instantiate $m)) 197 (func (export "run") (param "x" (list u8)) 198 (canon lift (core func $i "run") 199 (memory $i "memory") 200 (realloc (func $i "realloc")))) 201 ) 202 "# 203 ); 204 let component = Component::new(&engine, component)?; 205 let linker = Linker::new(&engine); 206 207 let invoke_component = { 208 let engine = engine.clone(); 209 async move { 210 let mut store = Store::new(&engine, ()); 211 store.set_fuel(u64::MAX).unwrap(); 212 store.fuel_async_yield_interval(Some(1)).unwrap(); 213 let instance = linker.instantiate_async(&mut store, &component).await?; 214 let func = instance.get_typed_func::<(Vec<u8>,), ()>(&mut store, "run")?; 215 func.call_async(&mut store, (vec![1, 2, 3],)).await?; 216 Ok::<_, anyhow::Error>(()) 217 } 218 }; 219 220 execute_across_threads(async move { 221 let mut store = Store::new(&engine, Some(Box::pin(invoke_component))); 222 let poll_once = wasmtime::Func::wrap_async(&mut store, |mut cx, _: ()| { 223 let invoke_component = cx.data_mut().take().unwrap(); 224 Box::new(async move { 225 match PollOnce::new(invoke_component).await { 226 Ok(result) => { 227 result?; 228 Ok(1) 229 } 230 Err(future) => { 231 *cx.data_mut() = Some(future); 232 Ok(0) 233 } 234 } 235 }) 236 }); 237 let poll_once = poll_once.typed::<(), i32>(&mut store)?; 238 while poll_once.call_async(&mut store, ()).await? != 1 { 239 // loop around to call again 240 } 241 Ok::<_, anyhow::Error>(()) 242 }) 243 .await?; 244 Ok(()) 245 } 246 247 /// Test async drop method for host resources. 248 #[tokio::test] 249 async fn drop_resource_async() -> Result<()> { 250 use std::sync::Arc; 251 use std::sync::Mutex; 252 253 let engine = super::async_engine(); 254 let c = Component::new( 255 &engine, 256 r#" 257 (component 258 (import "t" (type $t (sub resource))) 259 260 (core func $drop (canon resource.drop $t)) 261 262 (core module $m 263 (import "" "drop" (func $drop (param i32))) 264 (func (export "f") (param i32) 265 (call $drop (local.get 0)) 266 ) 267 ) 268 (core instance $i (instantiate $m 269 (with "" (instance 270 (export "drop" (func $drop)) 271 )) 272 )) 273 274 (func (export "f") (param "x" (own $t)) 275 (canon lift (core func $i "f"))) 276 ) 277 "#, 278 )?; 279 280 struct MyType; 281 282 let mut store = Store::new(&engine, ()); 283 let mut linker = Linker::new(&engine); 284 285 let drop_status = Arc::new(Mutex::new("not dropped")); 286 let ds = drop_status.clone(); 287 288 linker 289 .root() 290 .resource_async("t", ResourceType::host::<MyType>(), move |_, _| { 291 let ds = ds.clone(); 292 Box::new(async move { 293 *ds.lock().unwrap() = "before yield"; 294 tokio::task::yield_now().await; 295 *ds.lock().unwrap() = "after yield"; 296 Ok(()) 297 }) 298 })?; 299 let i = linker.instantiate_async(&mut store, &c).await?; 300 let f = i.get_typed_func::<(Resource<MyType>,), ()>(&mut store, "f")?; 301 302 execute_across_threads(async move { 303 let resource = Resource::new_own(100); 304 f.call_async(&mut store, (resource,)).await?; 305 f.post_return_async(&mut store).await?; 306 Ok::<_, anyhow::Error>(()) 307 }) 308 .await?; 309 310 assert_eq!("after yield", *drop_status.lock().unwrap()); 311 312 Ok(()) 313 } 314