1 #![cfg(not(miri))] 2 3 use super::REALLOC_AND_FREE; 4 use crate::call_hook::{Context, State, sync_call_hook}; 5 use anyhow::{Result, bail}; 6 use std::future::Future; 7 use std::pin::Pin; 8 use std::task::{self, Poll}; 9 use wasmtime::component::*; 10 use wasmtime::{CallHook, CallHookHandler, Config, Engine, Store, StoreContextMut}; 11 12 // Crate a synchronous Func, call it directly: 13 #[test] 14 fn call_wrapped_func() -> Result<()> { 15 let wat = r#" 16 (component 17 (import "f" (func $f)) 18 19 (core func $f_lower 20 (canon lower (func $f)) 21 ) 22 (core module $m 23 (import "" "" (func $f)) 24 25 (func $export 26 (call $f) 27 ) 28 29 (export "export" (func $export)) 30 ) 31 (core instance $i (instantiate $m 32 (with "" (instance 33 (export "" (func $f_lower)) 34 )) 35 )) 36 (func (export "export") 37 (canon lift 38 (core func $i "export") 39 ) 40 ) 41 ) 42 "#; 43 44 let engine = Engine::default(); 45 let component = Component::new(&engine, wat)?; 46 47 let mut linker = Linker::<State>::new(&engine); 48 linker 49 .root() 50 .func_wrap("f", |_, _: ()| -> Result<()> { Ok(()) })?; 51 52 let mut store = Store::new(&engine, State::default()); 53 store.call_hook(sync_call_hook); 54 let inst = linker 55 .instantiate(&mut store, &component) 56 .expect("instantiate"); 57 58 let export = inst 59 .get_typed_func::<(), ()>(&mut store, "export") 60 .expect("looking up `export`"); 61 62 export.call(&mut store, ())?; 63 export.post_return(&mut store)?; 64 65 let s = store.into_data(); 66 assert_eq!(s.calls_into_host, 1); 67 assert_eq!(s.returns_from_host, 1); 68 assert_eq!(s.calls_into_wasm, 1); 69 assert_eq!(s.returns_from_wasm, 1); 70 71 Ok(()) 72 } 73 74 // Call a func that turns a `list<u8>` into a `string`, to ensure that `realloc` calls are counted. 75 #[test] 76 fn call_func_with_realloc() -> Result<()> { 77 let wat = format!( 78 r#"(component 79 (core module $m 80 (memory (export "memory") 1) 81 (func (export "roundtrip") (param i32 i32) (result i32) 82 (local $base i32) 83 (local.set $base 84 (call $realloc 85 (i32.const 0) 86 (i32.const 0) 87 (i32.const 4) 88 (i32.const 8))) 89 (i32.store offset=0 90 (local.get $base) 91 (local.get 0)) 92 (i32.store offset=4 93 (local.get $base) 94 (local.get 1)) 95 (local.get $base) 96 ) 97 98 {REALLOC_AND_FREE} 99 ) 100 (core instance $i (instantiate $m)) 101 102 (func (export "list8-to-str") (param "a" (list u8)) (result string) 103 (canon lift 104 (core func $i "roundtrip") 105 (memory $i "memory") 106 (realloc (func $i "realloc")) 107 ) 108 ) 109 )"# 110 ); 111 112 let engine = Engine::default(); 113 let component = Component::new(&engine, wat)?; 114 let linker = Linker::<State>::new(&engine); 115 let mut store = Store::new(&engine, State::default()); 116 store.call_hook(sync_call_hook); 117 let inst = linker 118 .instantiate(&mut store, &component) 119 .expect("instantiate"); 120 121 let export = inst 122 .get_typed_func::<(&[u8],), (WasmStr,)>(&mut store, "list8-to-str") 123 .expect("looking up `list8-to-str`"); 124 125 let message = String::from("hello, world!"); 126 let res = export.call(&mut store, (message.as_bytes(),))?.0; 127 let result = res.to_str(&store)?; 128 assert_eq!(&message, &result); 129 130 export.post_return(&mut store)?; 131 132 // There are two wasm calls for the `list8-to-str` call and the guest realloc call for the list 133 // argument. 134 let s = store.into_data(); 135 assert_eq!(s.calls_into_host, 0); 136 assert_eq!(s.returns_from_host, 0); 137 assert_eq!(s.calls_into_wasm, 2); 138 assert_eq!(s.returns_from_wasm, 2); 139 140 Ok(()) 141 } 142 143 // Call a guest function that also defines a post-return. 144 #[test] 145 fn call_func_with_post_return() -> Result<()> { 146 let wat = r#" 147 (component 148 (core module $m 149 (func (export "roundtrip")) 150 (func (export "post-return")) 151 ) 152 (core instance $i (instantiate $m)) 153 154 (func (export "export") 155 (canon lift 156 (core func $i "roundtrip") 157 (post-return (func $i "post-return")) 158 ) 159 ) 160 )"#; 161 162 let engine = Engine::default(); 163 let component = Component::new(&engine, wat)?; 164 let linker = Linker::<State>::new(&engine); 165 let mut store = Store::new(&engine, State::default()); 166 store.call_hook(sync_call_hook); 167 let inst = linker 168 .instantiate(&mut store, &component) 169 .expect("instantiate"); 170 171 let export = inst 172 .get_typed_func::<(), ()>(&mut store, "export") 173 .expect("looking up `export`"); 174 175 export.call(&mut store, ())?; 176 177 // Before post-return, there will only have been one call into wasm. 178 assert_eq!(store.data().calls_into_wasm, 1); 179 assert_eq!(store.data().returns_from_wasm, 1); 180 181 export.post_return(&mut store)?; 182 183 // There are no host calls in this example, but the post-return does increment the count of 184 // wasm calls by 1, putting the total number of wasm calls at 2. 185 let s = store.into_data(); 186 assert_eq!(s.calls_into_host, 0); 187 assert_eq!(s.returns_from_host, 0); 188 assert_eq!(s.calls_into_wasm, 2); 189 assert_eq!(s.returns_from_wasm, 2); 190 191 Ok(()) 192 } 193 194 // Create an async Func, call it directly: 195 #[tokio::test] 196 async fn call_wrapped_async_func() -> Result<()> { 197 let wat = r#" 198 (component 199 (import "f" (func $f)) 200 201 (core func $f_lower 202 (canon lower (func $f)) 203 ) 204 (core module $m 205 (import "" "" (func $f)) 206 207 (func $export 208 (call $f) 209 ) 210 211 (export "export" (func $export)) 212 ) 213 (core instance $i (instantiate $m 214 (with "" (instance 215 (export "" (func $f_lower)) 216 )) 217 )) 218 (func (export "export") 219 (canon lift 220 (core func $i "export") 221 ) 222 ) 223 ) 224 "#; 225 226 let mut config = Config::new(); 227 config.async_support(true); 228 let engine = Engine::new(&config)?; 229 230 let component = Component::new(&engine, wat)?; 231 232 let mut linker = Linker::<State>::new(&engine); 233 linker 234 .root() 235 .func_wrap_async("f", |_, _: ()| Box::new(async { Ok(()) }))?; 236 237 let mut store = Store::new(&engine, State::default()); 238 store.call_hook(sync_call_hook); 239 240 let inst = linker 241 .instantiate_async(&mut store, &component) 242 .await 243 .expect("instantiate"); 244 245 let export = inst 246 .get_typed_func::<(), ()>(&mut store, "export") 247 .expect("looking up `export`"); 248 249 export.call_async(&mut store, ()).await?; 250 export.post_return_async(&mut store).await?; 251 252 let s = store.into_data(); 253 assert_eq!(s.calls_into_host, 1); 254 assert_eq!(s.returns_from_host, 1); 255 assert_eq!(s.calls_into_wasm, 1); 256 assert_eq!(s.returns_from_wasm, 1); 257 258 Ok(()) 259 } 260 261 #[test] 262 fn trapping() -> Result<()> { 263 const TRAP_IN_F: i32 = 0; 264 const TRAP_NEXT_CALL_HOST: i32 = 1; 265 const TRAP_NEXT_RETURN_HOST: i32 = 2; 266 const TRAP_NEXT_CALL_WASM: i32 = 3; 267 const TRAP_NEXT_RETURN_WASM: i32 = 4; 268 const DO_NOTHING: i32 = 5; 269 270 let engine = Engine::default(); 271 272 let mut linker = Linker::<State>::new(&engine); 273 274 linker 275 .root() 276 .func_wrap("f", |mut store: _, (action,): (i32,)| -> Result<()> { 277 assert_eq!(store.data().context.last(), Some(&Context::Host)); 278 assert_eq!(store.data().calls_into_host, store.data().calls_into_wasm); 279 280 match action { 281 TRAP_IN_F => bail!("trapping in f"), 282 TRAP_NEXT_CALL_HOST => store.data_mut().trap_next_call_host = true, 283 TRAP_NEXT_RETURN_HOST => store.data_mut().trap_next_return_host = true, 284 TRAP_NEXT_CALL_WASM => store.data_mut().trap_next_call_wasm = true, 285 TRAP_NEXT_RETURN_WASM => store.data_mut().trap_next_return_wasm = true, 286 _ => {} // Do nothing 287 } 288 289 Ok(()) 290 })?; 291 292 let wat = r#" 293 (component 294 (import "f" (func $f (param "action" s32))) 295 296 (core func $f_lower 297 (canon lower (func $f)) 298 ) 299 (core module $m 300 (import "" "" (func $f (param i32))) 301 302 (func $export (param i32) 303 (call $f (local.get 0)) 304 ) 305 306 (export "export" (func $export)) 307 ) 308 (core instance $i (instantiate $m 309 (with "" (instance 310 (export "" (func $f_lower)) 311 )) 312 )) 313 (func (export "export") (param "action" s32) 314 (canon lift 315 (core func $i "export") 316 ) 317 ) 318 ) 319 "#; 320 321 let component = Component::new(&engine, wat)?; 322 323 let run = |action: i32, again: bool| -> (State, Option<anyhow::Error>) { 324 let mut store = Store::new(&engine, State::default()); 325 store.call_hook(sync_call_hook); 326 let inst = linker 327 .instantiate(&mut store, &component) 328 .expect("instantiate"); 329 330 let export = inst 331 .get_typed_func::<(i32,), ()>(&mut store, "export") 332 .expect("looking up `export`"); 333 334 let mut r = export.call(&mut store, (action,)); 335 if r.is_ok() && again { 336 export.post_return(&mut store).unwrap(); 337 r = export.call(&mut store, (action,)); 338 } 339 (store.into_data(), r.err()) 340 }; 341 342 let (s, e) = run(DO_NOTHING, false); 343 assert!(e.is_none()); 344 assert_eq!(s.calls_into_host, 1); 345 assert_eq!(s.returns_from_host, 1); 346 assert_eq!(s.calls_into_wasm, 1); 347 assert_eq!(s.returns_from_wasm, 1); 348 349 let (s, e) = run(DO_NOTHING, true); 350 assert!(e.is_none()); 351 assert_eq!(s.calls_into_host, 2); 352 assert_eq!(s.returns_from_host, 2); 353 assert_eq!(s.calls_into_wasm, 2); 354 assert_eq!(s.returns_from_wasm, 2); 355 356 let (s, e) = run(TRAP_IN_F, false); 357 assert!(format!("{:?}", e.unwrap()).contains("trapping in f")); 358 assert_eq!(s.calls_into_host, 1); 359 assert_eq!(s.returns_from_host, 1); 360 assert_eq!(s.calls_into_wasm, 1); 361 assert_eq!(s.returns_from_wasm, 1); 362 363 // // trap in next call to host. No calls after the bit is set, so this trap shouldn't happen 364 let (s, e) = run(TRAP_NEXT_CALL_HOST, false); 365 assert!(e.is_none()); 366 assert_eq!(s.calls_into_host, 1); 367 assert_eq!(s.returns_from_host, 1); 368 assert_eq!(s.calls_into_wasm, 1); 369 assert_eq!(s.returns_from_wasm, 1); 370 371 // trap in next call to host. call again, so the second call into host traps: 372 let (s, e) = run(TRAP_NEXT_CALL_HOST, true); 373 println!("{:?}", e.as_ref().unwrap()); 374 assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on CallingHost")); 375 assert_eq!(s.calls_into_host, 2); 376 assert_eq!(s.returns_from_host, 1); 377 assert_eq!(s.calls_into_wasm, 2); 378 assert_eq!(s.returns_from_wasm, 2); 379 380 // trap in the return from host. should trap right away, without a second call 381 let (s, e) = run(TRAP_NEXT_RETURN_HOST, false); 382 assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on ReturningFromHost")); 383 assert_eq!(s.calls_into_host, 1); 384 assert_eq!(s.returns_from_host, 1); 385 assert_eq!(s.calls_into_wasm, 1); 386 assert_eq!(s.returns_from_wasm, 1); 387 388 // trap in next call to wasm. No calls after the bit is set, so this trap shouldn't happen: 389 let (s, e) = run(TRAP_NEXT_CALL_WASM, false); 390 assert!(e.is_none()); 391 assert_eq!(s.calls_into_host, 1); 392 assert_eq!(s.returns_from_host, 1); 393 assert_eq!(s.calls_into_wasm, 1); 394 assert_eq!(s.returns_from_wasm, 1); 395 396 // trap in next call to wasm. call again, so the second call into wasm traps: 397 let (s, e) = run(TRAP_NEXT_CALL_WASM, true); 398 assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on CallingWasm")); 399 assert_eq!(s.calls_into_host, 1); 400 assert_eq!(s.returns_from_host, 1); 401 assert_eq!(s.calls_into_wasm, 2); 402 assert_eq!(s.returns_from_wasm, 1); 403 404 // trap in the return from wasm. should trap right away, without a second call 405 let (s, e) = run(TRAP_NEXT_RETURN_WASM, false); 406 assert!(format!("{:?}", e.unwrap()).contains("call_hook: trapping on ReturningFromWasm")); 407 assert_eq!(s.calls_into_host, 1); 408 assert_eq!(s.returns_from_host, 1); 409 assert_eq!(s.calls_into_wasm, 1); 410 assert_eq!(s.returns_from_wasm, 1); 411 412 Ok(()) 413 } 414 415 #[tokio::test] 416 async fn timeout_async_hook() -> Result<()> { 417 struct HandlerR; 418 419 #[async_trait::async_trait] 420 impl CallHookHandler<State> for HandlerR { 421 async fn handle_call_event( 422 &self, 423 mut ctx: StoreContextMut<'_, State>, 424 ch: CallHook, 425 ) -> Result<()> { 426 let obj = ctx.data_mut(); 427 if obj.calls_into_host > 200 { 428 bail!("timeout"); 429 } 430 431 match ch { 432 CallHook::CallingHost => obj.calls_into_host += 1, 433 CallHook::CallingWasm => obj.calls_into_wasm += 1, 434 CallHook::ReturningFromHost => obj.returns_from_host += 1, 435 CallHook::ReturningFromWasm => obj.returns_from_wasm += 1, 436 } 437 438 Ok(()) 439 } 440 } 441 442 let wat = r#" 443 (component 444 (import "f" (func $f)) 445 446 (core func $f_lower 447 (canon lower (func $f)) 448 ) 449 (core module $m 450 (import "" "" (func $f)) 451 452 (func $export 453 (loop $start 454 (call $f) 455 (br $start)) 456 ) 457 458 (export "export" (func $export)) 459 ) 460 (core instance $i (instantiate $m 461 (with "" (instance 462 (export "" (func $f_lower)) 463 )) 464 )) 465 (func (export "export") 466 (canon lift 467 (core func $i "export") 468 ) 469 ) 470 ) 471 "#; 472 473 let mut config = Config::new(); 474 config.async_support(true); 475 let engine = Engine::new(&config)?; 476 477 let component = Component::new(&engine, wat)?; 478 479 let mut linker = Linker::<State>::new(&engine); 480 linker 481 .root() 482 .func_wrap_async("f", |_, _: ()| Box::new(async { Ok(()) }))?; 483 484 let mut store = Store::new(&engine, State::default()); 485 store.call_hook_async(HandlerR {}); 486 487 let inst = linker 488 .instantiate_async(&mut store, &component) 489 .await 490 .expect("instantiate"); 491 492 let export = inst 493 .get_typed_func::<(), ()>(&mut store, "export") 494 .expect("looking up `export`"); 495 496 let r = export.call_async(&mut store, ()).await; 497 assert!(format!("{:?}", r.unwrap_err()).contains("timeout")); 498 499 let s = store.into_data(); 500 assert!(s.calls_into_host > 1); 501 assert!(s.returns_from_host > 1); 502 assert_eq!(s.calls_into_wasm, 1); 503 assert_eq!(s.returns_from_wasm, 0); 504 505 Ok(()) 506 } 507 508 #[tokio::test] 509 async fn drop_suspended_async_hook() -> Result<()> { 510 struct Handler; 511 512 #[async_trait::async_trait] 513 impl CallHookHandler<u32> for Handler { 514 async fn handle_call_event( 515 &self, 516 mut ctx: StoreContextMut<'_, u32>, 517 _ch: CallHook, 518 ) -> Result<()> { 519 let state = ctx.data_mut(); 520 assert_eq!(*state, 0); 521 *state += 1; 522 let _dec = Decrement(state); 523 524 // Simulate some sort of event which takes a number of yields 525 for _ in 0..500 { 526 tokio::task::yield_now().await; 527 } 528 Ok(()) 529 } 530 } 531 532 let wat = r#" 533 (component 534 (import "f" (func $f)) 535 536 (core func $f_lower 537 (canon lower (func $f)) 538 ) 539 (core module $m 540 (import "" "" (func $f)) 541 542 (func $export 543 (call $f) 544 ) 545 546 (export "export" (func $export)) 547 ) 548 (core instance $i (instantiate $m 549 (with "" (instance 550 (export "" (func $f_lower)) 551 )) 552 )) 553 (func (export "export") 554 (canon lift 555 (core func $i "export") 556 ) 557 ) 558 ) 559 "#; 560 561 let mut config = Config::new(); 562 config.async_support(true); 563 let engine = Engine::new(&config)?; 564 565 let component = Component::new(&engine, wat)?; 566 567 let mut linker = Linker::<u32>::new(&engine); 568 linker.root().func_wrap_async("f", |mut store, _: ()| { 569 Box::new(async move { 570 let state = store.data_mut(); 571 assert_eq!(*state, 0); 572 *state += 1; 573 let _dec = Decrement(state); 574 for _ in 0.. { 575 tokio::task::yield_now().await; 576 } 577 Ok(()) 578 }) 579 })?; 580 581 let mut store = Store::new(&engine, 0); 582 store.call_hook_async(Handler); 583 584 let inst = linker 585 .instantiate_async(&mut store, &component) 586 .await 587 .expect("instantiate"); 588 589 let export = inst 590 .get_typed_func::<(), ()>(&mut store, "export") 591 .expect("looking up `export`"); 592 593 // Test that if we drop in the middle of an async hook that everything 594 // is alright. 595 PollNTimes { 596 future: Box::pin(export.call_async(&mut store, ())), 597 times: 200, 598 } 599 .await; 600 assert_eq!(*store.data(), 0); // double-check user dtors ran 601 602 return Ok(()); 603 604 // A helper struct to poll an inner `future` N `times` and then resolve. 605 // This is used above to test that when futures are dropped while they're 606 // pending everything works and is cleaned up on the Wasmtime side of 607 // things. 608 struct PollNTimes<F> { 609 future: F, 610 times: u32, 611 } 612 613 impl<F: Future + Unpin> Future for PollNTimes<F> 614 where 615 F::Output: std::fmt::Debug, 616 { 617 type Output = (); 618 fn poll(mut self: Pin<&mut Self>, task: &mut task::Context<'_>) -> Poll<()> { 619 for i in 0..self.times { 620 match Pin::new(&mut self.future).poll(task) { 621 Poll::Ready(v) => panic!("future should not be ready at {i}; result is {v:?}"), 622 Poll::Pending => {} 623 } 624 } 625 626 Poll::Ready(()) 627 } 628 } 629 630 // helper struct to decrement a counter on drop 631 struct Decrement<'a>(&'a mut u32); 632 633 impl Drop for Decrement<'_> { 634 fn drop(&mut self) { 635 *self.0 -= 1; 636 } 637 } 638 } 639