1#[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, error::OutOfMemory, prelude::*,
21};
22use alloc::sync::Arc;
23use core::alloc::Layout;
24
25pub const POISON: u8 = 0b00001111;
28
29#[macro_export]
31macro_rules! gc_assert {
32 ($($arg:tt)*) => {
33 if cfg!(gc_zeal) {
34 assert!($($arg)*);
35 }
36 };
37}
38
39pub const I31_DISCRIMINANT: u32 = 1;
41
42pub const VM_GC_HEADER_SIZE: u32 = 8;
44
45pub const VM_GC_HEADER_ALIGN: u32 = 8;
47
48pub const VM_GC_HEADER_KIND_OFFSET: u32 = 0;
50
51pub const VM_GC_HEADER_TYPE_INDEX_OFFSET: u32 = 4;
53
54pub fn byte_size_of_wasm_ty_in_gc_heap(ty: &WasmStorageType) -> u32 {
57 match ty {
58 WasmStorageType::I8 => 1,
59 WasmStorageType::I16 => 2,
60 WasmStorageType::Val(ty) => match ty {
61 WasmValType::I32 | WasmValType::F32 | WasmValType::Ref(_) => 4,
62 WasmValType::I64 | WasmValType::F64 => 8,
63 WasmValType::V128 => 16,
64 },
65 }
66}
67
68#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
71fn align_up(offset: &mut u32, max_align: &mut u32, align: u32) -> u32 {
72 debug_assert!(max_align.is_power_of_two());
73 debug_assert!(align.is_power_of_two());
74 *offset = offset.checked_add(align - 1).unwrap() & !(align - 1);
75 *max_align = core::cmp::max(*max_align, align);
76 *offset
77}
78
79#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
83fn field(size: &mut u32, align: &mut u32, bytes: u32) -> u32 {
84 let offset = align_up(size, align, bytes);
85 *size += bytes;
86 offset
87}
88
89#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
92fn common_array_layout(
93 ty: &WasmArrayType,
94 header_size: u32,
95 header_align: u32,
96 expected_array_length_offset: u32,
97) -> GcArrayLayout {
98 use core::mem;
99
100 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
101 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
102
103 let mut size = header_size;
104 let mut align = header_align;
105
106 let length_field_size = u32::try_from(mem::size_of::<u32>()).unwrap();
107 let length_field_offset = field(&mut size, &mut align, length_field_size);
108 assert_eq!(length_field_offset, expected_array_length_offset);
109
110 let elem_size = byte_size_of_wasm_ty_in_gc_heap(&ty.0.element_type);
111 let elems_offset = align_up(&mut size, &mut align, elem_size);
112 assert_eq!(elems_offset, size);
113
114 let elems_are_gc_refs = ty.0.element_type.is_vmgcref_type_and_not_i31();
115 if elems_are_gc_refs {
116 debug_assert_eq!(
117 length_field_offset + length_field_size,
118 elems_offset,
119 "DRC collector relies on GC ref elements appearing directly after the length field, without any padding",
120 );
121 }
122
123 GcArrayLayout {
124 base_size: size,
125 align,
126 elem_size,
127 elems_are_gc_refs,
128 }
129}
130
131#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
135fn common_struct_or_exn_layout(
136 fields: &[crate::WasmFieldType],
137 header_size: u32,
138 header_align: u32,
139) -> (u32, u32, TryVec<GcStructLayoutField>) {
140 use crate::PanicOnOom as _;
141
142 let mut size = header_size;
152 let mut align = header_align;
153
154 let fields = fields
155 .iter()
156 .map(|f| {
157 let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
158 let offset = field(&mut size, &mut align, field_size);
159 let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();
160 GcStructLayoutField { offset, is_gc_ref }
161 })
162 .try_collect::<TryVec<_>, _>()
163 .panic_on_oom();
164
165 let align_size_to = align;
168 align_up(&mut size, &mut align, align_size_to);
169
170 (size, align, fields)
171}
172
173#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
176fn common_struct_layout(
177 ty: &WasmStructType,
178 header_size: u32,
179 header_align: u32,
180) -> GcStructLayout {
181 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
182 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
183
184 let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);
185
186 GcStructLayout {
187 size,
188 align,
189 fields,
190 is_exception: false,
191 }
192}
193
194#[cfg(any(feature = "gc-null", feature = "gc-drc"))]
198fn common_exn_layout(ty: &WasmExnType, header_size: u32, header_align: u32) -> GcStructLayout {
199 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
200 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
201
202 assert!(header_align >= 8);
205 let header_size = header_size + 2 * u32::try_from(core::mem::size_of::<u32>()).unwrap();
206
207 let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);
208
209 GcStructLayout {
210 size,
211 align,
212 fields,
213 is_exception: true,
214 }
215}
216
217pub trait GcTypeLayouts {
220 fn array_length_field_offset(&self) -> u32;
225
226 fn exception_tag_instance_offset(&self) -> u32;
232
233 fn exception_tag_defined_offset(&self) -> u32;
239
240 fn gc_layout(&self, ty: &WasmCompositeType) -> Option<GcLayout> {
245 assert!(!ty.shared);
246 match &ty.inner {
247 WasmCompositeInnerType::Array(ty) => Some(self.array_layout(ty).into()),
248 WasmCompositeInnerType::Struct(ty) => Some(Arc::new(self.struct_layout(ty)).into()),
249 WasmCompositeInnerType::Func(_) => None,
250 WasmCompositeInnerType::Cont(_) => {
251 unimplemented!("Stack switching feature not compatible with GC, yet")
252 }
253 WasmCompositeInnerType::Exn(ty) => Some(Arc::new(self.exn_layout(ty)).into()),
254 }
255 }
256
257 fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout;
259
260 fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout;
262
263 fn exn_layout(&self, ty: &WasmExnType) -> GcStructLayout;
265}
266
267#[derive(Clone, Debug)]
269pub enum GcLayout {
270 Array(GcArrayLayout),
272
273 Struct(Arc<GcStructLayout>),
275}
276
277impl From<GcArrayLayout> for GcLayout {
278 fn from(layout: GcArrayLayout) -> Self {
279 Self::Array(layout)
280 }
281}
282
283impl From<Arc<GcStructLayout>> for GcLayout {
284 fn from(layout: Arc<GcStructLayout>) -> Self {
285 Self::Struct(layout)
286 }
287}
288
289impl TryClone for GcLayout {
290 fn try_clone(&self) -> core::result::Result<Self, wasmtime_core::error::OutOfMemory> {
291 Ok(self.clone())
292 }
293}
294
295impl GcLayout {
296 #[track_caller]
298 pub fn unwrap_struct(&self) -> &Arc<GcStructLayout> {
299 match self {
300 Self::Struct(s) => s,
301 _ => panic!("GcLayout::unwrap_struct on non-struct GC layout"),
302 }
303 }
304
305 #[track_caller]
307 pub fn unwrap_array(&self) -> &GcArrayLayout {
308 match self {
309 Self::Array(a) => a,
310 _ => panic!("GcLayout::unwrap_array on non-array GC layout"),
311 }
312 }
313}
314
315#[derive(Clone, Debug)]
330pub struct GcArrayLayout {
331 pub base_size: u32,
335
336 pub align: u32,
338
339 pub elem_size: u32,
341
342 pub elems_are_gc_refs: bool,
344}
345
346impl GcArrayLayout {
347 #[inline]
349 pub fn size_for_len(&self, len: u32) -> u32 {
350 self.elem_offset(len)
351 }
352
353 #[inline]
355 pub fn elem_offset(&self, i: u32) -> u32 {
356 self.base_size + i * self.elem_size
357 }
358
359 pub fn layout(&self, len: u32) -> Layout {
362 let size = self.size_for_len(len);
363 let size = usize::try_from(size).unwrap();
364 let align = usize::try_from(self.align).unwrap();
365 Layout::from_size_align(size, align).unwrap()
366 }
367}
368
369#[derive(Debug)]
385pub struct GcStructLayout {
386 pub size: u32,
388
389 pub align: u32,
391
392 pub fields: TryVec<GcStructLayoutField>,
395
396 pub is_exception: bool,
398}
399
400impl TryClone for GcStructLayout {
401 fn try_clone(&self) -> Result<Self, OutOfMemory> {
402 Ok(GcStructLayout {
403 size: self.size,
404 align: self.align,
405 fields: self.fields.try_clone()?,
406 is_exception: self.is_exception,
407 })
408 }
409}
410
411impl GcStructLayout {
412 pub fn layout(&self) -> Layout {
414 let size = usize::try_from(self.size).unwrap();
415 let align = usize::try_from(self.align).unwrap();
416 Layout::from_size_align(size, align).unwrap()
417 }
418}
419
420#[derive(Clone, Copy, Debug)]
422pub struct GcStructLayoutField {
423 pub offset: u32,
425
426 pub is_gc_ref: bool,
432}
433
434impl TryClone for GcStructLayoutField {
435 fn try_clone(&self) -> Result<Self, OutOfMemory> {
436 Ok(*self)
437 }
438}
439
440#[repr(u32)]
466#[derive(Clone, Copy, Debug, PartialEq, Eq)]
467#[rustfmt::skip]
468#[expect(missing_docs, reason = "self-describing variants")]
469pub enum VMGcKind {
470 ExternRef = 0b010000 << 26,
471 AnyRef = 0b100000 << 26,
472 EqRef = 0b101000 << 26,
473 ArrayRef = 0b101010 << 26,
474 StructRef = 0b101100 << 26,
475 ExnRef = 0b000001 << 26,
476}
477
478pub const VM_GC_KIND_SIZE: u8 = 4;
480
481const _: () = assert!(VM_GC_KIND_SIZE as usize == core::mem::size_of::<VMGcKind>());
482
483impl VMGcKind {
484 pub const MASK: u32 = 0b111111 << 26;
486
487 pub const UNUSED_MASK: u32 = !Self::MASK;
490
491 #[inline]
493 pub fn value_fits_in_unused_bits(value: u32) -> bool {
494 (value & Self::UNUSED_MASK) == value
495 }
496
497 #[inline]
500 pub fn from_high_bits_of_u32(val: u32) -> VMGcKind {
501 let masked = val & Self::MASK;
502 let result = Self::try_from_u32(masked)
503 .unwrap_or_else(|| panic!("invalid `VMGcKind`: {masked:#032b}"));
504
505 let poison_kind = u32::from_le_bytes([POISON, POISON, POISON, POISON]) & VMGcKind::MASK;
506 debug_assert_ne!(
507 masked, poison_kind,
508 "No valid `VMGcKind` should overlap with the poison pattern"
509 );
510
511 result
512 }
513
514 #[inline]
518 pub fn matches(self, other: Self) -> bool {
519 (self.as_u32() & other.as_u32()) == other.as_u32()
520 }
521
522 #[inline]
524 pub fn as_u32(self) -> u32 {
525 self as u32
526 }
527
528 #[inline]
532 pub fn try_from_u32(x: u32) -> Option<VMGcKind> {
533 match x {
534 _ if x == Self::ExternRef.as_u32() => Some(Self::ExternRef),
535 _ if x == Self::AnyRef.as_u32() => Some(Self::AnyRef),
536 _ if x == Self::EqRef.as_u32() => Some(Self::EqRef),
537 _ if x == Self::ArrayRef.as_u32() => Some(Self::ArrayRef),
538 _ if x == Self::StructRef.as_u32() => Some(Self::StructRef),
539 _ if x == Self::ExnRef.as_u32() => Some(Self::ExnRef),
540 _ => None,
541 }
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::VMGcKind::*;
548 use crate::prelude::*;
549
550 #[test]
551 fn kind_matches() {
552 let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef, ExnRef];
553
554 for (sup, subs) in [
555 (ExternRef, vec![]),
556 (AnyRef, vec![EqRef, ArrayRef, StructRef]),
557 (EqRef, vec![ArrayRef, StructRef]),
559 (ArrayRef, vec![]),
560 (StructRef, vec![]),
561 (ExnRef, vec![]),
562 ] {
563 assert!(sup.matches(sup));
564 for sub in &subs {
565 assert!(sub.matches(sup));
566 }
567 for kind in all.iter().filter(|k| **k != sup && !subs.contains(k)) {
568 assert!(!kind.matches(sup));
569 }
570 }
571 }
572}