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