1 #![cfg(not(miri))]
2 
3 use super::REALLOC_AND_FREE;
4 use anyhow::Result;
5 use std::ops::Deref;
6 use wasmtime::component::*;
7 use wasmtime::{Store, StoreContextMut, WasmBacktrace};
8 
9 #[test]
10 fn can_compile() -> Result<()> {
11     let engine = super::engine();
12     let libc = r#"
13         (core module $libc
14             (memory (export "memory") 1)
15             (func (export "realloc") (param i32 i32 i32 i32) (result i32)
16                 unreachable)
17         )
18         (core instance $libc (instantiate $libc))
19     "#;
20     Component::new(
21         &engine,
22         r#"(component
23             (import "a" (func $f))
24             (core func (canon lower (func $f)))
25         )"#,
26     )?;
27     Component::new(
28         &engine,
29         format!(
30             r#"(component
31                 (import "a" (func $f (param "a" string)))
32                 {libc}
33                 (core func (canon lower (func $f) (memory $libc "memory") (realloc (func $libc "realloc"))))
34             )"#
35         ),
36     )?;
37     Component::new(
38         &engine,
39         format!(
40             r#"(component
41                 (import "f1" (func $f1 (param "a" string) (result string)))
42                 {libc}
43                 (core func (canon lower (func $f1) (memory $libc "memory") (realloc (func $libc "realloc"))))
44 
45                 (import "f2" (func $f2 (param "a" u32) (result (list u8))))
46                 (core instance $libc2 (instantiate $libc))
47                 (core func (canon lower (func $f2) (memory $libc2 "memory") (realloc (func $libc2 "realloc"))))
48 
49                 (core func (canon lower (func $f1) (memory $libc2 "memory") (realloc (func $libc2 "realloc"))))
50                 (core func (canon lower (func $f2) (memory $libc "memory") (realloc (func $libc "realloc"))))
51             )"#
52         ),
53     )?;
54     Component::new(
55         &engine,
56         format!(
57             r#"(component
58                 (import "log" (func $log (param "a" string)))
59                 {libc}
60                 (core func $log_lower (canon lower (func $log) (memory $libc "memory") (realloc (func $libc "realloc"))))
61 
62                 (core module $logger
63                     (import "host" "log" (func $log (param i32 i32)))
64                     (import "libc" "memory" (memory 1))
65 
66                     (func (export "call")
67                         i32.const 0
68                         i32.const 0
69                         call $log)
70                 )
71                 (core instance $logger (instantiate $logger
72                     (with "host" (instance (export "log" (func $log_lower))))
73                     (with "libc" (instance $libc))
74                 ))
75 
76                 (func (export "call")
77                     (canon lift (core func $logger "call"))
78                 )
79             )"#
80         ),
81     )?;
82     Ok(())
83 }
84 
85 #[test]
86 fn simple() -> Result<()> {
87     let component = r#"
88         (component
89             (import "a" (func $log (param "a" string)))
90 
91             (core module $libc
92                 (memory (export "memory") 1)
93 
94                 (func (export "realloc") (param i32 i32 i32 i32) (result i32)
95                     unreachable)
96             )
97             (core instance $libc (instantiate $libc))
98             (core func $log_lower
99                 (canon lower (func $log) (memory $libc "memory") (realloc (func $libc "realloc")))
100             )
101             (core module $m
102                 (import "libc" "memory" (memory 1))
103                 (import "host" "log" (func $log (param i32 i32)))
104 
105                 (func (export "call")
106                     i32.const 5
107                     i32.const 11
108                     call $log)
109 
110                 (data (i32.const 5) "hello world")
111             )
112             (core instance $i (instantiate $m
113                 (with "libc" (instance $libc))
114                 (with "host" (instance (export "log" (func $log_lower))))
115             ))
116             (func (export "call")
117                 (canon lift (core func $i "call"))
118             )
119         )
120     "#;
121 
122     let engine = super::engine();
123     let component = Component::new(&engine, component)?;
124     let mut store = Store::new(&engine, None);
125     assert!(store.data().is_none());
126 
127     // First, test the static API
128 
129     let mut linker = Linker::new(&engine);
130     linker.root().func_wrap(
131         "a",
132         |mut store: StoreContextMut<'_, Option<String>>, (arg,): (WasmStr,)| -> Result<_> {
133             let s = arg.to_str(&store)?.to_string();
134             assert!(store.data().is_none());
135             *store.data_mut() = Some(s);
136             Ok(())
137         },
138     )?;
139     let instance = linker.instantiate(&mut store, &component)?;
140     instance
141         .get_typed_func::<(), ()>(&mut store, "call")?
142         .call(&mut store, ())?;
143     assert_eq!(store.data().as_ref().unwrap(), "hello world");
144 
145     // Next, test the dynamic API
146 
147     *store.data_mut() = None;
148     let mut linker = Linker::new(&engine);
149     linker.root().func_new(
150         &component,
151         "a",
152         |mut store: StoreContextMut<'_, Option<String>>, args, _results| {
153             if let Val::String(s) = &args[0] {
154                 assert!(store.data().is_none());
155                 *store.data_mut() = Some(s.to_string());
156                 Ok(())
157             } else {
158                 panic!()
159             }
160         },
161     )?;
162     let instance = linker.instantiate(&mut store, &component)?;
163     instance
164         .get_func(&mut store, "call")
165         .unwrap()
166         .call(&mut store, &[], &mut [])?;
167     assert_eq!(store.data().as_ref().unwrap(), "hello world");
168 
169     Ok(())
170 }
171 
172 #[test]
173 fn attempt_to_leave_during_malloc() -> Result<()> {
174     let component = r#"
175 (component
176   (import "thunk" (func $thunk))
177   (import "ret-string" (func $ret_string (result string)))
178 
179   (core module $host_shim
180     (table (export "table") 2 funcref)
181     (func $shim_thunk (export "thunk")
182       i32.const 0
183       call_indirect)
184     (func $shim_ret_string (export "ret-string") (param i32)
185       local.get 0
186       i32.const 1
187       call_indirect (param i32))
188   )
189   (core instance $host_shim (instantiate $host_shim))
190 
191   (core module $m
192     (import "host" "thunk" (func $thunk))
193     (import "host" "ret-string" (func $ret_string (param i32)))
194 
195     (memory (export "memory") 1)
196 
197     (func $realloc (export "realloc") (param i32 i32 i32 i32) (result i32)
198       call $thunk
199       unreachable)
200 
201     (func $run (export "run")
202       i32.const 8
203       call $ret_string)
204 
205     (func (export "take-string") (param i32 i32)
206         unreachable)
207   )
208   (core instance $m (instantiate $m (with "host" (instance $host_shim))))
209 
210   (core module $host_shim_filler_inner
211     (import "shim" "table" (table 2 funcref))
212     (import "host" "thunk" (func $thunk))
213     (import "host" "ret-string" (func $ret_string (param i32)))
214     (elem (i32.const 0) $thunk $ret_string)
215   )
216 
217   (core func $thunk_lower
218     (canon lower (func $thunk) (memory $m "memory") (realloc (func $m "realloc")))
219   )
220 
221   (core func $ret_string_lower
222     (canon lower (func $ret_string) (memory $m "memory") (realloc (func $m "realloc")))
223   )
224 
225   (core instance (instantiate $host_shim_filler_inner
226     (with "shim" (instance $host_shim))
227     (with "host" (instance
228       (export "thunk" (func $thunk_lower))
229       (export "ret-string" (func $ret_string_lower))
230     ))
231   ))
232 
233   (func (export "run")
234     (canon lift (core func $m "run"))
235   )
236   (func (export "take-string") (param "a" string)
237     (canon lift (core func $m "take-string") (memory $m "memory") (realloc (func $m "realloc")))
238   )
239 )
240     "#;
241 
242     let engine = super::engine();
243     let mut linker = Linker::new(&engine);
244     linker.root().func_wrap("thunk", |_, _: ()| -> Result<()> {
245         panic!("should not get here")
246     })?;
247     linker
248         .root()
249         .func_wrap("ret-string", |_, _: ()| -> Result<_> {
250             Ok(("hello".to_string(),))
251         })?;
252     let component = Component::new(&engine, component)?;
253     let mut store = Store::new(&engine, ());
254 
255     // Assert that during a host import if we return values to wasm that a trap
256     // happens if we try to leave the instance.
257     let trap = linker
258         .instantiate(&mut store, &component)?
259         .get_typed_func::<(), ()>(&mut store, "run")?
260         .call(&mut store, ())
261         .unwrap_err();
262     assert!(
263         format!("{trap:?}").contains("cannot leave component instance"),
264         "bad trap: {trap:?}",
265     );
266 
267     let trace = trap.downcast_ref::<WasmBacktrace>().unwrap().frames();
268     assert_eq!(trace.len(), 4);
269 
270     // This was our entry point...
271     assert_eq!(trace[3].module_name(), Some("m"));
272     assert_eq!(trace[3].func_name(), Some("run"));
273 
274     // ... which called an imported function which ends up being originally
275     // defined by the shim instance. The shim instance then does an indirect
276     // call through a table which goes to the `canon.lower`'d host function
277     assert_eq!(trace[2].module_name(), Some("host_shim"));
278     assert_eq!(trace[2].func_name(), Some("shim_ret_string"));
279 
280     // ... and the lowered host function will call realloc to allocate space for
281     // the result
282     assert_eq!(trace[1].module_name(), Some("m"));
283     assert_eq!(trace[1].func_name(), Some("realloc"));
284 
285     // ... but realloc calls the shim instance and tries to exit the
286     // component, triggering a dynamic trap
287     assert_eq!(trace[0].module_name(), Some("host_shim"));
288     assert_eq!(trace[0].func_name(), Some("shim_thunk"));
289 
290     // In addition to the above trap also ensure that when we enter a wasm
291     // component if we try to leave while lowering then that's also a dynamic
292     // trap.
293     let trap = linker
294         .instantiate(&mut store, &component)?
295         .get_typed_func::<(&str,), ()>(&mut store, "take-string")?
296         .call(&mut store, ("x",))
297         .unwrap_err();
298     assert!(
299         format!("{trap:?}").contains("cannot leave component instance"),
300         "bad trap: {trap:?}",
301     );
302     Ok(())
303 }
304 
305 #[test]
306 fn attempt_to_reenter_during_host() -> Result<()> {
307     let component = r#"
308 (component
309   (import "thunk" (func $thunk))
310   (core func $thunk_lower (canon lower (func $thunk)))
311 
312   (core module $m
313     (import "host" "thunk" (func $thunk))
314 
315     (func $run (export "run")
316       call $thunk)
317   )
318   (core instance $m (instantiate $m
319     (with "host" (instance (export "thunk" (func $thunk_lower))))
320   ))
321 
322   (func (export "run")
323     (canon lift (core func $m "run"))
324   )
325 )
326     "#;
327 
328     let engine = super::engine();
329     let component = Component::new(&engine, component)?;
330 
331     // First, test the static API
332 
333     struct StaticState {
334         func: Option<TypedFunc<(), ()>>,
335     }
336 
337     let mut store = Store::new(&engine, StaticState { func: None });
338     let mut linker = Linker::new(&engine);
339     linker.root().func_wrap(
340         "thunk",
341         |mut store: StoreContextMut<'_, StaticState>, _: ()| -> Result<()> {
342             let func = store.data_mut().func.take().unwrap();
343             let trap = func.call(&mut store, ()).unwrap_err();
344             assert!(
345                 format!("{trap:?}").contains("cannot reenter component instance"),
346                 "bad trap: {trap:?}",
347             );
348             Ok(())
349         },
350     )?;
351     let instance = linker.instantiate(&mut store, &component)?;
352     let func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
353     store.data_mut().func = Some(func);
354     func.call(&mut store, ())?;
355 
356     // Next, test the dynamic API
357 
358     struct DynamicState {
359         func: Option<Func>,
360     }
361 
362     let mut store = Store::new(&engine, DynamicState { func: None });
363     let mut linker = Linker::new(&engine);
364     linker.root().func_new(
365         &component,
366         "thunk",
367         |mut store: StoreContextMut<'_, DynamicState>, _, _| {
368             let func = store.data_mut().func.take().unwrap();
369             let trap = func.call(&mut store, &[], &mut []).unwrap_err();
370             assert!(
371                 format!("{trap:?}").contains("cannot reenter component instance"),
372                 "bad trap: {trap:?}",
373             );
374             Ok(())
375         },
376     )?;
377     let instance = linker.instantiate(&mut store, &component)?;
378     let func = instance.get_func(&mut store, "run").unwrap();
379     store.data_mut().func = Some(func);
380     func.call(&mut store, &[], &mut [])?;
381 
382     Ok(())
383 }
384 
385 #[test]
386 fn stack_and_heap_args_and_rets() -> Result<()> {
387     let component = format!(
388         r#"
389 (component
390   (type $many_params (tuple
391                       string string string string
392                       string string string string
393                       string))
394   (import "f1" (func $f1 (param "a" u32) (result u32)))
395   (import "f2" (func $f2 (param "a" $many_params) (result u32)))
396   (import "f3" (func $f3 (param "a" u32) (result string)))
397   (import "f4" (func $f4 (param "a" $many_params) (result string)))
398 
399   (core module $libc
400     {REALLOC_AND_FREE}
401     (memory (export "memory") 1)
402   )
403   (core instance $libc (instantiate (module $libc)))
404 
405   (core func $f1_lower (canon lower (func $f1) (memory $libc "memory") (realloc (func $libc "realloc"))))
406   (core func $f2_lower (canon lower (func $f2) (memory $libc "memory") (realloc (func $libc "realloc"))))
407   (core func $f3_lower (canon lower (func $f3) (memory $libc "memory") (realloc (func $libc "realloc"))))
408   (core func $f4_lower (canon lower (func $f4) (memory $libc "memory") (realloc (func $libc "realloc"))))
409 
410   (core module $m
411     (import "host" "f1" (func $f1 (param i32) (result i32)))
412     (import "host" "f2" (func $f2 (param i32) (result i32)))
413     (import "host" "f3" (func $f3 (param i32 i32)))
414     (import "host" "f4" (func $f4 (param i32 i32)))
415     (import "libc" "memory" (memory 1))
416 
417     (func $run (export "run")
418       block
419         i32.const 1
420         call $f1
421         i32.const 2
422         i32.eq
423         br_if 0
424         unreachable
425       end
426 
427       block
428         call $allocate_empty_strings
429         call $f2
430         i32.const 3
431         i32.eq
432         br_if 0
433         unreachable
434       end
435 
436       block
437         i32.const 8
438         i32.const 16000
439         call $f3
440         (call $validate_string_ret (i32.const 16000))
441       end
442 
443       block
444         call $allocate_empty_strings
445         i32.const 20000
446         call $f4
447         (call $validate_string_ret (i32.const 20000))
448       end
449     )
450 
451     (func $allocate_empty_strings (result i32)
452       (local $ret i32)
453       (local $offset i32)
454       (local $cnt i32)
455       (local.set $ret (i32.const 8000))
456       (local.set $cnt (i32.const 9))
457 
458       loop
459         (call $setup_str (i32.add (local.get $ret) (local.get $offset)))
460         (local.set $offset (i32.add (local.get $offset) (i32.const 8)))
461 
462         (local.tee $cnt (i32.add (local.get $cnt) (i32.const -1)))
463         br_if 0
464       end
465 
466       local.get $ret
467     )
468     (func $setup_str (param $addr i32)
469       (i32.store offset=0 (local.get $addr) (i32.const 1000))
470       (i32.store offset=4 (local.get $addr) (i32.const 3))
471     )
472 
473     (func $validate_string_ret (param $addr i32)
474       (local $base i32)
475       (local $len i32)
476       (local.set $base (i32.load (local.get $addr)))
477       (local.set $len (i32.load offset=4 (local.get $addr)))
478 
479       block
480         local.get $len
481         i32.const 3
482         i32.eq
483         br_if 0
484         unreachable
485       end
486 
487       (i32.load8_u offset=0 (local.get $base))
488       i32.const 120 ;; 'x'
489       i32.ne
490       if unreachable end
491 
492       (i32.load8_u offset=1 (local.get $base))
493       i32.const 121 ;; 'y'
494       i32.ne
495       if unreachable end
496 
497       (i32.load8_u offset=2 (local.get $base))
498       i32.const 122 ;; 'z'
499       i32.ne
500       if unreachable end
501     )
502 
503     (data (i32.const 1000) "abc")
504   )
505   (core instance $m (instantiate $m
506     (with "libc" (instance $libc))
507     (with "host" (instance
508       (export "f1" (func $f1_lower))
509       (export "f2" (func $f2_lower))
510       (export "f3" (func $f3_lower))
511       (export "f4" (func $f4_lower))
512     ))
513   ))
514 
515   (func (export "run")
516     (canon lift (core func $m "run"))
517   )
518 )
519         "#
520     );
521 
522     let engine = super::engine();
523     let component = Component::new(&engine, component)?;
524     let mut store = Store::new(&engine, ());
525 
526     // First, test the static API
527 
528     let mut linker = Linker::new(&engine);
529     linker
530         .root()
531         .func_wrap("f1", |_, (x,): (u32,)| -> Result<(u32,)> {
532             assert_eq!(x, 1);
533             Ok((2,))
534         })?;
535     linker.root().func_wrap(
536         "f2",
537         |cx: StoreContextMut<'_, ()>,
538          (arg,): ((
539             WasmStr,
540             WasmStr,
541             WasmStr,
542             WasmStr,
543             WasmStr,
544             WasmStr,
545             WasmStr,
546             WasmStr,
547             WasmStr,
548         ),)|
549          -> Result<(u32,)> {
550             assert_eq!(arg.0.to_str(&cx).unwrap(), "abc");
551             Ok((3,))
552         },
553     )?;
554     linker
555         .root()
556         .func_wrap("f3", |_, (arg,): (u32,)| -> Result<(String,)> {
557             assert_eq!(arg, 8);
558             Ok(("xyz".to_string(),))
559         })?;
560     linker.root().func_wrap(
561         "f4",
562         |cx: StoreContextMut<'_, ()>,
563          (arg,): ((
564             WasmStr,
565             WasmStr,
566             WasmStr,
567             WasmStr,
568             WasmStr,
569             WasmStr,
570             WasmStr,
571             WasmStr,
572             WasmStr,
573         ),)|
574          -> Result<(String,)> {
575             assert_eq!(arg.0.to_str(&cx).unwrap(), "abc");
576             Ok(("xyz".to_string(),))
577         },
578     )?;
579     let instance = linker.instantiate(&mut store, &component)?;
580     instance
581         .get_typed_func::<(), ()>(&mut store, "run")?
582         .call(&mut store, ())?;
583 
584     // Next, test the dynamic API
585 
586     let mut linker = Linker::new(&engine);
587     linker
588         .root()
589         .func_new(&component, "f1", |_, args, results| {
590             if let Val::U32(x) = &args[0] {
591                 assert_eq!(*x, 1);
592                 results[0] = Val::U32(2);
593                 Ok(())
594             } else {
595                 panic!()
596             }
597         })?;
598     linker
599         .root()
600         .func_new(&component, "f2", |_, args, results| {
601             if let Val::Tuple(tuple) = &args[0] {
602                 if let Val::String(s) = &tuple.values()[0] {
603                     assert_eq!(s.deref(), "abc");
604                     results[0] = Val::U32(3);
605                     Ok(())
606                 } else {
607                     panic!()
608                 }
609             } else {
610                 panic!()
611             }
612         })?;
613     linker
614         .root()
615         .func_new(&component, "f3", |_, args, results| {
616             if let Val::U32(x) = &args[0] {
617                 assert_eq!(*x, 8);
618                 results[0] = Val::String("xyz".into());
619                 Ok(())
620             } else {
621                 panic!();
622             }
623         })?;
624     linker
625         .root()
626         .func_new(&component, "f4", |_, args, results| {
627             if let Val::Tuple(tuple) = &args[0] {
628                 if let Val::String(s) = &tuple.values()[0] {
629                     assert_eq!(s.deref(), "abc");
630                     results[0] = Val::String("xyz".into());
631                     Ok(())
632                 } else {
633                     panic!()
634                 }
635             } else {
636                 panic!()
637             }
638         })?;
639     let instance = linker.instantiate(&mut store, &component)?;
640     instance
641         .get_func(&mut store, "run")
642         .unwrap()
643         .call(&mut store, &[], &mut [])?;
644 
645     Ok(())
646 }
647 
648 #[test]
649 fn bad_import_alignment() -> Result<()> {
650     let component = format!(
651         r#"
652 (component
653   (import "unaligned-retptr" (func $unaligned_retptr (result string)))
654   (type $many_arg (tuple
655     string string string string
656     string string string string
657     string
658   ))
659   (import "unaligned-argptr" (func $unaligned_argptr (param "a" $many_arg)))
660   (core module $libc_panic
661     (memory (export "memory") 1)
662     (func (export "realloc") (param i32 i32 i32 i32) (result i32)
663       unreachable)
664   )
665   (core instance $libc_panic (instantiate $libc_panic))
666 
667   (core func $unaligned_retptr_lower
668     (canon lower (func $unaligned_retptr) (memory $libc_panic "memory") (realloc (func $libc_panic "realloc")))
669   )
670   (core func $unaligned_argptr_lower
671     (canon lower (func $unaligned_argptr) (memory $libc_panic "memory") (realloc (func $libc_panic "realloc")))
672   )
673 
674   (core module $m
675     (import "host" "unaligned-retptr" (func $unaligned_retptr (param i32)))
676     (import "host" "unaligned-argptr" (func $unaligned_argptr (param i32)))
677 
678     (func (export "unaligned-retptr")
679      (call $unaligned_retptr (i32.const 1)))
680     (func (export "unaligned-argptr")
681      (call $unaligned_argptr (i32.const 1)))
682   )
683   (core instance $m (instantiate $m
684     (with "host" (instance
685       (export "unaligned-retptr" (func $unaligned_retptr_lower))
686       (export "unaligned-argptr" (func $unaligned_argptr_lower))
687     ))
688   ))
689 
690   (func (export "unaligned-retptr2")
691     (canon lift (core func $m "unaligned-retptr"))
692   )
693   (func (export "unaligned-argptr2")
694     (canon lift (core func $m "unaligned-argptr"))
695   )
696 )
697         "#
698     );
699 
700     let engine = super::engine();
701     let mut linker = Linker::new(&engine);
702     linker
703         .root()
704         .func_wrap("unaligned-retptr", |_, _: ()| -> Result<(String,)> {
705             Ok((String::new(),))
706         })?;
707     linker.root().func_wrap(
708         "unaligned-argptr",
709         |_,
710          _: ((
711             WasmStr,
712             WasmStr,
713             WasmStr,
714             WasmStr,
715             WasmStr,
716             WasmStr,
717             WasmStr,
718             WasmStr,
719             WasmStr,
720         ),)|
721          -> Result<()> { unreachable!() },
722     )?;
723     let component = Component::new(&engine, component)?;
724     let mut store = Store::new(&engine, ());
725 
726     let trap = linker
727         .instantiate(&mut store, &component)?
728         .get_typed_func::<(), ()>(&mut store, "unaligned-retptr2")?
729         .call(&mut store, ())
730         .unwrap_err();
731     assert!(
732         format!("{:?}", trap).contains("pointer not aligned"),
733         "{}",
734         trap
735     );
736     let trap = linker
737         .instantiate(&mut store, &component)?
738         .get_typed_func::<(), ()>(&mut store, "unaligned-argptr2")?
739         .call(&mut store, ())
740         .unwrap_err();
741     assert!(
742         format!("{:?}", trap).contains("pointer not aligned"),
743         "{}",
744         trap
745     );
746 
747     Ok(())
748 }
749 
750 #[test]
751 fn no_actual_wasm_code() -> Result<()> {
752     let component = r#"
753         (component
754             (import "f" (func $f))
755 
756             (core func $f_lower
757                 (canon lower (func $f))
758             )
759             (core module $m
760                 (import "" "" (func $f))
761                 (export "f" (func $f))
762             )
763             (core instance $i (instantiate $m
764                 (with "" (instance
765                     (export "" (func $f_lower))
766                 ))
767             ))
768             (func (export "thunk")
769                 (canon lift
770                     (core func $i "f")
771                 )
772             )
773         )
774     "#;
775 
776     let engine = super::engine();
777     let component = Component::new(&engine, component)?;
778     let mut store = Store::new(&engine, 0);
779 
780     // First, test the static API
781 
782     let mut linker = Linker::new(&engine);
783     linker.root().func_wrap(
784         "f",
785         |mut store: StoreContextMut<'_, u32>, _: ()| -> Result<()> {
786             *store.data_mut() += 1;
787             Ok(())
788         },
789     )?;
790 
791     let instance = linker.instantiate(&mut store, &component)?;
792     let thunk = instance.get_typed_func::<(), ()>(&mut store, "thunk")?;
793 
794     assert_eq!(*store.data(), 0);
795     thunk.call(&mut store, ())?;
796     assert_eq!(*store.data(), 1);
797 
798     // Next, test the dynamic API
799 
800     *store.data_mut() = 0;
801     let mut linker = Linker::new(&engine);
802     linker.root().func_new(
803         &component,
804         "f",
805         |mut store: StoreContextMut<'_, u32>, _, _| {
806             *store.data_mut() += 1;
807             Ok(())
808         },
809     )?;
810 
811     let instance = linker.instantiate(&mut store, &component)?;
812     let thunk = instance.get_func(&mut store, "thunk").unwrap();
813 
814     assert_eq!(*store.data(), 0);
815     thunk.call(&mut store, &[], &mut [])?;
816     assert_eq!(*store.data(), 1);
817 
818     Ok(())
819 }
820