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    let is_subtype = store.is_subtype_cached(actual, expected);
619
620    log::trace!("is_subtype(actual={actual:?}, expected={expected:?}) -> {is_subtype}",);
621    u32::from(is_subtype)
622}
623
624// Implementation of `memory.atomic.notify` for locally defined memories.
625#[cfg(feature = "threads")]
626fn memory_atomic_notify(
627    store: &mut dyn VMStore,
628    instance: InstanceId,
629    memory_index: u32,
630    addr_index: u64,
631    count: u32,
632) -> Result<u32, Trap> {
633    let memory = DefinedMemoryIndex::from_u32(memory_index);
634    store
635        .instance_mut(instance)
636        .get_defined_memory_mut(memory)
637        .atomic_notify(addr_index, count)
638}
639
640// Implementation of `memory.atomic.wait32` for locally defined memories.
641#[cfg(feature = "threads")]
642fn memory_atomic_wait32(
643    store: &mut dyn VMStore,
644    instance: InstanceId,
645    memory_index: u32,
646    addr_index: u64,
647    expected: u32,
648    timeout: u64,
649) -> Result<u32, Trap> {
650    let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout));
651    let memory = DefinedMemoryIndex::from_u32(memory_index);
652    Ok(store
653        .instance_mut(instance)
654        .get_defined_memory_mut(memory)
655        .atomic_wait32(addr_index, expected, timeout)? as u32)
656}
657
658// Implementation of `memory.atomic.wait64` for locally defined memories.
659#[cfg(feature = "threads")]
660fn memory_atomic_wait64(
661    store: &mut dyn VMStore,
662    instance: InstanceId,
663    memory_index: u32,
664    addr_index: u64,
665    expected: u64,
666    timeout: u64,
667) -> Result<u32, Trap> {
668    let timeout = (timeout as i64 >= 0).then(|| Duration::from_nanos(timeout));
669    let memory = DefinedMemoryIndex::from_u32(memory_index);
670    Ok(store
671        .instance_mut(instance)
672        .get_defined_memory_mut(memory)
673        .atomic_wait64(addr_index, expected, timeout)? as u32)
674}
675
676// Hook for when an instance runs out of fuel.
677fn out_of_gas(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()> {
678    block_on!(store, async |store, _| {
679        if !store.refuel() {
680            return Err(Trap::OutOfFuel.into());
681        }
682        #[cfg(feature = "async")]
683        if store.fuel_yield_interval.is_some() {
684            store.yield_now().await;
685        }
686        Ok(())
687    })?
688}
689
690// Hook for when an instance observes that the epoch has changed.
691#[cfg(target_has_atomic = "64")]
692fn new_epoch(store: &mut dyn VMStore, _instance: InstanceId) -> Result<NextEpoch> {
693    use crate::UpdateDeadline;
694
695    #[cfg(feature = "debug")]
696    {
697        store.block_on_debug_handler(crate::DebugEvent::EpochYield)?;
698    }
699
700    let update_deadline = store.new_epoch_updated_deadline()?;
701    block_on!(store, async move |store, asyncness| {
702        #[cfg(not(feature = "async"))]
703        let _ = asyncness;
704
705        let delta = match update_deadline {
706            UpdateDeadline::Interrupt => return Err(Trap::Interrupt.into()),
707            UpdateDeadline::Continue(delta) => delta,
708
709            // Note that custom errors are used here to avoid tripping up on the
710            // `block_on!` message that otherwise assumes
711            // async-configuration-after-the-fact.
712            #[cfg(feature = "async")]
713            UpdateDeadline::Yield(delta) => {
714                if asyncness != Asyncness::Yes {
715                    bail!(
716                        "cannot use `UpdateDeadline::Yield` without using \
717                         an async wasm entrypoint",
718                    );
719                }
720                store.yield_now().await;
721                delta
722            }
723            #[cfg(feature = "async")]
724            UpdateDeadline::YieldCustom(delta, future) => {
725                if asyncness != Asyncness::Yes {
726                    bail!(
727                        "cannot use `UpdateDeadline::YieldCustom` without using \
728                         an async wasm entrypoint",
729                    );
730                }
731                future.await;
732                delta
733            }
734        };
735
736        // Set a new deadline and return the new epoch deadline so
737        // the Wasm code doesn't have to reload it.
738        store.set_epoch_deadline(delta);
739        Ok(NextEpoch(store.get_epoch_deadline()))
740    })?
741}
742
743struct NextEpoch(u64);
744
745unsafe impl HostResultHasUnwindSentinel for NextEpoch {
746    type Abi = u64;
747    const SENTINEL: u64 = u64::MAX;
748    fn into_abi(self) -> u64 {
749        self.0
750    }
751}
752
753// Hook for validating malloc using wmemcheck_state.
754#[cfg(feature = "wmemcheck")]
755fn check_malloc(store: &mut dyn VMStore, instance: InstanceId, addr: u32, len: u32) -> Result<()> {
756    let instance = store.instance_mut(instance);
757    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
758        let result = wmemcheck_state.malloc(addr as usize, len as usize);
759        wmemcheck_state.memcheck_on();
760        match result {
761            Ok(()) => {}
762            Err(DoubleMalloc { addr, len }) => {
763                bail!("Double malloc at addr {:#x} of size {}", addr, len)
764            }
765            Err(OutOfBounds { addr, len }) => {
766                bail!("Malloc out of bounds at addr {:#x} of size {}", addr, len);
767            }
768            _ => {
769                panic!("unreachable")
770            }
771        }
772    }
773    Ok(())
774}
775
776// Hook for validating free using wmemcheck_state.
777#[cfg(feature = "wmemcheck")]
778fn check_free(store: &mut dyn VMStore, instance: InstanceId, addr: u32) -> Result<()> {
779    let instance = store.instance_mut(instance);
780    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
781        let result = wmemcheck_state.free(addr as usize);
782        wmemcheck_state.memcheck_on();
783        match result {
784            Ok(()) => {}
785            Err(InvalidFree { addr }) => {
786                bail!("Invalid free at addr {:#x}", addr)
787            }
788            _ => {
789                panic!("unreachable")
790            }
791        }
792    }
793    Ok(())
794}
795
796// Hook for validating load using wmemcheck_state.
797#[cfg(feature = "wmemcheck")]
798fn check_load(
799    store: &mut dyn VMStore,
800    instance: InstanceId,
801    num_bytes: u32,
802    addr: u32,
803    offset: u32,
804) -> Result<()> {
805    let instance = store.instance_mut(instance);
806    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
807        let result = wmemcheck_state.read(addr as usize + offset as usize, num_bytes as usize);
808        match result {
809            Ok(()) => {}
810            Err(InvalidRead { addr, len }) => {
811                bail!("Invalid load at addr {:#x} of size {}", addr, len);
812            }
813            Err(OutOfBounds { addr, len }) => {
814                bail!("Load out of bounds at addr {:#x} of size {}", addr, len);
815            }
816            _ => {
817                panic!("unreachable")
818            }
819        }
820    }
821    Ok(())
822}
823
824// Hook for validating store using wmemcheck_state.
825#[cfg(feature = "wmemcheck")]
826fn check_store(
827    store: &mut dyn VMStore,
828    instance: InstanceId,
829    num_bytes: u32,
830    addr: u32,
831    offset: u32,
832) -> Result<()> {
833    let instance = store.instance_mut(instance);
834    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
835        let result = wmemcheck_state.write(addr as usize + offset as usize, num_bytes as usize);
836        match result {
837            Ok(()) => {}
838            Err(InvalidWrite { addr, len }) => {
839                bail!("Invalid store at addr {:#x} of size {}", addr, len)
840            }
841            Err(OutOfBounds { addr, len }) => {
842                bail!("Store out of bounds at addr {:#x} of size {}", addr, len)
843            }
844            _ => {
845                panic!("unreachable")
846            }
847        }
848    }
849    Ok(())
850}
851
852// Hook for turning wmemcheck load/store validation off when entering a malloc function.
853#[cfg(feature = "wmemcheck")]
854fn malloc_start(store: &mut dyn VMStore, instance: InstanceId) {
855    let instance = store.instance_mut(instance);
856    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
857        wmemcheck_state.memcheck_off();
858    }
859}
860
861// Hook for turning wmemcheck load/store validation off when entering a free function.
862#[cfg(feature = "wmemcheck")]
863fn free_start(store: &mut dyn VMStore, instance: InstanceId) {
864    let instance = store.instance_mut(instance);
865    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
866        wmemcheck_state.memcheck_off();
867    }
868}
869
870// Hook for tracking wasm stack updates using wmemcheck_state.
871#[cfg(feature = "wmemcheck")]
872fn update_stack_pointer(_store: &mut dyn VMStore, _instance: InstanceId, _value: u32) {
873    // TODO: stack-tracing has yet to be finalized. All memory below
874    // the address of the top of the stack is marked as valid for
875    // loads and stores.
876    // if let Some(wmemcheck_state) = &mut instance.wmemcheck_state {
877    //     instance.wmemcheck_state.update_stack_pointer(value as usize);
878    // }
879}
880
881// Hook updating wmemcheck_state memory state vector every time memory.grow is called.
882#[cfg(feature = "wmemcheck")]
883fn update_mem_size(store: &mut dyn VMStore, instance: InstanceId, num_pages: u32) {
884    let instance = store.instance_mut(instance);
885    if let Some(wmemcheck_state) = instance.wmemcheck_state_mut() {
886        const KIB: usize = 1024;
887        let num_bytes = num_pages as usize * 64 * KIB;
888        wmemcheck_state.update_mem_size(num_bytes);
889    }
890}
891
892fn floor_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
893    val.wasm_floor()
894}
895
896fn floor_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
897    val.wasm_floor()
898}
899
900fn ceil_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
901    val.wasm_ceil()
902}
903
904fn ceil_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
905    val.wasm_ceil()
906}
907
908fn trunc_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
909    val.wasm_trunc()
910}
911
912fn trunc_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
913    val.wasm_trunc()
914}
915
916fn nearest_f32(_store: &mut dyn VMStore, _instance: InstanceId, val: f32) -> f32 {
917    val.wasm_nearest()
918}
919
920fn nearest_f64(_store: &mut dyn VMStore, _instance: InstanceId, val: f64) -> f64 {
921    val.wasm_nearest()
922}
923
924// This intrinsic is only used on x86_64 platforms as an implementation of
925// the `i8x16.swizzle` instruction when `pshufb` in SSSE3 is not available.
926#[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
927fn i8x16_swizzle(_store: &mut dyn VMStore, _instance: InstanceId, a: i8x16, b: i8x16) -> i8x16 {
928    union U {
929        reg: i8x16,
930        mem: [u8; 16],
931    }
932
933    unsafe {
934        let a = U { reg: a }.mem;
935        let b = U { reg: b }.mem;
936
937        // Use the `swizzle` semantics of returning 0 on any out-of-bounds
938        // index, rather than the x86 pshufb semantics, since Wasmtime uses
939        // this to implement `i8x16.swizzle`.
940        let select = |arr: &[u8; 16], byte: u8| {
941            if byte >= 16 { 0x00 } else { arr[byte as usize] }
942        };
943
944        U {
945            mem: [
946                select(&a, b[0]),
947                select(&a, b[1]),
948                select(&a, b[2]),
949                select(&a, b[3]),
950                select(&a, b[4]),
951                select(&a, b[5]),
952                select(&a, b[6]),
953                select(&a, b[7]),
954                select(&a, b[8]),
955                select(&a, b[9]),
956                select(&a, b[10]),
957                select(&a, b[11]),
958                select(&a, b[12]),
959                select(&a, b[13]),
960                select(&a, b[14]),
961                select(&a, b[15]),
962            ],
963        }
964        .reg
965    }
966}
967
968#[cfg(not(all(target_arch = "x86_64", target_feature = "sse")))]
969fn i8x16_swizzle(_store: &mut dyn VMStore, _instance: InstanceId, _a: i8x16, _b: i8x16) -> i8x16 {
970    unreachable!()
971}
972
973// This intrinsic is only used on x86_64 platforms as an implementation of
974// the `i8x16.shuffle` instruction when `pshufb` in SSSE3 is not available.
975#[cfg(all(target_arch = "x86_64", target_feature = "sse"))]
976fn i8x16_shuffle(
977    _store: &mut dyn VMStore,
978    _instance: InstanceId,
979    a: i8x16,
980    b: i8x16,
981    c: i8x16,
982) -> i8x16 {
983    union U {
984        reg: i8x16,
985        mem: [u8; 16],
986    }
987
988    unsafe {
989        let ab = [U { reg: a }.mem, U { reg: b }.mem];
990        let c = U { reg: c }.mem;
991
992        // Use the `shuffle` semantics of returning 0 on any out-of-bounds
993        // index, rather than the x86 pshufb semantics, since Wasmtime uses
994        // this to implement `i8x16.shuffle`.
995        let select = |arr: &[[u8; 16]; 2], byte: u8| {
996            if byte >= 32 {
997                0x00
998            } else if byte >= 16 {
999                arr[1][byte as usize - 16]
1000            } else {
1001                arr[0][byte as usize]
1002            }
1003        };
1004
1005        U {
1006            mem: [
1007                select(&ab, c[0]),
1008                select(&ab, c[1]),
1009                select(&ab, c[2]),
1010                select(&ab, c[3]),
1011                select(&ab, c[4]),
1012                select(&ab, c[5]),
1013                select(&ab, c[6]),
1014                select(&ab, c[7]),
1015                select(&ab, c[8]),
1016                select(&ab, c[9]),
1017                select(&ab, c[10]),
1018                select(&ab, c[11]),
1019                select(&ab, c[12]),
1020                select(&ab, c[13]),
1021                select(&ab, c[14]),
1022                select(&ab, c[15]),
1023            ],
1024        }
1025        .reg
1026    }
1027}
1028
1029#[cfg(not(all(target_arch = "x86_64", target_feature = "sse")))]
1030fn i8x16_shuffle(
1031    _store: &mut dyn VMStore,
1032    _instance: InstanceId,
1033    _a: i8x16,
1034    _b: i8x16,
1035    _c: i8x16,
1036) -> i8x16 {
1037    unreachable!()
1038}
1039
1040fn fma_f32x4(
1041    _store: &mut dyn VMStore,
1042    _instance: InstanceId,
1043    x: f32x4,
1044    y: f32x4,
1045    z: f32x4,
1046) -> f32x4 {
1047    union U {
1048        reg: f32x4,
1049        mem: [f32; 4],
1050    }
1051
1052    unsafe {
1053        let x = U { reg: x }.mem;
1054        let y = U { reg: y }.mem;
1055        let z = U { reg: z }.mem;
1056
1057        U {
1058            mem: [
1059                x[0].wasm_mul_add(y[0], z[0]),
1060                x[1].wasm_mul_add(y[1], z[1]),
1061                x[2].wasm_mul_add(y[2], z[2]),
1062                x[3].wasm_mul_add(y[3], z[3]),
1063            ],
1064        }
1065        .reg
1066    }
1067}
1068
1069fn fma_f64x2(
1070    _store: &mut dyn VMStore,
1071    _instance: InstanceId,
1072    x: f64x2,
1073    y: f64x2,
1074    z: f64x2,
1075) -> f64x2 {
1076    union U {
1077        reg: f64x2,
1078        mem: [f64; 2],
1079    }
1080
1081    unsafe {
1082        let x = U { reg: x }.mem;
1083        let y = U { reg: y }.mem;
1084        let z = U { reg: z }.mem;
1085
1086        U {
1087            mem: [x[0].wasm_mul_add(y[0], z[0]), x[1].wasm_mul_add(y[1], z[1])],
1088        }
1089        .reg
1090    }
1091}
1092
1093/// This intrinsic is just used to record trap information.
1094///
1095/// The `Infallible` "ok" type here means that this never returns success, it
1096/// only ever returns an error, and this hooks into the machinery to handle
1097/// `Result` values to record such trap information.
1098fn trap(_store: &mut dyn VMStore, _instance: InstanceId, code: u8) -> Result<Infallible> {
1099    match CompiledTrap::from_u8(code).unwrap() {
1100        CompiledTrap::Normal(trap) => Err(trap.into()),
1101        CompiledTrap::InternalAssert => bail_bug!("internal assert hit in wasm"),
1102        CompiledTrap::GcHeapCorrupt => bail_bug!("GC heap corruption detected"),
1103    }
1104}
1105
1106fn raise(store: &mut dyn VMStore, _instance: InstanceId) {
1107    // SAFETY: this is only called from compiled wasm so we know that wasm has
1108    // already been entered. It's a dynamic safety precondition that the trap
1109    // information has already been arranged to be present.
1110    unsafe { crate::runtime::vm::traphandlers::raise_preexisting_trap(store) }
1111}
1112
1113// Builtins for continuations. These are thin wrappers around the
1114// respective definitions in stack_switching.rs.
1115#[cfg(feature = "stack-switching")]
1116fn cont_new(
1117    store: &mut dyn VMStore,
1118    instance: InstanceId,
1119    func: *mut u8,
1120    param_count: u32,
1121    result_count: u32,
1122) -> Result<Option<AllocationSize>> {
1123    let ans =
1124        crate::vm::stack_switching::cont_new(store, instance, func, param_count, result_count)?;
1125    Ok(Some(AllocationSize(ans.cast::<u8>() as usize)))
1126}
1127
1128#[cfg(feature = "gc")]
1129fn get_instance_id(_store: &mut dyn VMStore, instance: InstanceId) -> u32 {
1130    instance.as_u32()
1131}
1132
1133#[cfg(feature = "gc")]
1134fn throw_ref(store: &mut dyn VMStore, _instance: InstanceId, exnref: u32) -> Result<()> {
1135    let exnref = VMGcRef::from_raw_u32(exnref).ok_or_else(|| Trap::NullReference)?;
1136    Err(store.set_pending_exception(&exnref))
1137}
1138
1139fn breakpoint(store: &mut dyn VMStore, _instance: InstanceId) -> Result<()> {
1140    #[cfg(feature = "debug")]
1141    {
1142        store.block_on_debug_handler(crate::DebugEvent::Breakpoint)?;
1143    }
1144    // Avoid unused-argument warning in no-debugger builds.
1145    let _ = store;
1146    Ok(())
1147}