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::prelude::*;
19use crate::{
20 WasmArrayType, WasmCompositeInnerType, WasmCompositeType, WasmStorageType, WasmStructType,
21 WasmValType,
22};
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 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
85 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
86
87 let mut size = header_size;
88 let mut align = header_align;
89
90 let length_field_offset = field(&mut size, &mut align, 4);
91 assert_eq!(length_field_offset, expected_array_length_offset);
92
93 let elem_size = byte_size_of_wasm_ty_in_gc_heap(&ty.0.element_type);
94 let elems_offset = align_up(&mut size, &mut align, elem_size);
95 assert_eq!(elems_offset, size);
96
97 GcArrayLayout {
98 base_size: size,
99 align,
100 elem_size,
101 }
102}
103
104/// Common code to define a GC struct's layout, given the size and alignment of
105/// the collector's GC header and its expected offset of the array length field.
106#[cfg(any(feature = "gc-drc", feature = "gc-null"))]
107fn common_struct_layout(
108 ty: &WasmStructType,
109 header_size: u32,
110 header_align: u32,
111) -> GcStructLayout {
112 assert!(header_size >= crate::VM_GC_HEADER_SIZE);
113 assert!(header_align >= crate::VM_GC_HEADER_ALIGN);
114
115 // Process each field, aligning it to its natural alignment.
116 //
117 // We don't try and do any fancy field reordering to minimize padding
118 // (yet?) because (a) the toolchain probably already did that and (b)
119 // we're just doing the simple thing first. We can come back and improve
120 // things here if we find that (a) isn't actually holding true in
121 // practice.
122 let mut size = header_size;
123 let mut align = header_align;
124
125 let fields = ty
126 .fields
127 .iter()
128 .map(|f| {
129 let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type);
130 field(&mut size, &mut align, field_size)
131 })
132 .collect();
133
134 // Ensure that the final size is a multiple of the alignment, for
135 // simplicity.
136 let align_size_to = align;
137 align_up(&mut size, &mut align, align_size_to);
138
139 GcStructLayout {
140 size,
141 align,
142 fields,
143 }
144}
145
146/// A trait for getting the layout of a Wasm GC struct or array inside a
147/// particular collector.
148pub trait GcTypeLayouts {
149 /// The offset of an array's length field.
150 ///
151 /// This must be the same for all arrays in the heap, regardless of their
152 /// element type.
153 fn array_length_field_offset(&self) -> u32;
154
155 /// Get this collector's layout for the given composite type.
156 ///
157 /// Returns `None` if the type is a function type, as functions are not
158 /// managed by the GC.
159 fn gc_layout(&self, ty: &WasmCompositeType) -> Option<GcLayout> {
160 assert!(!ty.shared);
161 match &ty.inner {
162 WasmCompositeInnerType::Array(ty) => Some(self.array_layout(ty).into()),
163 WasmCompositeInnerType::Struct(ty) => Some(self.struct_layout(ty).into()),
164 WasmCompositeInnerType::Func(_) => None,
165 WasmCompositeInnerType::Cont(_) => None,
166 }
167 }
168
169 /// Get this collector's layout for the given array type.
170 fn array_layout(&self, ty: &WasmArrayType) -> GcArrayLayout;
171
172 /// Get this collector's layout for the given struct type.
173 fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout;
174}
175
176/// The layout of a GC-managed object.
177#[derive(Clone, Debug)]
178pub enum GcLayout {
179 /// The layout of a GC-managed array object.
180 Array(GcArrayLayout),
181
182 /// The layout of a GC-managed struct object.
183 Struct(GcStructLayout),
184}
185
186impl From<GcArrayLayout> for GcLayout {
187 fn from(layout: GcArrayLayout) -> Self {
188 Self::Array(layout)
189 }
190}
191
192impl From<GcStructLayout> for GcLayout {
193 fn from(layout: GcStructLayout) -> Self {
194 Self::Struct(layout)
195 }
196}
197
198impl GcLayout {
199 /// Get the underlying `GcStructLayout`, or panic.
200 #[track_caller]
201 pub fn unwrap_struct(&self) -> &GcStructLayout {
202 match self {
203 Self::Struct(s) => s,
204 _ => panic!("GcLayout::unwrap_struct on non-struct GC layout"),
205 }
206 }
207
208 /// Get the underlying `GcArrayLayout`, or panic.
209 #[track_caller]
210 pub fn unwrap_array(&self) -> &GcArrayLayout {
211 match self {
212 Self::Array(a) => a,
213 _ => panic!("GcLayout::unwrap_array on non-array GC layout"),
214 }
215 }
216}
217
218/// The layout of a GC-managed array.
219///
220/// This layout is only valid for use with the GC runtime that created it. It is
221/// not valid to use one GC runtime's layout with another GC runtime, doing so
222/// is memory safe but will lead to general incorrectness like panics and wrong
223/// results.
224///
225/// All offsets are from the start of the object; that is, the size of the GC
226/// header (for example) is included in the offset.
227///
228/// All arrays are composed of the generic `VMGcHeader`, followed by
229/// collector-specific fields, followed by the contiguous array elements
230/// themselves. The array elements must be aligned to the element type's natural
231/// alignment.
232#[derive(Clone, Debug)]
233pub struct GcArrayLayout {
234 /// The size of this array object, without any elements.
235 ///
236 /// The array's elements, if any, must begin at exactly this offset.
237 pub base_size: u32,
238
239 /// The alignment of this array.
240 pub align: u32,
241
242 /// The size and natural alignment of each element in this array.
243 pub elem_size: u32,
244}
245
246impl GcArrayLayout {
247 /// Get the total size of this array for a given length of elements.
248 #[inline]
249 pub fn size_for_len(&self, len: u32) -> u32 {
250 self.elem_offset(len)
251 }
252
253 /// Get the offset of the `i`th element in an array with this layout.
254 #[inline]
255 pub fn elem_offset(&self, i: u32) -> u32 {
256 self.base_size + i * self.elem_size
257 }
258
259 /// Get a `core::alloc::Layout` for an array of this type with the given
260 /// length.
261 pub fn layout(&self, len: u32) -> Layout {
262 let size = self.size_for_len(len);
263 let size = usize::try_from(size).unwrap();
264 let align = usize::try_from(self.align).unwrap();
265 Layout::from_size_align(size, align).unwrap()
266 }
267}
268
269/// The layout for a GC-managed struct type.
270///
271/// This layout is only valid for use with the GC runtime that created it. It is
272/// not valid to use one GC runtime's layout with another GC runtime, doing so
273/// is memory safe but will lead to general incorrectness like panics and wrong
274/// results.
275///
276/// All offsets are from the start of the object; that is, the size of the GC
277/// header (for example) is included in the offset.
278#[derive(Clone, Debug)]
279pub struct GcStructLayout {
280 /// The size (in bytes) of this struct.
281 pub size: u32,
282
283 /// The alignment (in bytes) of this struct.
284 pub align: u32,
285
286 /// The fields of this struct. The `i`th entry is the `i`th struct field's
287 /// offset (in bytes) in the struct.
288 pub fields: Vec<u32>,
289}
290
291impl GcStructLayout {
292 /// Get a `core::alloc::Layout` for a struct of this type.
293 pub fn layout(&self) -> Layout {
294 let size = usize::try_from(self.size).unwrap();
295 let align = usize::try_from(self.align).unwrap();
296 Layout::from_size_align(size, align).unwrap()
297 }
298}
299
300/// The kind of an object in a GC heap.
301///
302/// Note that this type is accessed from Wasm JIT code.
303///
304/// `VMGcKind` is a bitset where to test if `a` is a subtype of an
305/// "abstract-ish" type `b`, we can simply use a single bitwise-and operation:
306///
307/// ```ignore
308/// a <: b iff a & b == b
309/// ```
310///
311/// For example, because `VMGcKind::AnyRef` has the high bit set, every kind
312/// representing some subtype of `anyref` also has its high bit set.
313///
314/// We say "abstract-ish" type because in addition to the abstract heap types
315/// (other than `i31`) we also have variants for `externref`s that have been
316/// converted into an `anyref` via `extern.convert_any` and `externref`s that
317/// have been converted into an `anyref` via `any.convert_extern`. Note that in
318/// the latter case, because `any.convert_extern $foo` produces a value that is
319/// not an instance of `eqref`, `VMGcKind::AnyOfExternRef & VMGcKind::EqRef !=
320/// VMGcKind::EqRef`.
321///
322/// Furthermore, this type only uses the highest 6 bits of its `u32`
323/// representation, allowing the lower 27 bytes to be bitpacked with other stuff
324/// as users see fit.
325#[repr(u32)]
326#[derive(Clone, Copy, Debug, PartialEq, Eq)]
327#[rustfmt::skip]
328#[allow(missing_docs, reason = "self-describing variants")]
329pub enum VMGcKind {
330 ExternRef = 0b01000 << 27,
331 AnyRef = 0b10000 << 27,
332 EqRef = 0b10100 << 27,
333 ArrayRef = 0b10101 << 27,
334 StructRef = 0b10110 << 27,
335}
336
337impl VMGcKind {
338 /// Mask this value with a `u32` to get just the bits that `VMGcKind` uses.
339 pub const MASK: u32 = 0b11111 << 27;
340
341 /// Mask this value with a `u32` that potentially contains a `VMGcKind` to
342 /// get the bits that `VMGcKind` doesn't use.
343 pub const UNUSED_MASK: u32 = !Self::MASK;
344
345 /// Does the given value fit in the unused bits of a `VMGcKind`?
346 #[inline]
347 pub fn value_fits_in_unused_bits(value: u32) -> bool {
348 (value & Self::UNUSED_MASK) == value
349 }
350
351 /// Convert the given value into a `VMGcKind` by masking off the unused
352 /// bottom bits.
353 #[inline]
354 pub fn from_high_bits_of_u32(val: u32) -> VMGcKind {
355 let masked = val & Self::MASK;
356 match masked {
357 x if x == Self::ExternRef.as_u32() => Self::ExternRef,
358 x if x == Self::AnyRef.as_u32() => Self::AnyRef,
359 x if x == Self::EqRef.as_u32() => Self::EqRef,
360 x if x == Self::ArrayRef.as_u32() => Self::ArrayRef,
361 x if x == Self::StructRef.as_u32() => Self::StructRef,
362 _ => panic!("invalid `VMGcKind`: {masked:#032b}"),
363 }
364 }
365
366 /// Does this kind match the other kind?
367 ///
368 /// That is, is this kind a subtype of the other kind?
369 #[inline]
370 pub fn matches(self, other: Self) -> bool {
371 (self.as_u32() & other.as_u32()) == other.as_u32()
372 }
373
374 /// Get this `VMGcKind` as a raw `u32`.
375 #[inline]
376 pub fn as_u32(self) -> u32 {
377 self as u32
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use super::VMGcKind::*;
384 use crate::prelude::*;
385
386 #[test]
387 fn kind_matches() {
388 let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef];
389
390 for (sup, subs) in [
391 (ExternRef, vec![]),
392 (AnyRef, vec![EqRef, ArrayRef, StructRef]),
393 (EqRef, vec![ArrayRef, StructRef]),
394 (ArrayRef, vec![]),
395 (StructRef, vec![]),
396 ] {
397 assert!(sup.matches(sup));
398 for sub in &subs {
399 assert!(sub.matches(sup));
400 }
401 for kind in all.iter().filter(|k| **k != sup && !subs.contains(k)) {
402 assert!(!kind.matches(sup));
403 }
404 }
405 }
406}