Skip to main content

wasmtime/runtime/vm/
libcalls.rs

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
57use crate::bail_bug;
58use crate::prelude::*;
59use crate::runtime::store::{Asyncness, InstanceId, StoreOpaque};
60#[cfg(feature = "gc")]
61use crate::runtime::vm::VMGcRef;
62use crate::runtime::vm::{self, HostResultHasUnwindSentinel, VMStore, f32x4, f64x2, i8x16};
63use core::convert::Infallible;
64use core::ptr::NonNull;
65#[cfg(feature = "threads")]
66use core::time::Duration;
67use wasmtime_core::math::WasmFloat;
68use wasmtime_environ::{
69    CompiledTrap, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, PassiveElemIndex, TableIndex,
70    Trap,
71};
72#[cfg(feature = "wmemcheck")]
73use wasmtime_wmemcheck::AccessError::{
74    DoubleMalloc, InvalidFree, InvalidRead, InvalidWrite, OutOfBounds,
75};
76
77/// Raw functions which are actually called from compiled code.
78///
79/// Invocation of a builtin currently looks like:
80///
81/// * A wasm function calls a cranelift-compiled trampoline that's generated
82///   once-per-builtin.
83/// * The cranelift-compiled trampoline performs any necessary actions to exit
84///   wasm, such as dealing with fp/pc/etc.
85/// * The cranelift-compiled trampoline loads a function pointer from an array
86///   stored in `VMContext` That function pointer is defined in this module.
87/// * This module runs, handling things like `catch_unwind` and `Result` and
88///   such.
89/// * This module delegates to the outer module (this file) which has the actual
90///   implementation.
91///
92/// For more information on converting from host-defined values to Cranelift ABI
93/// values see the `catch_unwind_and_record_trap` function.
94pub mod raw {
95    use crate::runtime::vm::{Instance, VMContext, f32x4, f64x2, i8x16};
96    use core::ptr::NonNull;
97
98    macro_rules! libcall {
99        (
100            $(
101                $( #[cfg($attr:meta)] )?
102                $name:ident( vmctx: vmctx $(, $pname:ident: $param:ident )* ) $(-> $result:ident)?;
103            )*
104        ) => {
105            $(
106                // This is the direct entrypoint from the compiled module which
107                // still has the raw signature.
108                //
109                // This will delegate to the outer module to the actual
110                // implementation and automatically perform `catch_unwind` along
111                // with conversion of the return value in the face of traps.
112                #[allow(improper_ctypes_definitions, reason = "__m128i known not FFI-safe")]
113                #[allow(unused_variables, reason = "macro-generated")]
114                #[allow(unreachable_code, reason = "some types uninhabited on some platforms")]
115                pub unsafe extern "C" fn $name(
116                    vmctx: NonNull<VMContext>,
117                    $( $pname : libcall!(@ty $param), )*
118                ) $(-> libcall!(@ty $result))? {
119                    $(#[cfg($attr)])?
120                    unsafe {
121                        Instance::enter_host_from_wasm(vmctx, |store, instance| {
122                            super::$name(store, instance, $($pname),*)
123                        })
124                    }
125                    $(
126                        #[cfg(not($attr))]
127                        {
128                            let _ = vmctx;
129                            unreachable!();
130                        }
131                    )?
132                }
133
134                // This works around a `rustc` bug where compiling with LTO
135                // will sometimes strip out some of these symbols resulting
136                // in a linking failure.
137                #[allow(improper_ctypes_definitions, reason = "__m128i known not FFI-safe")]
138                const _: () = {
139                    #[used]
140                    static I_AM_USED: unsafe extern "C" fn(
141                        NonNull<VMContext>,
142                        $( $pname : libcall!(@ty $param), )*
143                    ) $( -> libcall!(@ty $result))? = $name;
144                };
145            )*
146        };
147
148        (@ty u32) => (u32);
149        (@ty u64) => (u64);
150        (@ty f32) => (f32);
151        (@ty f64) => (f64);
152        (@ty u8) => (u8);
153        (@ty i8x16) => (i8x16);
154        (@ty f32x4) => (f32x4);
155        (@ty f64x2) => (f64x2);
156        (@ty bool) => (bool);
157        (@ty pointer) => (*mut u8);
158        (@ty size) => (usize);
159    }
160
161    wasmtime_environ::foreach_builtin_function!(libcall);
162}
163
164/// Uses the `$store` provided to invoke the async closure `$f` and block on the
165/// result.
166///
167/// This will internally multiplex on `$store.with_blocking(...)` vs simply
168/// asserting the closure is ready depending on whether a store's
169/// `can_block` flag is set or not.
170///
171/// FIXME: ideally this would be a function, not a macro. If this is a function
172/// though it would require placing a bound on the async closure $f where the
173/// returned future is itself `Send`. That's not possible in Rust right now,
174/// unfortunately.
175///
176/// As a workaround this takes advantage of the fact that we can assume that the
177/// compiler can infer that the future returned by `$f` is indeed `Send` so long
178/// as we don't try to name the type or place it behind a generic. In the future
179/// when we can bound the return future of async functions with `Send` this
180/// macro should be replaced with an equivalent function.
181macro_rules! block_on {
182    ($store:expr, $f:expr) => {{
183        let store: &mut StoreOpaque = $store;
184        let closure = assert_async_fn_closure($f);
185
186        if store.can_block() {
187            // If the store can block then that means it's on a fiber. We can
188            // forward to `block_on` and everything should be fine and dandy.
189            #[cfg(feature = "async")]
190            {
191                store.with_blocking(|store, cx| cx.block_on(closure(store, Asyncness::Yes)))
192            }
193            #[cfg(not(feature = "async"))]
194            {
195                unreachable!()
196            }
197        } else {
198            // If the store cannot block it's not on a fiber. That means that we get
199            // at most one poll of `closure(store)` here. In the typical case
200            // what this means is that nothing async is configured in the store
201            // and one poll should be all we need. There are niche cases where
202            // one poll is not sufficient though, for example:
203            //
204            // * Store is created.
205            // * Wasm is called.
206            // * Wasm calls host.
207            // * Host configures an async resource limiter, returns back to
208            //   wasm.
209            // * Wasm grows memory.
210            // * Limiter wants to block asynchronously.
211            //
212            // Technically there's nothing wrong with this, but it means that
213            // we're in wasm and one poll is not enough here. Given the niche
214            // nature of this scenario and how it's not really expected to work
215            // this translates failures in `closure` to a trap. This trap is
216            // only expected to show up in niche-ish scenarios, not for actual
217            // blocking work, as that would otherwise be too surprising.
218            vm::one_poll(closure(store, Asyncness::No)).ok_or_else(|| {
219                crate::format_err!(
220                    "
221
222A synchronously called wasm function invoked an async-defined libcall which
223failed to complete synchronously and is thus raising a trap. It's expected
224that this indicates that the store was configured to do async things after the
225original synchronous entrypoint to wasm was called. That's generally not
226supported in Wasmtime and async entrypoint should be used instead. If you're
227seeing this message in error please file an issue on Wasmtime.
228
229"
230                )
231            })
232        }
233    }};
234}
235
236fn assert_async_fn_closure<F, R>(f: F) -> F
237where
238    F: AsyncFnOnce(&mut StoreOpaque, Asyncness) -> R,
239{
240    f
241}
242
243fn memory_grow(
244    store: &mut dyn VMStore,
245    instance: InstanceId,
246    delta: u64,
247    memory_index: u32,
248) -> Result<Option<AllocationSize>> {
249    let memory_index = DefinedMemoryIndex::from_u32(memory_index);
250    let (mut limiter, store) = store.resource_limiter_and_store_opaque();
251    let limiter = limiter.as_mut();
252    block_on!(store, async |store, _| {
253        let instance = store.instance_mut(instance);
254        let module = instance.env_module();
255        let page_size_log2 = module.memories[module.memory_index(memory_index)].page_size_log2;
256
257        let result = instance
258            .memory_grow(limiter, memory_index, delta)
259            .await?
260            .map(|size_in_bytes| AllocationSize(size_in_bytes >> page_size_log2));
261
262        Ok(result)
263    })?
264}
265
266/// A helper structure to represent the return value of a memory or table growth
267/// call.
268///
269/// This represents a byte or element-based count of the size of an item on the
270/// host. For example a memory is how many bytes large the memory is, or a table
271/// is how many elements large it is. It's assumed that the value here is never
272/// -1 or -2 as that would mean the entire host address space is allocated which
273/// is not possible.
274struct AllocationSize(usize);
275
276/// Special implementation for growth-related libcalls.
277///
278/// Here the optional return value means:
279///
280/// * `Some(val)` - the growth succeeded and the previous size of the item was
281///   `val`.
282/// * `None` - the growth failed.
283///
284/// The failure case returns -1 (or `usize::MAX` as an unsigned integer) and the
285/// successful case returns the `val` itself. Note that -2 (`usize::MAX - 1`
286/// when unsigned) is unwind as a sentinel to indicate an unwind as no valid
287/// allocation can be that large.
288unsafe impl HostResultHasUnwindSentinel for Option<AllocationSize> {
289    type Abi = *mut u8;
290    const SENTINEL: *mut u8 = (usize::MAX - 1) as *mut u8;
291
292    fn into_abi(self) -> *mut u8 {
293        match self {
294            Some(size) => {
295                debug_assert!(size.0 < (usize::MAX - 1));
296                size.0 as *mut u8
297            }
298            None => usize::MAX as *mut u8,
299        }
300    }
301}
302
303/// Implementation of `table.grow`.
304unsafe fn table_grow(
305    store: &mut dyn VMStore,
306    instance: InstanceId,
307    defined_table_index: u32,
308    delta: u64,
309) -> Result<Option<AllocationSize>> {
310    let defined_table_index = DefinedTableIndex::from_u32(defined_table_index);
311    let (mut limiter, store) = store.resource_limiter_and_store_opaque();
312    let limiter = limiter.as_mut();
313    block_on!(store, async |store, _| unsafe {
314        let result = store
315            .instance_mut(instance)
316            .defined_table_grow(defined_table_index, limiter, delta)
317            .await?
318            .map(AllocationSize);
319        Ok(result)
320    })?
321}
322
323fn passive_elem_segment_len(
324    store: &mut dyn VMStore,
325    instance: InstanceId,
326    elem_index: u32,
327) -> usize {
328    let elem_index = PassiveElemIndex::from_u32(elem_index);
329    store
330        .instance_mut(instance)
331        .passive_element_segment(elem_index)
332        .len()
333}
334
335fn passive_elem_segment_base(
336    store: &mut dyn VMStore,
337    instance: InstanceId,
338    elem_index: u32,
339) -> *mut u8 {
340    let elem_index = PassiveElemIndex::from_u32(elem_index);
341    store
342        .instance_mut(instance)
343        .passive_element_segment(elem_index)
344        .as_mut_ptr()
345        .cast()
346}
347
348// Implementation of `elem.drop`.
349fn passive_elem_segment_drop(
350    store: &mut dyn VMStore,
351    instance: InstanceId,
352    elem_index: u32,
353) -> Result<()> {
354    let elem_index = PassiveElemIndex::from_u32(elem_index);
355    let (gc_store, instance) = store.optional_gc_store_and_instance_mut(instance);
356    instance.passive_elem_drop(gc_store, elem_index)?;
357    Ok(())
358}
359
360// Implementation of `memory.copy`.
361unsafe fn memory_copy(
362    _store: &mut dyn VMStore,
363    _instance: InstanceId,
364    dst: *mut u8,
365    src: *mut u8,
366    len: usize,
367) {
368    let src = src.cast_const();
369    // FIXME(#4203): this is known to not be sound in the presence of shared
370    // memories.
371    unsafe { src.copy_to(dst, len) }
372}
373
374unsafe fn memory_fill(
375    _store: &mut dyn VMStore,
376    _instance: InstanceId,
377    dst: *mut u8,
378    val: u32,
379    len: usize,
380) {
381    // FIXME(#4203): this is known to not be sound in the presence of shared
382    // memories.
383    unsafe {
384        #[expect(
385            clippy::cast_possible_truncation,
386            reason = "the libcall intentionally takes the raw 32-bit value, \
387                      and semantically that's intentionally truncated"
388        )]
389        dst.write_bytes(val as u8, len);
390    }
391}
392
393// Implementation of `ref.func`.
394fn ref_func(store: &mut dyn VMStore, instance: InstanceId, func_index: u32) -> NonNull<u8> {
395    let (instance, registry) = store.instance_and_module_registry_mut(instance);
396    instance
397        .get_func_ref(registry, FuncIndex::from_u32(func_index))
398        .expect("ref_func: funcref should always be available for given func index")
399        .cast()
400}
401
402// Returns a table entry after lazily initializing it.
403fn table_get_lazy_init_func_ref(
404    store: &mut dyn VMStore,
405    instance: InstanceId,
406    table_index: u32,
407    index: u64,
408) -> *mut u8 {
409    let table_index = TableIndex::from_u32(table_index);
410    let (instance, registry) = store.instance_and_module_registry_mut(instance);
411    let table = instance.get_table_with_lazy_init(registry, table_index, core::iter::once(index));
412    let elem = table
413        .get_func(index)
414        .expect("table access already bounds-checked");
415
416    match elem {
417        Some(ptr) => ptr.as_ptr().cast(),
418        None => core::ptr::null_mut(),
419    }
420}
421
422/// Drop a GC reference.
423#[cfg(feature = "gc-drc")]
424fn drop_gc_ref(store: &mut dyn VMStore, _instance: InstanceId, gc_ref: u32) {
425    log::trace!("libcalls::drop_gc_ref({gc_ref:#x})");
426    let gc_ref = VMGcRef::from_raw_u32(gc_ref).expect("non-null VMGcRef");
427    store
428        .store_opaque_mut()
429        .unwrap_gc_store_mut()
430        .drop_gc_ref(gc_ref);
431}
432
433/// Force a DRC GC cycle.
434#[cfg(feature = "gc-drc")]
435fn force_gc(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()> {
436    let store = store.store_opaque_mut();
437    block_on!(store, async |store, asyncness| {
438        store.gc(None, None, None, asyncness).await?;
439        Ok::<(), Error>(())
440    })??;
441    Ok(())
442}
443
444/// Grow the GC heap.
445#[cfg(feature = "gc-null")]
446fn grow_gc_heap(store: &mut dyn VMStore, _instance: InstanceId, bytes_needed: u64) -> Result<()> {
447    let orig_len = u64::try_from(
448        store
449            .require_gc_store()?
450            .gc_heap
451            .vmmemory()
452            .current_length(),
453    )
454    .unwrap();
455
456    let (mut limiter, store) = store.resource_limiter_and_store_opaque();
457    block_on!(store, async |store, asyncness| {
458        // We error below if there's still not enough space; swallow
459        // any growth failures here.
460        let _ = store
461            .grow_gc_heap(limiter.as_mut(), bytes_needed, asyncness)
462            .await;
463    })?;
464
465    // JIT code relies on the memory having grown by `bytes_needed` bytes if
466    // this libcall returns successfully, so trap if we didn't grow that much.
467    let new_len = u64::try_from(
468        store
469            .require_gc_store()?
470            .gc_heap
471            .vmmemory()
472            .current_length(),
473    )
474    .unwrap();
475    if orig_len
476        .checked_add(bytes_needed)
477        .is_none_or(|expected_len| new_len < expected_len)
478    {
479        return Err(crate::Trap::AllocationTooLarge.into());
480    }
481
482    Ok(())
483}
484
485/// Allocate a raw, unininitialized GC object for Wasm code.
486///
487/// The Wasm code is responsible for initializing the object.
488#[cfg(any(feature = "gc-drc", feature = "gc-copying"))]
489fn gc_alloc_raw(
490    store: &mut dyn VMStore,
491    _instance: InstanceId,
492    kind_and_reserved: u32,
493    shared_type_index: u32,
494    size: u32,
495    align: u32,
496) -> Result<core::num::NonZeroU32> {
497    use crate::vm::VMGcHeader;
498    use core::alloc::Layout;
499    use wasmtime_environ::{VMGcKind, VMSharedTypeIndex};
500
501    let kind = VMGcKind::from_high_bits_of_u32(kind_and_reserved);
502    log::trace!("gc_alloc_raw(kind={kind:?}, size={size}, align={align})");
503
504    let shared_type_index = VMSharedTypeIndex::from_u32(shared_type_index);
505    let mut header = VMGcHeader::from_kind_and_index(kind, shared_type_index);
506    header.set_reserved_u26(kind_and_reserved & VMGcKind::UNUSED_MASK);
507
508    let size = usize::try_from(size).unwrap();
509    let align = usize::try_from(align).unwrap();
510    assert!(align.is_power_of_two());
511    let layout = Layout::from_size_align(size, align).map_err(|e| {
512        let err = Error::from(crate::Trap::AllocationTooLarge);
513        err.context(e)
514    })?;
515
516    // Fast path: when the GC store already exists, try to allocate directly to
517    // skip the async/fiber machinery.
518    let opaque = store.store_opaque_mut();
519    if let Some(gc_store) = opaque.try_gc_store_mut() {
520        if let Ok(gc_ref) = gc_store.alloc_raw(header, layout)? {
521            let raw = gc_store.expose_gc_ref_to_wasm(gc_ref)?;
522            return Ok(raw);
523        }
524    }
525
526    let (mut limiter, store) = store.resource_limiter_and_store_opaque();
527    block_on!(store, async |store, asyncness| {
528        let gc_ref = store
529            .retry_after_gc_async(limiter.as_mut(), (), asyncness, |store, ()| {
530                store
531                    .unwrap_gc_store_mut()
532                    .alloc_raw(header, layout)?
533                    .map_err(|bytes_needed| crate::GcHeapOutOfMemory::new((), bytes_needed).into())
534            })
535            .await?;
536
537        store.unwrap_gc_store_mut().expose_gc_ref_to_wasm(gc_ref)
538    })?
539}
540
541// Intern a `funcref` into the GC heap, returning its `FuncRefTableId`.
542//
543// This libcall may not GC.
544#[cfg(feature = "gc")]
545unsafe fn intern_func_ref_for_gc_heap(
546    store: &mut dyn VMStore,
547    _instance: InstanceId,
548    func_ref: *mut u8,
549) -> Result<u32> {
550    use crate::runtime::vm::vmcontext::VMFuncRef;
551    use crate::{store::AutoAssertNoGc, vm::SendSyncPtr};
552    use core::ptr::NonNull;
553
554    let mut store = AutoAssertNoGc::new(store.store_opaque_mut());
555
556    let func_ref = func_ref.cast::<VMFuncRef>();
557    let func_ref = NonNull::new(func_ref).map(SendSyncPtr::new);
558
559    let func_ref_id = unsafe {
560        store
561            .require_gc_store_mut()?
562            .func_ref_table
563            .intern(func_ref)
564    };
565    Ok(func_ref_id.into_raw())
566}
567
568// Get the raw `VMFuncRef` pointer associated with a `FuncRefTableId` from an
569// earlier `intern_func_ref_for_gc_heap` call.
570//
571// This libcall may not GC.
572#[cfg(feature = "gc")]
573fn get_interned_func_ref(
574    store: &mut dyn VMStore,
575    instance: InstanceId,
576    func_ref_id: u32,
577    module_interned_type_index: u32,
578) -> Result<*mut u8> {
579    use super::FuncRefTableId;
580    use crate::store::AutoAssertNoGc;
581    use wasmtime_environ::{ModuleInternedTypeIndex, packed_option::ReservedValue};
582
583    let store = AutoAssertNoGc::new(store.store_opaque_mut());
584
585    let func_ref_id = FuncRefTableId::from_raw(func_ref_id);
586    let module_interned_type_index = ModuleInternedTypeIndex::from_bits(module_interned_type_index);
587
588    let func_ref = if module_interned_type_index.is_reserved_value() {
589        store
590            .unwrap_gc_store()
591            .func_ref_table
592            .get_untyped(func_ref_id)?
593    } else {
594        let types = store.engine().signatures();
595        let engine_ty = store
596            .instance(instance)
597            .engine_type_index(module_interned_type_index);
598        store
599            .unwrap_gc_store()
600            .func_ref_table
601            .get_typed(types, func_ref_id, engine_ty)?
602    };
603
604    Ok(func_ref.map_or(core::ptr::null_mut(), |f| f.as_ptr().cast()))
605}
606
607#[cfg(feature = "gc")]
608fn is_subtype(
609    store: &mut dyn VMStore,
610    _instance: InstanceId,
611    actual_engine_type: u32,
612    expected_engine_type: u32,
613) -> u32 {
614    use wasmtime_environ::VMSharedTypeIndex;
615
616    let actual = VMSharedTypeIndex::from_u32(actual_engine_type);
617    let expected = VMSharedTypeIndex::from_u32(expected_engine_type);
618
619    let is_subtype: bool = store.engine().signatures().is_subtype(actual, expected);
620
621    log::trace!("is_subtype(actual={actual:?}, expected={expected:?}) -> {is_subtype}",);
622    is_subtype as u32
623}
624
625// Implementation of `memory.atomic.notify` for locally defined memories.
626#[cfg(feature = "threads")]
627fn memory_atomic_notify(
628    store: &mut dyn VMStore,
629    instance: InstanceId,
630    memory_index: u32,
631    addr_index: u64,
632    count: u32,
633) -> Result<u32, Trap> {
634    let memory = DefinedMemoryIndex::from_u32(memory_index);
635    store
636        .instance_mut(instance)
637        .get_defined_memory_mut(memory)
638        .atomic_notify(addr_index, count)
639}
640
641// Implementation of `memory.atomic.wait32` for locally defined memories.
642#[cfg(feature = "threads")]
643fn memory_atomic_wait32(
644    store: &mut dyn VMStore,
645    instance: InstanceId,
646    memory_index: u32,
647    addr_index: u64,
648    expected: u32,
649    timeout: u64,
650) -> Result<u32, Trap> {
651    let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout));
652    let memory = DefinedMemoryIndex::from_u32(memory_index);
653    Ok(store
654        .instance_mut(instance)
655        .get_defined_memory_mut(memory)
656        .atomic_wait32(addr_index, expected, timeout)? as u32)
657}
658
659// Implementation of `memory.atomic.wait64` for locally defined memories.
660#[cfg(feature = "threads")]
661fn memory_atomic_wait64(
662    store: &mut dyn VMStore,
663    instance: InstanceId,
664    memory_index: u32,
665    addr_index: u64,
666    expected: u64,
667    timeout: u64,
668) -> Result<u32, Trap> {
669    let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout));
670    let memory = DefinedMemoryIndex::from_u32(memory_index);
671    Ok(store
672        .instance_mut(instance)
673        .get_defined_memory_mut(memory)
674        .atomic_wait64(addr_index, expected, timeout)? as u32)
675}
676
677// Hook for when an instance runs out of fuel.
678fn out_of_gas(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()> {
679    block_on!(store, async |store, _| {
680        if !store.refuel() {
681            return Err(Trap::OutOfFuel.into());
682        }
683        #[cfg(feature = "async")]
684        if store.fuel_yield_interval.is_some() {
685            store.yield_now().await;
686        }
687        Ok(())
688    })?
689}
690
691// Hook for when an instance observes that the epoch has changed.
692#[cfg(target_has_atomic = "64")]
693fn new_epoch(store: &mut dyn VMStore, _instance: InstanceId) -> Result<NextEpoch> {
694    use crate::UpdateDeadline;
695
696    #[cfg(feature = "debug")]
697    {
698        store.block_on_debug_handler(crate::DebugEvent::EpochYield)?;
699    }
700
701    let update_deadline = store.new_epoch_updated_deadline()?;
702    block_on!(store, async move |store, asyncness| {
703        #[cfg(not(feature = "async"))]
704        let _ = asyncness;
705
706        let delta = match update_deadline {
707            UpdateDeadline::Interrupt => return Err(Trap::Interrupt.into()),
708            UpdateDeadline::Continue(delta) => delta,
709
710            // Note that custom errors are used here to avoid tripping up on the
711            // `block_on!` message that otherwise assumes
712            // async-configuration-after-the-fact.
713            #[cfg(feature = "async")]
714            UpdateDeadline::Yield(delta) => {
715                if asyncness != Asyncness::Yes {
716                    bail!(
717                        "cannot use `UpdateDeadline::Yield` without using \
718                         an async wasm entrypoint",
719                    );
720                }
721                store.yield_now().await;
722                delta
723            }
724            #[cfg(feature = "async")]
725            UpdateDeadline::YieldCustom(delta, future) => {
726                if asyncness != Asyncness::Yes {
727                    bail!(
728                        "cannot use `UpdateDeadline::YieldCustom` without using \
729                         an async wasm entrypoint",
730                    );
731                }
732                future.await;
733                delta
734            }
735        };
736
737        // Set a new deadline and return the new epoch deadline so
738        // the Wasm code doesn't have to reload it.
739        store.set_epoch_deadline(delta);
740        Ok(NextEpoch(store.get_epoch_deadline()))
741    })?
742}
743
744struct NextEpoch(u64);
745
746unsafe impl HostResultHasUnwindSentinel for NextEpoch {
747    type Abi = u64;
748    const SENTINEL: u64 = u64::MAX;
749    fn into_abi(self) -> u64 {
750        self.0
751    }
752}
753
754// Hook for validating malloc using wmemcheck_state.
755#[cfg(feature = "wmemcheck")]
756fn check_malloc(store: &mut dyn VMStore, instance: InstanceId, addr: u32, len: u32) -> Result<()> {
757    let instance = store.instance_mut(instance);
758    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
759        let result = wmemcheck_state.malloc(addr as usize, len as usize);
760        wmemcheck_state.memcheck_on();
761        match result {
762            Ok(()) => {}
763            Err(DoubleMalloc { addr, len }) => {
764                bail!("Double malloc at addr {:#x} of size {}", addr, len)
765            }
766            Err(OutOfBounds { addr, len }) => {
767                bail!("Malloc out of bounds at addr {:#x} of size {}", addr, len);
768            }
769            _ => {
770                panic!("unreachable")
771            }
772        }
773    }
774    Ok(())
775}
776
777// Hook for validating free using wmemcheck_state.
778#[cfg(feature = "wmemcheck")]
779fn check_free(store: &mut dyn VMStore, instance: InstanceId, addr: u32) -> Result<()> {
780    let instance = store.instance_mut(instance);
781    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
782        let result = wmemcheck_state.free(addr as usize);
783        wmemcheck_state.memcheck_on();
784        match result {
785            Ok(()) => {}
786            Err(InvalidFree { addr }) => {
787                bail!("Invalid free at addr {:#x}", addr)
788            }
789            _ => {
790                panic!("unreachable")
791            }
792        }
793    }
794    Ok(())
795}
796
797// Hook for validating load using wmemcheck_state.
798#[cfg(feature = "wmemcheck")]
799fn check_load(
800    store: &mut dyn VMStore,
801    instance: InstanceId,
802    num_bytes: u32,
803    addr: u32,
804    offset: u32,
805) -> Result<()> {
806    let instance = store.instance_mut(instance);
807    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
808        let result = wmemcheck_state.read(addr as usize + offset as usize, num_bytes as usize);
809        match result {
810            Ok(()) => {}
811            Err(InvalidRead { addr, len }) => {
812                bail!("Invalid load at addr {:#x} of size {}", addr, len);
813            }
814            Err(OutOfBounds { addr, len }) => {
815                bail!("Load out of bounds at addr {:#x} of size {}", addr, len);
816            }
817            _ => {
818                panic!("unreachable")
819            }
820        }
821    }
822    Ok(())
823}
824
825// Hook for validating store using wmemcheck_state.
826#[cfg(feature = "wmemcheck")]
827fn check_store(
828    store: &mut dyn VMStore,
829    instance: InstanceId,
830    num_bytes: u32,
831    addr: u32,
832    offset: u32,
833) -> Result<()> {
834    let instance = store.instance_mut(instance);
835    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
836        let result = wmemcheck_state.write(addr as usize + offset as usize, num_bytes as usize);
837        match result {
838            Ok(()) => {}
839            Err(InvalidWrite { addr, len }) => {
840                bail!("Invalid store at addr {:#x} of size {}", addr, len)
841            }
842            Err(OutOfBounds { addr, len }) => {
843                bail!("Store out of bounds at addr {:#x} of size {}", addr, len)
844            }
845            _ => {
846                panic!("unreachable")
847            }
848        }
849    }
850    Ok(())
851}
852
853// Hook for turning wmemcheck load/store validation off when entering a malloc function.
854#[cfg(feature = "wmemcheck")]
855fn malloc_start(store: &mut dyn VMStore, instance: InstanceId) {
856    let instance = store.instance_mut(instance);
857    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
858        wmemcheck_state.memcheck_off();
859    }
860}
861
862// Hook for turning wmemcheck load/store validation off when entering a free function.
863#[cfg(feature = "wmemcheck")]
864fn free_start(store: &mut dyn VMStore, instance: InstanceId) {
865    let instance = store.instance_mut(instance);
866    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
867        wmemcheck_state.memcheck_off();
868    }
869}
870
871// Hook for tracking wasm stack updates using wmemcheck_state.
872#[cfg(feature = "wmemcheck")]
873fn update_stack_pointer(_store: &mut dyn VMStore, _instance: InstanceId, _value: u32) {
874    // TODO: stack-tracing has yet to be finalized. All memory below
875    // the address of the top of the stack is marked as valid for
876    // loads and stores.
877    // if let Some(wmemcheck_state) = &mut instance.wmemcheck_state {
878    //     instance.wmemcheck_state.update_stack_pointer(value as usize);
879    // }
880}
881
882// Hook updating wmemcheck_state memory state vector every time memory.grow is called.
883#[cfg(feature = "wmemcheck")]
884fn update_mem_size(store: &mut dyn VMStore, instance: InstanceId, num_pages: u32) {
885    let instance = store.instance_mut(instance);
886    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
887        const KIB: usize = 1024;
888        let num_bytes = num_pages as usize * 64 * KIB;
889        wmemcheck_state.update_mem_size(num_bytes);
890    }
891}
892
893fn floor_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
894    val.wasm_floor()
895}
896
897fn floor_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
898    val.wasm_floor()
899}
900
901fn ceil_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
902    val.wasm_ceil()
903}
904
905fn ceil_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
906    val.wasm_ceil()
907}
908
909fn trunc_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
910    val.wasm_trunc()
911}
912
913fn trunc_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
914    val.wasm_trunc()
915}
916
917fn nearest_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
918    val.wasm_nearest()
919}
920
921fn nearest_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
922    val.wasm_nearest()
923}
924
925// This intrinsic is only used on x86_64 platforms as an implementation of
926// the `i8x16.swizzle` instruction when `pshufb` in SSSE3 is not available.
927#[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
928fn i8x16_swizzle(_store: &mut dyn VMStore, _instance: InstanceId, a: i8x16, b: i8x16) -> i8x16 {
929    union U {
930        reg: i8x16,
931        mem: [u8; 16],
932    }
933
934    unsafe {
935        let a = U { reg: a }.mem;
936        let b = U { reg: b }.mem;
937
938        // Use the `swizzle` semantics of returning 0 on any out-of-bounds
939        // index, rather than the x86 pshufb semantics, since Wasmtime uses
940        // this to implement `i8x16.swizzle`.
941        let select = |arr: &[u8; 16], byte: u8| {
942            if byte >= 16 { 0x00 } else { arr[byte as usize] }
943        };
944
945        U {
946            mem: [
947                select(&a, b[0]),
948                select(&a, b[1]),
949                select(&a, b[2]),
950                select(&a, b[3]),
951                select(&a, b[4]),
952                select(&a, b[5]),
953                select(&a, b[6]),
954                select(&a, b[7]),
955                select(&a, b[8]),
956                select(&a, b[9]),
957                select(&a, b[10]),
958                select(&a, b[11]),
959                select(&a, b[12]),
960                select(&a, b[13]),
961                select(&a, b[14]),
962                select(&a, b[15]),
963            ],
964        }
965        .reg
966    }
967}
968
969#[cfg(not(all(target_arch = "x86_64", target_feature = "sse")))]
970fn i8x16_swizzle(_store: &mut dyn VMStore, _instance: InstanceId, _a: i8x16, _b: i8x16) -> i8x16 {
971    unreachable!()
972}
973
974// This intrinsic is only used on x86_64 platforms as an implementation of
975// the `i8x16.shuffle` instruction when `pshufb` in SSSE3 is not available.
976#[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
977fn i8x16_shuffle(
978    _store: &mut dyn VMStore,
979    _instance: InstanceId,
980    a: i8x16,
981    b: i8x16,
982    c: i8x16,
983) -> i8x16 {
984    union U {
985        reg: i8x16,
986        mem: [u8; 16],
987    }
988
989    unsafe {
990        let ab = [U { reg: a }.mem, U { reg: b }.mem];
991        let c = U { reg: c }.mem;
992
993        // Use the `shuffle` semantics of returning 0 on any out-of-bounds
994        // index, rather than the x86 pshufb semantics, since Wasmtime uses
995        // this to implement `i8x16.shuffle`.
996        let select = |arr: &[[u8; 16]; 2], byte: u8| {
997            if byte >= 32 {
998                0x00
999            } else if byte >= 16 {
1000                arr[1][byte as usize - 16]
1001            } else {
1002                arr[0][byte as usize]
1003            }
1004        };
1005
1006        U {
1007            mem: [
1008                select(&ab, c[0]),
1009                select(&ab, c[1]),
1010                select(&ab, c[2]),
1011                select(&ab, c[3]),
1012                select(&ab, c[4]),
1013                select(&ab, c[5]),
1014                select(&ab, c[6]),
1015                select(&ab, c[7]),
1016                select(&ab, c[8]),
1017                select(&ab, c[9]),
1018                select(&ab, c[10]),
1019                select(&ab, c[11]),
1020                select(&ab, c[12]),
1021                select(&ab, c[13]),
1022                select(&ab, c[14]),
1023                select(&ab, c[15]),
1024            ],
1025        }
1026        .reg
1027    }
1028}
1029
1030#[cfg(not(all(target_arch = "x86_64", target_feature = "sse")))]
1031fn i8x16_shuffle(
1032    _store: &mut dyn VMStore,
1033    _instance: InstanceId,
1034    _a: i8x16,
1035    _b: i8x16,
1036    _c: i8x16,
1037) -> i8x16 {
1038    unreachable!()
1039}
1040
1041fn fma_f32x4(
1042    _store: &mut dyn VMStore,
1043    _instance: InstanceId,
1044    x: f32x4,
1045    y: f32x4,
1046    z: f32x4,
1047) -> f32x4 {
1048    union U {
1049        reg: f32x4,
1050        mem: [f32; 4],
1051    }
1052
1053    unsafe {
1054        let x = U { reg: x }.mem;
1055        let y = U { reg: y }.mem;
1056        let z = U { reg: z }.mem;
1057
1058        U {
1059            mem: [
1060                x[0].wasm_mul_add(y[0], z[0]),
1061                x[1].wasm_mul_add(y[1], z[1]),
1062                x[2].wasm_mul_add(y[2], z[2]),
1063                x[3].wasm_mul_add(y[3], z[3]),
1064            ],
1065        }
1066        .reg
1067    }
1068}
1069
1070fn fma_f64x2(
1071    _store: &mut dyn VMStore,
1072    _instance: InstanceId,
1073    x: f64x2,
1074    y: f64x2,
1075    z: f64x2,
1076) -> f64x2 {
1077    union U {
1078        reg: f64x2,
1079        mem: [f64; 2],
1080    }
1081
1082    unsafe {
1083        let x = U { reg: x }.mem;
1084        let y = U { reg: y }.mem;
1085        let z = U { reg: z }.mem;
1086
1087        U {
1088            mem: [x[0].wasm_mul_add(y[0], z[0]), x[1].wasm_mul_add(y[1], z[1])],
1089        }
1090        .reg
1091    }
1092}
1093
1094/// This intrinsic is just used to record trap information.
1095///
1096/// The `Infallible` "ok" type here means that this never returns success, it
1097/// only ever returns an error, and this hooks into the machinery to handle
1098/// `Result` values to record such trap information.
1099fn trap(_store: &mut dyn VMStore, _instance: InstanceId, code: u8) -> Result<Infallible> {
1100    match CompiledTrap::from_u8(code).unwrap() {
1101        CompiledTrap::Normal(trap) => Err(trap.into()),
1102        CompiledTrap::InternalAssert => bail_bug!("internal assert hit in wasm"),
1103        CompiledTrap::GcHeapCorrupt => bail_bug!("GC heap corruption detected"),
1104    }
1105}
1106
1107fn raise(store: &mut dyn VMStore, _instance: InstanceId) {
1108    // SAFETY: this is only called from compiled wasm so we know that wasm has
1109    // already been entered. It's a dynamic safety precondition that the trap
1110    // information has already been arranged to be present.
1111    unsafe { crate::runtime::vm::traphandlers::raise_preexisting_trap(store) }
1112}
1113
1114// Builtins for continuations. These are thin wrappers around the
1115// respective definitions in stack_switching.rs.
1116#[cfg(feature = "stack-switching")]
1117fn cont_new(
1118    store: &mut dyn VMStore,
1119    instance: InstanceId,
1120    func: *mut u8,
1121    param_count: u32,
1122    result_count: u32,
1123) -> Result<Option<AllocationSize>> {
1124    let ans =
1125        crate::vm::stack_switching::cont_new(store, instance, func, param_count, result_count)?;
1126    Ok(Some(AllocationSize(ans.cast::<u8>() as usize)))
1127}
1128
1129#[cfg(feature = "gc")]
1130fn get_instance_id(_store: &mut dyn VMStore, instance: InstanceId) -> u32 {
1131    instance.as_u32()
1132}
1133
1134#[cfg(feature = "gc")]
1135fn throw_ref(store: &mut dyn VMStore, _instance: InstanceId, exnref: u32) -> Result<()> {
1136    let exnref = VMGcRef::from_raw_u32(exnref).ok_or_else(|| Trap::NullReference)?;
1137    Err(store.set_pending_exception(&exnref))
1138}
1139
1140fn breakpoint(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()> {
1141    #[cfg(feature = "debug")]
1142    {
1143        store.block_on_debug_handler(crate::DebugEvent::Breakpoint)?;
1144    }
1145    // Avoid unused-argument warning in no-debugger builds.
1146    let _ = store;
1147    Ok(())
1148}