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
32#[macro_export]
34macro_rules! gc_assert {
35 ($($arg:tt)*) => {
36 if cfg!(gc_zeal) {
37 assert!($($arg)*);
38 }
39 };
40}
41
42pub const I31_DISCRIMINANT: u32 = 1;
44
45pub const VM_GC_HEADER_SIZE: u32 = 8;
47
48pub const VM_GC_HEADER_ALIGN: u32 = 8;
50
51pub const VM_GC_HEADER_KIND_OFFSET: u32 = 0;
53
54pub const VM_GC_HEADER_TYPE_INDEX_OFFSET: u32 = 4;
56
57pub fn byte_size_of_wasm_ty_in_gc_heap(ty: &WasmStorageType) -> u32 {
60 match ty {
61 WasmStorageType::I8 => 1,
62 WasmStorageType::I16 => 2,
63 WasmStorageType::Val(ty) => match ty {
64 WasmValType::I32 | WasmValType::F32 | WasmValType::Ref(_) => 4,
65 WasmValType::I64 | WasmValType::F64 => 8,
66 WasmValType::V128 => 16,
67 },
68 }
69}
70
71#[cfg(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying"))]
74fn align_up(offset: &mut u32, max_align: &mut u32, align: u32) -> u32 {
75 debug_assert!(max_align.is_power_of_two());
76 debug_assert!(align.is_power_of_two());
77 *offset = offset.checked_add(align - 1).unwrap() & !(align - 1);
78 *max_align = core::cmp::max(*max_align, align);
79 *offset
80}
81
82#[cfg(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying"))]
86fn field(size: &mut u32, align: &mut u32, bytes: u32) -> u32 {
87 let offset = align_up(size, align, bytes);
88 *size += bytes;
89 offset
90}
91
92#[cfg(any(feature = "gc-drc", feature = "gc-null", feature = "gc-copying"))]
95fn common_array_layout(
96 ty: &WasmArrayType,
97 header_size: u32,
98 header_align: u32,
99 expected_array_length_offset: u32,
100) -> GcArrayLayout {
101 use core::mem;
102
103 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
104 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
105
106 let mut size = header_size;
107 let mut align = header_align;
108
109 let length_field_size = u32::try_from(mem::size_of::<u32>()).unwrap();
110 let length_field_offset = field(&mut size, &mut align, length_field_size);
111 assert_eq!(length_field_offset, expected_array_length_offset);
112
113 let elem_size = byte_size_of_wasm_ty_in_gc_heap(&ty.0.element_type);
114 let elems_offset = align_up(&mut size, &mut align, elem_size);
115 assert_eq!(elems_offset, size);
116
117 let elems_are_gc_refs = ty.0.element_type.is_vmgcref_type_and_not_i31();
118 if elems_are_gc_refs {
119 debug_assert_eq!(
120 length_field_offset + length_field_size,
121 elems_offset,
122 "DRC collector relies on GC ref elements appearing directly after the length field, without any padding",
123 );
124 }
125
126 GcArrayLayout {
127 base_size: size,
128 align,
129 elem_size,
130 elems_are_gc_refs,
131 }
132}
133
134#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
138fn common_struct_or_exn_layout(
139 fields: &[crate::WasmFieldType],
140 header_size: u32,
141 header_align: u32,
142) -> (u32, u32, TryVec<GcStructLayoutField>) {
143 use crate::PanicOnOom as _;
144
145 let mut size = header_size;
155 let mut align = header_align;
156
157 let fields = fields
158 .iter()
159 .map(|f| {
160 let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
161 let offset = field(&mut size, &mut align, field_size);
162 let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();
163 GcStructLayoutField { offset, is_gc_ref }
164 })
165 .try_collect::<TryVec<_>, _>()
166 .panic_on_oom();
167
168 let align_size_to = align;
171 align_up(&mut size, &mut align, align_size_to);
172
173 (size, align, fields)
174}
175
176#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
179fn common_struct_layout(
180 ty: &WasmStructType,
181 header_size: u32,
182 header_align: u32,
183) -> GcStructLayout {
184 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
185 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
186
187 let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);
188
189 GcStructLayout {
190 size,
191 align,
192 fields,
193 is_exception: false,
194 }
195}
196
197#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
201fn common_exn_layout(ty: &WasmExnType, header_size: u32, header_align: u32) -> GcStructLayout {
202 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
203 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
204
205 assert!(header_align >= 8);
208 let header_size = header_size + 2 * u32::try_from(core::mem::size_of::<u32>()).unwrap();
209
210 let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align);
211
212 GcStructLayout {
213 size,
214 align,
215 fields,
216 is_exception: true,
217 }
218}
219
220pub trait GcTypeLayouts {
223 fn array_length_field_offset(&self) -> u32;
228
229 fn exception_tag_instance_offset(&self) -> u32;
235
236 fn exception_tag_defined_offset(&self) -> u32;
242
243 fn gc_layout(&self, ty: &WasmCompositeType) -> Option<GcLayout> {
248 assert!(!ty.shared);
249 match &ty.inner {
250 WasmCompositeInnerType::Array(ty) => Some(self.array_layout(ty).into()),
251 WasmCompositeInnerType::Struct(ty) => Some(Arc::new(self.struct_layout(ty)).into()),
252 WasmCompositeInnerType::Func(_) => None,
253 WasmCompositeInnerType::Cont(_) => {
254 unimplemented!("Stack switching feature not compatible with GC, yet")
255 }
256 WasmCompositeInnerType::Exn(ty) => Some(Arc::new(self.exn_layout(ty)).into()),
257 }
258 }
259
260 fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout;
262
263 fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout;
265
266 fn exn_layout(&self, ty: &WasmExnType) -> GcStructLayout;
268}
269
270#[derive(Clone, Debug)]
272pub enum GcLayout {
273 Array(GcArrayLayout),
275
276 Struct(Arc<GcStructLayout>),
278}
279
280impl From<GcArrayLayout> for GcLayout {
281 fn from(layout: GcArrayLayout) -> Self {
282 Self::Array(layout)
283 }
284}
285
286impl From<Arc<GcStructLayout>> for GcLayout {
287 fn from(layout: Arc<GcStructLayout>) -> Self {
288 Self::Struct(layout)
289 }
290}
291
292impl TryClone for GcLayout {
293 fn try_clone(&self) -> core::result::Result<Self, wasmtime_core::error::OutOfMemory> {
294 Ok(self.clone())
295 }
296}
297
298impl GcLayout {
299 #[track_caller]
301 pub fn unwrap_struct(&self) -> &Arc<GcStructLayout> {
302 match self {
303 Self::Struct(s) => s,
304 _ => panic!("GcLayout::unwrap_struct on non-struct GC layout"),
305 }
306 }
307
308 #[track_caller]
310 pub fn unwrap_array(&self) -> &GcArrayLayout {
311 match self {
312 Self::Array(a) => a,
313 _ => panic!("GcLayout::unwrap_array on non-array GC layout"),
314 }
315 }
316}
317
318#[derive(Clone, Debug)]
333pub struct GcArrayLayout {
334 pub base_size: u32,
338
339 pub align: u32,
341
342 pub elem_size: u32,
344
345 pub elems_are_gc_refs: bool,
347}
348
349impl GcArrayLayout {
350 #[inline]
352 pub fn size_for_len(&self, len: u32) -> Option<u32> {
353 self.elem_offset(len)
354 }
355
356 #[inline]
358 pub fn elem_offset(&self, i: u32) -> Option<u32> {
359 let elem_offset = i.checked_mul(self.elem_size)?;
360 self.base_size.checked_add(elem_offset)
361 }
362
363 pub fn layout(&self, len: u32) -> Option<Layout> {
366 let size = self.size_for_len(len)?;
367 let size = usize::try_from(size).unwrap();
368 let align = usize::try_from(self.align).unwrap();
369 Layout::from_size_align(size, align).ok()
370 }
371}
372
373#[derive(Debug)]
389pub struct GcStructLayout {
390 pub size: u32,
392
393 pub align: u32,
395
396 pub fields: TryVec<GcStructLayoutField>,
399
400 pub is_exception: bool,
402}
403
404impl TryClone for GcStructLayout {
405 fn try_clone(&self) -> Result<Self, OutOfMemory> {
406 Ok(GcStructLayout {
407 size: self.size,
408 align: self.align,
409 fields: self.fields.try_clone()?,
410 is_exception: self.is_exception,
411 })
412 }
413}
414
415impl GcStructLayout {
416 pub fn layout(&self) -> Layout {
418 let size = usize::try_from(self.size).unwrap();
419 let align = usize::try_from(self.align).unwrap();
420 Layout::from_size_align(size, align).unwrap()
421 }
422}
423
424#[derive(Clone, Copy, Debug)]
426pub struct GcStructLayoutField {
427 pub offset: u32,
429
430 pub is_gc_ref: bool,
436}
437
438impl TryClone for GcStructLayoutField {
439 fn try_clone(&self) -> Result<Self, OutOfMemory> {
440 Ok(*self)
441 }
442}
443
444#[repr(u32)]
470#[derive(Clone, Copy, Debug, PartialEq, Eq)]
471#[rustfmt::skip]
472#[expect(missing_docs, reason = "self-describing variants")]
473pub enum VMGcKind {
474 ExternRef = 0b010000 << 26,
475 AnyRef = 0b100000 << 26,
476 EqRef = 0b101000 << 26,
477 ArrayRef = 0b101010 << 26,
478 StructRef = 0b101100 << 26,
479 ExnRef = 0b000001 << 26,
480}
481
482pub const VM_GC_KIND_SIZE: u8 = 4;
484
485const _: () = assert!(VM_GC_KIND_SIZE as usize == core::mem::size_of::<VMGcKind>());
486
487impl VMGcKind {
488 pub const MASK: u32 = 0b111111 << 26;
490
491 pub const UNUSED_MASK: u32 = !Self::MASK;
494
495 #[inline]
497 pub fn value_fits_in_unused_bits(value: u32) -> bool {
498 (value & Self::UNUSED_MASK) == value
499 }
500
501 #[inline]
504 pub fn from_high_bits_of_u32(val: u32) -> VMGcKind {
505 let masked = val & Self::MASK;
506 let result = Self::try_from_u32(masked)
507 .unwrap_or_else(|| panic!("invalid `VMGcKind`: {masked:#032b}"));
508
509 let poison_kind = u32::from_le_bytes([POISON, POISON, POISON, POISON]) & VMGcKind::MASK;
510 debug_assert_ne!(
511 masked, poison_kind,
512 "No valid `VMGcKind` should overlap with the poison pattern"
513 );
514
515 result
516 }
517
518 #[inline]
522 pub fn matches(self, other: Self) -> bool {
523 (self.as_u32() & other.as_u32()) == other.as_u32()
524 }
525
526 #[inline]
528 pub fn as_u32(self) -> u32 {
529 self as u32
530 }
531
532 #[inline]
536 pub fn try_from_u32(x: u32) -> Option<VMGcKind> {
537 match x {
538 _ if x == Self::ExternRef.as_u32() => Some(Self::ExternRef),
539 _ if x == Self::AnyRef.as_u32() => Some(Self::AnyRef),
540 _ if x == Self::EqRef.as_u32() => Some(Self::EqRef),
541 _ if x == Self::ArrayRef.as_u32() => Some(Self::ArrayRef),
542 _ if x == Self::StructRef.as_u32() => Some(Self::StructRef),
543 _ if x == Self::ExnRef.as_u32() => Some(Self::ExnRef),
544 _ => None,
545 }
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::VMGcKind::*;
552 use crate::prelude::*;
553
554 #[test]
555 fn kind_matches() {
556 let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef, ExnRef];
557
558 for (sup, subs) in [
559 (ExternRef, vec![]),
560 (AnyRef, vec![EqRef, ArrayRef, StructRef]),
561 (EqRef, vec![ArrayRef, StructRef]),
563 (ArrayRef, vec![]),
564 (StructRef, vec![]),
565 (ExnRef, vec![]),
566 ] {
567 assert!(sup.matches(sup));
568 for sub in &subs {
569 assert!(sub.matches(sup));
570 }
571 for kind in all.iter().filter(|k| **k != sup && !subs.contains(k)) {
572 assert!(!kind.matches(sup));
573 }
574 }
575 }
576}