Skip to main content

wasmtime_environ/
gc.rs

1//! Target- and pointer-width-agnostic definitions of GC-related types and
2//! constants.
3//!
4//! These definitions are suitable for use both during compilation and at
5//! runtime.
6//!
7//! Note: We don't bother gating these on `cfg(feature = "gc")` because that
8//! makes downstream uses pretty annoying, and the primary thing we want to gate
9//! on our various `gc` cargo features is the actual garbage collection
10//! functions and their associated impact on binary size anyways.
11
12#[cfg(feature = "gc-drc")]
13pub mod drc;
14
15#[cfg(feature = "gc-null")]
16pub mod null;
17
18use crate::{
19    WasmArrayType, WasmCompositeInnerType, WasmCompositeType, WasmExnType, WasmStorageType,
20    WasmStructType, WasmValType, error::OutOfMemory, prelude::*,
21};
22use alloc::sync::Arc;
23use core::alloc::Layout;
24
25/// Poison byte written over unallocated GC heap memory when `cfg(gc_zeal)` is
26/// enabled.
27pub const POISON: u8 = 0b00001111;
28
29/// Assert a condition, but only when `gc_zeal` is enabled.
30#[macro_export]
31macro_rules! gc_assert {
32    ($($arg:tt)*) => {
33        if cfg!(gc_zeal) {
34            assert!($($arg)*);
35        }
36    };
37}
38
39/// Discriminant to check whether GC reference is an `i31ref` or not.
40pub const I31_DISCRIMINANT: u32 = 1;
41
42/// The size of the `VMGcHeader` in bytes.
43pub const VM_GC_HEADER_SIZE: u32 = 8;
44
45/// The minimum alignment of the `VMGcHeader` in bytes.
46pub const VM_GC_HEADER_ALIGN: u32 = 8;
47
48/// The offset of the `VMGcKind` field in the `VMGcHeader`.
49pub const VM_GC_HEADER_KIND_OFFSET: u32 = 0;
50
51/// The offset of the `VMSharedTypeIndex` field in the `VMGcHeader`.
52pub const VM_GC_HEADER_TYPE_INDEX_OFFSET: u32 = 4;
53
54/// Get the byte size of the given Wasm type when it is stored inside the GC
55/// heap.
56pub fn byte_size_of_wasm_ty_in_gc_heap(ty: &WasmStorageType) -> u32 {
57    match ty {
58        WasmStorageType::I8 => 1,
59        WasmStorageType::I16 => 2,
60        WasmStorageType::Val(ty) => match ty {
61            WasmValType::I32 | WasmValType::F32 | WasmValType::Ref(_) => 4,
62            WasmValType::I64 | WasmValType::F64 => 8,
63            WasmValType::V128 => 16,
64        },
65    }
66}
67
68/// Align `offset` up to `bytes`, updating `max_align` if `align` is the
69/// new maximum alignment, and returning the aligned offset.
70#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
71fn align_up(offset: &mut u32, max_align: &mut u32, align: u32) -> u32 {
72    debug_assert!(max_align.is_power_of_two());
73    debug_assert!(align.is_power_of_two());
74    *offset = offset.checked_add(align - 1).unwrap() & !(align - 1);
75    *max_align = core::cmp::max(*max_align, align);
76    *offset
77}
78
79/// Define a new field of size and alignment `bytes`, updating the object's
80/// total `size` and `align` as necessary. The offset of the new field is
81/// returned.
82#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
83fn field(size: &mut u32, align: &mut u32, bytes: u32) -> u32 {
84    let offset = align_up(size, align, bytes);
85    *size += bytes;
86    offset
87}
88
89/// Common code to define a GC array's layout, given the size and alignment of
90/// the collector's GC header and its expected offset of the array length field.
91#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
92fn common_array_layout(
93    ty: &WasmArrayType,
94    header_size: u32,
95    header_align: u32,
96    expected_array_length_offset: u32,
97) -> GcArrayLayout {
98    use core::mem;
99
100    assert!(header_size >= crate::VM_GC_HEADER_SIZE);
101    assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
102
103    let mut size = header_size;
104    let mut align = header_align;
105
106    let length_field_size = u32::try_from(mem::size_of::<u32>()).unwrap();
107    let length_field_offset = field(&mut size, &mut align, length_field_size);
108    assert_eq!(length_field_offset, expected_array_length_offset);
109
110    let elem_size = byte_size_of_wasm_ty_in_gc_heap(&ty.0.element_type);
111    let elems_offset = align_up(&mut size, &mut align, elem_size);
112    assert_eq!(elems_offset, size);
113
114    let elems_are_gc_refs = ty.0.element_type.is_vmgcref_type_and_not_i31();
115    if elems_are_gc_refs {
116        debug_assert_eq!(
117            length_field_offset + length_field_size,
118            elems_offset,
119            "DRC collector relies on GC ref elements appearing directly after the length field, without any padding",
120        );
121    }
122
123    GcArrayLayout {
124        base_size: size,
125        align,
126        elem_size,
127        elems_are_gc_refs,
128    }
129}
130
131/// Shared layout code for structs and exception objects, which are
132/// identical except for the tag field (present in
133/// exceptions). Returns `(size, align, fields)`.
134#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
135fn common_struct_or_exn_layout(
136    fields: &[crate::WasmFieldType],
137    header_size: u32,
138    header_align: u32,
139) -> (u32, u32, TryVec<GcStructLayoutField>) {
140    use crate::PanicOnOom as _;
141
142    // Process each field, aligning it to its natural alignment.
143    //
144    // We don't try and do any fancy field reordering to minimize padding (yet?)
145    // because (a) the toolchain probably already did that and (b) we're just
146    // doing the simple thing first, and (c) this is tricky in the presence of
147    // subtyping where we need a subtype's fields to be assigned the same
148    // offsets as its supertype's fields. We can come back and improve things
149    // here if we find that (a) isn't actually holding true in practice.
150
151    let mut size = header_size;
152    let mut align = header_align;
153
154    let fields = fields
155        .iter()
156        .map(|f| {
157            let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
158            let offset = field(&mut size, &mut align, field_size);
159            let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();
160            GcStructLayoutField { offset, is_gc_ref }
161        })
162        .try_collect::<TryVec<_>, _>()
163        .panic_on_oom();
164
165    // Ensure that the final size is a multiple of the alignment, for
166    // simplicity.
167    let align_size_to = align;
168    align_up(&mut size, &mut align, align_size_to);
169
170    (size, align, fields)
171}
172
173/// Common code to define a GC struct's layout, given the size and alignment of
174/// the collector's GC header and its expected offset of the array length field.
175#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
176fn common_struct_layout(
177    ty: &WasmStructType,
178    header_size: u32,
179    header_align: u32,
180) -> GcStructLayout {
181    assert!(header_size >= crate::VM_GC_HEADER_SIZE);
182    assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
183
184    let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);
185
186    GcStructLayout {
187        size,
188        align,
189        fields,
190        is_exception: false,
191    }
192}
193
194/// Common code to define a GC exception object's layout, given the
195/// size and alignment of the collector's GC header and its expected
196/// offset of the array length field.
197#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
198fn common_exn_layout(ty: &WasmExnType, header_size: u32, header_align: u32) -> GcStructLayout {
199    assert!(header_size >= crate::VM_GC_HEADER_SIZE);
200    assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
201
202    // Compute a struct layout, with extra header size for the
203    // `(instance_idx, tag_idx)` fields.
204    assert!(header_align >= 8);
205    let header_size = header_size + 2 * u32::try_from(core::mem::size_of::<u32>()).unwrap();
206
207    let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);
208
209    GcStructLayout {
210        size,
211        align,
212        fields,
213        is_exception: true,
214    }
215}
216
217/// A trait for getting the layout of a Wasm GC struct or array inside a
218/// particular collector.
219pub trait GcTypeLayouts {
220    /// The offset of an array's length field.
221    ///
222    /// This must be the same for all arrays in the heap, regardless of their
223    /// element type.
224    fn array_length_field_offset(&self) -> u32;
225
226    /// The offset of an exception object's tag reference: defining
227    /// instance index field.
228    ///
229    /// This must be the same for all exception objects in the heap,
230    /// regardless of their specific signature.
231    fn exception_tag_instance_offset(&self) -> u32;
232
233    /// The offset of an exception object's tag reference: defined tag
234    /// index field.
235    ///
236    /// This must be the same for all exception objects in the heap,
237    /// regardless of their specific signature.
238    fn exception_tag_defined_offset(&self) -> u32;
239
240    /// Get this collector's layout for the given composite type.
241    ///
242    /// Returns `None` if the type is a function type, as functions are not
243    /// managed by the GC.
244    fn gc_layout(&self, ty: &WasmCompositeType) -> Option<GcLayout> {
245        assert!(!ty.shared);
246        match &ty.inner {
247            WasmCompositeInnerType::Array(ty) => Some(self.array_layout(ty).into()),
248            WasmCompositeInnerType::Struct(ty) => Some(Arc::new(self.struct_layout(ty)).into()),
249            WasmCompositeInnerType::Func(_) => None,
250            WasmCompositeInnerType::Cont(_) => {
251                unimplemented!("Stack switching feature not compatible with GC, yet")
252            }
253            WasmCompositeInnerType::Exn(ty) => Some(Arc::new(self.exn_layout(ty)).into()),
254        }
255    }
256
257    /// Get this collector's layout for the given array type.
258    fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout;
259
260    /// Get this collector's layout for the given struct type.
261    fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout;
262
263    /// Get this collector's layout for the given exception type.
264    fn exn_layout(&self, ty: &WasmExnType) -> GcStructLayout;
265}
266
267/// The layout of a GC-managed object.
268#[derive(Clone, Debug)]
269pub enum GcLayout {
270    /// The layout of a GC-managed array object.
271    Array(GcArrayLayout),
272
273    /// The layout of a GC-managed struct or exception object.
274    Struct(Arc<GcStructLayout>),
275}
276
277impl From<GcArrayLayout> for GcLayout {
278    fn from(layout: GcArrayLayout) -> Self {
279        Self::Array(layout)
280    }
281}
282
283impl From<Arc<GcStructLayout>> for GcLayout {
284    fn from(layout: Arc<GcStructLayout>) -> Self {
285        Self::Struct(layout)
286    }
287}
288
289impl TryClone for GcLayout {
290    fn try_clone(&self) -> core::result::Result<Self, wasmtime_core::error::OutOfMemory> {
291        Ok(self.clone())
292    }
293}
294
295impl GcLayout {
296    /// Get the underlying `GcStructLayout`, or panic.
297    #[track_caller]
298    pub fn unwrap_struct(&self) -> &Arc<GcStructLayout> {
299        match self {
300            Self::Struct(s) => s,
301            _ => panic!("GcLayout::unwrap_struct on non-struct GC layout"),
302        }
303    }
304
305    /// Get the underlying `GcArrayLayout`, or panic.
306    #[track_caller]
307    pub fn unwrap_array(&self) -> &GcArrayLayout {
308        match self {
309            Self::Array(a) => a,
310            _ => panic!("GcLayout::unwrap_array on non-array GC layout"),
311        }
312    }
313}
314
315/// The layout of a GC-managed array.
316///
317/// This layout is only valid for use with the GC runtime that created it. It is
318/// not valid to use one GC runtime's layout with another GC runtime, doing so
319/// is memory safe but will lead to general incorrectness like panics and wrong
320/// results.
321///
322/// All offsets are from the start of the object; that is, the size of the GC
323/// header (for example) is included in the offset.
324///
325/// All arrays are composed of the generic `VMGcHeader`, followed by
326/// collector-specific fields, followed by the contiguous array elements
327/// themselves. The array elements must be aligned to the element type's natural
328/// alignment.
329#[derive(Clone, Debug)]
330pub struct GcArrayLayout {
331    /// The size of this array object, without any elements.
332    ///
333    /// The array's elements, if any, must begin at exactly this offset.
334    pub base_size: u32,
335
336    /// The alignment of this array.
337    pub align: u32,
338
339    /// The size and natural alignment of each element in this array.
340    pub elem_size: u32,
341
342    /// Whether or not the elements of this array are GC references or not.
343    pub elems_are_gc_refs: bool,
344}
345
346impl GcArrayLayout {
347    /// Get the total size of this array for a given length of elements.
348    #[inline]
349    pub fn size_for_len(&self, len: u32) -> u32 {
350        self.elem_offset(len)
351    }
352
353    /// Get the offset of the `i`th element in an array with this layout.
354    #[inline]
355    pub fn elem_offset(&self, i: u32) -> u32 {
356        self.base_size + i * self.elem_size
357    }
358
359    /// Get a `core::alloc::Layout` for an array of this type with the given
360    /// length.
361    pub fn layout(&self, len: u32) -> Layout {
362        let size = self.size_for_len(len);
363        let size = usize::try_from(size).unwrap();
364        let align = usize::try_from(self.align).unwrap();
365        Layout::from_size_align(size, align).unwrap()
366    }
367}
368
369/// The layout for a GC-managed struct type or exception type.
370///
371/// This layout is only valid for use with the GC runtime that created it. It is
372/// not valid to use one GC runtime's layout with another GC runtime, doing so
373/// is memory safe but will lead to general incorrectness like panics and wrong
374/// results.
375///
376/// All offsets are from the start of the object; that is, the size of the GC
377/// header (for example) is included in the offset.
378///
379/// Note that these are reused between structs and exceptions to avoid
380/// unnecessary code duplication. In both cases, the objects are
381/// tuples of typed fields with a certain size. The only difference in
382/// practice is that an exception object also carries a tag reference
383/// (at a fixed offset as per `GcTypeLayouts::exception_tag_offset`).
384#[derive(Debug)]
385pub struct GcStructLayout {
386    /// The size (in bytes) of this struct.
387    pub size: u32,
388
389    /// The alignment (in bytes) of this struct.
390    pub align: u32,
391
392    /// The fields of this struct. The `i`th entry contains information about
393    /// the `i`th struct field's layout.
394    pub fields: TryVec<GcStructLayoutField>,
395
396    /// Whether this is an exception object layout.
397    pub is_exception: bool,
398}
399
400impl TryClone for GcStructLayout {
401    fn try_clone(&self) -> Result<Self, OutOfMemory> {
402        Ok(GcStructLayout {
403            size: self.size,
404            align: self.align,
405            fields: self.fields.try_clone()?,
406            is_exception: self.is_exception,
407        })
408    }
409}
410
411impl GcStructLayout {
412    /// Get a `core::alloc::Layout` for a struct of this type.
413    pub fn layout(&self) -> Layout {
414        let size = usize::try_from(self.size).unwrap();
415        let align = usize::try_from(self.align).unwrap();
416        Layout::from_size_align(size, align).unwrap()
417    }
418}
419
420/// A field in a `GcStructLayout`.
421#[derive(Clone, Copy, Debug)]
422pub struct GcStructLayoutField {
423    /// The offset (in bytes) of this field inside instances of this type.
424    pub offset: u32,
425
426    /// Whether or not this field might contain a reference to another GC
427    /// object.
428    ///
429    /// Note: it is okay for this to be `false` for `i31ref`s, since they never
430    /// actually reference another GC object.
431    pub is_gc_ref: bool,
432}
433
434impl TryClone for GcStructLayoutField {
435    fn try_clone(&self) -> Result<Self, OutOfMemory> {
436        Ok(*self)
437    }
438}
439
440/// The kind of an object in a GC heap.
441///
442/// Note that this type is accessed from Wasm JIT code.
443///
444/// `VMGcKind` is a bitset where to test if `a` is a subtype of an
445/// "abstract-ish" type `b`, we can simply use a single bitwise-and operation:
446///
447/// ```ignore
448/// a <: b   iff   a & b == b
449/// ```
450///
451/// For example, because `VMGcKind::AnyRef` has the high bit set, every kind
452/// representing some subtype of `anyref` also has its high bit set.
453///
454/// We say "abstract-ish" type because in addition to the abstract heap types
455/// (other than `i31`) we also have variants for `externref`s that have been
456/// converted into an `anyref` via `extern.convert_any` and `externref`s that
457/// have been converted into an `anyref` via `any.convert_extern`. Note that in
458/// the latter case, because `any.convert_extern $foo` produces a value that is
459/// not an instance of `eqref`, `VMGcKind::AnyOfExternRef & VMGcKind::EqRef !=
460/// VMGcKind::EqRef`.
461///
462/// Furthermore, this type only uses the highest 6 bits of its `u32`
463/// representation, allowing the lower 26 bits to be bitpacked with other stuff
464/// as users see fit.
465#[repr(u32)]
466#[derive(Clone, Copy, Debug, PartialEq, Eq)]
467#[rustfmt::skip]
468#[expect(missing_docs, reason = "self-describing variants")]
469pub enum VMGcKind {
470    ExternRef      = 0b010000 << 26,
471    AnyRef         = 0b100000 << 26,
472    EqRef          = 0b101000 << 26,
473    ArrayRef       = 0b101010 << 26,
474    StructRef      = 0b101100 << 26,
475    ExnRef         = 0b000001 << 26,
476}
477
478/// The size of the `VMGcKind` in bytes.
479pub const VM_GC_KIND_SIZE: u8 = 4;
480
481const _: () = assert!(VM_GC_KIND_SIZE as usize == core::mem::size_of::<VMGcKind>());
482
483impl VMGcKind {
484    /// Mask this value with a `u32` to get just the bits that `VMGcKind` uses.
485    pub const MASK: u32 = 0b111111 << 26;
486
487    /// Mask this value with a `u32` that potentially contains a `VMGcKind` to
488    /// get the bits that `VMGcKind` doesn't use.
489    pub const UNUSED_MASK: u32 = !Self::MASK;
490
491    /// Does the given value fit in the unused bits of a `VMGcKind`?
492    #[inline]
493    pub fn value_fits_in_unused_bits(value: u32) -> bool {
494        (value & Self::UNUSED_MASK) == value
495    }
496
497    /// Convert the given value into a `VMGcKind` by masking off the unused
498    /// bottom bits.
499    #[inline]
500    pub fn from_high_bits_of_u32(val: u32) -> VMGcKind {
501        let masked = val & Self::MASK;
502        let result = Self::try_from_u32(masked)
503            .unwrap_or_else(|| panic!("invalid `VMGcKind`: {masked:#032b}"));
504
505        let poison_kind = u32::from_le_bytes([POISON, POISON, POISON, POISON]) & VMGcKind::MASK;
506        debug_assert_ne!(
507            masked, poison_kind,
508            "No valid `VMGcKind` should overlap with the poison pattern"
509        );
510
511        result
512    }
513
514    /// Does this kind match the other kind?
515    ///
516    /// That is, is this kind a subtype of the other kind?
517    #[inline]
518    pub fn matches(self, other: Self) -> bool {
519        (self.as_u32() & other.as_u32()) == other.as_u32()
520    }
521
522    /// Get this `VMGcKind` as a raw `u32`.
523    #[inline]
524    pub fn as_u32(self) -> u32 {
525        self as u32
526    }
527
528    /// Try to convert a `u32` into a `VMGcKind`.
529    ///
530    /// Returns `None` if the value doesn't match any known kind.
531    #[inline]
532    pub fn try_from_u32(x: u32) -> Option<VMGcKind> {
533        match x {
534            _ if x == Self::ExternRef.as_u32() => Some(Self::ExternRef),
535            _ if x == Self::AnyRef.as_u32() => Some(Self::AnyRef),
536            _ if x == Self::EqRef.as_u32() => Some(Self::EqRef),
537            _ if x == Self::ArrayRef.as_u32() => Some(Self::ArrayRef),
538            _ if x == Self::StructRef.as_u32() => Some(Self::StructRef),
539            _ if x == Self::ExnRef.as_u32() => Some(Self::ExnRef),
540            _ => None,
541        }
542    }
543}
544
545#[cfg(test)]
546mod tests {
547    use super::VMGcKind::*;
548    use crate::prelude::*;
549
550    #[test]
551    fn kind_matches() {
552        let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef, ExnRef];
553
554        for (sup, subs) in [
555            (ExternRef, vec![]),
556            (AnyRef, vec![EqRef, ArrayRef, StructRef]),
557            // N.B.: exnref is not an eqref.
558            (EqRef, vec![ArrayRef, StructRef]),
559            (ArrayRef, vec![]),
560            (StructRef, vec![]),
561            (ExnRef, vec![]),
562        ] {
563            assert!(sup.matches(sup));
564            for sub in &subs {
565                assert!(sub.matches(sup));
566            }
567            for kind in all.iter().filter(|k| **k != sup && !subs.contains(k)) {
568                assert!(!kind.matches(sup));
569            }
570        }
571    }
572}