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