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