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}