1 //! Runtime library calls. 2 //! 3 //! Note that Wasm compilers may sometimes perform these inline rather than 4 //! calling them, particularly when CPUs have special instructions which compute 5 //! them directly. 6 //! 7 //! These functions are called by compiled Wasm code, and therefore must take 8 //! certain care about some things: 9 //! 10 //! * They must only contain basic, raw i32/i64/f32/f64/pointer parameters that 11 //! are safe to pass across the system ABI. 12 //! 13 //! * If any nested function propagates an `Err(trap)` out to the library 14 //! function frame, we need to raise it. This involves some nasty and quite 15 //! unsafe code under the covers! Notably, after raising the trap, drops 16 //! **will not** be run for local variables! This can lead to things like 17 //! leaking `InstanceHandle`s which leads to never deallocating JIT code, 18 //! instances, and modules if we are not careful! 19 //! 20 //! * The libcall must be entered via a Wasm-to-libcall trampoline that saves 21 //! the last Wasm FP and PC for stack walking purposes. (For more details, see 22 //! `crates/wasmtime/src/runtime/vm/backtrace.rs`.) 23 //! 24 //! To make it easier to correctly handle all these things, **all** libcalls 25 //! must be defined via the `libcall!` helper macro! See its doc comments below 26 //! for an example, or just look at the rest of the file. 27 //! 28 //! ## Dealing with `externref`s 29 //! 30 //! When receiving a raw `*mut u8` that is actually a `VMExternRef` reference, 31 //! convert it into a proper `VMExternRef` with `VMExternRef::clone_from_raw` as 32 //! soon as apossible. Any GC before raw pointer is converted into a reference 33 //! can potentially collect the referenced object, which could lead to use after 34 //! free. 35 //! 36 //! Avoid this by eagerly converting into a proper `VMExternRef`! (Unfortunately 37 //! there is no macro to help us automatically get this correct, so stay 38 //! vigilant!) 39 //! 40 //! ```ignore 41 //! pub unsafe extern "C" my_libcall_takes_ref(raw_extern_ref: *mut u8) { 42 //! // Before `clone_from_raw`, `raw_extern_ref` is potentially unrooted, 43 //! // and doing GC here could lead to use after free! 44 //! 45 //! let my_extern_ref = if raw_extern_ref.is_null() { 46 //! None 47 //! } else { 48 //! Some(VMExternRef::clone_from_raw(raw_extern_ref)) 49 //! }; 50 //! 51 //! // Now that we did `clone_from_raw`, it is safe to do a GC (or do 52 //! // anything else that might transitively GC, like call back into 53 //! // Wasm!) 54 //! } 55 //! ``` 56 57 #[cfg(feature = "stack-switching")] 58 use super::stack_switching::VMContObj; 59 use crate::prelude::*; 60 use crate::runtime::store::StoreInstanceId; 61 #[cfg(feature = "gc")] 62 use crate::runtime::vm::VMGcRef; 63 use crate::runtime::vm::table::TableElementType; 64 use crate::runtime::vm::vmcontext::VMFuncRef; 65 use crate::runtime::vm::{ 66 HostResultHasUnwindSentinel, Instance, TrapReason, VMStore, f32x4, f64x2, i8x16, 67 }; 68 use core::convert::Infallible; 69 use core::pin::Pin; 70 use core::ptr::NonNull; 71 #[cfg(feature = "threads")] 72 use core::time::Duration; 73 use wasmtime_environ::{ 74 DataIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, FuncIndex, MemoryIndex, 75 TableIndex, Trap, 76 }; 77 #[cfg(feature = "wmemcheck")] 78 use wasmtime_wmemcheck::AccessError::{ 79 DoubleMalloc, InvalidFree, InvalidRead, InvalidWrite, OutOfBounds, 80 }; 81 82 /// Raw functions which are actually called from compiled code. 83 /// 84 /// Invocation of a builtin currently looks like: 85 /// 86 /// * A wasm function calls a cranelift-compiled trampoline that's generated 87 /// once-per-builtin. 88 /// * The cranelift-compiled trampoline performs any necessary actions to exit 89 /// wasm, such as dealing with fp/pc/etc. 90 /// * The cranelift-compiled trampoline loads a function pointer from an array 91 /// stored in `VMContext` That function pointer is defined in this module. 92 /// * This module runs, handling things like `catch_unwind` and `Result` and 93 /// such. 94 /// * This module delegates to the outer module (this file) which has the actual 95 /// implementation. 96 /// 97 /// For more information on converting from host-defined values to Cranelift ABI 98 /// values see the `catch_unwind_and_record_trap` function. 99 pub mod raw { 100 use crate::runtime::vm::{InstanceAndStore, VMContext, f32x4, f64x2, i8x16}; 101 use core::ptr::NonNull; 102 103 macro_rules! libcall { 104 ( 105 $( 106 $( #[cfg($attr:meta)] )? 107 $name:ident( vmctx: vmctx $(, $pname:ident: $param:ident )* ) $(-> $result:ident)?; 108 )* 109 ) => { 110 $( 111 // This is the direct entrypoint from the compiled module which 112 // still has the raw signature. 113 // 114 // This will delegate to the outer module to the actual 115 // implementation and automatically perform `catch_unwind` along 116 // with conversion of the return value in the face of traps. 117 #[allow(improper_ctypes_definitions, reason = "__m128i known not FFI-safe")] 118 pub unsafe extern "C" fn $name( 119 vmctx: NonNull<VMContext>, 120 $( $pname : libcall!(@ty $param), )* 121 ) $(-> libcall!(@ty $result))? { 122 $(#[cfg($attr)])? 123 { 124 crate::runtime::vm::traphandlers::catch_unwind_and_record_trap(|| unsafe { 125 InstanceAndStore::from_vmctx(vmctx, |pair| { 126 let (instance, store) = pair.unpack_mut(); 127 super::$name(store, instance, $($pname),*) 128 }) 129 }) 130 } 131 $( 132 #[cfg(not($attr))] 133 { 134 let _ = vmctx; 135 unreachable!(); 136 } 137 )? 138 } 139 140 // This works around a `rustc` bug where compiling with LTO 141 // will sometimes strip out some of these symbols resulting 142 // in a linking failure. 143 #[allow(improper_ctypes_definitions, reason = "__m128i known not FFI-safe")] 144 const _: () = { 145 #[used] 146 static I_AM_USED: unsafe extern "C" fn( 147 NonNull<VMContext>, 148 $( $pname : libcall!(@ty $param), )* 149 ) $( -> libcall!(@ty $result))? = $name; 150 }; 151 )* 152 }; 153 154 (@ty u32) => (u32); 155 (@ty u64) => (u64); 156 (@ty f32) => (f32); 157 (@ty f64) => (f64); 158 (@ty u8) => (u8); 159 (@ty i8x16) => (i8x16); 160 (@ty f32x4) => (f32x4); 161 (@ty f64x2) => (f64x2); 162 (@ty bool) => (bool); 163 (@ty pointer) => (*mut u8); 164 } 165 166 wasmtime_environ::foreach_builtin_function!(libcall); 167 } 168 169 fn memory_grow( 170 store: &mut dyn VMStore, 171 mut instance: Pin<&mut Instance>, 172 delta: u64, 173 memory_index: u32, 174 ) -> Result<Option<AllocationSize>, TrapReason> { 175 let memory_index = DefinedMemoryIndex::from_u32(memory_index); 176 let module = instance.env_module(); 177 let page_size_log2 = module.memories[module.memory_index(memory_index)].page_size_log2; 178 179 let result = instance 180 .as_mut() 181 .memory_grow(store, memory_index, delta)? 182 .map(|size_in_bytes| AllocationSize(size_in_bytes >> page_size_log2)); 183 184 Ok(result) 185 } 186 187 /// A helper structure to represent the return value of a memory or table growth 188 /// call. 189 /// 190 /// This represents a byte or element-based count of the size of an item on the 191 /// host. For example a memory is how many bytes large the memory is, or a table 192 /// is how many elements large it is. It's assumed that the value here is never 193 /// -1 or -2 as that would mean the entire host address space is allocated which 194 /// is not possible. 195 struct AllocationSize(usize); 196 197 /// Special implementation for growth-related libcalls. 198 /// 199 /// Here the optional return value means: 200 /// 201 /// * `Some(val)` - the growth succeeded and the previous size of the item was 202 /// `val`. 203 /// * `None` - the growth failed. 204 /// 205 /// The failure case returns -1 (or `usize::MAX` as an unsigned integer) and the 206 /// successful case returns the `val` itself. Note that -2 (`usize::MAX - 1` 207 /// when unsigned) is unwind as a sentinel to indicate an unwind as no valid 208 /// allocation can be that large. 209 unsafe impl HostResultHasUnwindSentinel for Option<AllocationSize> { 210 type Abi = *mut u8; 211 const SENTINEL: *mut u8 = (usize::MAX - 1) as *mut u8; 212 213 fn into_abi(self) -> *mut u8 { 214 match self { 215 Some(size) => { 216 debug_assert!(size.0 < (usize::MAX - 1)); 217 size.0 as *mut u8 218 } 219 None => usize::MAX as *mut u8, 220 } 221 } 222 } 223 224 /// Implementation of `table.grow` for `funcref` tables. 225 unsafe fn table_grow_func_ref( 226 store: &mut dyn VMStore, 227 mut instance: Pin<&mut Instance>, 228 defined_table_index: u32, 229 delta: u64, 230 init_value: *mut u8, 231 ) -> Result<Option<AllocationSize>> { 232 let defined_table_index = DefinedTableIndex::from_u32(defined_table_index); 233 let table_index = instance.env_module().table_index(defined_table_index); 234 debug_assert!(matches!( 235 instance.as_mut().table_element_type(table_index), 236 TableElementType::Func, 237 )); 238 let element = NonNull::new(init_value.cast::<VMFuncRef>()); 239 let result = instance 240 .defined_table_grow(defined_table_index, |table| unsafe { 241 table.grow_func(store, delta, element) 242 })? 243 .map(AllocationSize); 244 Ok(result) 245 } 246 247 /// Implementation of `table.grow` for GC-reference tables. 248 #[cfg(feature = "gc")] 249 unsafe fn table_grow_gc_ref( 250 store: &mut dyn VMStore, 251 mut instance: Pin<&mut Instance>, 252 defined_table_index: u32, 253 delta: u64, 254 init_value: u32, 255 ) -> Result<Option<AllocationSize>> { 256 let defined_table_index = DefinedTableIndex::from_u32(defined_table_index); 257 let table_index = instance.env_module().table_index(defined_table_index); 258 debug_assert!(matches!( 259 instance.as_mut().table_element_type(table_index), 260 TableElementType::GcRef, 261 )); 262 263 let element = VMGcRef::from_raw_u32(init_value); 264 let result = instance 265 .defined_table_grow(defined_table_index, |table| unsafe { 266 table.grow_gc_ref(store, delta, element.as_ref()) 267 })? 268 .map(AllocationSize); 269 Ok(result) 270 } 271 272 #[cfg(feature = "stack-switching")] 273 unsafe fn table_grow_cont_obj( 274 store: &mut dyn VMStore, 275 mut instance: Pin<&mut Instance>, 276 defined_table_index: u32, 277 delta: u64, 278 // The following two values together form the initial Option<VMContObj>. 279 // A None value is indicated by the pointer being null. 280 init_value_contref: *mut u8, 281 init_value_revision: u64, 282 ) -> Result<Option<AllocationSize>> { 283 let defined_table_index = DefinedTableIndex::from_u32(defined_table_index); 284 let table_index = instance.env_module().table_index(defined_table_index); 285 debug_assert!(matches!( 286 instance.as_mut().table_element_type(table_index), 287 TableElementType::Cont, 288 )); 289 let element = unsafe { VMContObj::from_raw_parts(init_value_contref, init_value_revision) }; 290 let result = instance 291 .defined_table_grow(defined_table_index, |table| unsafe { 292 table.grow_cont(store, delta, element) 293 })? 294 .map(AllocationSize); 295 Ok(result) 296 } 297 298 /// Implementation of `table.fill` for `funcref`s. 299 unsafe fn table_fill_func_ref( 300 _store: &mut dyn VMStore, 301 instance: Pin<&mut Instance>, 302 table_index: u32, 303 dst: u64, 304 val: *mut u8, 305 len: u64, 306 ) -> Result<()> { 307 let table_index = DefinedTableIndex::from_u32(table_index); 308 let table = instance.get_defined_table(table_index); 309 match table.element_type() { 310 TableElementType::Func => { 311 let val = NonNull::new(val.cast::<VMFuncRef>()); 312 table.fill_func(dst, val, len)?; 313 Ok(()) 314 } 315 TableElementType::GcRef => unreachable!(), 316 TableElementType::Cont => unreachable!(), 317 } 318 } 319 320 #[cfg(feature = "gc")] 321 unsafe fn table_fill_gc_ref( 322 store: &mut dyn VMStore, 323 instance: Pin<&mut Instance>, 324 table_index: u32, 325 dst: u64, 326 val: u32, 327 len: u64, 328 ) -> Result<()> { 329 let table_index = DefinedTableIndex::from_u32(table_index); 330 let table = instance.get_defined_table(table_index); 331 match table.element_type() { 332 TableElementType::Func => unreachable!(), 333 TableElementType::GcRef => { 334 let gc_store = store.store_opaque_mut().unwrap_gc_store_mut(); 335 let gc_ref = VMGcRef::from_raw_u32(val); 336 table.fill_gc_ref(Some(gc_store), dst, gc_ref.as_ref(), len)?; 337 Ok(()) 338 } 339 340 TableElementType::Cont => unreachable!(), 341 } 342 } 343 344 #[cfg(feature = "stack-switching")] 345 unsafe fn table_fill_cont_obj( 346 _store: &mut dyn VMStore, 347 instance: Pin<&mut Instance>, 348 table_index: u32, 349 dst: u64, 350 value_contref: *mut u8, 351 value_revision: u64, 352 len: u64, 353 ) -> Result<()> { 354 let table_index = DefinedTableIndex::from_u32(table_index); 355 let table = instance.get_defined_table(table_index); 356 match table.element_type() { 357 TableElementType::Cont => { 358 let contobj = unsafe { VMContObj::from_raw_parts(value_contref, value_revision) }; 359 table.fill_cont(dst, contobj, len)?; 360 Ok(()) 361 } 362 _ => panic!("Wrong table filling function"), 363 } 364 } 365 366 // Implementation of `table.copy`. 367 fn table_copy( 368 store: &mut dyn VMStore, 369 mut instance: Pin<&mut Instance>, 370 dst_table_index: u32, 371 src_table_index: u32, 372 dst: u64, 373 src: u64, 374 len: u64, 375 ) -> Result<(), Trap> { 376 let dst_table_index = TableIndex::from_u32(dst_table_index); 377 let src_table_index = TableIndex::from_u32(src_table_index); 378 let store = store.store_opaque_mut(); 379 380 // Convert the two table indices relative to `instance` into two 381 // defining instances and the defined table index within that instance. 382 let (dst_def_index, dst_instance) = instance 383 .as_mut() 384 .defined_table_index_and_instance(dst_table_index); 385 let dst_instance_id = dst_instance.id(); 386 let (src_def_index, src_instance) = instance 387 .as_mut() 388 .defined_table_index_and_instance(src_table_index); 389 let src_instance_id = src_instance.id(); 390 391 let src_table = crate::Table::from_raw( 392 StoreInstanceId::new(store.id(), src_instance_id), 393 src_def_index, 394 ); 395 let dst_table = crate::Table::from_raw( 396 StoreInstanceId::new(store.id(), dst_instance_id), 397 dst_def_index, 398 ); 399 400 // SAFETY: this is only safe if the two tables have the same type, and that 401 // was validated during wasm-validation time. 402 unsafe { crate::Table::copy_raw(store, &dst_table, dst, &src_table, src, len) } 403 } 404 405 // Implementation of `table.init`. 406 fn table_init( 407 store: &mut dyn VMStore, 408 instance: Pin<&mut Instance>, 409 table_index: u32, 410 elem_index: u32, 411 dst: u64, 412 src: u64, 413 len: u64, 414 ) -> Result<(), Trap> { 415 let table_index = TableIndex::from_u32(table_index); 416 let elem_index = ElemIndex::from_u32(elem_index); 417 instance.table_init( 418 store.store_opaque_mut(), 419 table_index, 420 elem_index, 421 dst, 422 src, 423 len, 424 ) 425 } 426 427 // Implementation of `elem.drop`. 428 fn elem_drop(_store: &mut dyn VMStore, instance: Pin<&mut Instance>, elem_index: u32) { 429 let elem_index = ElemIndex::from_u32(elem_index); 430 instance.elem_drop(elem_index) 431 } 432 433 // Implementation of `memory.copy`. 434 fn memory_copy( 435 _store: &mut dyn VMStore, 436 instance: Pin<&mut Instance>, 437 dst_index: u32, 438 dst: u64, 439 src_index: u32, 440 src: u64, 441 len: u64, 442 ) -> Result<(), Trap> { 443 let src_index = MemoryIndex::from_u32(src_index); 444 let dst_index = MemoryIndex::from_u32(dst_index); 445 instance.memory_copy(dst_index, dst, src_index, src, len) 446 } 447 448 // Implementation of `memory.fill` for locally defined memories. 449 fn memory_fill( 450 _store: &mut dyn VMStore, 451 instance: Pin<&mut Instance>, 452 memory_index: u32, 453 dst: u64, 454 val: u32, 455 len: u64, 456 ) -> Result<(), Trap> { 457 let memory_index = DefinedMemoryIndex::from_u32(memory_index); 458 #[expect(clippy::cast_possible_truncation, reason = "known to truncate here")] 459 instance.memory_fill(memory_index, dst, val as u8, len) 460 } 461 462 // Implementation of `memory.init`. 463 fn memory_init( 464 _store: &mut dyn VMStore, 465 instance: Pin<&mut Instance>, 466 memory_index: u32, 467 data_index: u32, 468 dst: u64, 469 src: u32, 470 len: u32, 471 ) -> Result<(), Trap> { 472 let memory_index = MemoryIndex::from_u32(memory_index); 473 let data_index = DataIndex::from_u32(data_index); 474 instance.memory_init(memory_index, data_index, dst, src, len) 475 } 476 477 // Implementation of `ref.func`. 478 fn ref_func( 479 _store: &mut dyn VMStore, 480 instance: Pin<&mut Instance>, 481 func_index: u32, 482 ) -> NonNull<u8> { 483 instance 484 .get_func_ref(FuncIndex::from_u32(func_index)) 485 .expect("ref_func: funcref should always be available for given func index") 486 .cast() 487 } 488 489 // Implementation of `data.drop`. 490 fn data_drop(_store: &mut dyn VMStore, instance: Pin<&mut Instance>, data_index: u32) { 491 let data_index = DataIndex::from_u32(data_index); 492 instance.data_drop(data_index) 493 } 494 495 // Returns a table entry after lazily initializing it. 496 unsafe fn table_get_lazy_init_func_ref( 497 _store: &mut dyn VMStore, 498 instance: Pin<&mut Instance>, 499 table_index: u32, 500 index: u64, 501 ) -> *mut u8 { 502 let table_index = TableIndex::from_u32(table_index); 503 let table = instance.get_table_with_lazy_init(table_index, core::iter::once(index)); 504 let elem = (*table) 505 .get_func(index) 506 .expect("table access already bounds-checked"); 507 508 match elem { 509 Some(ptr) => ptr.as_ptr().cast(), 510 None => core::ptr::null_mut(), 511 } 512 } 513 514 /// Drop a GC reference. 515 #[cfg(feature = "gc-drc")] 516 unsafe fn drop_gc_ref(store: &mut dyn VMStore, _instance: Pin<&mut Instance>, gc_ref: u32) { 517 log::trace!("libcalls::drop_gc_ref({gc_ref:#x})"); 518 let gc_ref = VMGcRef::from_raw_u32(gc_ref).expect("non-null VMGcRef"); 519 store 520 .store_opaque_mut() 521 .unwrap_gc_store_mut() 522 .drop_gc_ref(gc_ref); 523 } 524 525 /// Grow the GC heap. 526 #[cfg(feature = "gc-null")] 527 unsafe fn grow_gc_heap( 528 store: &mut dyn VMStore, 529 _instance: Pin<&mut Instance>, 530 bytes_needed: u64, 531 ) -> Result<()> { 532 let orig_len = u64::try_from( 533 store 534 .require_gc_store()? 535 .gc_heap 536 .vmmemory() 537 .current_length(), 538 ) 539 .unwrap(); 540 541 unsafe { 542 store 543 .maybe_async_gc(None, Some(bytes_needed)) 544 .context("failed to grow the GC heap") 545 .context(crate::Trap::AllocationTooLarge)?; 546 } 547 548 // JIT code relies on the memory having grown by `bytes_needed` bytes if 549 // this libcall returns successfully, so trap if we didn't grow that much. 550 let new_len = u64::try_from( 551 store 552 .require_gc_store()? 553 .gc_heap 554 .vmmemory() 555 .current_length(), 556 ) 557 .unwrap(); 558 if orig_len 559 .checked_add(bytes_needed) 560 .is_none_or(|expected_len| new_len < expected_len) 561 { 562 return Err(crate::Trap::AllocationTooLarge.into()); 563 } 564 565 Ok(()) 566 } 567 568 /// Allocate a raw, unininitialized GC object for Wasm code. 569 /// 570 /// The Wasm code is responsible for initializing the object. 571 #[cfg(feature = "gc-drc")] 572 unsafe fn gc_alloc_raw( 573 store: &mut dyn VMStore, 574 instance: Pin<&mut Instance>, 575 kind_and_reserved: u32, 576 module_interned_type_index: u32, 577 size: u32, 578 align: u32, 579 ) -> Result<core::num::NonZeroU32> { 580 use crate::vm::VMGcHeader; 581 use core::alloc::Layout; 582 use wasmtime_environ::{ModuleInternedTypeIndex, VMGcKind}; 583 584 let kind = VMGcKind::from_high_bits_of_u32(kind_and_reserved); 585 log::trace!("gc_alloc_raw(kind={kind:?}, size={size}, align={align})"); 586 587 let module = instance 588 .runtime_module() 589 .expect("should never allocate GC types defined in a dummy module"); 590 591 let module_interned_type_index = ModuleInternedTypeIndex::from_u32(module_interned_type_index); 592 let shared_type_index = module 593 .signatures() 594 .shared_type(module_interned_type_index) 595 .expect("should have engine type index for module type index"); 596 597 let mut header = VMGcHeader::from_kind_and_index(kind, shared_type_index); 598 header.set_reserved_u26(kind_and_reserved & VMGcKind::UNUSED_MASK); 599 600 let size = usize::try_from(size).unwrap(); 601 let align = usize::try_from(align).unwrap(); 602 assert!(align.is_power_of_two()); 603 let layout = Layout::from_size_align(size, align).map_err(|e| { 604 let err = Error::from(crate::Trap::AllocationTooLarge); 605 err.context(e) 606 })?; 607 608 let store = store.store_opaque_mut(); 609 let gc_ref = unsafe { 610 store.retry_after_gc_maybe_async((), |store, ()| { 611 store 612 .unwrap_gc_store_mut() 613 .alloc_raw(header, layout)? 614 .map_err(|bytes_needed| crate::GcHeapOutOfMemory::new((), bytes_needed).into()) 615 })? 616 }; 617 618 let raw = store.unwrap_gc_store_mut().expose_gc_ref_to_wasm(gc_ref); 619 Ok(raw) 620 } 621 622 // Intern a `funcref` into the GC heap, returning its `FuncRefTableId`. 623 // 624 // This libcall may not GC. 625 #[cfg(feature = "gc")] 626 unsafe fn intern_func_ref_for_gc_heap( 627 store: &mut dyn VMStore, 628 _instance: Pin<&mut Instance>, 629 func_ref: *mut u8, 630 ) -> Result<u32> { 631 use crate::{store::AutoAssertNoGc, vm::SendSyncPtr}; 632 use core::ptr::NonNull; 633 634 let mut store = AutoAssertNoGc::new(store.store_opaque_mut()); 635 636 let func_ref = func_ref.cast::<VMFuncRef>(); 637 let func_ref = NonNull::new(func_ref).map(SendSyncPtr::new); 638 639 let func_ref_id = unsafe { 640 store 641 .require_gc_store_mut()? 642 .func_ref_table 643 .intern(func_ref) 644 }; 645 Ok(func_ref_id.into_raw()) 646 } 647 648 // Get the raw `VMFuncRef` pointer associated with a `FuncRefTableId` from an 649 // earlier `intern_func_ref_for_gc_heap` call. 650 // 651 // This libcall may not GC. 652 #[cfg(feature = "gc")] 653 unsafe fn get_interned_func_ref( 654 store: &mut dyn VMStore, 655 instance: Pin<&mut Instance>, 656 func_ref_id: u32, 657 module_interned_type_index: u32, 658 ) -> *mut u8 { 659 use super::FuncRefTableId; 660 use crate::store::AutoAssertNoGc; 661 use wasmtime_environ::{ModuleInternedTypeIndex, packed_option::ReservedValue}; 662 663 let store = AutoAssertNoGc::new(store.store_opaque_mut()); 664 665 let func_ref_id = FuncRefTableId::from_raw(func_ref_id); 666 let module_interned_type_index = ModuleInternedTypeIndex::from_bits(module_interned_type_index); 667 668 let func_ref = if module_interned_type_index.is_reserved_value() { 669 store 670 .unwrap_gc_store() 671 .func_ref_table 672 .get_untyped(func_ref_id) 673 } else { 674 let types = store.engine().signatures(); 675 let engine_ty = instance.engine_type_index(module_interned_type_index); 676 store 677 .unwrap_gc_store() 678 .func_ref_table 679 .get_typed(types, func_ref_id, engine_ty) 680 }; 681 682 func_ref.map_or(core::ptr::null_mut(), |f| f.as_ptr().cast()) 683 } 684 685 /// Implementation of the `array.new_data` instruction. 686 #[cfg(feature = "gc")] 687 unsafe fn array_new_data( 688 store: &mut dyn VMStore, 689 instance: Pin<&mut Instance>, 690 array_type_index: u32, 691 data_index: u32, 692 src: u32, 693 len: u32, 694 ) -> Result<core::num::NonZeroU32> { 695 use crate::ArrayType; 696 use wasmtime_environ::ModuleInternedTypeIndex; 697 698 let store = store.store_opaque_mut(); 699 let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index); 700 let data_index = DataIndex::from_u32(data_index); 701 702 // Calculate the byte-length of the data (as opposed to the element-length 703 // of the array). 704 let data_range = instance.wasm_data_range(data_index); 705 let shared_ty = instance.engine_type_index(array_type_index); 706 let array_ty = ArrayType::from_shared_type_index(store.engine(), shared_ty); 707 let one_elem_size = array_ty 708 .element_type() 709 .data_byte_size() 710 .expect("Wasm validation ensures that this type have a defined byte size"); 711 let byte_len = len 712 .checked_mul(one_elem_size) 713 .and_then(|x| usize::try_from(x).ok()) 714 .ok_or_else(|| Trap::MemoryOutOfBounds)?; 715 716 // Get the data from the segment, checking bounds. 717 let src = usize::try_from(src).map_err(|_| Trap::MemoryOutOfBounds)?; 718 let data = instance 719 .wasm_data(data_range) 720 .get(src..) 721 .and_then(|d| d.get(..byte_len)) 722 .ok_or_else(|| Trap::MemoryOutOfBounds)?; 723 724 // Allocate the (uninitialized) array. 725 let gc_layout = store 726 .engine() 727 .signatures() 728 .layout(shared_ty) 729 .expect("array types have GC layouts"); 730 let array_layout = gc_layout.unwrap_array(); 731 let array_ref = unsafe { 732 store.retry_after_gc_maybe_async((), |store, ()| { 733 store 734 .unwrap_gc_store_mut() 735 .alloc_uninit_array(shared_ty, len, &array_layout)? 736 .map_err(|bytes_needed| crate::GcHeapOutOfMemory::new((), bytes_needed).into()) 737 })? 738 }; 739 740 // Copy the data into the array, initializing it. 741 store 742 .unwrap_gc_store_mut() 743 .gc_object_data(array_ref.as_gc_ref()) 744 .copy_from_slice(array_layout.base_size, data); 745 746 // Return the array to Wasm! 747 let raw = store 748 .unwrap_gc_store_mut() 749 .expose_gc_ref_to_wasm(array_ref.into()); 750 Ok(raw) 751 } 752 753 /// Implementation of the `array.init_data` instruction. 754 #[cfg(feature = "gc")] 755 unsafe fn array_init_data( 756 store: &mut dyn VMStore, 757 instance: Pin<&mut Instance>, 758 array_type_index: u32, 759 array: u32, 760 dst: u32, 761 data_index: u32, 762 src: u32, 763 len: u32, 764 ) -> Result<()> { 765 use crate::ArrayType; 766 use wasmtime_environ::ModuleInternedTypeIndex; 767 768 let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index); 769 let data_index = DataIndex::from_u32(data_index); 770 771 log::trace!( 772 "array.init_data(array={array:#x}, dst={dst}, data_index={data_index:?}, src={src}, len={len})", 773 ); 774 775 // Null check the array. 776 let gc_ref = VMGcRef::from_raw_u32(array).ok_or_else(|| Trap::NullReference)?; 777 let array = gc_ref 778 .into_arrayref(&*store.unwrap_gc_store().gc_heap) 779 .expect("gc ref should be an array"); 780 781 let dst = usize::try_from(dst).map_err(|_| Trap::MemoryOutOfBounds)?; 782 let src = usize::try_from(src).map_err(|_| Trap::MemoryOutOfBounds)?; 783 let len = usize::try_from(len).map_err(|_| Trap::MemoryOutOfBounds)?; 784 785 // Bounds check the array. 786 let array_len = array.len(store.store_opaque()); 787 let array_len = usize::try_from(array_len).map_err(|_| Trap::ArrayOutOfBounds)?; 788 if dst.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > array_len { 789 return Err(Trap::ArrayOutOfBounds.into()); 790 } 791 792 // Calculate the byte length from the array length. 793 let shared_ty = instance.engine_type_index(array_type_index); 794 let array_ty = ArrayType::from_shared_type_index(store.engine(), shared_ty); 795 let one_elem_size = array_ty 796 .element_type() 797 .data_byte_size() 798 .expect("Wasm validation ensures that this type have a defined byte size"); 799 let data_len = len 800 .checked_mul(usize::try_from(one_elem_size).unwrap()) 801 .ok_or_else(|| Trap::MemoryOutOfBounds)?; 802 803 // Get the data from the segment, checking its bounds. 804 let data_range = instance.wasm_data_range(data_index); 805 let data = instance 806 .wasm_data(data_range) 807 .get(src..) 808 .and_then(|d| d.get(..data_len)) 809 .ok_or_else(|| Trap::MemoryOutOfBounds)?; 810 811 // Copy the data into the array. 812 813 let dst_offset = u32::try_from(dst) 814 .unwrap() 815 .checked_mul(one_elem_size) 816 .unwrap(); 817 818 let array_layout = store 819 .engine() 820 .signatures() 821 .layout(shared_ty) 822 .expect("array types have GC layouts"); 823 let array_layout = array_layout.unwrap_array(); 824 825 let obj_offset = array_layout.base_size.checked_add(dst_offset).unwrap(); 826 827 store 828 .unwrap_gc_store_mut() 829 .gc_object_data(array.as_gc_ref()) 830 .copy_from_slice(obj_offset, data); 831 832 Ok(()) 833 } 834 835 #[cfg(feature = "gc")] 836 unsafe fn array_new_elem( 837 store: &mut dyn VMStore, 838 mut instance: Pin<&mut Instance>, 839 array_type_index: u32, 840 elem_index: u32, 841 src: u32, 842 len: u32, 843 ) -> Result<core::num::NonZeroU32> { 844 use crate::{ 845 ArrayRef, ArrayRefPre, ArrayType, Func, OpaqueRootScope, RootedGcRefImpl, Val, 846 store::AutoAssertNoGc, 847 vm::const_expr::{ConstEvalContext, ConstExprEvaluator}, 848 }; 849 use wasmtime_environ::{ModuleInternedTypeIndex, TableSegmentElements}; 850 851 // Convert indices to their typed forms. 852 let array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index); 853 let elem_index = ElemIndex::from_u32(elem_index); 854 855 let mut storage = None; 856 let elements = instance.passive_element_segment(&mut storage, elem_index); 857 858 let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?; 859 let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?; 860 861 let shared_ty = instance.engine_type_index(array_type_index); 862 let array_ty = ArrayType::from_shared_type_index(store.engine(), shared_ty); 863 let pre = ArrayRefPre::_new(store, array_ty); 864 865 let mut store = OpaqueRootScope::new(&mut **store); 866 // Turn the elements into `Val`s. 867 let mut vals = Vec::with_capacity(usize::try_from(elements.len()).unwrap()); 868 match elements { 869 TableSegmentElements::Functions(fs) => { 870 vals.extend( 871 fs.get(src..) 872 .and_then(|s| s.get(..len)) 873 .ok_or_else(|| Trap::TableOutOfBounds)? 874 .iter() 875 .map(|f| { 876 let raw_func_ref = instance.as_mut().get_func_ref(*f); 877 let func = 878 unsafe { raw_func_ref.map(|p| Func::from_vm_func_ref(store.id(), p)) }; 879 Val::FuncRef(func) 880 }), 881 ); 882 } 883 TableSegmentElements::Expressions(xs) => { 884 let xs = xs 885 .get(src..) 886 .and_then(|s| s.get(..len)) 887 .ok_or_else(|| Trap::TableOutOfBounds)?; 888 889 let mut const_context = ConstEvalContext::new(instance.id()); 890 let mut const_evaluator = ConstExprEvaluator::default(); 891 892 vals.extend(xs.iter().map(|x| unsafe { 893 *const_evaluator 894 .eval(&mut store, &mut const_context, x) 895 .expect("const expr should be valid") 896 })); 897 } 898 } 899 900 let array = unsafe { ArrayRef::new_fixed_maybe_async(&mut store, &pre, &vals)? }; 901 902 let mut store = AutoAssertNoGc::new(&mut store); 903 let gc_ref = array.try_clone_gc_ref(&mut store)?; 904 let raw = store.unwrap_gc_store_mut().expose_gc_ref_to_wasm(gc_ref); 905 Ok(raw) 906 } 907 908 #[cfg(feature = "gc")] 909 unsafe fn array_init_elem( 910 store: &mut dyn VMStore, 911 mut instance: Pin<&mut Instance>, 912 array_type_index: u32, 913 array: u32, 914 dst: u32, 915 elem_index: u32, 916 src: u32, 917 len: u32, 918 ) -> Result<()> { 919 use crate::{ 920 ArrayRef, Func, OpaqueRootScope, Val, 921 store::AutoAssertNoGc, 922 vm::const_expr::{ConstEvalContext, ConstExprEvaluator}, 923 }; 924 use wasmtime_environ::{ModuleInternedTypeIndex, TableSegmentElements}; 925 926 let mut store = OpaqueRootScope::new(store.store_opaque_mut()); 927 928 // Convert the indices into their typed forms. 929 let _array_type_index = ModuleInternedTypeIndex::from_u32(array_type_index); 930 let elem_index = ElemIndex::from_u32(elem_index); 931 932 log::trace!( 933 "array.init_elem(array={array:#x}, dst={dst}, elem_index={elem_index:?}, src={src}, len={len})", 934 ); 935 936 // Convert the raw GC ref into a `Rooted<ArrayRef>`. 937 let array = VMGcRef::from_raw_u32(array).ok_or_else(|| Trap::NullReference)?; 938 let array = store.unwrap_gc_store_mut().clone_gc_ref(&array); 939 let array = { 940 let mut no_gc = AutoAssertNoGc::new(&mut store); 941 ArrayRef::from_cloned_gc_ref(&mut no_gc, array) 942 }; 943 944 // Bounds check the destination within the array. 945 let array_len = array._len(&store)?; 946 log::trace!("array_len = {array_len}"); 947 if dst.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > array_len { 948 return Err(Trap::ArrayOutOfBounds.into()); 949 } 950 951 // Get the passive element segment. 952 let mut storage = None; 953 let elements = instance.passive_element_segment(&mut storage, elem_index); 954 955 // Convert array offsets into `usize`s. 956 let src = usize::try_from(src).map_err(|_| Trap::TableOutOfBounds)?; 957 let len = usize::try_from(len).map_err(|_| Trap::TableOutOfBounds)?; 958 959 // Turn the elements into `Val`s. 960 let vals = match elements { 961 TableSegmentElements::Functions(fs) => fs 962 .get(src..) 963 .and_then(|s| s.get(..len)) 964 .ok_or_else(|| Trap::TableOutOfBounds)? 965 .iter() 966 .map(|f| { 967 let raw_func_ref = instance.as_mut().get_func_ref(*f); 968 let func = unsafe { raw_func_ref.map(|p| Func::from_vm_func_ref(store.id(), p)) }; 969 Val::FuncRef(func) 970 }) 971 .collect::<Vec<_>>(), 972 TableSegmentElements::Expressions(xs) => { 973 let mut const_context = ConstEvalContext::new(instance.id()); 974 let mut const_evaluator = ConstExprEvaluator::default(); 975 976 xs.get(src..) 977 .and_then(|s| s.get(..len)) 978 .ok_or_else(|| Trap::TableOutOfBounds)? 979 .iter() 980 .map(|x| unsafe { 981 *const_evaluator 982 .eval(&mut store, &mut const_context, x) 983 .expect("const expr should be valid") 984 }) 985 .collect::<Vec<_>>() 986 } 987 }; 988 989 // Copy the values into the array. 990 for (i, val) in vals.into_iter().enumerate() { 991 let i = u32::try_from(i).unwrap(); 992 let j = dst.checked_add(i).unwrap(); 993 array._set(&mut store, j, val)?; 994 } 995 996 Ok(()) 997 } 998 999 // TODO: Specialize this libcall for only non-GC array elements, so we never 1000 // have to do GC barriers and their associated indirect calls through the `dyn 1001 // GcHeap`. Instead, implement those copies inline in Wasm code. Then, use bulk 1002 // `memcpy`-style APIs to do the actual copies here. 1003 #[cfg(feature = "gc")] 1004 unsafe fn array_copy( 1005 store: &mut dyn VMStore, 1006 _instance: Pin<&mut Instance>, 1007 dst_array: u32, 1008 dst: u32, 1009 src_array: u32, 1010 src: u32, 1011 len: u32, 1012 ) -> Result<()> { 1013 use crate::{ArrayRef, OpaqueRootScope, store::AutoAssertNoGc}; 1014 1015 log::trace!( 1016 "array.copy(dst_array={dst_array:#x}, dst_index={dst}, src_array={src_array:#x}, src_index={src}, len={len})", 1017 ); 1018 1019 let mut store = OpaqueRootScope::new(store.store_opaque_mut()); 1020 let mut store = AutoAssertNoGc::new(&mut store); 1021 1022 // Convert the raw GC refs into `Rooted<ArrayRef>`s. 1023 let dst_array = VMGcRef::from_raw_u32(dst_array).ok_or_else(|| Trap::NullReference)?; 1024 let dst_array = store.unwrap_gc_store_mut().clone_gc_ref(&dst_array); 1025 let dst_array = ArrayRef::from_cloned_gc_ref(&mut store, dst_array); 1026 let src_array = VMGcRef::from_raw_u32(src_array).ok_or_else(|| Trap::NullReference)?; 1027 let src_array = store.unwrap_gc_store_mut().clone_gc_ref(&src_array); 1028 let src_array = ArrayRef::from_cloned_gc_ref(&mut store, src_array); 1029 1030 // Bounds check the destination array's elements. 1031 let dst_array_len = dst_array._len(&store)?; 1032 if dst.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > dst_array_len { 1033 return Err(Trap::ArrayOutOfBounds.into()); 1034 } 1035 1036 // Bounds check the source array's elements. 1037 let src_array_len = src_array._len(&store)?; 1038 if src.checked_add(len).ok_or_else(|| Trap::ArrayOutOfBounds)? > src_array_len { 1039 return Err(Trap::ArrayOutOfBounds.into()); 1040 } 1041 1042 let mut store = AutoAssertNoGc::new(&mut store); 1043 // If `src_array` and `dst_array` are the same array, then we are 1044 // potentially doing an overlapping copy, so make sure to copy elements in 1045 // the order that doesn't clobber the source elements before they are 1046 // copied. If they are different arrays, the order doesn't matter, but we 1047 // simply don't bother checking. 1048 if src > dst { 1049 for i in 0..len { 1050 let src_elem = src_array._get(&mut store, src + i)?; 1051 let dst_i = dst + i; 1052 dst_array._set(&mut store, dst_i, src_elem)?; 1053 } 1054 } else { 1055 for i in (0..len).rev() { 1056 let src_elem = src_array._get(&mut store, src + i)?; 1057 let dst_i = dst + i; 1058 dst_array._set(&mut store, dst_i, src_elem)?; 1059 } 1060 } 1061 Ok(()) 1062 } 1063 1064 #[cfg(feature = "gc")] 1065 unsafe fn is_subtype( 1066 store: &mut dyn VMStore, 1067 _instance: Pin<&mut Instance>, 1068 actual_engine_type: u32, 1069 expected_engine_type: u32, 1070 ) -> u32 { 1071 use wasmtime_environ::VMSharedTypeIndex; 1072 1073 let actual = VMSharedTypeIndex::from_u32(actual_engine_type); 1074 let expected = VMSharedTypeIndex::from_u32(expected_engine_type); 1075 1076 let is_subtype: bool = store.engine().signatures().is_subtype(actual, expected); 1077 1078 log::trace!("is_subtype(actual={actual:?}, expected={expected:?}) -> {is_subtype}",); 1079 is_subtype as u32 1080 } 1081 1082 // Implementation of `memory.atomic.notify` for locally defined memories. 1083 #[cfg(feature = "threads")] 1084 fn memory_atomic_notify( 1085 _store: &mut dyn VMStore, 1086 instance: Pin<&mut Instance>, 1087 memory_index: u32, 1088 addr_index: u64, 1089 count: u32, 1090 ) -> Result<u32, Trap> { 1091 let memory = DefinedMemoryIndex::from_u32(memory_index); 1092 instance 1093 .get_defined_memory_mut(memory) 1094 .atomic_notify(addr_index, count) 1095 } 1096 1097 // Implementation of `memory.atomic.wait32` for locally defined memories. 1098 #[cfg(feature = "threads")] 1099 fn memory_atomic_wait32( 1100 _store: &mut dyn VMStore, 1101 instance: Pin<&mut Instance>, 1102 memory_index: u32, 1103 addr_index: u64, 1104 expected: u32, 1105 timeout: u64, 1106 ) -> Result<u32, Trap> { 1107 let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout)); 1108 let memory = DefinedMemoryIndex::from_u32(memory_index); 1109 Ok(instance 1110 .get_defined_memory_mut(memory) 1111 .atomic_wait32(addr_index, expected, timeout)? as u32) 1112 } 1113 1114 // Implementation of `memory.atomic.wait64` for locally defined memories. 1115 #[cfg(feature = "threads")] 1116 fn memory_atomic_wait64( 1117 _store: &mut dyn VMStore, 1118 instance: Pin<&mut Instance>, 1119 memory_index: u32, 1120 addr_index: u64, 1121 expected: u64, 1122 timeout: u64, 1123 ) -> Result<u32, Trap> { 1124 let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout)); 1125 let memory = DefinedMemoryIndex::from_u32(memory_index); 1126 Ok(instance 1127 .get_defined_memory_mut(memory) 1128 .atomic_wait64(addr_index, expected, timeout)? as u32) 1129 } 1130 1131 // Hook for when an instance runs out of fuel. 1132 fn out_of_gas(store: &mut dyn VMStore, _instance: Pin<&mut Instance>) -> Result<()> { 1133 store.out_of_gas() 1134 } 1135 1136 // Hook for when an instance observes that the epoch has changed. 1137 #[cfg(target_has_atomic = "64")] 1138 fn new_epoch(store: &mut dyn VMStore, _instance: Pin<&mut Instance>) -> Result<NextEpoch> { 1139 store.new_epoch().map(NextEpoch) 1140 } 1141 1142 struct NextEpoch(u64); 1143 1144 unsafe impl HostResultHasUnwindSentinel for NextEpoch { 1145 type Abi = u64; 1146 const SENTINEL: u64 = u64::MAX; 1147 fn into_abi(self) -> u64 { 1148 self.0 1149 } 1150 } 1151 1152 // Hook for validating malloc using wmemcheck_state. 1153 #[cfg(feature = "wmemcheck")] 1154 unsafe fn check_malloc( 1155 _store: &mut dyn VMStore, 1156 instance: Pin<&mut Instance>, 1157 addr: u32, 1158 len: u32, 1159 ) -> Result<()> { 1160 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() { 1161 let result = wmemcheck_state.malloc(addr as usize, len as usize); 1162 wmemcheck_state.memcheck_on(); 1163 match result { 1164 Ok(()) => {} 1165 Err(DoubleMalloc { addr, len }) => { 1166 bail!("Double malloc at addr {:#x} of size {}", addr, len) 1167 } 1168 Err(OutOfBounds { addr, len }) => { 1169 bail!("Malloc out of bounds at addr {:#x} of size {}", addr, len); 1170 } 1171 _ => { 1172 panic!("unreachable") 1173 } 1174 } 1175 } 1176 Ok(()) 1177 } 1178 1179 // Hook for validating free using wmemcheck_state. 1180 #[cfg(feature = "wmemcheck")] 1181 unsafe fn check_free( 1182 _store: &mut dyn VMStore, 1183 instance: Pin<&mut Instance>, 1184 addr: u32, 1185 ) -> Result<()> { 1186 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() { 1187 let result = wmemcheck_state.free(addr as usize); 1188 wmemcheck_state.memcheck_on(); 1189 match result { 1190 Ok(()) => {} 1191 Err(InvalidFree { addr }) => { 1192 bail!("Invalid free at addr {:#x}", addr) 1193 } 1194 _ => { 1195 panic!("unreachable") 1196 } 1197 } 1198 } 1199 Ok(()) 1200 } 1201 1202 // Hook for validating load using wmemcheck_state. 1203 #[cfg(feature = "wmemcheck")] 1204 fn check_load( 1205 _store: &mut dyn VMStore, 1206 instance: Pin<&mut Instance>, 1207 num_bytes: u32, 1208 addr: u32, 1209 offset: u32, 1210 ) -> Result<()> { 1211 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() { 1212 let result = wmemcheck_state.read(addr as usize + offset as usize, num_bytes as usize); 1213 match result { 1214 Ok(()) => {} 1215 Err(InvalidRead { addr, len }) => { 1216 bail!("Invalid load at addr {:#x} of size {}", addr, len); 1217 } 1218 Err(OutOfBounds { addr, len }) => { 1219 bail!("Load out of bounds at addr {:#x} of size {}", addr, len); 1220 } 1221 _ => { 1222 panic!("unreachable") 1223 } 1224 } 1225 } 1226 Ok(()) 1227 } 1228 1229 // Hook for validating store using wmemcheck_state. 1230 #[cfg(feature = "wmemcheck")] 1231 fn check_store( 1232 _store: &mut dyn VMStore, 1233 instance: Pin<&mut Instance>, 1234 num_bytes: u32, 1235 addr: u32, 1236 offset: u32, 1237 ) -> Result<()> { 1238 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() { 1239 let result = wmemcheck_state.write(addr as usize + offset as usize, num_bytes as usize); 1240 match result { 1241 Ok(()) => {} 1242 Err(InvalidWrite { addr, len }) => { 1243 bail!("Invalid store at addr {:#x} of size {}", addr, len) 1244 } 1245 Err(OutOfBounds { addr, len }) => { 1246 bail!("Store out of bounds at addr {:#x} of size {}", addr, len) 1247 } 1248 _ => { 1249 panic!("unreachable") 1250 } 1251 } 1252 } 1253 Ok(()) 1254 } 1255 1256 // Hook for turning wmemcheck load/store validation off when entering a malloc function. 1257 #[cfg(feature = "wmemcheck")] 1258 fn malloc_start(_store: &mut dyn VMStore, instance: Pin<&mut Instance>) { 1259 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() { 1260 wmemcheck_state.memcheck_off(); 1261 } 1262 } 1263 1264 // Hook for turning wmemcheck load/store validation off when entering a free function. 1265 #[cfg(feature = "wmemcheck")] 1266 fn free_start(_store: &mut dyn VMStore, instance: Pin<&mut Instance>) { 1267 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() { 1268 wmemcheck_state.memcheck_off(); 1269 } 1270 } 1271 1272 // Hook for tracking wasm stack updates using wmemcheck_state. 1273 #[cfg(feature = "wmemcheck")] 1274 fn update_stack_pointer(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, _value: u32) { 1275 // TODO: stack-tracing has yet to be finalized. All memory below 1276 // the address of the top of the stack is marked as valid for 1277 // loads and stores. 1278 // if let Some(wmemcheck_state) = &mut instance.wmemcheck_state { 1279 // instance.wmemcheck_state.update_stack_pointer(value as usize); 1280 // } 1281 } 1282 1283 // Hook updating wmemcheck_state memory state vector every time memory.grow is called. 1284 #[cfg(feature = "wmemcheck")] 1285 fn update_mem_size(_store: &mut dyn VMStore, instance: Pin<&mut Instance>, num_pages: u32) { 1286 if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() { 1287 const KIB: usize = 1024; 1288 let num_bytes = num_pages as usize * 64 * KIB; 1289 wmemcheck_state.update_mem_size(num_bytes); 1290 } 1291 } 1292 1293 fn floor_f32(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, val: f32) -> f32 { 1294 wasmtime_math::WasmFloat::wasm_floor(val) 1295 } 1296 1297 fn floor_f64(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, val: f64) -> f64 { 1298 wasmtime_math::WasmFloat::wasm_floor(val) 1299 } 1300 1301 fn ceil_f32(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, val: f32) -> f32 { 1302 wasmtime_math::WasmFloat::wasm_ceil(val) 1303 } 1304 1305 fn ceil_f64(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, val: f64) -> f64 { 1306 wasmtime_math::WasmFloat::wasm_ceil(val) 1307 } 1308 1309 fn trunc_f32(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, val: f32) -> f32 { 1310 wasmtime_math::WasmFloat::wasm_trunc(val) 1311 } 1312 1313 fn trunc_f64(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, val: f64) -> f64 { 1314 wasmtime_math::WasmFloat::wasm_trunc(val) 1315 } 1316 1317 fn nearest_f32(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, val: f32) -> f32 { 1318 wasmtime_math::WasmFloat::wasm_nearest(val) 1319 } 1320 1321 fn nearest_f64(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>, val: f64) -> f64 { 1322 wasmtime_math::WasmFloat::wasm_nearest(val) 1323 } 1324 1325 // This intrinsic is only used on x86_64 platforms as an implementation of 1326 // the `i8x16.swizzle` instruction when `pshufb` in SSSE3 is not available. 1327 #[cfg(all(target_arch = "x86_64", target_feature = "sse"))] 1328 fn i8x16_swizzle( 1329 _store: &mut dyn VMStore, 1330 _instance: Pin<&mut Instance>, 1331 a: i8x16, 1332 b: i8x16, 1333 ) -> i8x16 { 1334 union U { 1335 reg: i8x16, 1336 mem: [u8; 16], 1337 } 1338 1339 unsafe { 1340 let a = U { reg: a }.mem; 1341 let b = U { reg: b }.mem; 1342 1343 // Use the `swizzle` semantics of returning 0 on any out-of-bounds 1344 // index, rather than the x86 pshufb semantics, since Wasmtime uses 1345 // this to implement `i8x16.swizzle`. 1346 let select = |arr: &[u8; 16], byte: u8| { 1347 if byte >= 16 { 0x00 } else { arr[byte as usize] } 1348 }; 1349 1350 U { 1351 mem: [ 1352 select(&a, b[0]), 1353 select(&a, b[1]), 1354 select(&a, b[2]), 1355 select(&a, b[3]), 1356 select(&a, b[4]), 1357 select(&a, b[5]), 1358 select(&a, b[6]), 1359 select(&a, b[7]), 1360 select(&a, b[8]), 1361 select(&a, b[9]), 1362 select(&a, b[10]), 1363 select(&a, b[11]), 1364 select(&a, b[12]), 1365 select(&a, b[13]), 1366 select(&a, b[14]), 1367 select(&a, b[15]), 1368 ], 1369 } 1370 .reg 1371 } 1372 } 1373 1374 #[cfg(not(all(target_arch = "x86_64", target_feature = "sse")))] 1375 fn i8x16_swizzle( 1376 _store: &mut dyn VMStore, 1377 _instance: Pin<&mut Instance>, 1378 _a: i8x16, 1379 _b: i8x16, 1380 ) -> i8x16 { 1381 unreachable!() 1382 } 1383 1384 // This intrinsic is only used on x86_64 platforms as an implementation of 1385 // the `i8x16.shuffle` instruction when `pshufb` in SSSE3 is not available. 1386 #[cfg(all(target_arch = "x86_64", target_feature = "sse"))] 1387 fn i8x16_shuffle( 1388 _store: &mut dyn VMStore, 1389 _instance: Pin<&mut Instance>, 1390 a: i8x16, 1391 b: i8x16, 1392 c: i8x16, 1393 ) -> i8x16 { 1394 union U { 1395 reg: i8x16, 1396 mem: [u8; 16], 1397 } 1398 1399 unsafe { 1400 let ab = [U { reg: a }.mem, U { reg: b }.mem]; 1401 let c = U { reg: c }.mem; 1402 1403 // Use the `shuffle` semantics of returning 0 on any out-of-bounds 1404 // index, rather than the x86 pshufb semantics, since Wasmtime uses 1405 // this to implement `i8x16.shuffle`. 1406 let select = |arr: &[[u8; 16]; 2], byte: u8| { 1407 if byte >= 32 { 1408 0x00 1409 } else if byte >= 16 { 1410 arr[1][byte as usize - 16] 1411 } else { 1412 arr[0][byte as usize] 1413 } 1414 }; 1415 1416 U { 1417 mem: [ 1418 select(&ab, c[0]), 1419 select(&ab, c[1]), 1420 select(&ab, c[2]), 1421 select(&ab, c[3]), 1422 select(&ab, c[4]), 1423 select(&ab, c[5]), 1424 select(&ab, c[6]), 1425 select(&ab, c[7]), 1426 select(&ab, c[8]), 1427 select(&ab, c[9]), 1428 select(&ab, c[10]), 1429 select(&ab, c[11]), 1430 select(&ab, c[12]), 1431 select(&ab, c[13]), 1432 select(&ab, c[14]), 1433 select(&ab, c[15]), 1434 ], 1435 } 1436 .reg 1437 } 1438 } 1439 1440 #[cfg(not(all(target_arch = "x86_64", target_feature = "sse")))] 1441 fn i8x16_shuffle( 1442 _store: &mut dyn VMStore, 1443 _instance: Pin<&mut Instance>, 1444 _a: i8x16, 1445 _b: i8x16, 1446 _c: i8x16, 1447 ) -> i8x16 { 1448 unreachable!() 1449 } 1450 1451 fn fma_f32x4( 1452 _store: &mut dyn VMStore, 1453 _instance: Pin<&mut Instance>, 1454 x: f32x4, 1455 y: f32x4, 1456 z: f32x4, 1457 ) -> f32x4 { 1458 union U { 1459 reg: f32x4, 1460 mem: [f32; 4], 1461 } 1462 1463 unsafe { 1464 let x = U { reg: x }.mem; 1465 let y = U { reg: y }.mem; 1466 let z = U { reg: z }.mem; 1467 1468 U { 1469 mem: [ 1470 wasmtime_math::WasmFloat::wasm_mul_add(x[0], y[0], z[0]), 1471 wasmtime_math::WasmFloat::wasm_mul_add(x[1], y[1], z[1]), 1472 wasmtime_math::WasmFloat::wasm_mul_add(x[2], y[2], z[2]), 1473 wasmtime_math::WasmFloat::wasm_mul_add(x[3], y[3], z[3]), 1474 ], 1475 } 1476 .reg 1477 } 1478 } 1479 1480 fn fma_f64x2( 1481 _store: &mut dyn VMStore, 1482 _instance: Pin<&mut Instance>, 1483 x: f64x2, 1484 y: f64x2, 1485 z: f64x2, 1486 ) -> f64x2 { 1487 union U { 1488 reg: f64x2, 1489 mem: [f64; 2], 1490 } 1491 1492 unsafe { 1493 let x = U { reg: x }.mem; 1494 let y = U { reg: y }.mem; 1495 let z = U { reg: z }.mem; 1496 1497 U { 1498 mem: [ 1499 wasmtime_math::WasmFloat::wasm_mul_add(x[0], y[0], z[0]), 1500 wasmtime_math::WasmFloat::wasm_mul_add(x[1], y[1], z[1]), 1501 ], 1502 } 1503 .reg 1504 } 1505 } 1506 1507 /// This intrinsic is just used to record trap information. 1508 /// 1509 /// The `Infallible` "ok" type here means that this never returns success, it 1510 /// only ever returns an error, and this hooks into the machinery to handle 1511 /// `Result` values to record such trap information. 1512 fn trap( 1513 _store: &mut dyn VMStore, 1514 _instance: Pin<&mut Instance>, 1515 code: u8, 1516 ) -> Result<Infallible, TrapReason> { 1517 Err(TrapReason::Wasm( 1518 wasmtime_environ::Trap::from_u8(code).unwrap(), 1519 )) 1520 } 1521 1522 fn raise(_store: &mut dyn VMStore, _instance: Pin<&mut Instance>) { 1523 // SAFETY: this is only called from compiled wasm so we know that wasm has 1524 // already been entered. It's a dynamic safety precondition that the trap 1525 // information has already been arranged to be present. 1526 #[cfg(has_host_compiler_backend)] 1527 unsafe { 1528 crate::runtime::vm::traphandlers::raise_preexisting_trap() 1529 } 1530 1531 // When Cranelift isn't in use then this is an unused libcall for Pulley, so 1532 // just insert a stub to catch bugs if it's accidentally called. 1533 #[cfg(not(has_host_compiler_backend))] 1534 unreachable!() 1535 } 1536 1537 // Builtins for continuations. These are thin wrappers around the 1538 // respective definitions in stack_switching.rs. 1539 #[cfg(feature = "stack-switching")] 1540 fn cont_new( 1541 store: &mut dyn VMStore, 1542 instance: Pin<&mut Instance>, 1543 func: *mut u8, 1544 param_count: u32, 1545 result_count: u32, 1546 ) -> Result<Option<AllocationSize>, TrapReason> { 1547 let ans = 1548 crate::vm::stack_switching::cont_new(store, instance, func, param_count, result_count)?; 1549 Ok(Some(AllocationSize(ans.cast::<u8>() as usize))) 1550 } 1551