wasmtime_runtime/
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/runtime/src/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::externref::VMExternRef;
58use crate::table::{Table, TableElementType};
59use crate::vmcontext::{VMCallerCheckedFuncRef, VMContext};
60use crate::TrapReason;
61use anyhow::Result;
62use std::mem;
63use std::ptr::{self, NonNull};
64use std::time::{Duration, Instant};
65use wasmtime_environ::{
66    DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, Trap,
67};
68
69/// Actually public trampolines which are used by the runtime as the entrypoint
70/// for libcalls.
71///
72/// Note that the trampolines here are actually defined in inline assembly right
73/// now to ensure that the fp/sp on exit are recorded for backtraces to work
74/// properly.
75pub mod trampolines {
76    use crate::{TrapReason, VMContext};
77
78    macro_rules! libcall {
79        (
80            $(
81                $( #[$attr:meta] )*
82                $name:ident( vmctx: vmctx $(, $pname:ident: $param:ident )* ) $( -> $result:ident )?;
83            )*
84        ) => {paste::paste! {
85            $(
86                // The actual libcall itself, which has the `pub` name here, is
87                // defined via the `wasm_to_libcall_trampoline!` macro on
88                // supported platforms or otherwise in inline assembly for
89                // platforms like s390x which don't have stable `global_asm!`
90                // yet.
91                extern "C" {
92                    #[allow(missing_docs)]
93                    #[allow(improper_ctypes)]
94                    pub fn $name(
95                        vmctx: *mut VMContext,
96                        $( $pname: libcall!(@ty $param), )*
97                    ) $(-> libcall!(@ty $result))?;
98                }
99
100                wasm_to_libcall_trampoline!($name ; [<impl_ $name>]);
101
102                // This is the direct entrypoint from the inline assembly which
103                // still has the same raw signature as the trampoline itself.
104                // This will delegate to the outer module to the actual
105                // implementation and automatically perform `catch_unwind` along
106                // with conversion of the return value in the face of traps.
107                //
108                // Note that rust targets which support `global_asm!` can use
109                // the `sym` operator to get the symbol here, but other targets
110                // like s390x need to use outlined assembly files which requires
111                // `no_mangle`.
112                #[cfg_attr(target_arch = "s390x", no_mangle)]
113                unsafe extern "C" fn [<impl_ $name>](
114                    vmctx : *mut VMContext,
115                    $( $pname : libcall!(@ty $param), )*
116                ) $( -> libcall!(@ty $result))? {
117                    let result = std::panic::catch_unwind(|| {
118                        super::$name(vmctx, $($pname),*)
119                    });
120                    match result {
121                        Ok(ret) => LibcallResult::convert(ret),
122                        Err(panic) => crate::traphandlers::resume_panic(panic),
123                    }
124                }
125
126                // This works around a `rustc` bug where compiling with LTO
127                // will sometimes strip out some of these symbols resulting
128                // in a linking failure.
129                #[allow(non_upper_case_globals)]
130                #[used]
131                static [<impl_ $name _ref>]: unsafe extern "C" fn(
132                    *mut VMContext,
133                    $( $pname : libcall!(@ty $param), )*
134                ) $( -> libcall!(@ty $result))? = [<impl_ $name>];
135
136            )*
137        }};
138
139        (@ty i32) => (u32);
140        (@ty i64) => (u64);
141        (@ty reference) => (*mut u8);
142        (@ty pointer) => (*mut u8);
143        (@ty vmctx) => (*mut VMContext);
144    }
145
146    wasmtime_environ::foreach_builtin_function!(libcall);
147
148    // Helper trait to convert results of libcalls below into the ABI of what
149    // the libcall expects.
150    //
151    // This basically entirely exists for the `Result` implementation which
152    // "unwraps" via a throwing of a trap.
153    trait LibcallResult {
154        type Abi;
155        unsafe fn convert(self) -> Self::Abi;
156    }
157
158    impl LibcallResult for () {
159        type Abi = ();
160        unsafe fn convert(self) {}
161    }
162
163    impl<T, E> LibcallResult for Result<T, E>
164    where
165        E: Into<TrapReason>,
166    {
167        type Abi = T;
168        unsafe fn convert(self) -> T {
169            match self {
170                Ok(t) => t,
171                Err(e) => crate::traphandlers::raise_trap(e.into()),
172            }
173        }
174    }
175
176    impl LibcallResult for *mut u8 {
177        type Abi = *mut u8;
178        unsafe fn convert(self) -> *mut u8 {
179            self
180        }
181    }
182}
183
184unsafe fn memory32_grow(
185    vmctx: *mut VMContext,
186    delta: u64,
187    memory_index: u32,
188) -> Result<*mut u8, TrapReason> {
189    let instance = (*vmctx).instance_mut();
190    let memory_index = MemoryIndex::from_u32(memory_index);
191    let result =
192        match instance
193            .memory_grow(memory_index, delta)
194            .map_err(|error| TrapReason::User {
195                error,
196                needs_backtrace: true,
197            })? {
198            Some(size_in_bytes) => size_in_bytes / (wasmtime_environ::WASM_PAGE_SIZE as usize),
199            None => usize::max_value(),
200        };
201    Ok(result as *mut _)
202}
203
204// Implementation of `table.grow`.
205//
206// Table grow can invoke user code provided in a ResourceLimiter{,Async}, so we
207// need to catch a possible panic.
208unsafe fn table_grow(
209    vmctx: *mut VMContext,
210    table_index: u32,
211    delta: u32,
212    // NB: we don't know whether this is a pointer to a `VMCallerCheckedFuncRef`
213    // or is a `VMExternRef` until we look at the table type.
214    init_value: *mut u8,
215) -> Result<u32> {
216    let instance = (*vmctx).instance_mut();
217    let table_index = TableIndex::from_u32(table_index);
218    let element = match instance.table_element_type(table_index) {
219        TableElementType::Func => (init_value as *mut VMCallerCheckedFuncRef).into(),
220        TableElementType::Extern => {
221            let init_value = if init_value.is_null() {
222                None
223            } else {
224                Some(VMExternRef::clone_from_raw(init_value))
225            };
226            init_value.into()
227        }
228    };
229    Ok(match instance.table_grow(table_index, delta, element)? {
230        Some(r) => r,
231        None => -1_i32 as u32,
232    })
233}
234
235use table_grow as table_grow_funcref;
236use table_grow as table_grow_externref;
237
238// Implementation of `table.fill`.
239unsafe fn table_fill(
240    vmctx: *mut VMContext,
241    table_index: u32,
242    dst: u32,
243    // NB: we don't know whether this is a `VMExternRef` or a pointer to a
244    // `VMCallerCheckedFuncRef` until we look at the table's element type.
245    val: *mut u8,
246    len: u32,
247) -> Result<(), Trap> {
248    let instance = (*vmctx).instance_mut();
249    let table_index = TableIndex::from_u32(table_index);
250    let table = &mut *instance.get_table(table_index);
251    match table.element_type() {
252        TableElementType::Func => {
253            let val = val as *mut VMCallerCheckedFuncRef;
254            table.fill(dst, val.into(), len)
255        }
256        TableElementType::Extern => {
257            let val = if val.is_null() {
258                None
259            } else {
260                Some(VMExternRef::clone_from_raw(val))
261            };
262            table.fill(dst, val.into(), len)
263        }
264    }
265}
266
267use table_fill as table_fill_funcref;
268use table_fill as table_fill_externref;
269
270// Implementation of `table.copy`.
271unsafe fn table_copy(
272    vmctx: *mut VMContext,
273    dst_table_index: u32,
274    src_table_index: u32,
275    dst: u32,
276    src: u32,
277    len: u32,
278) -> Result<(), Trap> {
279    let dst_table_index = TableIndex::from_u32(dst_table_index);
280    let src_table_index = TableIndex::from_u32(src_table_index);
281    let instance = (*vmctx).instance_mut();
282    let dst_table = instance.get_table(dst_table_index);
283    // Lazy-initialize the whole range in the source table first.
284    let src_range = src..(src.checked_add(len).unwrap_or(u32::MAX));
285    let src_table = instance.get_table_with_lazy_init(src_table_index, src_range);
286    Table::copy(dst_table, src_table, dst, src, len)
287}
288
289// Implementation of `table.init`.
290unsafe fn table_init(
291    vmctx: *mut VMContext,
292    table_index: u32,
293    elem_index: u32,
294    dst: u32,
295    src: u32,
296    len: u32,
297) -> Result<(), Trap> {
298    let table_index = TableIndex::from_u32(table_index);
299    let elem_index = ElemIndex::from_u32(elem_index);
300    let instance = (*vmctx).instance_mut();
301    instance.table_init(table_index, elem_index, dst, src, len)
302}
303
304// Implementation of `elem.drop`.
305unsafe fn elem_drop(vmctx: *mut VMContext, elem_index: u32) {
306    let elem_index = ElemIndex::from_u32(elem_index);
307    let instance = (*vmctx).instance_mut();
308    instance.elem_drop(elem_index);
309}
310
311// Implementation of `memory.copy` for locally defined memories.
312unsafe fn memory_copy(
313    vmctx: *mut VMContext,
314    dst_index: u32,
315    dst: u64,
316    src_index: u32,
317    src: u64,
318    len: u64,
319) -> Result<(), Trap> {
320    let src_index = MemoryIndex::from_u32(src_index);
321    let dst_index = MemoryIndex::from_u32(dst_index);
322    let instance = (*vmctx).instance_mut();
323    instance.memory_copy(dst_index, dst, src_index, src, len)
324}
325
326// Implementation of `memory.fill` for locally defined memories.
327unsafe fn memory_fill(
328    vmctx: *mut VMContext,
329    memory_index: u32,
330    dst: u64,
331    val: u32,
332    len: u64,
333) -> Result<(), Trap> {
334    let memory_index = MemoryIndex::from_u32(memory_index);
335    let instance = (*vmctx).instance_mut();
336    instance.memory_fill(memory_index, dst, val as u8, len)
337}
338
339// Implementation of `memory.init`.
340unsafe fn memory_init(
341    vmctx: *mut VMContext,
342    memory_index: u32,
343    data_index: u32,
344    dst: u64,
345    src: u32,
346    len: u32,
347) -> Result<(), Trap> {
348    let memory_index = MemoryIndex::from_u32(memory_index);
349    let data_index = DataIndex::from_u32(data_index);
350    let instance = (*vmctx).instance_mut();
351    instance.memory_init(memory_index, data_index, dst, src, len)
352}
353
354// Implementation of `ref.func`.
355unsafe fn ref_func(vmctx: *mut VMContext, func_index: u32) -> *mut u8 {
356    let instance = (*vmctx).instance_mut();
357    let anyfunc = instance
358        .get_caller_checked_anyfunc(FuncIndex::from_u32(func_index))
359        .expect("ref_func: caller_checked_anyfunc should always be available for given func index");
360    anyfunc as *mut _
361}
362
363// Implementation of `data.drop`.
364unsafe fn data_drop(vmctx: *mut VMContext, data_index: u32) {
365    let data_index = DataIndex::from_u32(data_index);
366    let instance = (*vmctx).instance_mut();
367    instance.data_drop(data_index)
368}
369
370// Returns a table entry after lazily initializing it.
371unsafe fn table_get_lazy_init_funcref(
372    vmctx: *mut VMContext,
373    table_index: u32,
374    index: u32,
375) -> *mut u8 {
376    let instance = (*vmctx).instance_mut();
377    let table_index = TableIndex::from_u32(table_index);
378    let table = instance.get_table_with_lazy_init(table_index, std::iter::once(index));
379    let elem = (*table)
380        .get(index)
381        .expect("table access already bounds-checked");
382
383    elem.into_ref_asserting_initialized() as *mut _
384}
385
386// Drop a `VMExternRef`.
387unsafe fn drop_externref(_vmctx: *mut VMContext, externref: *mut u8) {
388    let externref = externref as *mut crate::externref::VMExternData;
389    let externref = NonNull::new(externref).unwrap();
390    crate::externref::VMExternData::drop_and_dealloc(externref);
391}
392
393// Do a GC and insert the given `externref` into the
394// `VMExternRefActivationsTable`.
395unsafe fn activations_table_insert_with_gc(vmctx: *mut VMContext, externref: *mut u8) {
396    let externref = VMExternRef::clone_from_raw(externref);
397    let instance = (*vmctx).instance();
398    let (activations_table, module_info_lookup) = (*instance.store()).externref_activations_table();
399
400    // Invariant: all `externref`s on the stack have an entry in the activations
401    // table. So we need to ensure that this `externref` is in the table
402    // *before* we GC, even though `insert_with_gc` will ensure that it is in
403    // the table *after* the GC. This technically results in one more hash table
404    // look up than is strictly necessary -- which we could avoid by having an
405    // additional GC method that is aware of these GC-triggering references --
406    // but it isn't really a concern because this is already a slow path.
407    activations_table.insert_without_gc(externref.clone());
408
409    activations_table.insert_with_gc(externref, module_info_lookup);
410}
411
412// Perform a Wasm `global.get` for `externref` globals.
413unsafe fn externref_global_get(vmctx: *mut VMContext, index: u32) -> *mut u8 {
414    let index = GlobalIndex::from_u32(index);
415    let instance = (*vmctx).instance_mut();
416    let global = instance.defined_or_imported_global_ptr(index);
417    match (*global).as_externref().clone() {
418        None => ptr::null_mut(),
419        Some(externref) => {
420            let raw = externref.as_raw();
421            let (activations_table, module_info_lookup) =
422                (*instance.store()).externref_activations_table();
423            activations_table.insert_with_gc(externref, module_info_lookup);
424            raw
425        }
426    }
427}
428
429// Perform a Wasm `global.set` for `externref` globals.
430unsafe fn externref_global_set(vmctx: *mut VMContext, index: u32, externref: *mut u8) {
431    let externref = if externref.is_null() {
432        None
433    } else {
434        Some(VMExternRef::clone_from_raw(externref))
435    };
436
437    let index = GlobalIndex::from_u32(index);
438    let instance = (*vmctx).instance_mut();
439    let global = instance.defined_or_imported_global_ptr(index);
440
441    // Swap the new `externref` value into the global before we drop the old
442    // value. This protects against an `externref` with a `Drop` implementation
443    // that calls back into Wasm and touches this global again (we want to avoid
444    // it observing a halfway-deinitialized value).
445    let old = mem::replace((*global).as_externref_mut(), externref);
446    drop(old);
447}
448
449// Implementation of `memory.atomic.notify` for locally defined memories.
450unsafe fn memory_atomic_notify(
451    vmctx: *mut VMContext,
452    memory_index: u32,
453    addr_index: u64,
454    count: u32,
455) -> Result<u32, Trap> {
456    let memory = MemoryIndex::from_u32(memory_index);
457    let instance = (*vmctx).instance_mut();
458    instance
459        .get_runtime_memory(memory)
460        .atomic_notify(addr_index, count)
461}
462
463// Implementation of `memory.atomic.wait32` for locally defined memories.
464unsafe fn memory_atomic_wait32(
465    vmctx: *mut VMContext,
466    memory_index: u32,
467    addr_index: u64,
468    expected: u32,
469    timeout: u64,
470) -> Result<u32, Trap> {
471    // convert timeout to Instant, before any wait happens on locking
472    let timeout = (timeout as i64 >= 0).then(|| Instant::now() + Duration::from_nanos(timeout));
473    let memory = MemoryIndex::from_u32(memory_index);
474    let instance = (*vmctx).instance_mut();
475    Ok(instance
476        .get_runtime_memory(memory)
477        .atomic_wait32(addr_index, expected, timeout)? as u32)
478}
479
480// Implementation of `memory.atomic.wait64` for locally defined memories.
481unsafe fn memory_atomic_wait64(
482    vmctx: *mut VMContext,
483    memory_index: u32,
484    addr_index: u64,
485    expected: u64,
486    timeout: u64,
487) -> Result<u32, Trap> {
488    // convert timeout to Instant, before any wait happens on locking
489    let timeout = (timeout as i64 >= 0).then(|| Instant::now() + Duration::from_nanos(timeout));
490    let memory = MemoryIndex::from_u32(memory_index);
491    let instance = (*vmctx).instance_mut();
492    Ok(instance
493        .get_runtime_memory(memory)
494        .atomic_wait64(addr_index, expected, timeout)? as u32)
495}
496
497// Hook for when an instance runs out of fuel.
498unsafe fn out_of_gas(vmctx: *mut VMContext) -> Result<()> {
499    (*(*vmctx).instance().store()).out_of_gas()
500}
501
502// Hook for when an instance observes that the epoch has changed.
503unsafe fn new_epoch(vmctx: *mut VMContext) -> Result<u64> {
504    (*(*vmctx).instance().store()).new_epoch()
505}
506
507/// This module contains functions which are used for resolving relocations at
508/// runtime if necessary.
509///
510/// These functions are not used by default and currently the only platform
511/// they're used for is on x86_64 when SIMD is disabled and then SSE features
512/// are further disabled. In these configurations Cranelift isn't allowed to use
513/// native CPU instructions so it falls back to libcalls and we rely on the Rust
514/// standard library generally for implementing these.
515#[allow(missing_docs)]
516pub mod relocs {
517    pub extern "C" fn floorf32(f: f32) -> f32 {
518        f.floor()
519    }
520
521    pub extern "C" fn floorf64(f: f64) -> f64 {
522        f.floor()
523    }
524
525    pub extern "C" fn ceilf32(f: f32) -> f32 {
526        f.ceil()
527    }
528
529    pub extern "C" fn ceilf64(f: f64) -> f64 {
530        f.ceil()
531    }
532
533    pub extern "C" fn truncf32(f: f32) -> f32 {
534        f.trunc()
535    }
536
537    pub extern "C" fn truncf64(f: f64) -> f64 {
538        f.trunc()
539    }
540
541    const TOINT_32: f32 = 1.0 / f32::EPSILON;
542    const TOINT_64: f64 = 1.0 / f64::EPSILON;
543
544    // NB: replace with `round_ties_even` from libstd when it's stable as
545    // tracked by rust-lang/rust#96710
546    pub extern "C" fn nearestf32(x: f32) -> f32 {
547        // Rust doesn't have a nearest function; there's nearbyint, but it's not
548        // stabilized, so do it manually.
549        // Nearest is either ceil or floor depending on which is nearest or even.
550        // This approach exploited round half to even default mode.
551        let i = x.to_bits();
552        let e = i >> 23 & 0xff;
553        if e >= 0x7f_u32 + 23 {
554            // Check for NaNs.
555            if e == 0xff {
556                // Read the 23-bits significand.
557                if i & 0x7fffff != 0 {
558                    // Ensure it's arithmetic by setting the significand's most
559                    // significant bit to 1; it also works for canonical NaNs.
560                    return f32::from_bits(i | (1 << 22));
561                }
562            }
563            x
564        } else {
565            (x.abs() + TOINT_32 - TOINT_32).copysign(x)
566        }
567    }
568
569    pub extern "C" fn nearestf64(x: f64) -> f64 {
570        let i = x.to_bits();
571        let e = i >> 52 & 0x7ff;
572        if e >= 0x3ff_u64 + 52 {
573            // Check for NaNs.
574            if e == 0x7ff {
575                // Read the 52-bits significand.
576                if i & 0xfffffffffffff != 0 {
577                    // Ensure it's arithmetic by setting the significand's most
578                    // significant bit to 1; it also works for canonical NaNs.
579                    return f64::from_bits(i | (1 << 51));
580                }
581            }
582            x
583        } else {
584            (x.abs() + TOINT_64 - TOINT_64).copysign(x)
585        }
586    }
587
588    pub extern "C" fn fmaf32(a: f32, b: f32, c: f32) -> f32 {
589        a.mul_add(b, c)
590    }
591
592    pub extern "C" fn fmaf64(a: f64, b: f64, c: f64) -> f64 {
593        a.mul_add(b, c)
594    }
595}