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