//! Target- and pointer-width-agnostic definitions of GC-related types and1//! constants.2//!3//! These definitions are suitable for use both during compilation and at4//! runtime.5//!6//! Note: We don't bother gating these on `cfg(feature = "gc")` because that7//! makes downstream uses pretty annoying, and the primary thing we want to gate8//! on our various `gc` cargo features is the actual garbage collection9//! functions and their associated impact on binary size anyways.1011#[cfg(feature = "gc-drc")]12pub mod drc;1314#[cfg(feature = "gc-null")]15pub mod null;1617use crate::{18WasmArrayType, WasmCompositeInnerType, WasmCompositeType, WasmStorageType, WasmStructType,19WasmValType,20};21use crate::{WasmExnType, prelude::*};22use core::alloc::Layout;2324/// Discriminant to check whether GC reference is an `i31ref` or not.25pub const I31_DISCRIMINANT: u32 = 1;2627/// The size of the `VMGcHeader` in bytes.28pub const VM_GC_HEADER_SIZE: u32 = 8;2930/// The minimum alignment of the `VMGcHeader` in bytes.31pub const VM_GC_HEADER_ALIGN: u32 = 8;3233/// The offset of the `VMGcKind` field in the `VMGcHeader`.34pub const VM_GC_HEADER_KIND_OFFSET: u32 = 0;3536/// The offset of the `VMSharedTypeIndex` field in the `VMGcHeader`.37pub const VM_GC_HEADER_TYPE_INDEX_OFFSET: u32 = 4;3839/// Get the byte size of the given Wasm type when it is stored inside the GC40/// heap.41pub fn byte_size_of_wasm_ty_in_gc_heap(ty: &WasmStorageType) -> u32 {42match ty {43WasmStorageType::I8 => 1,44WasmStorageType::I16 => 2,45WasmStorageType::Val(ty) => match ty {46WasmValType::I32 | WasmValType::F32 | WasmValType::Ref(_) => 4,47WasmValType::I64 | WasmValType::F64 => 8,48WasmValType::V128 => 16,49},50}51}5253/// Align `offset` up to `bytes`, updating `max_align` if `align` is the54/// new maximum alignment, and returning the aligned offset.55#[cfg(any(feature = "gc-drc", feature = "gc-null"))]56fn align_up(offset: &mut u32, max_align: &mut u32, align: u32) -> u32 {57debug_assert!(max_align.is_power_of_two());58debug_assert!(align.is_power_of_two());59*offset = offset.checked_add(align - 1).unwrap() & !(align - 1);60*max_align = core::cmp::max(*max_align, align);61*offset62}6364/// Define a new field of size and alignment `bytes`, updating the object's65/// total `size` and `align` as necessary. The offset of the new field is66/// returned.67#[cfg(any(feature = "gc-drc", feature = "gc-null"))]68fn field(size: &mut u32, align: &mut u32, bytes: u32) -> u32 {69let offset = align_up(size, align, bytes);70*size += bytes;71offset72}7374/// Common code to define a GC array's layout, given the size and alignment of75/// the collector's GC header and its expected offset of the array length field.76#[cfg(any(feature = "gc-drc", feature = "gc-null"))]77fn common_array_layout(78ty: &WasmArrayType,79header_size: u32,80header_align: u32,81expected_array_length_offset: u32,82) -> GcArrayLayout {83use core::mem;8485assert!(header_size >= crate::VM_GC_HEADER_SIZE);86assert!(header_align >= crate::VM_GC_HEADER_ALIGN);8788let mut size = header_size;89let mut align = header_align;9091let length_field_size = u32::try_from(mem::size_of::<u32>()).unwrap();92let length_field_offset = field(&mut size, &mut align, length_field_size);93assert_eq!(length_field_offset, expected_array_length_offset);9495let elem_size = byte_size_of_wasm_ty_in_gc_heap(&ty.0.element_type);96let elems_offset = align_up(&mut size, &mut align, elem_size);97assert_eq!(elems_offset, size);9899let elems_are_gc_refs = ty.0.element_type.is_vmgcref_type_and_not_i31();100if elems_are_gc_refs {101debug_assert_eq!(102length_field_offset + length_field_size,103elems_offset,104"DRC collector relies on GC ref elements appearing directly after the length field, without any padding",105);106}107108GcArrayLayout {109base_size: size,110align,111elem_size,112elems_are_gc_refs,113}114}115116/// Shared layout code for structs and exception objects, which are117/// identical except for the tag field (present in118/// exceptions). Returns `(size, align, fields)`.119#[cfg(any(feature = "gc-null", feature = "gc-drc"))]120fn common_struct_or_exn_layout(121fields: &[crate::WasmFieldType],122header_size: u32,123header_align: u32,124) -> (u32, u32, Vec<GcStructLayoutField>) {125// Process each field, aligning it to its natural alignment.126//127// We don't try and do any fancy field reordering to minimize padding (yet?)128// because (a) the toolchain probably already did that and (b) we're just129// doing the simple thing first, and (c) this is tricky in the presence of130// subtyping where we need a subtype's fields to be assigned the same131// offsets as its supertype's fields. We can come back and improve things132// here if we find that (a) isn't actually holding true in practice.133134let mut size = header_size;135let mut align = header_align;136137let fields = fields138.iter()139.map(|f| {140let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);141let offset = field(&mut size, &mut align, field_size);142let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();143GcStructLayoutField { offset, is_gc_ref }144})145.collect();146147// Ensure that the final size is a multiple of the alignment, for148// simplicity.149let align_size_to = align;150align_up(&mut size, &mut align, align_size_to);151152(size, align, fields)153}154155/// Common code to define a GC struct's layout, given the size and alignment of156/// the collector's GC header and its expected offset of the array length field.157#[cfg(any(feature = "gc-null", feature = "gc-drc"))]158fn common_struct_layout(159ty: &WasmStructType,160header_size: u32,161header_align: u32,162) -> GcStructLayout {163assert!(header_size >= crate::VM_GC_HEADER_SIZE);164assert!(header_align >= crate::VM_GC_HEADER_ALIGN);165166let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);167168GcStructLayout {169size,170align,171fields,172is_exception: false,173}174}175176/// Common code to define a GC exception object's layout, given the177/// size and alignment of the collector's GC header and its expected178/// offset of the array length field.179#[cfg(any(feature = "gc-null", feature = "gc-drc"))]180fn common_exn_layout(ty: &WasmExnType, header_size: u32, header_align: u32) -> GcStructLayout {181assert!(header_size >= crate::VM_GC_HEADER_SIZE);182assert!(header_align >= crate::VM_GC_HEADER_ALIGN);183184// Compute a struct layout, with extra header size for the185// `(instance_idx, tag_idx)` fields.186assert!(header_align >= 8);187let header_size = header_size + 2 * u32::try_from(core::mem::size_of::<u32>()).unwrap();188189let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);190191GcStructLayout {192size,193align,194fields,195is_exception: true,196}197}198199/// A trait for getting the layout of a Wasm GC struct or array inside a200/// particular collector.201pub trait GcTypeLayouts {202/// The offset of an array's length field.203///204/// This must be the same for all arrays in the heap, regardless of their205/// element type.206fn array_length_field_offset(&self) -> u32;207208/// The offset of an exception object's tag reference: defining209/// instance index field.210///211/// This must be the same for all exception objects in the heap,212/// regardless of their specific signature.213fn exception_tag_instance_offset(&self) -> u32;214215/// The offset of an exception object's tag reference: defined tag216/// index field.217///218/// This must be the same for all exception objects in the heap,219/// regardless of their specific signature.220fn exception_tag_defined_offset(&self) -> u32;221222/// Get this collector's layout for the given composite type.223///224/// Returns `None` if the type is a function type, as functions are not225/// managed by the GC.226fn gc_layout(&self, ty: &WasmCompositeType) -> Option<GcLayout> {227assert!(!ty.shared);228match &ty.inner {229WasmCompositeInnerType::Array(ty) => Some(self.array_layout(ty).into()),230WasmCompositeInnerType::Struct(ty) => Some(self.struct_layout(ty).into()),231WasmCompositeInnerType::Func(_) => None,232WasmCompositeInnerType::Cont(_) => {233unimplemented!("Stack switching feature not compatbile with GC, yet")234}235WasmCompositeInnerType::Exn(ty) => Some(self.exn_layout(ty).into()),236}237}238239/// Get this collector's layout for the given array type.240fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout;241242/// Get this collector's layout for the given struct type.243fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout;244245/// Get this collector's layout for the given exception type.246fn exn_layout(&self, ty: &WasmExnType) -> GcStructLayout;247}248249/// The layout of a GC-managed object.250#[derive(Clone, Debug)]251pub enum GcLayout {252/// The layout of a GC-managed array object.253Array(GcArrayLayout),254255/// The layout of a GC-managed struct or exception object.256Struct(GcStructLayout),257}258259impl From<GcArrayLayout> for GcLayout {260fn from(layout: GcArrayLayout) -> Self {261Self::Array(layout)262}263}264265impl From<GcStructLayout> for GcLayout {266fn from(layout: GcStructLayout) -> Self {267Self::Struct(layout)268}269}270271impl GcLayout {272/// Get the underlying `GcStructLayout`, or panic.273#[track_caller]274pub fn unwrap_struct(&self) -> &GcStructLayout {275match self {276Self::Struct(s) => s,277_ => panic!("GcLayout::unwrap_struct on non-struct GC layout"),278}279}280281/// Get the underlying `GcArrayLayout`, or panic.282#[track_caller]283pub fn unwrap_array(&self) -> &GcArrayLayout {284match self {285Self::Array(a) => a,286_ => panic!("GcLayout::unwrap_array on non-array GC layout"),287}288}289}290291/// The layout of a GC-managed array.292///293/// This layout is only valid for use with the GC runtime that created it. It is294/// not valid to use one GC runtime's layout with another GC runtime, doing so295/// is memory safe but will lead to general incorrectness like panics and wrong296/// results.297///298/// All offsets are from the start of the object; that is, the size of the GC299/// header (for example) is included in the offset.300///301/// All arrays are composed of the generic `VMGcHeader`, followed by302/// collector-specific fields, followed by the contiguous array elements303/// themselves. The array elements must be aligned to the element type's natural304/// alignment.305#[derive(Clone, Debug)]306pub struct GcArrayLayout {307/// The size of this array object, without any elements.308///309/// The array's elements, if any, must begin at exactly this offset.310pub base_size: u32,311312/// The alignment of this array.313pub align: u32,314315/// The size and natural alignment of each element in this array.316pub elem_size: u32,317318/// Whether or not the elements of this array are GC references or not.319pub elems_are_gc_refs: bool,320}321322impl GcArrayLayout {323/// Get the total size of this array for a given length of elements.324#[inline]325pub fn size_for_len(&self, len: u32) -> u32 {326self.elem_offset(len)327}328329/// Get the offset of the `i`th element in an array with this layout.330#[inline]331pub fn elem_offset(&self, i: u32) -> u32 {332self.base_size + i * self.elem_size333}334335/// Get a `core::alloc::Layout` for an array of this type with the given336/// length.337pub fn layout(&self, len: u32) -> Layout {338let size = self.size_for_len(len);339let size = usize::try_from(size).unwrap();340let align = usize::try_from(self.align).unwrap();341Layout::from_size_align(size, align).unwrap()342}343}344345/// The layout for a GC-managed struct type or exception type.346///347/// This layout is only valid for use with the GC runtime that created it. It is348/// not valid to use one GC runtime's layout with another GC runtime, doing so349/// is memory safe but will lead to general incorrectness like panics and wrong350/// results.351///352/// All offsets are from the start of the object; that is, the size of the GC353/// header (for example) is included in the offset.354///355/// Note that these are reused between structs and exceptions to avoid356/// unnecessary code duplication. In both cases, the objects are357/// tuples of typed fields with a certain size. The only difference in358/// practice is that an exception object also carries a tag reference359/// (at a fixed offset as per `GcTypeLayouts::exception_tag_offset`).360#[derive(Clone, Debug)]361pub struct GcStructLayout {362/// The size (in bytes) of this struct.363pub size: u32,364365/// The alignment (in bytes) of this struct.366pub align: u32,367368/// The fields of this struct. The `i`th entry contains information about369/// the `i`th struct field's layout.370pub fields: Vec<GcStructLayoutField>,371372/// Whether this is an exception object layout.373pub is_exception: bool,374}375376impl GcStructLayout {377/// Get a `core::alloc::Layout` for a struct of this type.378pub fn layout(&self) -> Layout {379let size = usize::try_from(self.size).unwrap();380let align = usize::try_from(self.align).unwrap();381Layout::from_size_align(size, align).unwrap()382}383}384385/// A field in a `GcStructLayout`.386#[derive(Clone, Copy, Debug)]387pub struct GcStructLayoutField {388/// The offset (in bytes) of this field inside instances of this type.389pub offset: u32,390391/// Whether or not this field might contain a reference to another GC392/// object.393///394/// Note: it is okay for this to be `false` for `i31ref`s, since they never395/// actually reference another GC object.396pub is_gc_ref: bool,397}398399/// The kind of an object in a GC heap.400///401/// Note that this type is accessed from Wasm JIT code.402///403/// `VMGcKind` is a bitset where to test if `a` is a subtype of an404/// "abstract-ish" type `b`, we can simply use a single bitwise-and operation:405///406/// ```ignore407/// a <: b iff a & b == b408/// ```409///410/// For example, because `VMGcKind::AnyRef` has the high bit set, every kind411/// representing some subtype of `anyref` also has its high bit set.412///413/// We say "abstract-ish" type because in addition to the abstract heap types414/// (other than `i31`) we also have variants for `externref`s that have been415/// converted into an `anyref` via `extern.convert_any` and `externref`s that416/// have been converted into an `anyref` via `any.convert_extern`. Note that in417/// the latter case, because `any.convert_extern $foo` produces a value that is418/// not an instance of `eqref`, `VMGcKind::AnyOfExternRef & VMGcKind::EqRef !=419/// VMGcKind::EqRef`.420///421/// Furthermore, this type only uses the highest 6 bits of its `u32`422/// representation, allowing the lower 26 bits to be bitpacked with other stuff423/// as users see fit.424#[repr(u32)]425#[derive(Clone, Copy, Debug, PartialEq, Eq)]426#[rustfmt::skip]427#[expect(missing_docs, reason = "self-describing variants")]428pub enum VMGcKind {429ExternRef = 0b010000 << 26,430AnyRef = 0b100000 << 26,431EqRef = 0b101000 << 26,432ArrayRef = 0b101010 << 26,433StructRef = 0b101100 << 26,434ExnRef = 0b000001 << 26,435}436437/// The size of the `VMGcKind` in bytes.438pub const VM_GC_KIND_SIZE: u8 = 4;439440const _: () = assert!(VM_GC_KIND_SIZE as usize == core::mem::size_of::<VMGcKind>());441442impl VMGcKind {443/// Mask this value with a `u32` to get just the bits that `VMGcKind` uses.444pub const MASK: u32 = 0b111111 << 26;445446/// Mask this value with a `u32` that potentially contains a `VMGcKind` to447/// get the bits that `VMGcKind` doesn't use.448pub const UNUSED_MASK: u32 = !Self::MASK;449450/// Does the given value fit in the unused bits of a `VMGcKind`?451#[inline]452pub fn value_fits_in_unused_bits(value: u32) -> bool {453(value & Self::UNUSED_MASK) == value454}455456/// Convert the given value into a `VMGcKind` by masking off the unused457/// bottom bits.458#[inline]459pub fn from_high_bits_of_u32(val: u32) -> VMGcKind {460let masked = val & Self::MASK;461match masked {462x if x == Self::ExternRef.as_u32() => Self::ExternRef,463x if x == Self::AnyRef.as_u32() => Self::AnyRef,464x if x == Self::EqRef.as_u32() => Self::EqRef,465x if x == Self::ArrayRef.as_u32() => Self::ArrayRef,466x if x == Self::StructRef.as_u32() => Self::StructRef,467x if x == Self::ExnRef.as_u32() => Self::ExnRef,468_ => panic!("invalid `VMGcKind`: {masked:#032b}"),469}470}471472/// Does this kind match the other kind?473///474/// That is, is this kind a subtype of the other kind?475#[inline]476pub fn matches(self, other: Self) -> bool {477(self.as_u32() & other.as_u32()) == other.as_u32()478}479480/// Get this `VMGcKind` as a raw `u32`.481#[inline]482pub fn as_u32(self) -> u32 {483self as u32484}485}486487#[cfg(test)]488mod tests {489use super::VMGcKind::*;490use crate::prelude::*;491492#[test]493fn kind_matches() {494let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef, ExnRef];495496for (sup, subs) in [497(ExternRef, vec![]),498(AnyRef, vec![EqRef, ArrayRef, StructRef]),499// N.B.: exnref is not an eqref.500(EqRef, vec![ArrayRef, StructRef]),501(ArrayRef, vec![]),502(StructRef, vec![]),503(ExnRef, vec![]),504] {505assert!(sup.matches(sup));506for sub in &subs {507assert!(sub.matches(sup));508}509for kind in all.iter().filter(|k| **k != sup && !subs.contains(k)) {510assert!(!kind.matches(sup));511}512}513}514}515516517