Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/all/call_hook.rs
1691 views
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
913