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) -> Result<(u32, u32, TryVec<GcStructLayoutField>), OutOfMemory> {
143 let mut size = header_size;
153 let mut align = header_align;
154
155 let fields = fields
156 .iter()
157 .map(|f| {
158 let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
159 let offset = field(&mut size, &mut align, field_size);
160 let is_gc_ref = f.element_type.is_vmgcref_type_and_not_i31();
161 GcStructLayoutField { offset, is_gc_ref }
162 })
163 .try_collect::<TryVec<_>, _>()?;
164
165 let align_size_to = align;
168 align_up(&mut size, &mut align, align_size_to);
169
170 Ok((size, align, fields))
171}
172
173#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
176fn common_struct_layout(
177 ty: &WasmStructType,
178 header_size: u32,
179 header_align: u32,
180) -> Result<GcStructLayout, OutOfMemory> {
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 Ok(GcStructLayout {
187 size,
188 align,
189 fields,
190 is_exception: false,
191 })
192}
193
194#[cfg(any(feature = "gc-null", feature = "gc-drc", feature = "gc-copying"))]
198fn common_exn_layout(
199 ty: &WasmExnType,
200 header_size: u32,
201 header_align: u32,
202) -> Result<GcStructLayout, OutOfMemory> {
203 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
204 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
205
206 assert!(header_align >= 8);
209 let header_size = header_size + 2 * u32::try_from(core::mem::size_of::<u32>()).unwrap();
210
211 let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align)?;
212
213 Ok(GcStructLayout {
214 size,
215 align,
216 fields,
217 is_exception: true,
218 })
219}
220
221pub trait GcTypeLayouts {
224 fn array_length_field_offset(&self) -> u32;
229
230 fn exception_tag_instance_offset(&self) -> u32;
236
237 fn exception_tag_defined_offset(&self) -> u32;
243
244 fn gc_layout(&self, ty: &WasmCompositeType) -> Result<Option<GcLayout>, OutOfMemory> {
249 assert!(!ty.shared);
250 match &ty.inner {
251 WasmCompositeInnerType::Array(ty) => Ok(Some(self.array_layout(ty).into())),
252 WasmCompositeInnerType::Struct(ty) => {
253 Ok(Some(Arc::new(self.struct_layout(ty)?).into()))
254 }
255 WasmCompositeInnerType::Func(_) => Ok(None),
256 WasmCompositeInnerType::Cont(_) => {
257 unimplemented!("Stack switching feature not compatible with GC, yet")
258 }
259 WasmCompositeInnerType::Exn(ty) => Ok(Some(Arc::new(self.exn_layout(ty)?).into())),
260 }
261 }
262
263 fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout;
265
266 fn struct_layout(&self, ty: &WasmStructType) -> Result<GcStructLayout, OutOfMemory>;
268
269 fn exn_layout(&self, ty: &WasmExnType) -> Result<GcStructLayout, OutOfMemory>;
271}
272
273#[derive(Clone, Debug)]
275pub enum GcLayout {
276 Array(GcArrayLayout),
278
279 Struct(Arc<GcStructLayout>),
281}
282
283impl From<GcArrayLayout> for GcLayout {
284 fn from(layout: GcArrayLayout) -> Self {
285 Self::Array(layout)
286 }
287}
288
289impl From<Arc<GcStructLayout>> for GcLayout {
290 fn from(layout: Arc<GcStructLayout>) -> Self {
291 Self::Struct(layout)
292 }
293}
294
295impl TryClone for GcLayout {
296 fn try_clone(&self) -> core::result::Result<Self, wasmtime_core::error::OutOfMemory> {
297 Ok(self.clone())
298 }
299}
300
301impl GcLayout {
302 #[track_caller]
304 pub fn unwrap_struct(&self) -> &Arc<GcStructLayout> {
305 match self {
306 Self::Struct(s) => s,
307 _ => panic!("GcLayout::unwrap_struct on non-struct GC layout"),
308 }
309 }
310
311 #[track_caller]
313 pub fn unwrap_array(&self) -> &GcArrayLayout {
314 match self {
315 Self::Array(a) => a,
316 _ => panic!("GcLayout::unwrap_array on non-array GC layout"),
317 }
318 }
319}
320
321#[derive(Clone, Debug)]
336pub struct GcArrayLayout {
337 pub base_size: u32,
341
342 pub align: u32,
344
345 pub elem_size: u32,
347
348 pub elems_are_gc_refs: bool,
350}
351
352impl GcArrayLayout {
353 #[inline]
355 pub fn size_for_len(&self, len: u32) -> Option<u32> {
356 self.elem_offset(len)
357 }
358
359 #[inline]
361 pub fn elem_offset(&self, i: u32) -> Option<u32> {
362 let elem_offset = i.checked_mul(self.elem_size)?;
363 self.base_size.checked_add(elem_offset)
364 }
365
366 pub fn layout(&self, len: u32) -> Option<Layout> {
369 let size = self.size_for_len(len)?;
370 let size = usize::try_from(size).unwrap();
371 let align = usize::try_from(self.align).unwrap();
372 Layout::from_size_align(size, align).ok()
373 }
374}
375
376#[derive(Debug)]
392pub struct GcStructLayout {
393 pub size: u32,
395
396 pub align: u32,
398
399 pub fields: TryVec<GcStructLayoutField>,
402
403 pub is_exception: bool,
405}
406
407impl TryClone for GcStructLayout {
408 fn try_clone(&self) -> Result<Self, OutOfMemory> {
409 Ok(GcStructLayout {
410 size: self.size,
411 align: self.align,
412 fields: self.fields.try_clone()?,
413 is_exception: self.is_exception,
414 })
415 }
416}
417
418impl GcStructLayout {
419 pub fn layout(&self) -> Layout {
421 let size = usize::try_from(self.size).unwrap();
422 let align = usize::try_from(self.align).unwrap();
423 Layout::from_size_align(size, align).unwrap()
424 }
425}
426
427#[derive(Clone, Copy, Debug)]
429pub struct GcStructLayoutField {
430 pub offset: u32,
432
433 pub is_gc_ref: bool,
439}
440
441impl TryClone for GcStructLayoutField {
442 fn try_clone(&self) -> Result<Self, OutOfMemory> {
443 Ok(*self)
444 }
445}
446
447#[repr(u32)]
473#[derive(Clone, Copy, Debug, PartialEq, Eq)]
474#[rustfmt::skip]
475#[expect(missing_docs, reason = "self-describing variants")]
476pub enum VMGcKind {
477 ExternRef = 0b010000 << 26,
478 AnyRef = 0b100000 << 26,
479 EqRef = 0b101000 << 26,
480 ArrayRef = 0b101010 << 26,
481 StructRef = 0b101100 << 26,
482 ExnRef = 0b000001 << 26,
483}
484
485pub const VM_GC_KIND_SIZE: u8 = 4;
487
488const _: () = assert!(VM_GC_KIND_SIZE as usize == core::mem::size_of::<VMGcKind>());
489
490impl VMGcKind {
491 pub const MASK: u32 = 0b111111 << 26;
493
494 pub const UNUSED_MASK: u32 = !Self::MASK;
497
498 #[inline]
500 pub fn value_fits_in_unused_bits(value: u32) -> bool {
501 (value & Self::UNUSED_MASK) == value
502 }
503
504 #[inline]
507 pub fn from_high_bits_of_u32(val: u32) -> VMGcKind {
508 let masked = val & Self::MASK;
509 let result = Self::try_from_u32(masked)
510 .unwrap_or_else(|| panic!("invalid `VMGcKind`: {masked:#032b}"));
511
512 let poison_kind = u32::from_le_bytes([POISON, POISON, POISON, POISON]) & VMGcKind::MASK;
513 debug_assert_ne!(
514 masked, poison_kind,
515 "No valid `VMGcKind` should overlap with the poison pattern"
516 );
517
518 result
519 }
520
521 #[inline]
525 pub fn matches(self, other: Self) -> bool {
526 (self.as_u32() & other.as_u32()) == other.as_u32()
527 }
528
529 #[inline]
531 pub fn as_u32(self) -> u32 {
532 self as u32
533 }
534
535 #[inline]
539 pub fn try_from_u32(x: u32) -> Option<VMGcKind> {
540 match x {
541 _ if x == Self::ExternRef.as_u32() => Some(Self::ExternRef),
542 _ if x == Self::AnyRef.as_u32() => Some(Self::AnyRef),
543 _ if x == Self::EqRef.as_u32() => Some(Self::EqRef),
544 _ if x == Self::ArrayRef.as_u32() => Some(Self::ArrayRef),
545 _ if x == Self::StructRef.as_u32() => Some(Self::StructRef),
546 _ if x == Self::ExnRef.as_u32() => Some(Self::ExnRef),
547 _ => None,
548 }
549 }
550}
551
552#[cfg(test)]
553mod tests {
554 use super::VMGcKind::*;
555 use crate::prelude::*;
556
557 #[test]
558 fn kind_matches() {
559 let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef, ExnRef];
560
561 for (sup, subs) in [
562 (ExternRef, vec![]),
563 (AnyRef, vec![EqRef, ArrayRef, StructRef]),
564 (EqRef, vec![ArrayRef, StructRef]),
566 (ArrayRef, vec![]),
567 (StructRef, vec![]),
568 (ExnRef, vec![]),
569 ] {
570 assert!(sup.matches(sup));
571 for sub in &subs {
572 assert!(sub.matches(sup));
573 }
574 for kind in all.iter().filter(|k| **k != sup && !subs.contains(k)) {
575 assert!(!kind.matches(sup));
576 }
577 }
578 }
579}