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