1#[cfg(feature = "gc-drc")]
13pub mod drc;
14
15#[cfg(feature = "gc-null")]
16pub mod null;
17
18#[cfg(feature = "gc-copying")]
19pub mod copying;
20
21use crate::{
22 WasmArrayType, WasmCompositeInnerType, WasmCompositeType, WasmExnType, WasmStorageType,
23 WasmStructType, WasmValType, error::OutOfMemory, prelude::*,
24};
25use alloc::sync::Arc;
26use core::alloc::Layout;
27
28pub const POISON: u8 = 0b00001111;
31
32pub const DRC_HEADER_MARK_BIT: u32 = 1 << 0;
36
37pub const DRC_HEADER_IN_OVER_APPROX_LIST_BIT: u32 = 1 << 1;
40
41pub const DRC_MIN_OVER_APPROX_STACK_ROOTS_GC_THRESHOLD: i64 = 1024;
44
45#[macro_export]
47macro_rules! gc_assert {
48 ($($arg:tt)*) => {
49 if cfg!(gc_zeal) {
50 assert!($($arg)*);
51 }
52 };
53}
54
55pub const I31_DISCRIMINANT: u32 = 1;
57
58pub const VM_GC_HEADER_SIZE: u32 = 8;
60
61pub const VM_GC_HEADER_ALIGN: u32 = 8;
63
64pub const VM_GC_HEADER_KIND_OFFSET: u32 = 0;
66
67pub const VM_GC_HEADER_TYPE_INDEX_OFFSET: u32 = 4;
69
70pub fn byte_size_of_wasm_ty_in_gc_heap(ty: &WasmStorageType) -> u32 {
73 match ty {
74 WasmStorageType::I8 => 1,
75 WasmStorageType::I16 => 2,
76 WasmStorageType::Val(ty) => match ty {
77 WasmValType::I32 | WasmValType::F32 | WasmValType::Ref(_) => 4,
78 WasmValType::I64 | WasmValType::F64 => 8,
79 WasmValType::V128 => 16,
80 },
81 }
82}
83
84#[cfg(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying"))]
87fn align_up(offset: &mut u32, max_align: &mut u32, align: u32) -> u32 {
88 debug_assert!(max_align.is_power_of_two());
89 debug_assert!(align.is_power_of_two());
90 *offset = offset.checked_add(align - 1).unwrap() & !(align - 1);
91 *max_align = core::cmp::max(*max_align, align);
92 *offset
93}
94
95#[cfg(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying"))]
99fn field(size: &mut u32, align: &mut u32, bytes: u32) -> u32 {
100 let offset = align_up(size, align, bytes);
101 *size += bytes;
102 offset
103}
104
105#[cfg(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying"))]
108fn common_array_layout(
109 ty: &WasmArrayType,
110 header_size: u32,
111 header_align: u32,
112 expected_array_length_offset: u32,
113) -> GcArrayLayout {
114 use core::mem;
115
116 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
117 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
118
119 let mut size = header_size;
120 let mut align = header_align;
121
122 let length_field_size = u32::try_from(mem::size_of::<u32>()).unwrap();
123 let length_field_offset = field(&mut size, &mut align, length_field_size);
124 assert_eq!(length_field_offset, expected_array_length_offset);
125
126 let elem_size = byte_size_of_wasm_ty_in_gc_heap(&ty.0.element_type);
127 let elems_offset = align_up(&mut size, &mut align, elem_size);
128 assert_eq!(elems_offset, size);
129
130 let elems_are_gc_refs = ty.0.element_type.is_vmgcref_type_and_not_i31();
131 if elems_are_gc_refs {
132 debug_assert_eq!(
133 length_field_offset + length_field_size,
134 elems_offset,
135 "DRC collector relies on GC ref elements appearing directly after the length field, without any padding",
136 );
137 }
138
139 GcArrayLayout {
140 base_size: size,
141 align,
142 elem_size,
143 elems_are_gc_refs,
144 }
145}
146
147#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
151fn common_struct_or_exn_layout(
152 fields: &[crate::WasmFieldType],
153 header_size: u32,
154 header_align: u32,
155) -> Result<(u32, u32, TryVec<GcStructLayoutField>), OutOfMemory> {
156 let mut size = header_size;
166 let mut align = header_align;
167
168 let fields = fields
169 .iter()
170 .map(|f| {
171 let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
172 let offset = field(&mut size, &mut align, field_size);
173 let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();
174 GcStructLayoutField { offset, is_gc_ref }
175 })
176 .try_collect::<TryVec<_>, _>()?;
177
178 let align_size_to = align;
181 align_up(&mut size, &mut align, align_size_to);
182
183 Ok((size, align, fields))
184}
185
186#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
189fn common_struct_layout(
190 ty: &WasmStructType,
191 header_size: u32,
192 header_align: u32,
193) -> Result<GcStructLayout, OutOfMemory> {
194 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
195 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
196
197 let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align)?;
198
199 Ok(GcStructLayout {
200 size,
201 align,
202 fields,
203 is_exception: false,
204 })
205}
206
207#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
211fn common_exn_layout(
212 ty: &WasmExnType,
213 header_size: u32,
214 header_align: u32,
215) -> Result<GcStructLayout, OutOfMemory> {
216 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
217 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
218
219 assert!(header_align >= 8);
222 let header_size = header_size + 2 * u32::try_from(core::mem::size_of::<u32>()).unwrap();
223
224 let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align)?;
225
226 Ok(GcStructLayout {
227 size,
228 align,
229 fields,
230 is_exception: true,
231 })
232}
233
234pub trait GcTypeLayouts {
237 fn array_length_field_offset(&self) -> u32;
242
243 fn exception_tag_instance_offset(&self) -> u32;
249
250 fn exception_tag_defined_offset(&self) -> u32;
256
257 fn gc_layout(&self, ty: &WasmCompositeType) -> Result<Option<GcLayout>, OutOfMemory> {
262 assert!(!ty.shared);
263 match &ty.inner {
264 WasmCompositeInnerType::Array(ty) => Ok(Some(self.array_layout(ty).into())),
265 WasmCompositeInnerType::Struct(ty) => {
266 Ok(Some(Arc::new(self.struct_layout(ty)?).into()))
267 }
268 WasmCompositeInnerType::Func(_) => Ok(None),
269 WasmCompositeInnerType::Cont(_) => {
270 unimplemented!("Stack switching feature not compatible with GC, yet")
271 }
272 WasmCompositeInnerType::Exn(ty) => Ok(Some(Arc::new(self.exn_layout(ty)?).into())),
273 }
274 }
275
276 fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout;
278
279 fn struct_layout(&self, ty: &WasmStructType) -> Result<GcStructLayout, OutOfMemory>;
281
282 fn exn_layout(&self, ty: &WasmExnType) -> Result<GcStructLayout, OutOfMemory>;
284}
285
286#[derive(Clone, Debug)]
288pub enum GcLayout {
289 Array(GcArrayLayout),
291
292 Struct(Arc<GcStructLayout>),
294}
295
296impl From<GcArrayLayout> for GcLayout {
297 fn from(layout: GcArrayLayout) -> Self {
298 Self::Array(layout)
299 }
300}
301
302impl From<Arc<GcStructLayout>> for GcLayout {
303 fn from(layout: Arc<GcStructLayout>) -> Self {
304 Self::Struct(layout)
305 }
306}
307
308impl TryClone for GcLayout {
309 fn try_clone(&self) -> core::result::Result<Self, wasmtime_core::error::OutOfMemory> {
310 Ok(self.clone())
311 }
312}
313
314impl GcLayout {
315 #[track_caller]
317 pub fn unwrap_struct(&self) -> &Arc<GcStructLayout> {
318 match self {
319 Self::Struct(s) => s,
320 _ => panic!("GcLayout::unwrap_struct on non-struct GC layout"),
321 }
322 }
323
324 #[track_caller]
326 pub fn unwrap_array(&self) -> &GcArrayLayout {
327 match self {
328 Self::Array(a) => a,
329 _ => panic!("GcLayout::unwrap_array on non-array GC layout"),
330 }
331 }
332}
333
334#[derive(Clone, Debug)]
349pub struct GcArrayLayout {
350 pub base_size: u32,
354
355 pub align: u32,
357
358 pub elem_size: u32,
360
361 pub elems_are_gc_refs: bool,
363}
364
365impl GcArrayLayout {
366 #[inline]
368 pub fn size_for_len(&self, len: u32) -> Option<u32> {
369 self.elem_offset(len)
370 }
371
372 #[inline]
374 pub fn elem_offset(&self, i: u32) -> Option<u32> {
375 let elem_offset = i.checked_mul(self.elem_size)?;
376 self.base_size.checked_add(elem_offset)
377 }
378
379 pub fn layout(&self, len: u32) -> Option<Layout> {
382 let size = self.size_for_len(len)?;
383 let size = usize::try_from(size).unwrap();
384 let align = usize::try_from(self.align).unwrap();
385 Layout::from_size_align(size, align).ok()
386 }
387}
388
389#[derive(Debug)]
405pub struct GcStructLayout {
406 pub size: u32,
408
409 pub align: u32,
411
412 pub fields: TryVec<GcStructLayoutField>,
415
416 pub is_exception: bool,
418}
419
420impl TryClone for GcStructLayout {
421 fn try_clone(&self) -> Result<Self, OutOfMemory> {
422 Ok(GcStructLayout {
423 size: self.size,
424 align: self.align,
425 fields: self.fields.try_clone()?,
426 is_exception: self.is_exception,
427 })
428 }
429}
430
431impl GcStructLayout {
432 pub fn layout(&self) -> Layout {
434 let size = usize::try_from(self.size).unwrap();
435 let align = usize::try_from(self.align).unwrap();
436 Layout::from_size_align(size, align).unwrap()
437 }
438}
439
440#[derive(Clone, Copy, Debug)]
442pub struct GcStructLayoutField {
443 pub offset: u32,
445
446 pub is_gc_ref: bool,
452}
453
454impl TryClone for GcStructLayoutField {
455 fn try_clone(&self) -> Result<Self, OutOfMemory> {
456 Ok(*self)
457 }
458}
459
460#[repr(u32)]
486#[derive(Clone, Copy, Debug, PartialEq, Eq)]
487#[rustfmt::skip]
488#[expect(missing_docs, reason = "self-describing variants")]
489pub enum VMGcKind {
490 ExternRef = 0b010000 << 26,
491 AnyRef = 0b100000 << 26,
492 EqRef = 0b101000 << 26,
493 ArrayRef = 0b101010 << 26,
494 StructRef = 0b101100 << 26,
495 ExnRef = 0b000001 << 26,
496}
497
498pub const VM_GC_KIND_SIZE: u8 = 4;
500
501const _: () = assert!(VM_GC_KIND_SIZE as usize == core::mem::size_of::<VMGcKind>());
502
503impl VMGcKind {
504 pub const MASK: u32 = 0b111111 << 26;
506
507 pub const UNUSED_MASK: u32 = !Self::MASK;
510
511 #[inline]
513 pub fn value_fits_in_unused_bits(value: u32) -> bool {
514 (value & Self::UNUSED_MASK) == value
515 }
516
517 #[inline]
520 pub fn from_high_bits_of_u32(val: u32) -> VMGcKind {
521 let masked = val & Self::MASK;
522 let result = Self::try_from_u32(masked)
523 .unwrap_or_else(|| panic!("invalid `VMGcKind`: {masked:#032b}"));
524
525 let poison_kind = u32::from_le_bytes([POISON, POISON, POISON, POISON]) & VMGcKind::MASK;
526 debug_assert_ne!(
527 masked, poison_kind,
528 "No valid `VMGcKind` should overlap with the poison pattern"
529 );
530
531 result
532 }
533
534 #[inline]
538 pub fn matches(self, other: Self) -> bool {
539 (self.as_u32() & other.as_u32()) == other.as_u32()
540 }
541
542 #[inline]
544 pub fn as_u32(self) -> u32 {
545 self as u32
546 }
547
548 #[inline]
552 pub fn try_from_u32(x: u32) -> Option<VMGcKind> {
553 match x {
554 _ if x == Self::ExternRef.as_u32() => Some(Self::ExternRef),
555 _ if x == Self::AnyRef.as_u32() => Some(Self::AnyRef),
556 _ if x == Self::EqRef.as_u32() => Some(Self::EqRef),
557 _ if x == Self::ArrayRef.as_u32() => Some(Self::ArrayRef),
558 _ if x == Self::StructRef.as_u32() => Some(Self::StructRef),
559 _ if x == Self::ExnRef.as_u32() => Some(Self::ExnRef),
560 _ => None,
561 }
562 }
563}
564
565#[cfg(test)]
566mod tests {
567 use super::VMGcKind::*;
568 use crate::prelude::*;
569
570 #[test]
571 fn kind_matches() {
572 let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef, ExnRef];
573
574 for (sup, subs) in [
575 (ExternRef, vec![]),
576 (AnyRef, vec![EqRef, ArrayRef, StructRef]),
577 (EqRef, vec![ArrayRef, StructRef]),
579 (ArrayRef, vec![]),
580 (StructRef, vec![]),
581 (ExnRef, vec![]),
582 ] {
583 assert!(sup.matches(sup));
584 for sub in &subs {
585 assert!(sub.matches(sup));
586 }
587 for kind in all.iter().filter(|k| **k != sup && !subs.contains(k)) {
588 assert!(!kind.matches(sup));
589 }
590 }
591 }
592}