xref: /wasmtime-44.0.1/tests/all/call_hook.rs (revision fee9be21)
1 #![cfg(not(miri))]
2 
3 use anyhow::bail;
4 use std::future::Future;
5 use std::pin::Pin;
6 use std::task::{self, Poll};
7 use wasmtime::*;
8 
9 // Crate a synchronous Func, call it directly:
10 #[test]
11 fn call_wrapped_func() -> Result<(), Error> {
12     let mut store = Store::<State>::default();
13     store.call_hook(sync_call_hook);
14 
15     fn verify(state: &State) {
16         // Calling this func will switch context into wasm, then back to host:
17         assert_eq!(state.context, vec![Context::Wasm, Context::Host]);
18 
19         assert_eq!(state.calls_into_host, state.returns_from_host + 1);
20         assert_eq!(state.calls_into_wasm, state.returns_from_wasm + 1);
21     }
22 
23     let mut funcs = Vec::new();
24     funcs.push(Func::wrap(
25         &mut store,
26         |caller: Caller<State>, a: i32, b: i64, c: f32, d: f64| {
27             verify(caller.data());
28 
29             assert_eq!(a, 1);
30             assert_eq!(b, 2);
31             assert_eq!(c, 3.0);
32             assert_eq!(d, 4.0);
33         },
34     ));
35     let func_ty = FuncType::new(
36         store.engine(),
37         [ValType::I32, ValType::I64, ValType::F32, ValType::F64],
38         [],
39     );
40     funcs.push(Func::new(
41         &mut store,
42         func_ty,
43         |caller: Caller<State>, params, results| {
44             verify(caller.data());
45 
46             assert_eq!(params.len(), 4);
47             assert_eq!(params[0].i32().unwrap(), 1);
48             assert_eq!(params[1].i64().unwrap(), 2);
49             assert_eq!(params[2].f32().unwrap(), 3.0);
50             assert_eq!(params[3].f64().unwrap(), 4.0);
51             assert_eq!(results.len(), 0);
52             Ok(())
53         },
54     ));
55     let func_ty = FuncType::new(
56         store.engine(),
57         [ValType::I32, ValType::I64, ValType::F32, ValType::F64],
58         [],
59     );
60     funcs.push(unsafe {
61         Func::new_unchecked(&mut store, func_ty, |caller: Caller<State>, space| {
62             verify(caller.data());
63 
64             assert_eq!(space[0].get_i32(), 1i32);
65             assert_eq!(space[1].get_i64(), 2i64);
66             assert_eq!(space[2].get_f32(), 3.0f32.to_bits());
67             assert_eq!(space[3].get_f64(), 4.0f64.to_bits());
68             Ok(())
69         })
70     });
71 
72     let mut n = 0;
73     for f in funcs.iter() {
74         f.call(
75             &mut store,
76             &[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()],
77             &mut [],
78         )?;
79         n += 1;
80 
81         // One switch from vm to host to call f, another in return from f.
82         assert_eq!(store.data().calls_into_host, n);
83         assert_eq!(store.data().returns_from_host, n);
84         assert_eq!(store.data().calls_into_wasm, n);
85         assert_eq!(store.data().returns_from_wasm, n);
86 
87         f.typed::<(i32, i64, f32, f64), ()>(&store)?
88             .call(&mut store, (1, 2, 3.0, 4.0))?;
89         n += 1;
90 
91         assert_eq!(store.data().calls_into_host, n);
92         assert_eq!(store.data().returns_from_host, n);
93         assert_eq!(store.data().calls_into_wasm, n);
94         assert_eq!(store.data().returns_from_wasm, n);
95 
96         unsafe {
97             let mut args = [
98                 Val::I32(1).to_raw(&mut store)?,
99                 Val::I64(2).to_raw(&mut store)?,
100                 Val::F32(3.0f32.to_bits()).to_raw(&mut store)?,
101                 Val::F64(4.0f64.to_bits()).to_raw(&mut store)?,
102             ];
103             f.call_unchecked(&mut store, &mut args)?;
104         }
105         n += 1;
106 
107         assert_eq!(store.data().calls_into_host, n);
108         assert_eq!(store.data().returns_from_host, n);
109         assert_eq!(store.data().calls_into_wasm, n);
110         assert_eq!(store.data().returns_from_wasm, n);
111     }
112 
113     Ok(())
114 }
115 
116 // Create an async Func, call it directly:
117 #[tokio::test]
118 async fn call_wrapped_async_func() -> Result<(), Error> {
119     let mut config = Config::new();
120     config.async_support(true);
121     let engine = Engine::new(&config)?;
122     let mut store = Store::new(&engine, State::default());
123     store.call_hook(sync_call_hook);
124     let f = Func::wrap_async(
125         &mut store,
126         |caller: Caller<State>, (a, b, c, d): (i32, i64, f32, f64)| {
127             Box::new(async move {
128                 // Calling this func will switch context into wasm, then back to host:
129                 assert_eq!(caller.data().context, vec![Context::Wasm, Context::Host]);
130 
131                 assert_eq!(
132                     caller.data().calls_into_host,
133                     caller.data().returns_from_host + 1
134                 );
135                 assert_eq!(
136                     caller.data().calls_into_wasm,
137                     caller.data().returns_from_wasm + 1
138                 );
139 
140                 assert_eq!(a, 1);
141                 assert_eq!(b, 2);
142                 assert_eq!(c, 3.0);
143                 assert_eq!(d, 4.0);
144             })
145         },
146     );
147 
148     f.call_async(
149         &mut store,
150         &[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()],
151         &mut [],
152     )
153     .await?;
154 
155     // One switch from vm to host to call f, another in return from f.
156     assert_eq!(store.data().calls_into_host, 1);
157     assert_eq!(store.data().returns_from_host, 1);
158     assert_eq!(store.data().calls_into_wasm, 1);
159     assert_eq!(store.data().returns_from_wasm, 1);
160 
161     f.typed::<(i32, i64, f32, f64), ()>(&store)?
162         .call_async(&mut store, (1, 2, 3.0, 4.0))
163         .await?;
164 
165     assert_eq!(store.data().calls_into_host, 2);
166     assert_eq!(store.data().returns_from_host, 2);
167     assert_eq!(store.data().calls_into_wasm, 2);
168     assert_eq!(store.data().returns_from_wasm, 2);
169 
170     Ok(())
171 }
172 
173 // Use the Linker to define a sync func, call it through WebAssembly:
174 #[test]
175 fn call_linked_func() -> Result<(), Error> {
176     let engine = Engine::default();
177     let mut store = Store::new(&engine, State::default());
178     store.call_hook(sync_call_hook);
179     let mut linker = Linker::new(&engine);
180 
181     linker.func_wrap(
182         "host",
183         "f",
184         |caller: Caller<State>, a: i32, b: i64, c: f32, d: f64| {
185             // Calling this func will switch context into wasm, then back to host:
186             assert_eq!(caller.data().context, vec![Context::Wasm, Context::Host]);
187 
188             assert_eq!(
189                 caller.data().calls_into_host,
190                 caller.data().returns_from_host + 1
191             );
192             assert_eq!(
193                 caller.data().calls_into_wasm,
194                 caller.data().returns_from_wasm + 1
195             );
196 
197             assert_eq!(a, 1);
198             assert_eq!(b, 2);
199             assert_eq!(c, 3.0);
200             assert_eq!(d, 4.0);
201         },
202     )?;
203 
204     let wat = r#"
205         (module
206             (import "host" "f"
207                 (func $f (param i32) (param i64) (param f32) (param f64)))
208             (func (export "export")
209                 (call $f (i32.const 1) (i64.const 2) (f32.const 3.0) (f64.const 4.0)))
210         )
211     "#;
212     let module = Module::new(&engine, wat)?;
213 
214     let inst = linker.instantiate(&mut store, &module)?;
215     let export = inst
216         .get_export(&mut store, "export")
217         .expect("get export")
218         .into_func()
219         .expect("export is func");
220 
221     export.call(&mut store, &[], &mut [])?;
222 
223     // One switch from vm to host to call f, another in return from f.
224     assert_eq!(store.data().calls_into_host, 1);
225     assert_eq!(store.data().returns_from_host, 1);
226     assert_eq!(store.data().calls_into_wasm, 1);
227     assert_eq!(store.data().returns_from_wasm, 1);
228 
229     export.typed::<(), ()>(&store)?.call(&mut store, ())?;
230 
231     assert_eq!(store.data().calls_into_host, 2);
232     assert_eq!(store.data().returns_from_host, 2);
233     assert_eq!(store.data().calls_into_wasm, 2);
234     assert_eq!(store.data().returns_from_wasm, 2);
235 
236     Ok(())
237 }
238 
239 // Use the Linker to define an async func, call it through WebAssembly:
240 #[tokio::test]
241 async fn call_linked_func_async() -> Result<(), Error> {
242     let mut config = Config::new();
243     config.async_support(true);
244     let engine = Engine::new(&config)?;
245     let mut store = Store::new(&engine, State::default());
246     store.call_hook(sync_call_hook);
247 
248     let f = Func::wrap_async(
249         &mut store,
250         |caller: Caller<State>, (a, b, c, d): (i32, i64, f32, f64)| {
251             Box::new(async move {
252                 // Calling this func will switch context into wasm, then back to host:
253                 assert_eq!(caller.data().context, vec![Context::Wasm, Context::Host]);
254 
255                 assert_eq!(
256                     caller.data().calls_into_host,
257                     caller.data().returns_from_host + 1
258                 );
259                 assert_eq!(
260                     caller.data().calls_into_wasm,
261                     caller.data().returns_from_wasm + 1
262                 );
263                 assert_eq!(a, 1);
264                 assert_eq!(b, 2);
265                 assert_eq!(c, 3.0);
266                 assert_eq!(d, 4.0);
267             })
268         },
269     );
270 
271     let mut linker = Linker::new(&engine);
272 
273     linker.define(&mut store, "host", "f", f)?;
274 
275     let wat = r#"
276         (module
277             (import "host" "f"
278                 (func $f (param i32) (param i64) (param f32) (param f64)))
279             (func (export "export")
280                 (call $f (i32.const 1) (i64.const 2) (f32.const 3.0) (f64.const 4.0)))
281         )
282     "#;
283     let module = Module::new(&engine, wat)?;
284 
285     let inst = linker.instantiate_async(&mut store, &module).await?;
286     let export = inst
287         .get_export(&mut store, "export")
288         .expect("get export")
289         .into_func()
290         .expect("export is func");
291 
292     export.call_async(&mut store, &[], &mut []).await?;
293 
294     // One switch from vm to host to call f, another in return from f.
295     assert_eq!(store.data().calls_into_host, 1);
296     assert_eq!(store.data().returns_from_host, 1);
297     assert_eq!(store.data().calls_into_wasm, 1);
298     assert_eq!(store.data().returns_from_wasm, 1);
299 
300     export
301         .typed::<(), ()>(&store)?
302         .call_async(&mut store, ())
303         .await?;
304 
305     assert_eq!(store.data().calls_into_host, 2);
306     assert_eq!(store.data().returns_from_host, 2);
307     assert_eq!(store.data().calls_into_wasm, 2);
308     assert_eq!(store.data().returns_from_wasm, 2);
309 
310     Ok(())
311 }
312 
313 #[test]
314 fn instantiate() -> Result<(), Error> {
315     let mut store = Store::<State>::default();
316     store.call_hook(sync_call_hook);
317 
318     let m = Module::new(store.engine(), "(module)")?;
319     Instance::new(&mut store, &m, &[])?;
320     assert_eq!(store.data().calls_into_wasm, 0);
321     assert_eq!(store.data().calls_into_host, 0);
322 
323     let m = Module::new(store.engine(), "(module (func) (start 0))")?;
324     Instance::new(&mut store, &m, &[])?;
325     assert_eq!(store.data().calls_into_wasm, 1);
326     assert_eq!(store.data().calls_into_host, 0);
327 
328     Ok(())
329 }
330 
331 #[tokio::test]
332 async fn instantiate_async() -> Result<(), Error> {
333     let mut config = Config::new();
334     config.async_support(true);
335     let engine = Engine::new(&config)?;
336     let mut store = Store::new(&engine, State::default());
337     store.call_hook(sync_call_hook);
338 
339     let m = Module::new(store.engine(), "(module)")?;
340     Instance::new_async(&mut store, &m, &[]).await?;
341     assert_eq!(store.data().calls_into_wasm, 0);
342     assert_eq!(store.data().calls_into_host, 0);
343 
344     let m = Module::new(store.engine(), "(module (func) (start 0))")?;
345     Instance::new_async(&mut store, &m, &[]).await?;
346     assert_eq!(store.data().calls_into_wasm, 1);
347     assert_eq!(store.data().calls_into_host, 0);
348 
349     Ok(())
350 }
351 
352 #[test]
353 fn recursion() -> Result<(), Error> {
354     // Make sure call hook behaves reasonably when called recursively
355 
356     let engine = Engine::default();
357     let mut store = Store::new(&engine, State::default());
358     store.call_hook(sync_call_hook);
359     let mut linker = Linker::new(&engine);
360 
361     linker.func_wrap("host", "f", |mut caller: Caller<State>, n: i32| {
362         assert_eq!(caller.data().context.last(), Some(&Context::Host));
363 
364         assert_eq!(caller.data().calls_into_host, caller.data().calls_into_wasm);
365 
366         // Recurse
367         if n > 0 {
368             caller
369                 .get_export("export")
370                 .expect("caller exports \"export\"")
371                 .into_func()
372                 .expect("export is a func")
373                 .typed::<i32, ()>(&caller)
374                 .expect("export typing")
375                 .call(&mut caller, n - 1)
376                 .unwrap()
377         }
378     })?;
379 
380     let wat = r#"
381         (module
382             (import "host" "f"
383                 (func $f (param i32)))
384             (func (export "export") (param i32)
385                 (call $f (local.get 0)))
386         )
387     "#;
388     let module = Module::new(&engine, wat)?;
389 
390     let inst = linker.instantiate(&mut store, &module)?;
391     let export = inst
392         .get_export(&mut store, "export")
393         .expect("get export")
394         .into_func()
395         .expect("export is func");
396 
397     // Recursion depth:
398     let n: usize = 10;
399 
400     export.call(&mut store, &[Val::I32(n as i32)], &mut [])?;
401 
402     // Recurse down to 0: n+1 calls
403     assert_eq!(store.data().calls_into_host, n + 1);
404     assert_eq!(store.data().returns_from_host, n + 1);
405     assert_eq!(store.data().calls_into_wasm, n + 1);
406     assert_eq!(store.data().returns_from_wasm, n + 1);
407 
408     export
409         .typed::<i32, ()>(&store)?
410         .call(&mut store, n as i32)?;
411 
412     assert_eq!(store.data().calls_into_host, 2 * (n + 1));
413     assert_eq!(store.data().returns_from_host, 2 * (n + 1));
414     assert_eq!(store.data().calls_into_wasm, 2 * (n + 1));
415     assert_eq!(store.data().returns_from_wasm, 2 * (n + 1));
416 
417     Ok(())
418 }
419 
420 #[test]
421 fn trapping() -> Result<(), Error> {
422     const TRAP_IN_F: i32 = 0;
423     const TRAP_NEXT_CALL_HOST: i32 = 1;
424     const TRAP_NEXT_RETURN_HOST: i32 = 2;
425     const TRAP_NEXT_CALL_WASM: i32 = 3;
426     const TRAP_NEXT_RETURN_WASM: i32 = 4;
427 
428     let engine = Engine::default();
429 
430     let mut linker = Linker::new(&engine);
431 
432     linker.func_wrap(
433         "host",
434         "f",
435         |mut caller: Caller<State>, action: i32, recur: i32| -> Result<()> {
436             assert_eq!(caller.data().context.last(), Some(&Context::Host));
437             assert_eq!(caller.data().calls_into_host, caller.data().calls_into_wasm);
438 
439             match action {
440                 TRAP_IN_F => bail!("trapping in f"),
441                 TRAP_NEXT_CALL_HOST => caller.data_mut().trap_next_call_host = true,
442                 TRAP_NEXT_RETURN_HOST => caller.data_mut().trap_next_return_host = true,
443                 TRAP_NEXT_CALL_WASM => caller.data_mut().trap_next_call_wasm = true,
444                 TRAP_NEXT_RETURN_WASM => caller.data_mut().trap_next_return_wasm = true,
445                 _ => {} // Do nothing
446             }
447 
448             // recur so that we can trigger a next call.
449             // propagate its trap, if it traps!
450             if recur > 0 {
451                 let _ = caller
452                     .get_export("export")
453                     .expect("caller exports \"export\"")
454                     .into_func()
455                     .expect("export is a func")
456                     .typed::<(i32, i32), ()>(&caller)
457                     .expect("export typing")
458                     .call(&mut caller, (action, 0))?;
459             }
460 
461             Ok(())
462         },
463     )?;
464 
465     let wat = r#"
466         (module
467             (import "host" "f"
468                 (func $f (param i32) (param i32)))
469             (func (export "export") (param i32) (param i32)
470                 (call $f (local.get 0) (local.get 1)))
471         )
472     "#;
473     let module = Module::new(&engine, wat)?;
474 
475     let run = |action: i32, recur: bool| -> (State, Option<Error>) {
476         let mut store = Store::new(&engine, State::default());
477         store.call_hook(sync_call_hook);
478         let inst = linker
479             .instantiate(&mut store, &module)
480             .expect("instantiate");
481         let export = inst
482             .get_export(&mut store, "export")
483             .expect("get export")
484             .into_func()
485             .expect("export is func");
486 
487         let r = export.call(
488             &mut store,
489             &[Val::I32(action), Val::I32(if recur { 1 } else { 0 })],
490             &mut [],
491         );
492         (store.into_data(), r.err())
493     };
494 
495     let (s, e) = run(TRAP_IN_F, false);
496     assert!(format!("{:?}", e.unwrap()).contains("trapping in f"));
497     assert_eq!(s.calls_into_host, 1);
498     assert_eq!(s.returns_from_host, 1);
499     assert_eq!(s.calls_into_wasm, 1);
500     assert_eq!(s.returns_from_wasm, 1);
501 
502     // trap in next call to host. No calls after the bit is set, so this trap shouldn't happen
503     let (s, e) = run(TRAP_NEXT_CALL_HOST, false);
504     assert!(e.is_none());
505     assert_eq!(s.calls_into_host, 1);
506     assert_eq!(s.returns_from_host, 1);
507     assert_eq!(s.calls_into_wasm, 1);
508     assert_eq!(s.returns_from_wasm, 1);
509 
510     // trap in next call to host. recur, so the second call into host traps:
511     let (s, e) = run(TRAP_NEXT_CALL_HOST, true);
512     assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on CallingHost"));
513     assert_eq!(s.calls_into_host, 2);
514     assert_eq!(s.returns_from_host, 1);
515     assert_eq!(s.calls_into_wasm, 2);
516     assert_eq!(s.returns_from_wasm, 2);
517 
518     // trap in the return from host. should trap right away, without recursion
519     let (s, e) = run(TRAP_NEXT_RETURN_HOST, false);
520     assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on ReturningFromHost"));
521     assert_eq!(s.calls_into_host, 1);
522     assert_eq!(s.returns_from_host, 1);
523     assert_eq!(s.calls_into_wasm, 1);
524     assert_eq!(s.returns_from_wasm, 1);
525 
526     // trap in next call to wasm. No calls after the bit is set, so this trap shouldn't happen:
527     let (s, e) = run(TRAP_NEXT_CALL_WASM, false);
528     assert!(e.is_none());
529     assert_eq!(s.calls_into_host, 1);
530     assert_eq!(s.returns_from_host, 1);
531     assert_eq!(s.calls_into_wasm, 1);
532     assert_eq!(s.returns_from_wasm, 1);
533 
534     // trap in next call to wasm. recur, so the second call into wasm traps:
535     let (s, e) = run(TRAP_NEXT_CALL_WASM, true);
536     assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on CallingWasm"));
537     assert_eq!(s.calls_into_host, 1);
538     assert_eq!(s.returns_from_host, 1);
539     assert_eq!(s.calls_into_wasm, 2);
540     assert_eq!(s.returns_from_wasm, 1);
541 
542     // trap in the return from wasm. should trap right away, without recursion
543     let (s, e) = run(TRAP_NEXT_RETURN_WASM, false);
544     assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on ReturningFromWasm"));
545     assert_eq!(s.calls_into_host, 1);
546     assert_eq!(s.returns_from_host, 1);
547     assert_eq!(s.calls_into_wasm, 1);
548     assert_eq!(s.returns_from_wasm, 1);
549 
550     Ok(())
551 }
552 
553 #[tokio::test]
554 async fn basic_async_hook() -> Result<(), Error> {
555     struct HandlerR;
556 
557     #[async_trait::async_trait]
558     impl CallHookHandler<State> for HandlerR {
559         async fn handle_call_event(
560             &self,
561             ctx: StoreContextMut<'_, State>,
562             ch: CallHook,
563         ) -> Result<()> {
564             sync_call_hook(ctx, ch)
565         }
566     }
567     let mut config = Config::new();
568     config.async_support(true);
569     let engine = Engine::new(&config)?;
570     let mut store = Store::new(&engine, State::default());
571     store.call_hook_async(HandlerR {});
572 
573     assert_eq!(store.data().calls_into_host, 0);
574     assert_eq!(store.data().returns_from_host, 0);
575     assert_eq!(store.data().calls_into_wasm, 0);
576     assert_eq!(store.data().returns_from_wasm, 0);
577 
578     let mut linker = Linker::new(&engine);
579 
580     linker.func_wrap(
581         "host",
582         "f",
583         |caller: Caller<State>, a: i32, b: i64, c: f32, d: f64| {
584             // Calling this func will switch context into wasm, then back to host:
585             assert_eq!(caller.data().context, vec![Context::Wasm, Context::Host]);
586 
587             assert_eq!(
588                 caller.data().calls_into_host,
589                 caller.data().returns_from_host + 1
590             );
591             assert_eq!(
592                 caller.data().calls_into_wasm,
593                 caller.data().returns_from_wasm + 1
594             );
595 
596             assert_eq!(a, 1);
597             assert_eq!(b, 2);
598             assert_eq!(c, 3.0);
599             assert_eq!(d, 4.0);
600         },
601     )?;
602 
603     let wat = r#"
604         (module
605             (import "host" "f"
606                 (func $f (param i32) (param i64) (param f32) (param f64)))
607             (func (export "export")
608                 (call $f (i32.const 1) (i64.const 2) (f32.const 3.0) (f64.const 4.0)))
609         )
610     "#;
611     let module = Module::new(&engine, wat)?;
612 
613     let inst = linker.instantiate_async(&mut store, &module).await?;
614     let export = inst
615         .get_export(&mut store, "export")
616         .expect("get export")
617         .into_func()
618         .expect("export is func");
619 
620     export.call_async(&mut store, &[], &mut []).await?;
621 
622     // One switch from vm to host to call f, another in return from f.
623     assert_eq!(store.data().calls_into_host, 1);
624     assert_eq!(store.data().returns_from_host, 1);
625     assert_eq!(store.data().calls_into_wasm, 1);
626     assert_eq!(store.data().returns_from_wasm, 1);
627 
628     Ok(())
629 }
630 
631 #[tokio::test]
632 async fn timeout_async_hook() -> Result<(), Error> {
633     struct HandlerR;
634 
635     #[async_trait::async_trait]
636     impl CallHookHandler<State> for HandlerR {
637         async fn handle_call_event(
638             &self,
639             mut ctx: StoreContextMut<'_, State>,
640             ch: CallHook,
641         ) -> Result<()> {
642             let obj = ctx.data_mut();
643             if obj.calls_into_host > 200 {
644                 bail!("timeout");
645             }
646 
647             match ch {
648                 CallHook::CallingHost => obj.calls_into_host += 1,
649                 CallHook::CallingWasm => obj.calls_into_wasm += 1,
650                 CallHook::ReturningFromHost => obj.returns_from_host += 1,
651                 CallHook::ReturningFromWasm => obj.returns_from_wasm += 1,
652             }
653 
654             Ok(())
655         }
656     }
657 
658     let mut config = Config::new();
659     config.async_support(true);
660     let engine = Engine::new(&config)?;
661     let mut store = Store::new(&engine, State::default());
662     store.call_hook_async(HandlerR {});
663 
664     assert_eq!(store.data().calls_into_host, 0);
665     assert_eq!(store.data().returns_from_host, 0);
666     assert_eq!(store.data().calls_into_wasm, 0);
667     assert_eq!(store.data().returns_from_wasm, 0);
668 
669     let mut linker = Linker::new(&engine);
670 
671     linker.func_wrap(
672         "host",
673         "f",
674         |_caller: Caller<State>, a: i32, b: i64, c: f32, d: f64| {
675             assert_eq!(a, 1);
676             assert_eq!(b, 2);
677             assert_eq!(c, 3.0);
678             assert_eq!(d, 4.0);
679         },
680     )?;
681 
682     let wat = r#"
683         (module
684             (import "host" "f"
685                 (func $f (param i32) (param i64) (param f32) (param f64)))
686             (func (export "export")
687                 (loop $start
688                     (call $f (i32.const 1) (i64.const 2) (f32.const 3.0) (f64.const 4.0))
689                     (br $start)))
690         )
691     "#;
692     let module = Module::new(&engine, wat)?;
693 
694     let inst = linker.instantiate_async(&mut store, &module).await?;
695     let export = inst
696         .get_typed_func::<(), ()>(&mut store, "export")
697         .expect("export is func");
698 
699     store.set_epoch_deadline(1);
700     store.epoch_deadline_async_yield_and_update(1);
701     assert!(export.call_async(&mut store, ()).await.is_err());
702 
703     // One switch from vm to host to call f, another in return from f.
704     assert!(store.data().calls_into_host > 1);
705     assert!(store.data().returns_from_host > 1);
706     assert_eq!(store.data().calls_into_wasm, 1);
707     assert_eq!(store.data().returns_from_wasm, 0);
708 
709     Ok(())
710 }
711 
712 #[tokio::test]
713 async fn drop_suspended_async_hook() -> Result<(), Error> {
714     struct Handler;
715 
716     #[async_trait::async_trait]
717     impl CallHookHandler<u32> for Handler {
718         async fn handle_call_event(
719             &self,
720             mut ctx: StoreContextMut<'_, u32>,
721             _ch: CallHook,
722         ) -> Result<()> {
723             let state = ctx.data_mut();
724             assert_eq!(*state, 0);
725             *state += 1;
726             let _dec = Decrement(state);
727 
728             // Simulate some sort of event which takes a number of yields
729             for _ in 0..500 {
730                 tokio::task::yield_now().await;
731             }
732             Ok(())
733         }
734     }
735 
736     let mut config = Config::new();
737     config.async_support(true);
738     let engine = Engine::new(&config)?;
739     let mut store = Store::new(&engine, 0);
740     store.call_hook_async(Handler);
741 
742     let mut linker = Linker::new(&engine);
743 
744     // Simulate a host function that has lots of yields with an infinite loop.
745     linker.func_wrap_async("host", "f", |mut cx, _: ()| {
746         Box::new(async move {
747             let state = cx.data_mut();
748             assert_eq!(*state, 0);
749             *state += 1;
750             let _dec = Decrement(state);
751             for _ in 0.. {
752                 tokio::task::yield_now().await;
753             }
754         })
755     })?;
756 
757     let wat = r#"
758         (module
759             (import "host" "f" (func $f))
760             (func (export "") call $f)
761         )
762     "#;
763     let module = Module::new(&engine, wat)?;
764 
765     let inst = linker.instantiate_async(&mut store, &module).await?;
766     assert_eq!(*store.data(), 0);
767     let export = inst
768         .get_typed_func::<(), ()>(&mut store, "")
769         .expect("export is func");
770 
771     // First test that if we drop in the middle of an async hook that everything
772     // is alright.
773     PollNTimes {
774         future: Box::pin(export.call_async(&mut store, ())),
775         times: 200,
776     }
777     .await;
778     assert_eq!(*store.data(), 0); // double-check user dtors ran
779 
780     // Next test that if we drop while in a host async function that everything
781     // is also alright.
782     PollNTimes {
783         future: Box::pin(export.call_async(&mut store, ())),
784         times: 1_000,
785     }
786     .await;
787     assert_eq!(*store.data(), 0); // double-check user dtors ran
788 
789     return Ok(());
790 
791     // A helper struct to poll an inner `future` N `times` and then resolve.
792     // This is used above to test that when futures are dropped while they're
793     // pending everything works and is cleaned up on the Wasmtime side of
794     // things.
795     struct PollNTimes<F> {
796         future: F,
797         times: u32,
798     }
799 
800     impl<F: Future + Unpin> Future for PollNTimes<F> {
801         type Output = ();
802         fn poll(mut self: Pin<&mut Self>, task: &mut task::Context<'_>) -> Poll<()> {
803             for _ in 0..self.times {
804                 match Pin::new(&mut self.future).poll(task) {
805                     Poll::Ready(_) => panic!("future should not be ready"),
806                     Poll::Pending => {}
807                 }
808             }
809 
810             Poll::Ready(())
811         }
812     }
813 
814     // helper struct to decrement a counter on drop
815     struct Decrement<'a>(&'a mut u32);
816 
817     impl Drop for Decrement<'_> {
818         fn drop(&mut self) {
819             *self.0 -= 1;
820         }
821     }
822 }
823 
824 #[derive(Debug, PartialEq, Eq)]
825 pub enum Context {
826     Host,
827     Wasm,
828 }
829 
830 pub struct State {
831     pub context: Vec<Context>,
832 
833     pub calls_into_host: usize,
834     pub returns_from_host: usize,
835     pub calls_into_wasm: usize,
836     pub returns_from_wasm: usize,
837 
838     pub trap_next_call_host: bool,
839     pub trap_next_return_host: bool,
840     pub trap_next_call_wasm: bool,
841     pub trap_next_return_wasm: bool,
842 }
843 
844 impl Default for State {
845     fn default() -> Self {
846         State {
847             context: Vec::new(),
848             calls_into_host: 0,
849             returns_from_host: 0,
850             calls_into_wasm: 0,
851             returns_from_wasm: 0,
852             trap_next_call_host: false,
853             trap_next_return_host: false,
854             trap_next_call_wasm: false,
855             trap_next_return_wasm: false,
856         }
857     }
858 }
859 
860 impl State {
861     // This implementation asserts that hooks are always called in a stack-like manner.
862     fn call_hook(&mut self, s: CallHook) -> Result<()> {
863         match s {
864             CallHook::CallingHost => {
865                 self.calls_into_host += 1;
866                 if self.trap_next_call_host {
867                     bail!("call_hook: trapping on CallingHost");
868                 } else {
869                     self.context.push(Context::Host);
870                 }
871             }
872             CallHook::ReturningFromHost => match self.context.pop() {
873                 Some(Context::Host) => {
874                     self.returns_from_host += 1;
875                     if self.trap_next_return_host {
876                         bail!("call_hook: trapping on ReturningFromHost");
877                     }
878                 }
879                 c => panic!(
880                     "illegal context: expected Some(Host), got {:?}. remaining: {:?}",
881                     c, self.context
882                 ),
883             },
884             CallHook::CallingWasm => {
885                 self.calls_into_wasm += 1;
886                 if self.trap_next_call_wasm {
887                     bail!("call_hook: trapping on CallingWasm");
888                 } else {
889                     self.context.push(Context::Wasm);
890                 }
891             }
892             CallHook::ReturningFromWasm => match self.context.pop() {
893                 Some(Context::Wasm) => {
894                     self.returns_from_wasm += 1;
895                     if self.trap_next_return_wasm {
896                         bail!("call_hook: trapping on ReturningFromWasm");
897                     }
898                 }
899                 c => panic!(
900                     "illegal context: expected Some(Wasm), got {:?}. remaining: {:?}",
901                     c, self.context
902                 ),
903             },
904         }
905         Ok(())
906     }
907 }
908 
909 pub fn sync_call_hook(mut ctx: StoreContextMut<'_, State>, transition: CallHook) -> Result<()> {
910     ctx.data_mut().call_hook(transition)
911 }
912