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