Skip to main content

wasmtime_environ/
vmctxtypes.rs

1//! Centralized definitions of the layouts of Wasmtime's two "vmctx" types:
2//! `VMContext`, the runtime context for a core Wasm instance, and
3//! `VMComponentContext`, the runtime context for a component.
4//!
5//! Unlike the `VM*` types defined by `for_each_vm_type!`, neither of these has
6//! a corresponding `#[repr(C)]` Rust `struct`: their sizes depend on the module
7//! or component being instantiated, so they are dynamically laid out and
8//! accessed exclusively through the computed offsets in `VMOffsets` and
9//! `VMComponentOffsets`. That layout is defined exactly once here, via the
10//! higher-order `for_each_vmctx_type!` macro, and each consumer generates its
11//! view of it from that single source of truth.
12
13/// Invoke the given macro `$mac` once, passing it the layout of each of
14/// Wasmtime's "vmctx" types.
15///
16/// This is a higher-order macro: callers define a `macro_rules!` macro that
17/// matches the grammar described below and pass its name as an argument to this
18/// macro's invocation, e.g. `for_each_vmctx_type!(define_vmctx_offsets)`.
19///
20/// # Grammar
21///
22/// The layout below is written in exactly the same grammar that it is handed to
23/// `$mac` in: this macro has a single rule, and that rule does nothing but
24/// forward those tokens along. Writing the layout in the grammar that consumers
25/// match makes it more verbose than a bespoke input syntax would be, but in
26/// exchange there is no normalization pass in between, so what a consumer matches
27/// is exactly what a reader of the layout sees.
28///
29/// `$mac` receives one brace-delimited group per vmctx type:
30///
31/// ```ignore
32/// {
33///     VMContext vmctx
34///     static { ...entries... }
35///     dynamic { ...entries... }
36/// }
37/// ```
38///
39/// where `vmctx` is the name used for accessor methods of that type, `static`
40/// holds the fixed-width prefix whose offsets depend only on the target pointer
41/// size, and `dynamic` holds the rest, whose offsets additionally depend on the
42/// module or component being compiled.
43///
44/// Each entry within a section is a keyword naming the entry's shape followed by
45/// a single delimited group, so that a consumer can iterate over a section with
46/// `$( ...$kind:ident $entry:tt... )*` and dispatch on one whole entry at a time
47/// instead of munching the section token by token. An entry is one of:
48///
49/// * `align { ptr }` or `align { N }`: round the running offset up to the target
50///   pointer size, or to `N` bytes. Alignment is *always* explicit: it is never
51///   derived from a field's type, because the layout being described here does
52///   not necessarily align every field to its natural alignment, and inserting
53///   padding that the layout does not actually have would silently corrupt every
54///   subsequent offset.
55///
56/// * `field { <attrs> <name>: <ty> }`: a single field.
57///
58/// * `array { <attrs> <name>[<count>; <IndexType>]: <ty> }` (`dynamic` only): an
59///   array of `self.<count>` elements, indexed by `<IndexType>`.
60///
61/// * `optional { <attrs> <name>[if <flag>]: <ty> }` (`dynamic` only): a field that
62///   is present when `self.<flag>` is true and absent (zero-sized) otherwise.
63///
64/// A field's type is always the last thing in its entry. Consumers capture it
65/// as a trailing `$($fty:tt)*` and re-dispatch on its tokens only where they
66/// actually need to classify it into a size or a Cranelift type; a `:ty`
67/// capture would be opaque forever, and so could never be classified at
68/// all.
69///
70/// `<attrs>` is a possibly-empty sequence of marker attributes, which consumers
71/// match with `$(# $fattr:tt)*`. They do not affect the layout, but they do affect
72/// the accesses generated for the field:
73///
74/// * `#[aggregate]`: this field is a composite (a nested struct, or an array of
75///   them) rather than a single scalar. Compiled code accesses such a field's
76///   interior piecewise, so there is no one Cranelift type for the field as a
77///   whole and no alias-region accessor is generated for it. Its offset is still
78///   generated, since that is what interior accesses are computed relative to.
79///
80/// * `#[readonly]` and/or `#[can_move]`: describe how Cranelift may treat loads
81///   and stores of this field.
82///
83/// * `#[access_as = Type]`: this field is *declared* with one type (because that
84///   is what determines its size and stride) but *accessed* as another. For
85///   example, the component context's `may_leave` flags are each stored in a
86///   whole `VMGlobalDefinition` but only ever accessed as a `u32`.
87///
88/// Doc comments are deliberately *not* accepted on fields; use `//` comments for
89/// prose about the layout. Accessor documentation is synthesized from the field
90/// names instead, so that there is only one place a field can be described.
91///
92/// A consumer that only cares about one of the two types can filter with a
93/// literal-name arm followed by a catch-all, e.g.
94///
95/// ```ignore
96/// (@one VMContext $snake:ident dynamic { $($dyn:tt)* }) => { ...generate... };
97/// (@one $other:ident $snake:ident dynamic { $($dyn:tt)* }) => {};
98/// ```
99#[macro_export]
100macro_rules! for_each_vmctx_type {
101    ($mac:ident) => {
102        $mac! {
103            {
104                VMContext vmctx
105
106                // Fixed-width data comes first so that the calculation of these
107                // fields' offsets is a compile-time constant when using
108                // `HostPtr`.
109                static {
110                    field { #[readonly] #[can_move] magic: u32 }
111
112                    // NB: this is where the four bytes of padding after `magic`
113                    // live on targets with eight-byte pointers.
114                    align { ptr }
115
116                    field { #[readonly] #[can_move] store_context: VmPtr<VMStoreContext> }
117
118                    field { #[readonly] #[can_move] builtin_functions: VmPtr<VMBuiltinFunctionsArray> }
119
120                    field { epoch_ptr: VmPtr<AtomicU64> }
121
122                    // A pointer that different collectors use however they see
123                    // fit.
124                    field { #[readonly] #[can_move] gc_heap_data: VmPtr<u8> }
125
126                    field { #[readonly] #[can_move] type_ids: VmPtr<VMSharedTypeIndex> }
127                }
128
129                // Variable-width fields come after the fixed-width fields
130                // above. Memory-related items are placed first as they are some
131                // of the most frequently accessed items, and minimizing their
132                // offset can shrink the size of load/store instruction offset
133                // immediates on platforms like x64 and Pulley (e.g. fit in an
134                // 8-bit offset instead of needing a 32-bit offset).
135                dynamic {
136                    array {
137                        #[aggregate]
138                        imported_memories[num_imported_memories; MemoryIndex]: VMMemoryImport
139                    }
140
141                    array {
142                        #[readonly]
143                        #[can_move]
144                        memories[num_defined_memories; DefinedMemoryIndex]: VmPtr<VMMemoryDefinition>
145                    }
146
147                    array {
148                        #[aggregate]
149                        owned_memories[num_owned_memories; OwnedMemoryIndex]: VMMemoryDefinition
150                    }
151
152                    array {
153                        #[aggregate]
154                        imported_functions[num_imported_functions; FuncIndex]: VMFunctionImport
155                    }
156
157                    array {
158                        #[aggregate]
159                        imported_tables[num_imported_tables; TableIndex]: VMTableImport
160                    }
161
162                    array {
163                        #[aggregate]
164                        imported_globals[num_imported_globals; GlobalIndex]: VMGlobalImport
165                    }
166
167                    array {
168                        #[aggregate]
169                        imported_tags[num_imported_tags; TagIndex]: VMTagImport
170                    }
171
172                    array {
173                        #[aggregate]
174                        tables[num_defined_tables; DefinedTableIndex]: VMTableDefinition
175                    }
176
177                    align { 16 }
178
179                    array {
180                        #[aggregate]
181                        globals[num_defined_globals; DefinedGlobalIndex]: VMGlobalDefinition
182                    }
183
184                    array {
185                        #[aggregate]
186                        tags[num_defined_tags; DefinedTagIndex]: VMTagDefinition
187                    }
188
189                    array {
190                        #[aggregate]
191                        func_refs[num_escaped_funcs; FuncRefIndex]: VMFuncRef
192                    }
193
194                    optional {
195                        #[aggregate]
196                        startup_func_ref[if has_startup_func]: VMFuncRef
197                    }
198
199                    array {
200                        runtime_data_bases[num_runtime_data; RuntimeDataIndex]: VmPtr<u8>
201                    }
202
203                    array {
204                        runtime_data_lengths[num_runtime_data; RuntimeDataIndex]: u32
205                    }
206                }
207            }
208
209            {
210                VMComponentContext vmcomponent
211
212                static {
213                    // NB: `magic` must be at offset zero; this is relied upon by
214                    // `VMComponentContext::from_opaque`.
215                    field { #[readonly] #[can_move] magic: u32 }
216
217                    align { ptr }
218
219                    field { #[readonly] builtins: VmPtr<VMComponentBuiltins> }
220
221                    field { #[readonly] #[can_move] store_context: VmPtr<VMStoreContext> }
222                }
223
224                dynamic {
225                    align { 16 }
226
227                    // Each of these flags gets a whole `VMGlobalDefinition`'s
228                    // worth of space, but only its first four bytes are ever
229                    // accessed.
230                    array {
231                        #[access_as = u32]
232                        may_leave[num_runtime_component_instances; RuntimeComponentInstanceIndex]: VMGlobalDefinition
233                    }
234
235                    field { #[access_as = u32] task_may_block: VMGlobalDefinition }
236
237                    align { ptr }
238
239                    array {
240                        #[aggregate]
241                        trampoline_func_refs[num_trampolines; TrampolineIndex]: VMFuncRef
242                    }
243
244                    array {
245                        #[aggregate]
246                        intrinsic_func_refs[num_unsafe_intrinsics; UnsafeIntrinsic]: VMFuncRef
247                    }
248
249                    array {
250                        #[aggregate]
251                        lowerings[num_lowerings; LoweredIndex]: VMLowering
252                    }
253
254                    array {
255                        memories
256                            [num_runtime_memories; RuntimeMemoryIndex]: VmPtr<VMMemoryDefinition>
257                    }
258
259                    array {
260                        #[aggregate]
261                        tables[num_runtime_tables; RuntimeTableIndex]: VMTableImport
262                    }
263
264                    array {
265                        reallocs[num_runtime_reallocs; RuntimeReallocIndex]: VmPtr<VMFuncRef>
266                    }
267
268                    array {
269                        callbacks[num_runtime_callbacks; RuntimeCallbackIndex]: VmPtr<VMFuncRef>
270                    }
271
272                    array {
273                        post_returns[num_runtime_post_returns; RuntimePostReturnIndex]: VmPtr<VMFuncRef>
274                    }
275
276                    array {
277                        #[readonly]
278                        resource_destructors[num_resources; ResourceIndex]: VmPtr<VMFuncRef>
279                    }
280                }
281            }
282        }
283    };
284}
285
286/// Round `offset` up to a multiple of `align`.
287#[inline]
288pub(crate) fn align_up(offset: u32, align: u32) -> u32 {
289    debug_assert!(align.is_power_of_two());
290    (offset + (align - 1)) & !(align - 1)
291}
292
293/// Add two offsets, panicking on overflow.
294#[inline]
295pub(crate) fn cadd(a: u32, b: u32) -> u32 {
296    a.checked_add(b).unwrap()
297}
298
299/// Multiply an element count by an element size, panicking on overflow.
300#[inline]
301pub(crate) fn cmul(count: u32, size: u32) -> u32 {
302    count.checked_mul(size).unwrap()
303}
304
305/// Classify one of the field types accepted by `for_each_vmctx_type!` to its
306/// size in bytes as a `u32`, given a `PtrSize`.
307#[allow(
308    unused_macro_rules,
309    reason = "some element types only appear in `VMComponentContext`, and so are \
310              unused when the `component-model` feature is disabled"
311)]
312macro_rules! vmctx_field_size {
313    (($p:expr) u32) => {
314        4u32
315    };
316    (($p:expr) VmPtr < $g:ident >) => {
317        u32::from($p)
318    };
319    // `VMLowering` is a pair of pointers, and is not itself defined by
320    // `for_each_vm_type!`.
321    (($p:expr) VMLowering) => {
322        2u32 * u32::from($p)
323    };
324    // Anything else names one of the `VM*` types, and so has an
325    // `offsets::VMFoo` generated for it by `for_each_vm_type!`. Deferring to
326    // that keeps this macro from having to mirror the list of `VM*` types, and
327    // a type that has no such entry fails to resolve here.
328    (($p:expr) $Name:ident) => {
329        u32::from(crate::vmoffsets::offsets::$Name($p).size())
330    };
331}
332
333/// Classify a `for_each_vmctx_type!` alignment step to its alignment in bytes
334/// as a `u32`, given a `PtrSize`.
335macro_rules! vmctx_align_value {
336    (($p:expr) ptr) => {
337        u32::from($p)
338    };
339    (($p:expr) $n:literal) => {
340        $n
341    };
342}
343
344/// Generate the accessors for, and the layout computation of, the
345/// dynamically-positioned fields of one of the vmctx types.
346#[allow(
347    unused_macro_rules,
348    reason = "single dynamically-positioned fields only appear in \
349              `VMComponentContext`, and so are unused when the `component-model` \
350              feature is disabled"
351)]
352macro_rules! define_vmctx_dynamic_offsets {
353    (@accessors ($s:ident) [ $($kind:ident $entry:tt)* ]) => {
354        $( define_vmctx_dynamic_offsets!(@accessor ($s) $kind $entry); )*
355    };
356
357    (@accessor ($s:ident) align { $al:tt }) => {};
358
359    (@accessor ($s:ident) field { $(# $fattr:tt)* $fname:ident : $($fty:tt)* }) => {
360        #[doc = concat!("The offset of the `", stringify!($fname), "` field.")]
361        #[inline]
362        pub fn $fname(&$s) -> u32 {
363            $s.$fname
364        }
365    };
366
367    (@accessor ($s:ident) array {
368        $(# $fattr:tt)* $fname:ident [ $count:ident ; $Index:ident ] : $($fty:tt)*
369    }) => {
370        #[doc = concat!("The offsets of the `", stringify!($fname), "` array.")]
371        #[inline]
372        pub fn $fname(&$s) -> $crate::ArrayOffsets<$Index> {
373            $crate::ArrayOffsets::new(
374                $s.$fname,
375                vmctx_field_size!(($s.ptr.size()) $($fty)*),
376                $s.$count,
377            )
378        }
379    };
380
381    (@accessor ($s:ident) optional {
382        $(# $fattr:tt)* $fname:ident [ if $flag:ident ] : $($fty:tt)*
383    }) => {
384        #[doc = concat!(
385            "The offset of the `", stringify!($fname), "` field.\n\n",
386            "Panics if `", stringify!($flag), "` is false, in which case this \
387             field is not present at all."
388        )]
389        #[inline]
390        pub fn $fname(&$s) -> u32 {
391            assert!($s.$flag);
392            $s.$fname
393        }
394    };
395
396    (@compute_fn ($s:ident, $next:ident) $snake:ident [ $($kind:ident $entry:tt)* ]) => {
397        /// Compute the offset of each dynamically-positioned field, and this
398        /// vmctx's total size.
399        fn compute_field_offsets(&mut $s) {
400            let mut $next = u32::from($s.ptr.$snake().end_of_static_fields());
401            $( define_vmctx_dynamic_offsets!(@compute ($s, $next) $kind $entry); )*
402            $s.size = $next;
403        }
404    };
405
406    (@compute ($s:ident, $next:ident) align { $al:tt }) => {
407        $next = crate::vmctxtypes::align_up($next, vmctx_align_value!(($s.ptr.size()) $al));
408    };
409
410    (@compute ($s:ident, $next:ident) field {
411        $(# $fattr:tt)* $fname:ident : $($fty:tt)*
412    }) => {
413        $s.$fname = $next;
414        $next = crate::vmctxtypes::cadd($next, vmctx_field_size!(($s.ptr.size()) $($fty)*));
415    };
416
417    (@compute ($s:ident, $next:ident) array {
418        $(# $fattr:tt)* $fname:ident [ $count:ident ; $Index:ident ] : $($fty:tt)*
419    }) => {
420        $s.$fname = $next;
421        $next = crate::vmctxtypes::cadd(
422            $next,
423            crate::vmctxtypes::cmul($s.$count, vmctx_field_size!(($s.ptr.size()) $($fty)*)),
424        );
425    };
426
427    (@compute ($s:ident, $next:ident) optional {
428        $(# $fattr:tt)* $fname:ident [ if $flag:ident ] : $($fty:tt)*
429    }) => {
430        $s.$fname = $next;
431        $next = crate::vmctxtypes::cadd(
432            $next,
433            if $s.$flag {
434                vmctx_field_size!(($s.ptr.size()) $($fty)*)
435            } else {
436                0
437            },
438        );
439    };
440}
441
442/// The offsets of one array field within a vmctx.
443#[derive(Debug, Clone, Copy)]
444pub struct ArrayOffsets<I> {
445    begin: u32,
446    stride: u32,
447    count: u32,
448    _index: core::marker::PhantomData<I>,
449}
450
451impl<I> ArrayOffsets<I> {
452    /// Create the offsets for an array of `count` elements which begins at
453    /// `begin` within its vmctx and whose elements are `stride` bytes apart.
454    #[inline]
455    pub fn new(begin: u32, stride: u32, count: u32) -> Self {
456        ArrayOffsets {
457            begin,
458            stride,
459            count,
460            _index: core::marker::PhantomData,
461        }
462    }
463
464    /// The offset of the start of this array within its vmctx.
465    #[inline]
466    pub fn begin(&self) -> u32 {
467        self.begin
468    }
469
470    /// The number of bytes between the start of consecutive elements of this
471    /// array.
472    #[inline]
473    pub fn stride(&self) -> u32 {
474        self.stride
475    }
476
477    /// The number of elements in this array.
478    #[inline]
479    pub fn count(&self) -> u32 {
480        self.count
481    }
482}
483
484impl<I: VmctxArrayIndex> ArrayOffsets<I> {
485    /// The offset of the given element within this array's vmctx.
486    ///
487    /// Panics if `index` is out of bounds for this array.
488    #[inline]
489    pub fn at(&self, index: I) -> u32 {
490        let index = index.vmctx_array_index();
491        assert!(index < self.count);
492        self.begin + index * self.stride
493    }
494}
495
496/// An index type that can be used to index one of a vmctx's arrays.
497pub trait VmctxArrayIndex: Copy {
498    /// This index's position within its array.
499    fn vmctx_array_index(self) -> u32;
500}
501
502/// Implement `VmctxArrayIndex` for entity references.
503macro_rules! impl_vmctx_array_index {
504    ($($ty:ty),* $(,)?) => {
505        $(
506            impl $crate::VmctxArrayIndex for $ty {
507                #[inline]
508                fn vmctx_array_index(self) -> u32 {
509                    self.as_u32()
510                }
511            }
512        )*
513    };
514}