Skip to main content

wasmtime_environ/
builtin.rs

1/// Helper macro to iterate over all builtin functions and their signatures.
2#[macro_export]
3macro_rules! foreach_builtin_function {
4    ($mac:ident) => {
5        $mac! {
6            // Returns an index for wasm's `memory.grow` builtin function.
7            memory_grow(vmctx: vmctx, delta: u64, index: u32) -> pointer;
8            // Returns an index for wasm's `memory.copy`
9            memory_copy(vmctx: vmctx, dst: pointer, src: pointer, len: size);
10            // Returns an index for wasm's `memory.fill` instruction.
11            memory_fill(vmctx: vmctx, dst: pointer, val: u32, len: size);
12            // Returns the current size of the passive `elem` segment.
13            passive_elem_segment_len(vmctx: vmctx, elem: u32) -> size;
14            // Returns the base address of the passive `elem` segment.
15            passive_elem_segment_base(vmctx: vmctx, elem: u32) -> pointer;
16            // Guts of `elem.drop` for passive data segments.
17            passive_elem_segment_drop(vmctx: vmctx, elem: u32) -> bool;
18            // Returns a table entry after lazily initializing it.
19            table_get_lazy_init_func_ref(vmctx: vmctx, table: u32, index: u64) -> pointer;
20            // Grows `table` by `delta` elements, returning the destination
21            // address that new elements should be written at.
22            table_grow(vmctx: vmctx, table: u32, delta: u64) -> pointer;
23            // Returns an index for wasm's `memory.atomic.notify` instruction.
24            #[cfg(feature = "threads")]
25            memory_atomic_notify(vmctx: vmctx, memory: u32, addr: u64, count: u32) -> u64;
26            // Returns an index for wasm's `memory.atomic.wait32` instruction.
27            #[cfg(feature = "threads")]
28            memory_atomic_wait32(vmctx: vmctx, memory: u32, addr: u64, expected: u32, timeout: u64) -> u64;
29            // Returns an index for wasm's `memory.atomic.wait64` instruction.
30            #[cfg(feature = "threads")]
31            memory_atomic_wait64(vmctx: vmctx, memory: u32, addr: u64, expected: u64, timeout: u64) -> u64;
32            // Invoked when fuel has run out while executing a function.
33            out_of_gas(vmctx: vmctx) -> bool;
34            // Invoked when we reach a new epoch.
35            #[cfg(target_has_atomic = "64")]
36            new_epoch(vmctx: vmctx) -> u64;
37            // Invoked before malloc returns.
38            #[cfg(feature = "wmemcheck")]
39            check_malloc(vmctx: vmctx, addr: u32, len: u32) -> bool;
40            // Invoked before the free returns.
41            #[cfg(feature = "wmemcheck")]
42            check_free(vmctx: vmctx, addr: u32) -> bool;
43            // Invoked before a load is executed.
44            #[cfg(feature = "wmemcheck")]
45            check_load(vmctx: vmctx, num_bytes: u32, addr: u32, offset: u32) -> bool;
46            // Invoked before a store is executed.
47            #[cfg(feature = "wmemcheck")]
48            check_store(vmctx: vmctx, num_bytes: u32, addr: u32, offset: u32) -> bool;
49            // Invoked after malloc is called.
50            #[cfg(feature = "wmemcheck")]
51            malloc_start(vmctx: vmctx);
52            // Invoked after free is called.
53            #[cfg(feature = "wmemcheck")]
54            free_start(vmctx: vmctx);
55            // Invoked when wasm stack pointer is updated.
56            #[cfg(feature = "wmemcheck")]
57            update_stack_pointer(vmctx: vmctx, value: u32);
58            // Invoked before memory.grow is called.
59            #[cfg(feature = "wmemcheck")]
60            update_mem_size(vmctx: vmctx, num_bytes: u32);
61
62            // Drop a non-stack GC reference (eg an overwritten table entry)
63            // once it will no longer be used again. (Note: `val` is not of type
64            // `reference` because it needn't appear in any stack maps, as it
65            // must not be live after this call.)
66            #[cfg(feature = "gc-drc")]
67            drop_gc_ref(vmctx: vmctx, val: u32);
68
69            // Grow the GC heap by `bytes_needed` bytes.
70            //
71            // Traps if growing the GC heap fails.
72            #[cfg(feature = "gc-null")]
73            grow_gc_heap(vmctx: vmctx, bytes_needed: u64) -> bool;
74
75            // Allocate a new, uninitialized GC object and return a reference to
76            // it.
77            #[cfg(any(feature = "gc-drc", feature = "gc-copying"))]
78            gc_alloc_raw(
79                vmctx: vmctx,
80                kind: u32,
81                shared_type_index: u32,
82                size: u32,
83                align: u32
84            ) -> u32;
85
86            // Intern a `funcref` into the GC heap, returning its
87            // `FuncRefTableId`.
88            //
89            // This libcall may not GC.
90            #[cfg(feature = "gc")]
91            intern_func_ref_for_gc_heap(
92                vmctx: vmctx,
93                func_ref: pointer
94            ) -> u64;
95
96            // Get the raw `VMFuncRef` pointer associated with a
97            // `FuncRefTableId` from an earlier `intern_func_ref_for_gc_heap`
98            // call.
99            //
100            // This libcall may not GC.
101            //
102            // Passes in the `ModuleInternedTypeIndex` of the funcref's expected
103            // type, or `ModuleInternedTypeIndex::reserved_value()` if we are
104            // getting the function reference as an untyped `funcref` rather
105            // than a typed `(ref $ty)`.
106            //
107            // TODO: We will want to eventually expose the table directly to
108            // Wasm code, so that it doesn't need to make a libcall to go from
109            // id to `VMFuncRef`. That will be a little tricky: it will also
110            // require updating the pointer to the slab in the `VMContext` (or
111            // `VMStoreContext` or wherever we put it) when the slab is
112            // resized.
113            #[cfg(feature = "gc")]
114            get_interned_func_ref(
115                vmctx: vmctx,
116                func_ref_id: u32,
117                module_interned_type_index: u32
118            ) -> pointer;
119
120            // Returns whether `actual_engine_type` is a subtype of
121            // `expected_engine_type`.
122            #[cfg(feature = "gc")]
123            is_subtype(
124                vmctx: vmctx,
125                actual_engine_type: u32,
126                expected_engine_type: u32
127            ) -> u32;
128
129            // Wasm floating-point routines for when the CPU instructions aren't available.
130            ceil_f32(vmctx: vmctx, x: f32) -> f32;
131            ceil_f64(vmctx: vmctx, x: f64) -> f64;
132            floor_f32(vmctx: vmctx, x: f32) -> f32;
133            floor_f64(vmctx: vmctx, x: f64) -> f64;
134            trunc_f32(vmctx: vmctx, x: f32) -> f32;
135            trunc_f64(vmctx: vmctx, x: f64) -> f64;
136            nearest_f32(vmctx: vmctx, x: f32) -> f32;
137            nearest_f64(vmctx: vmctx, x: f64) -> f64;
138            i8x16_swizzle(vmctx: vmctx, a: i8x16, b: i8x16) -> i8x16;
139            i8x16_shuffle(vmctx: vmctx, a: i8x16, b: i8x16, c: i8x16) -> i8x16;
140            fma_f32x4(vmctx: vmctx, x: f32x4, y: f32x4, z: f32x4) -> f32x4;
141            fma_f64x2(vmctx: vmctx, x: f64x2, y: f64x2, z: f64x2) -> f64x2;
142
143            // Raises an unconditional trap with the specified code.
144            //
145            // This is used when signals-based-traps are disabled for backends
146            // when an illegal instruction can't be executed for example.
147            trap(vmctx: vmctx, code: u8);
148
149            // Raises an unconditional trap where the trap information must have
150            // been previously filled in.
151            raise(vmctx: vmctx);
152
153            // Creates a new continuation from a funcref.
154            #[cfg(feature = "stack-switching")]
155            cont_new(vmctx: vmctx, r: pointer, param_count: u32, result_count: u32, gc_refs: u32) -> pointer;
156
157            // Return the instance ID for a given vmctx.
158            #[cfg(feature = "gc")]
159            get_instance_id(vmctx: vmctx) -> u32;
160
161            // Throw an exception.
162            #[cfg(feature = "gc")]
163            throw_ref(vmctx: vmctx, exnref: u32) -> bool;
164
165            // Force a GC cycle for the DRC collector.
166            #[cfg(feature = "gc-drc")]
167            force_gc(vmctx: vmctx) -> bool;
168
169            // Process a debug breakpoint.
170            breakpoint(vmctx: vmctx) -> bool;
171
172            // Intern a continuation reference into the GC heap's side table.
173            #[cfg(all(feature = "gc", feature = "stack-switching"))]
174            intern_contref_for_gc_heap(
175                vmctx: vmctx,
176                contref: pointer,
177                revision: pointer
178            ) -> u64;
179
180            // Resolve an interned continuation-reference ID and write its
181            // pointer and revision witness to `result`.
182            #[cfg(all(feature = "gc", feature = "stack-switching"))]
183            get_interned_contref(
184                vmctx: vmctx,
185                contref_id: u32,
186                result: pointer
187            ) -> bool;
188
189            // Helpers used around stack switch instructions in
190            // ASan-instrumented builds.
191            #[cfg(feature = "stack-switching")]
192            asan_start_switch_fiber(
193                vmctx: vmctx,
194                fake_stack_save: pointer,
195                source_csi: pointer,
196                target_csi: pointer
197            );
198            #[cfg(feature = "stack-switching")]
199            asan_finish_switch_fiber(vmctx: vmctx, fake_stack: pointer, current_csi: pointer);
200
201            // Handles a store of `len` bytes, whose bits are `lo` and `hi`, to
202            // watched bytes of a linear memory, before the store.
203            memory_watch_store(vmctx: vmctx, memory: u32, addr: u64, len: u32, lo: u64, hi: u64) -> bool;
204            // Handles a bulk write (`memory.{fill,copy,init}`) of `len` bytes
205            // to a linear memory, before it, if any of the bytes are watched.
206            memory_watch_range(vmctx: vmctx, memory: u32, addr: u64, len: u64) -> bool;
207        }
208    };
209}
210
211/// Helper macro to define a builtin type such as `BuiltinFunctionIndex` and
212/// `ComponentBuiltinFunctionIndex` using the iterator macro, e.g.
213/// `foreach_builtin_function`, as the way to generate accessor methods.
214macro_rules! declare_builtin_index {
215    (
216        $(#[$attr:meta])*
217        pub struct $index_name:ident : $for_each_builtin:ident ;
218    ) => {
219        $(#[$attr])*
220        #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
221        pub struct $index_name(u32);
222
223        impl core::fmt::Debug for $index_name {
224            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
225                f.debug_struct(stringify!($index_name))
226                    .field("index", &self.0)
227                    .field("ctor", &self.ctor_name())
228                    .finish()
229            }
230        }
231
232        impl $index_name {
233            /// Create a new builtin from its raw index
234            pub const fn from_u32(i: u32) -> Self {
235                assert!(i < Self::len());
236                Self(i)
237            }
238
239            /// Return the index as an u32 number.
240            pub const fn index(&self) -> u32 {
241                self.0
242            }
243
244            $for_each_builtin!(define_ctor_name);
245
246            $for_each_builtin!(declare_builtin_index_constructors);
247        }
248
249        // Builtins are reached by indexing the array of function pointers that
250        // a vmctx points at.
251        impl $crate::VmctxArrayIndex for $index_name {
252            #[inline]
253            fn vmctx_array_index(self) -> u32 {
254                self.index()
255            }
256        }
257
258        #[cfg(test)]
259        impl arbitrary::Arbitrary<'_> for $index_name {
260            fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
261                Ok(Self(u.int_in_range(0..=Self::len() - 1)?))
262            }
263        }
264    };
265}
266
267/// Helper macro used by the above macro.
268macro_rules! define_ctor_name {
269    (
270        $(
271            $( #[$attr:meta] )*
272                $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
273        )*
274    ) => {
275        /// Returns the name of the constructor that creates this index.
276        pub fn ctor_name(&self) -> &'static str {
277            let mut _i = self.0;
278            $(
279                if _i == 0 {
280                    return stringify!($name);
281                }
282                _i -= 1;
283            )*
284            unreachable!()
285        }
286    }
287}
288
289/// Helper macro used by the above macro.
290macro_rules! declare_builtin_index_constructors {
291    (
292        $(
293            $( #[$attr:meta] )*
294            $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
295        )*
296    ) => {
297        declare_builtin_index_constructors!(
298            @indices;
299            0;
300            $( $( #[$attr] )* $name; )*
301        );
302
303        /// Returns a symbol name for this builtin.
304        pub fn name(&self) -> &'static str {
305            $(
306                if *self == Self::$name() {
307                    return stringify!($name);
308                }
309            )*
310            unreachable!()
311        }
312    };
313
314    // Base case: no more indices to declare, so define the total number of
315    // function indices.
316    (
317        @indices;
318        $len:expr;
319    ) => {
320        /// Returns the total number of builtin functions.
321        pub const fn len() -> u32 {
322            $len
323        }
324    };
325
326    // Recursive case: declare the next index, and then keep declaring the rest of
327    // the indices.
328    (
329         @indices;
330         $index:expr;
331         $( #[$this_attr:meta] )*
332         $this_name:ident;
333         $(
334             $( #[$rest_attr:meta] )*
335             $rest_name:ident;
336         )*
337    ) => {
338        #[expect(missing_docs, reason = "macro-generated")]
339        pub const fn $this_name() -> Self {
340            Self($index)
341        }
342
343        declare_builtin_index_constructors!(
344            @indices;
345            ($index + 1);
346            $( $( #[$rest_attr] )* $rest_name; )*
347        );
348    }
349}
350
351// Define `struct BuiltinFunctionIndex`
352declare_builtin_index! {
353    /// An index type for builtin functions.
354    pub struct BuiltinFunctionIndex : foreach_builtin_function;
355}
356
357/// Return value of [`BuiltinFunctionIndex::trap_sentinel`].
358pub enum TrapSentinel {
359    /// A falsy or zero value indicates a trap.
360    Falsy,
361    /// The value `-2` indicates a trap (used for growth-related builtins).
362    NegativeTwo,
363    /// The value `-1` indicates a trap .
364    NegativeOne,
365    /// Any negative value indicates a trap.
366    Negative,
367}
368
369impl BuiltinFunctionIndex {
370    /// Describes the return value of this builtin and what represents a trap.
371    ///
372    /// Libcalls don't raise traps themselves and instead delegate to compilers
373    /// to do so. This means that some return values of libcalls indicate a trap
374    /// is happening and this is represented with sentinel values. This function
375    /// returns the description of the sentinel value which indicates a trap, if
376    /// any. If `None` is returned from this function then this builtin cannot
377    /// generate a trap.
378    #[allow(unreachable_code, unused_macro_rules, reason = "macro-generated code")]
379    pub fn trap_sentinel(&self) -> Option<TrapSentinel> {
380        macro_rules! trap_sentinel {
381            (
382                $(
383                    $( #[$attr:meta] )*
384                    $name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
385                )*
386            ) => {{
387                $(
388                    $(#[$attr])*
389                    if *self == BuiltinFunctionIndex::$name() {
390                        let mut _ret = None;
391                        $(_ret = Some(trap_sentinel!(@get $name $result));)?
392                        return _ret;
393                    }
394                )*
395
396                None
397            }};
398
399            // Growth-related functions return -2 as a sentinel.
400            (@get memory_grow pointer) => (TrapSentinel::NegativeTwo);
401            (@get table_grow pointer) => (TrapSentinel::NegativeTwo);
402
403            // Atomics-related functions return a negative value to indicate a trap.
404            (@get memory_atomic_notify u64) => (TrapSentinel::Negative);
405            (@get memory_atomic_wait32 u64) => (TrapSentinel::Negative);
406            (@get memory_atomic_wait64 u64) => (TrapSentinel::Negative);
407
408            // GC allocation functions return a u32 which is zero to indicate a
409            // trap.
410            (@get gc_alloc_raw u32) => (TrapSentinel::Falsy);
411            (@get array_new_data u32) => (TrapSentinel::Falsy);
412            (@get array_new_elem u32) => (TrapSentinel::Falsy);
413
414            // The final epoch represents a trap
415            (@get new_epoch u64) => (TrapSentinel::NegativeOne);
416
417            // Failure here indicates GC heap corruption.
418            (@get get_interned_func_ref pointer) => (TrapSentinel::NegativeOne);
419
420            // These libcalls can't trap
421            (@get table_get_lazy_init_func_ref pointer) => (return None);
422            (@get intern_func_ref_for_gc_heap u64) => (return None);
423            (@get intern_contref_for_gc_heap u64) => (return None);
424            (@get is_subtype u32) => (return None);
425            (@get ceil_f32 f32) => (return None);
426            (@get ceil_f64 f64) => (return None);
427            (@get floor_f32 f32) => (return None);
428            (@get floor_f64 f64) => (return None);
429            (@get trunc_f32 f32) => (return None);
430            (@get trunc_f64 f64) => (return None);
431            (@get nearest_f32 f32) => (return None);
432            (@get nearest_f64 f64) => (return None);
433            (@get i8x16_swizzle i8x16) => (return None);
434            (@get i8x16_shuffle i8x16) => (return None);
435            (@get fma_f32x4 f32x4) => (return None);
436            (@get fma_f64x2 f64x2) => (return None);
437            (@get passive_data_segment_base pointer) => (return None);
438            (@get passive_elem_segment_len size) => (return None);
439            (@get passive_elem_segment_base pointer) => (return None);
440
441            (@get cont_new pointer) => (TrapSentinel::Negative);
442
443            (@get get_instance_id u32) => (return None);
444
445            // Bool-returning functions use `false` as an indicator of a trap.
446            (@get $name:ident bool) => (TrapSentinel::Falsy);
447
448            (@get $name:ident $ret:ident) => (
449                compile_error!(concat!("no trap sentinel registered for ", stringify!($name)))
450            )
451        }
452
453        foreach_builtin_function!(trap_sentinel)
454    }
455}