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