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::prelude::*;
19use crate::{
20    WasmArrayType, WasmCompositeInnerType, WasmCompositeType, WasmStorageType, WasmStructType,
21    WasmValType,
22};
23use core::alloc::Layout;
24
25/// Discriminant to check whether GC reference is an `i31ref` or not.
26pub const I31_DISCRIMINANT: u64 = 1;
27
28/// A mask that can be used to check for non-null and non-i31ref GC references
29/// with a single bitwise-and operation.
30pub const NON_NULL_NON_I31_MASK: u64 = !I31_DISCRIMINANT;
31
32/// The size of the `VMGcHeader` in bytes.
33pub const VM_GC_HEADER_SIZE: u32 = 8;
34
35/// The minimum alignment of the `VMGcHeader` in bytes.
36pub const VM_GC_HEADER_ALIGN: u32 = 8;
37
38/// The offset of the `VMGcKind` field in the `VMGcHeader`.
39pub const VM_GC_HEADER_KIND_OFFSET: u32 = 0;
40
41/// The offset of the `VMSharedTypeIndex` field in the `VMGcHeader`.
42pub const VM_GC_HEADER_TYPE_INDEX_OFFSET: u32 = 4;
43
44/// Get the byte size of the given Wasm type when it is stored inside the GC
45/// heap.
46pub fn byte_size_of_wasm_ty_in_gc_heap(ty: &WasmStorageType) -> u32 {
47    match ty {
48        WasmStorageType::I8 => 1,
49        WasmStorageType::I16 => 2,
50        WasmStorageType::Val(ty) => match ty {
51            WasmValType::I32 | WasmValType::F32 | WasmValType::Ref(_) => 4,
52            WasmValType::I64 | WasmValType::F64 => 8,
53            WasmValType::V128 => 16,
54        },
55    }
56}
57
58/// Align `offset` up to `bytes`, updating `max_align` if `align` is the
59/// new maximum alignment, and returning the aligned offset.
60#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
61fn align_up(offset: &mut u32, max_align: &mut u32, align: u32) -> u32 {
62    debug_assert!(max_align.is_power_of_two());
63    debug_assert!(align.is_power_of_two());
64    *offset = offset.checked_add(align - 1).unwrap() & !(align - 1);
65    *max_align = core::cmp::max(*max_align, align);
66    *offset
67}
68
69/// Define a new field of size and alignment `bytes`, updating the object's
70/// total `size` and `align` as necessary. The offset of the new field is
71/// returned.
72#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
73fn field(size: &mut u32, align: &mut u32, bytes: u32) -> u32 {
74    let offset = align_up(size, align, bytes);
75    *size += bytes;
76    offset
77}
78
79/// Common code to define a GC array's layout, given the size and alignment of
80/// the collector's GC header and its expected offset of the array length field.
81#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
82fn common_array_layout(
83    ty: &WasmArrayType,
84    header_size: u32,
85    header_align: u32,
86    expected_array_length_offset: u32,
87) -> GcArrayLayout {
88    assert!(header_size >= crate::VM_GC_HEADER_SIZE);
89    assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
90
91    let mut size = header_size;
92    let mut align = header_align;
93
94    let length_field_offset = field(&mut size, &mut align, 4);
95    assert_eq!(length_field_offset, expected_array_length_offset);
96
97    let elem_size = byte_size_of_wasm_ty_in_gc_heap(&ty.0.element_type);
98    let elems_offset = align_up(&mut size, &mut align, elem_size);
99    assert_eq!(elems_offset, size);
100
101    GcArrayLayout {
102        base_size: size,
103        align,
104        elem_size,
105    }
106}
107
108/// Common code to define a GC struct's layout, given the size and alignment of
109/// the collector's GC header and its expected offset of the array length field.
110#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
111fn common_struct_layout(
112    ty: &WasmStructType,
113    header_size: u32,
114    header_align: u32,
115) -> GcStructLayout {
116    assert!(header_size >= crate::VM_GC_HEADER_SIZE);
117    assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
118
119    // Process each field, aligning it to its natural alignment.
120    //
121    // We don't try and do any fancy field reordering to minimize padding
122    // (yet?) because (a) the toolchain probably already did that and (b)
123    // we're just doing the simple thing first. We can come back and improve
124    // things here if we find that (a) isn't actually holding true in
125    // practice.
126    let mut size = header_size;
127    let mut align = header_align;
128
129    let fields = ty
130        .fields
131        .iter()
132        .map(|f| {
133            let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
134            field(&mut size, &mut align, field_size)
135        })
136        .collect();
137
138    // Ensure that the final size is a multiple of the alignment, for
139    // simplicity.
140    let align_size_to = align;
141    align_up(&mut size, &mut align, align_size_to);
142
143    GcStructLayout {
144        size,
145        align,
146        fields,
147    }
148}
149
150/// A trait for getting the layout of a Wasm GC struct or array inside a
151/// particular collector.
152pub trait GcTypeLayouts {
153    /// The offset of an array's length field.
154    ///
155    /// This must be the same for all arrays in the heap, regardless of their
156    /// element type.
157    fn array_length_field_offset(&self) -> u32;
158
159    /// Get this collector's layout for the given composite type.
160    ///
161    /// Returns `None` if the type is a function type, as functions are not
162    /// managed by the GC.
163    fn gc_layout(&self, ty: &WasmCompositeType) -> Option<GcLayout> {
164        assert!(!ty.shared);
165        match &ty.inner {
166            WasmCompositeInnerType::Array(ty) => Some(self.array_layout(ty).into()),
167            WasmCompositeInnerType::Struct(ty) => Some(self.struct_layout(ty).into()),
168            WasmCompositeInnerType::Func(_) => None,
169        }
170    }
171
172    /// Get this collector's layout for the given array type.
173    fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout;
174
175    /// Get this collector's layout for the given struct type.
176    fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout;
177}
178
179/// The layout of a GC-managed object.
180#[derive(Clone, Debug)]
181pub enum GcLayout {
182    /// The layout of a GC-managed array object.
183    Array(GcArrayLayout),
184
185    /// The layout of a GC-managed struct object.
186    Struct(GcStructLayout),
187}
188
189impl From<GcArrayLayout> for GcLayout {
190    fn from(layout: GcArrayLayout) -> Self {
191        Self::Array(layout)
192    }
193}
194
195impl From<GcStructLayout> for GcLayout {
196    fn from(layout: GcStructLayout) -> Self {
197        Self::Struct(layout)
198    }
199}
200
201impl GcLayout {
202    /// Get the underlying `GcStructLayout`, or panic.
203    #[track_caller]
204    pub fn unwrap_struct(&self) -> &GcStructLayout {
205        match self {
206            Self::Struct(s) => s,
207            _ => panic!("GcLayout::unwrap_struct on non-struct GC layout"),
208        }
209    }
210
211    /// Get the underlying `GcArrayLayout`, or panic.
212    #[track_caller]
213    pub fn unwrap_array(&self) -> &GcArrayLayout {
214        match self {
215            Self::Array(a) => a,
216            _ => panic!("GcLayout::unwrap_array on non-array GC layout"),
217        }
218    }
219}
220
221/// The layout of a GC-managed array.
222///
223/// This layout is only valid for use with the GC runtime that created it. It is
224/// not valid to use one GC runtime's layout with another GC runtime, doing so
225/// is memory safe but will lead to general incorrectness like panics and wrong
226/// results.
227///
228/// All offsets are from the start of the object; that is, the size of the GC
229/// header (for example) is included in the offset.
230///
231/// All arrays are composed of the generic `VMGcHeader`, followed by
232/// collector-specific fields, followed by the contiguous array elements
233/// themselves. The array elements must be aligned to the element type's natural
234/// alignment.
235#[derive(Clone, Debug)]
236pub struct GcArrayLayout {
237    /// The size of this array object, without any elements.
238    ///
239    /// The array's elements, if any, must begin at exactly this offset.
240    pub base_size: u32,
241
242    /// The alignment of this array.
243    pub align: u32,
244
245    /// The size and natural alignment of each element in this array.
246    pub elem_size: u32,
247}
248
249impl GcArrayLayout {
250    /// Get the total size of this array for a given length of elements.
251    #[inline]
252    pub fn size_for_len(&self, len: u32) -> u32 {
253        self.elem_offset(len)
254    }
255
256    /// Get the offset of the `i`th element in an array with this layout.
257    #[inline]
258    pub fn elem_offset(&self, i: u32) -> u32 {
259        self.base_size + i * self.elem_size
260    }
261
262    /// Get a `core::alloc::Layout` for an array of this type with the given
263    /// length.
264    pub fn layout(&self, len: u32) -> Layout {
265        let size = self.size_for_len(len);
266        let size = usize::try_from(size).unwrap();
267        let align = usize::try_from(self.align).unwrap();
268        Layout::from_size_align(size, align).unwrap()
269    }
270}
271
272/// The layout for a GC-managed struct type.
273///
274/// This layout is only valid for use with the GC runtime that created it. It is
275/// not valid to use one GC runtime's layout with another GC runtime, doing so
276/// is memory safe but will lead to general incorrectness like panics and wrong
277/// results.
278///
279/// All offsets are from the start of the object; that is, the size of the GC
280/// header (for example) is included in the offset.
281#[derive(Clone, Debug)]
282pub struct GcStructLayout {
283    /// The size (in bytes) of this struct.
284    pub size: u32,
285
286    /// The alignment (in bytes) of this struct.
287    pub align: u32,
288
289    /// The fields of this struct. The `i`th entry is the `i`th struct field's
290    /// offset (in bytes) in the struct.
291    pub fields: Vec<u32>,
292}
293
294impl GcStructLayout {
295    /// Get a `core::alloc::Layout` for a struct of this type.
296    pub fn layout(&self) -> Layout {
297        let size = usize::try_from(self.size).unwrap();
298        let align = usize::try_from(self.align).unwrap();
299        Layout::from_size_align(size, align).unwrap()
300    }
301}
302
303/// The kind of an object in a GC heap.
304///
305/// Note that this type is accessed from Wasm JIT code.
306///
307/// `VMGcKind` is a bitset where to test if `a` is a subtype of an
308/// "abstract-ish" type `b`, we can simply use a single bitwise-and operation:
309///
310/// ```ignore
311/// a <: b   iff   a & b == b
312/// ```
313///
314/// For example, because `VMGcKind::AnyRef` has the high bit set, every kind
315/// representing some subtype of `anyref` also has its high bit set.
316///
317/// We say "abstract-ish" type because in addition to the abstract heap types
318/// (other than `i31`) we also have variants for `externref`s that have been
319/// converted into an `anyref` via `extern.convert_any` and `externref`s that
320/// have been converted into an `anyref` via `any.convert_extern`. Note that in
321/// the latter case, because `any.convert_extern $foo` produces a value that is
322/// not an instance of `eqref`, `VMGcKind::AnyOfExternRef & VMGcKind::EqRef !=
323/// VMGcKind::EqRef`.
324///
325/// Furthermore, this type only uses the highest 6 bits of its `u32`
326/// representation, allowing the lower 27 bytes to be bitpacked with other stuff
327/// as users see fit.
328#[repr(u32)]
329#[derive(Clone, Copy, Debug, PartialEq, Eq)]
330#[rustfmt::skip]
331#[allow(missing_docs, reason = "self-describing variants")]
332pub enum VMGcKind {
333    ExternRef      = 0b01000 << 27,
334    AnyRef         = 0b10000 << 27,
335    EqRef          = 0b10100 << 27,
336    ArrayRef       = 0b10101 << 27,
337    StructRef      = 0b10110 << 27,
338}
339
340impl VMGcKind {
341    /// Mask this value with a `u32` to get just the bits that `VMGcKind` uses.
342    pub const MASK: u32 = 0b11111 << 27;
343
344    /// Mask this value with a `u32` that potentially contains a `VMGcKind` to
345    /// get the bits that `VMGcKind` doesn't use.
346    pub const UNUSED_MASK: u32 = !Self::MASK;
347
348    /// Does the given value fit in the unused bits of a `VMGcKind`?
349    #[inline]
350    pub fn value_fits_in_unused_bits(value: u32) -> bool {
351        (value & Self::UNUSED_MASK) == value
352    }
353
354    /// Convert the given value into a `VMGcKind` by masking off the unused
355    /// bottom bits.
356    #[inline]
357    pub fn from_high_bits_of_u32(val: u32) -> VMGcKind {
358        let masked = val & Self::MASK;
359        match masked {
360            x if x == Self::ExternRef.as_u32() => Self::ExternRef,
361            x if x == Self::AnyRef.as_u32() => Self::AnyRef,
362            x if x == Self::EqRef.as_u32() => Self::EqRef,
363            x if x == Self::ArrayRef.as_u32() => Self::ArrayRef,
364            x if x == Self::StructRef.as_u32() => Self::StructRef,
365            _ => panic!("invalid `VMGcKind`: {masked:#032b}"),
366        }
367    }
368
369    /// Does this kind match the other kind?
370    ///
371    /// That is, is this kind a subtype of the other kind?
372    #[inline]
373    pub fn matches(self, other: Self) -> bool {
374        (self.as_u32() & other.as_u32()) == other.as_u32()
375    }
376
377    /// Get this `VMGcKind` as a raw `u32`.
378    #[inline]
379    pub fn as_u32(self) -> u32 {
380        self as u32
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::VMGcKind::*;
387    use crate::prelude::*;
388
389    #[test]
390    fn kind_matches() {
391        let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef];
392
393        for (sup, subs) in [
394            (ExternRef, vec![]),
395            (AnyRef, vec![EqRef, ArrayRef, StructRef]),
396            (EqRef, vec![ArrayRef, StructRef]),
397            (ArrayRef, vec![]),
398            (StructRef, vec![]),
399        ] {
400            assert!(sup.matches(sup));
401            for sub in &subs {
402                assert!(sub.matches(sup));
403            }
404            for kind in all.iter().filter(|k| **k != sup && !subs.contains(k)) {
405                assert!(!kind.matches(sup));
406            }
407        }
408    }
409}