Skip to main content

wasmtime/runtime/vm/gc/
gc_ref.rs

1use crate::runtime::vm::{FuncRefTableId, GcHeap, GcStore, I31, SendSyncPtr};
2use crate::store::AutoAssertNoGc;
3use crate::{
4    AnyRef, ExnRef, ExternRef, Func, HeapTopType, Result, StorageType, Val, ValType, bail_bug,
5};
6use core::fmt;
7use core::marker;
8use core::num::NonZeroU32;
9use wasmtime_core::truncate::{truncate_i32_to_i8, truncate_i32_to_i16};
10use wasmtime_environ::packed_option::ReservedValue;
11use wasmtime_environ::{VMGcKind, VMSharedTypeIndex};
12
13pub use crate::runtime::vm::vmcontext::VMGcHeader;
14
15unsafe impl GcHeapObject for VMGcHeader {
16    #[inline]
17    fn is(_: &VMGcHeader) -> bool {
18        true
19    }
20}
21
22const _: () = {
23    use core::mem::offset_of;
24    use wasmtime_environ::*;
25    assert!((VM_GC_HEADER_SIZE as usize) == core::mem::size_of::<VMGcHeader>());
26    assert!((VM_GC_HEADER_ALIGN as usize) == core::mem::align_of::<VMGcHeader>());
27    assert!((VM_GC_HEADER_KIND_OFFSET as usize) == offset_of!(VMGcHeader, kind));
28    assert!((VM_GC_HEADER_TYPE_INDEX_OFFSET as usize) == offset_of!(VMGcHeader, ty));
29};
30
31impl VMGcHeader {
32    /// Create the header for an `externref`.
33    pub fn externref() -> Self {
34        Self::from_kind_and_index(VMGcKind::ExternRef, VMSharedTypeIndex::reserved_value())
35    }
36
37    /// Create the header for the given kind and type index.
38    pub fn from_kind_and_index(kind: VMGcKind, ty: VMSharedTypeIndex) -> Self {
39        let kind = kind.as_u32();
40        Self { kind, ty }
41    }
42
43    /// Get the kind of GC object that this is.
44    pub fn kind(&self) -> VMGcKind {
45        VMGcKind::from_high_bits_of_u32(self.kind)
46    }
47
48    /// Get the reserved 26 bits in this header.
49    ///
50    /// These are bits are reserved for `GcRuntime` implementations to make use
51    /// of however they see fit.
52    pub fn reserved_u26(&self) -> u32 {
53        self.kind & VMGcKind::UNUSED_MASK
54    }
55
56    /// Set the 26-bit reserved value.
57    ///
58    /// # Panics
59    ///
60    /// Panics if the given `value` has any of the upper 6 bits set.
61    pub fn set_reserved_u26(&mut self, value: u32) {
62        assert!(
63            VMGcKind::value_fits_in_unused_bits(value),
64            "VMGcHeader::set_reserved_u26 with value using more than 26 bits: \
65             {value:#034b} ({value}, {value:#010x})"
66        );
67        self.kind &= VMGcKind::MASK;
68        self.kind |= value;
69    }
70
71    /// Set the 26-bit reserved value.
72    ///
73    /// # Safety
74    ///
75    /// The given `value` must only use the lower 26 bits; its upper 6 bits must
76    /// be unset.
77    pub unsafe fn unchecked_set_reserved_u26(&mut self, value: u32) {
78        debug_assert_eq!(value & VMGcKind::MASK, 0);
79        self.kind &= VMGcKind::MASK;
80        self.kind |= value;
81    }
82
83    /// Get this object's specific concrete type.
84    pub fn ty(&self) -> Option<VMSharedTypeIndex> {
85        if self.ty.is_reserved_value() {
86            None
87        } else {
88            Some(self.ty)
89        }
90    }
91}
92
93/// A raw, unrooted GC reference.
94///
95/// A `VMGcRef` is either:
96///
97/// * A reference to some kind of object on the GC heap, but we don't know
98///   exactly which kind without further reflection. Furthermore, this is not
99///   actually a pointer, but a compact index into a Wasm GC heap.
100///
101/// * An `i31ref`: it doesn't actually reference an object in the GC heap, but
102///   is instead an inline, unboxed 31-bit integer.
103///
104/// ## `VMGcRef` and GC Barriers
105///
106/// Depending on the garbage collector in use, cloning, writing, and dropping a
107/// `VMGcRef` may require invoking GC barriers (little snippets of code provided
108/// by the collector to ensure it is correctly tracking all GC references).
109///
110/// Therefore, to encourage correct usage of GC barriers, this type does *NOT*
111/// implement `Clone` or `Copy`. Use `GcStore::clone_gc_ref`,
112/// `GcStore::write_gc_ref`, and `GcStore::drop_gc_ref` to clone, write, and
113/// drop `VMGcRef`s respectively.
114///
115/// As an escape hatch, if you really need to copy a `VMGcRef` without invoking
116/// GC barriers and you understand why that will not lead to GC bugs in this
117/// particular case, you can use the `unchecked_copy` method.
118#[derive(Debug, PartialEq, Eq, Hash)]
119#[repr(transparent)]
120pub struct VMGcRef(NonZeroU32);
121
122impl<T> From<TypedGcRef<T>> for VMGcRef {
123    #[inline]
124    fn from(value: TypedGcRef<T>) -> Self {
125        value.gc_ref
126    }
127}
128
129impl fmt::LowerHex for VMGcRef {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        self.0.fmt(f)
132    }
133}
134
135impl fmt::UpperHex for VMGcRef {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        self.0.fmt(f)
138    }
139}
140
141impl fmt::Pointer for VMGcRef {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        write!(f, "{self:#x}")
144    }
145}
146
147impl VMGcRef {
148    /// If this bit is set on a GC reference, then the GC reference is actually an
149    /// unboxed `i31`.
150    ///
151    /// Must be kept in sync with `wasmtime_cranelift::I31_REF_DISCRIMINANT`.
152    pub const I31_REF_DISCRIMINANT: u32 = 1;
153
154    /// Create a new `VMGcRef` from the given raw u32 value.
155    ///
156    /// Does not discriminate between indices into a GC heap and `i31ref`s.
157    ///
158    /// Returns `None` for zero values.
159    ///
160    /// The given index should point to a valid GC-managed object within this
161    /// reference's associated heap. Failure to uphold this will be memory safe,
162    /// but will lead to general failures such as panics or incorrect results.
163    pub fn from_raw_u32(raw: u32) -> Option<Self> {
164        Some(Self::from_raw_non_zero_u32(NonZeroU32::new(raw)?))
165    }
166
167    /// Create a new `VMGcRef` from the given index into a GC heap.
168    ///
169    /// The given index should point to a valid GC-managed object within this
170    /// reference's associated heap. Failure to uphold this will be memory safe,
171    /// but will lead to general failures such as panics or incorrect results.
172    ///
173    /// Returns `None` when the index is not 2-byte aligned and therefore
174    /// conflicts with the `i31ref` discriminant.
175    pub fn from_heap_index(index: NonZeroU32) -> Option<Self> {
176        if (index.get() & Self::I31_REF_DISCRIMINANT) == 0 {
177            Some(Self::from_raw_non_zero_u32(index))
178        } else {
179            None
180        }
181    }
182
183    /// Create a new `VMGcRef` from the given raw value.
184    ///
185    /// Does not discriminate between indices into a GC heap and `i31ref`s.
186    pub fn from_raw_non_zero_u32(raw: NonZeroU32) -> Self {
187        VMGcRef(raw)
188    }
189
190    /// Construct a new `VMGcRef` from an unboxed 31-bit integer.
191    #[inline]
192    pub fn from_i31(val: I31) -> Self {
193        let val = (val.get_u32() << 1) | Self::I31_REF_DISCRIMINANT;
194        debug_assert_ne!(val, 0);
195        let non_zero = unsafe { NonZeroU32::new_unchecked(val) };
196        VMGcRef::from_raw_non_zero_u32(non_zero)
197    }
198
199    /// Copy this `VMGcRef` without running the GC's clone barriers.
200    ///
201    /// Prefer calling `clone(&mut GcStore)` instead! This is mostly an internal
202    /// escape hatch for collector implementations.
203    ///
204    /// Failure to run GC barriers when they would otherwise be necessary can
205    /// lead to leaks, panics, and wrong results. It cannot lead to memory
206    /// unsafety, however.
207    pub fn unchecked_copy(&self) -> Self {
208        VMGcRef(self.0)
209    }
210
211    /// Copy this `i31` GC reference, which never requires any GC barriers.
212    ///
213    /// Panics if this is not an `i31`.
214    pub fn copy_i31(&self) -> Self {
215        assert!(self.is_i31());
216        self.unchecked_copy()
217    }
218
219    /// Get this GC reference as a u32 index into its GC heap.
220    ///
221    /// Panics when called for `i31ref`s.
222    pub fn heap_index(&self) -> Result<NonZeroU32> {
223        if self.is_i31() {
224            bail_bug!("attempted to get heap index of i31")
225        }
226
227        Ok(self.0)
228    }
229
230    /// Get this GC reference as a raw, non-zero u32 value, regardless whether
231    /// it is actually a reference to a GC object or is an `i31ref`.
232    pub fn as_raw_non_zero_u32(&self) -> NonZeroU32 {
233        self.0
234    }
235
236    /// Get this GC reference as a raw u32 value, regardless whether it is
237    /// actually a reference to a GC object or is an `i31ref`.
238    pub fn as_raw_u32(&self) -> u32 {
239        self.0.get()
240    }
241
242    /// Creates a typed GC reference from `self`, checking that `self` actually
243    /// is a `T`.
244    ///
245    /// If this is not a GC reference to a `T`, then `Err(self)` is returned.
246    pub fn into_typed<T>(self, gc_heap: &impl GcHeap) -> Result<TypedGcRef<T>, Self>
247    where
248        T: GcHeapObject,
249    {
250        if self.is_i31() {
251            return Err(self);
252        }
253        if self.is_typed::<T>(gc_heap) {
254            Ok(TypedGcRef {
255                gc_ref: self,
256                _phantom: marker::PhantomData,
257            })
258        } else {
259            Err(self)
260        }
261    }
262
263    /// Creates a typed GC reference without actually checking that `self` is a
264    /// `T`.
265    ///
266    /// `self` should point to a `T` object. Failure to uphold this invariant is
267    /// memory safe, but will lead to general incorrectness such as panics or
268    /// wrong results.
269    pub fn into_typed_unchecked<T>(self) -> TypedGcRef<T>
270    where
271        T: GcHeapObject,
272    {
273        debug_assert!(!self.is_i31());
274        TypedGcRef {
275            gc_ref: self,
276            _phantom: marker::PhantomData,
277        }
278    }
279
280    /// Is this GC reference pointing to a `T`?
281    pub fn is_typed<T>(&self, gc_heap: &impl GcHeap) -> bool
282    where
283        T: GcHeapObject,
284    {
285        if self.is_i31() {
286            return false;
287        }
288        match gc_heap.header(&self) {
289            Ok(header) => T::is(header),
290            Err(_) => false,
291        }
292    }
293
294    /// Borrow `self` as a typed GC reference, checking that `self` actually is
295    /// a `T`.
296    #[inline]
297    pub fn as_typed<T>(&self, gc_heap: &impl GcHeap) -> Option<&TypedGcRef<T>>
298    where
299        T: GcHeapObject,
300    {
301        if self.is_i31() {
302            return None;
303        }
304        if self.is_typed::<T>(gc_heap) {
305            let ptr = self as *const VMGcRef;
306            let ret = unsafe { &*ptr.cast() };
307            assert!(matches!(
308                ret,
309                TypedGcRef {
310                    gc_ref: VMGcRef(_),
311                    _phantom
312                }
313            ));
314            Some(ret)
315        } else {
316            None
317        }
318    }
319
320    /// Creates a typed GC reference without actually checking that `self` is a
321    /// `T`.
322    ///
323    /// `self` should point to a `T` object. Failure to uphold this invariant is
324    /// memory safe, but will lead to general incorrectness such as panics or
325    /// wrong results.
326    pub fn as_typed_unchecked<T>(&self) -> &TypedGcRef<T>
327    where
328        T: GcHeapObject,
329    {
330        debug_assert!(!self.is_i31());
331        let ptr = self as *const VMGcRef;
332        let ret = unsafe { &*ptr.cast() };
333        assert!(matches!(
334            ret,
335            TypedGcRef {
336                gc_ref: VMGcRef(_),
337                _phantom
338            }
339        ));
340        ret
341    }
342
343    /// Get a reference to the GC header that this GC reference is pointing to.
344    ///
345    /// Returns `None` when this is an `i31ref` and doesn't actually point to a
346    /// GC header.
347    pub fn gc_header<'a>(&self, gc_heap: &'a (impl GcHeap + ?Sized)) -> Option<&'a VMGcHeader> {
348        if self.is_i31() {
349            None
350        } else {
351            gc_heap.header(self).ok()
352        }
353    }
354
355    /// Is this `VMGcRef` actually an unboxed 31-bit integer, and not actually a
356    /// GC reference?
357    #[inline]
358    pub fn is_i31(&self) -> bool {
359        let val = self.0.get();
360        (val & Self::I31_REF_DISCRIMINANT) != 0
361    }
362
363    /// Get the underlying `i31`, if any.
364    #[inline]
365    pub fn as_i31(&self) -> Option<I31> {
366        if self.is_i31() {
367            let val = self.0.get();
368            Some(I31::wrapping_u32(val >> 1))
369        } else {
370            None
371        }
372    }
373
374    /// Get the underlying `i31`, panicking if this is not an `i31`.
375    #[inline]
376    pub fn unwrap_i31(&self) -> I31 {
377        self.as_i31().unwrap()
378    }
379
380    /// Is this `VMGcRef` a `VMExternRef`?
381    #[inline]
382    pub fn is_extern_ref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool {
383        self.gc_header(gc_heap)
384            .map_or(false, |h| h.kind().matches(VMGcKind::ExternRef))
385    }
386
387    /// Is this `VMGcRef` an `anyref`?
388    #[inline]
389    pub fn is_any_ref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool {
390        self.is_i31()
391            || self
392                .gc_header(gc_heap)
393                .map_or(false, |h| h.kind().matches(VMGcKind::AnyRef))
394    }
395
396    pub fn read_val(
397        &self,
398        store: &mut AutoAssertNoGc,
399        ty: &StorageType,
400        offset: u32,
401    ) -> Result<Val> {
402        let data = store.require_gc_store_mut()?.gc_object_data(self)?;
403        Ok(match ty {
404            StorageType::I8 => Val::I32(data.read_u8(offset)?.into()),
405            StorageType::I16 => Val::I32(data.read_u16(offset)?.into()),
406            StorageType::ValType(ValType::I32) => Val::I32(data.read_i32(offset)?),
407            StorageType::ValType(ValType::I64) => Val::I64(data.read_i64(offset)?),
408            StorageType::ValType(ValType::F32) => Val::F32(data.read_u32(offset)?),
409            StorageType::ValType(ValType::F64) => Val::F64(data.read_u64(offset)?),
410            StorageType::ValType(ValType::V128) => Val::V128(data.read_v128(offset)?),
411            StorageType::ValType(ValType::Ref(r)) => match r.heap_type().top() {
412                HeapTopType::Extern => {
413                    let raw = data.read_u32(offset)?;
414                    Val::ExternRef(ExternRef::_from_raw(store, raw))
415                }
416                HeapTopType::Any => {
417                    let raw = data.read_u32(offset)?;
418                    Val::AnyRef(AnyRef::_from_raw(store, raw))
419                }
420                HeapTopType::Exn => {
421                    let raw = data.read_u32(offset)?;
422                    Val::ExnRef(ExnRef::_from_raw(store, raw))
423                }
424                HeapTopType::Func => {
425                    let func_ref_id = data.read_u32(offset)?;
426                    let func_ref_id = FuncRefTableId::from_raw(func_ref_id);
427                    let func_ref = store
428                        .unwrap_gc_store()
429                        .func_ref_table
430                        .get_untyped(func_ref_id)?;
431                    Val::FuncRef(unsafe {
432                        func_ref.map(|p| Func::from_vm_func_ref(store.id(), p.as_non_null()))
433                    })
434                }
435                HeapTopType::Cont => bail_bug!("continuation references are unsupported"),
436            },
437        })
438    }
439
440    pub fn initialize_val(
441        &self,
442        store: &mut AutoAssertNoGc,
443        ty: &StorageType,
444        offset: u32,
445        val: Val,
446    ) -> Result<()> {
447        let gcstore = store.require_gc_store_mut()?;
448        match val {
449            Val::I32(i) if ty.is_i8() => gcstore
450                .gc_object_data(self)?
451                .write_i8(offset, truncate_i32_to_i8(i)),
452            Val::I32(i) if ty.is_i16() => gcstore
453                .gc_object_data(self)?
454                .write_i16(offset, truncate_i32_to_i16(i)),
455            Val::I32(i) => gcstore.gc_object_data(self)?.write_i32(offset, i),
456            Val::I64(i) => gcstore.gc_object_data(self)?.write_i64(offset, i),
457            Val::F32(f) => gcstore.gc_object_data(self)?.write_u32(offset, f),
458            Val::F64(f) => gcstore.gc_object_data(self)?.write_u64(offset, f),
459            Val::V128(v) => gcstore.gc_object_data(self)?.write_v128(offset, v),
460
461            // NB: We don't need to do a write barrier when initializing a
462            // field, because there is nothing being overwritten. Therefore, we
463            // just the clone barrier.
464            Val::ExternRef(x) => {
465                let x = match x {
466                    None => 0,
467                    Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
468                };
469                store
470                    .require_gc_store_mut()?
471                    .gc_object_data(self)?
472                    .write_u32(offset, x)
473            }
474            Val::AnyRef(x) => {
475                let x = match x {
476                    None => 0,
477                    Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
478                };
479                store
480                    .require_gc_store_mut()?
481                    .gc_object_data(self)?
482                    .write_u32(offset, x)
483            }
484            Val::ExnRef(x) => {
485                let x = match x {
486                    None => 0,
487                    Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
488                };
489                store
490                    .require_gc_store_mut()?
491                    .gc_object_data(self)?
492                    .write_u32(offset, x)
493            }
494
495            Val::FuncRef(f) => {
496                let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store)));
497                let gcstore = store.require_gc_store_mut()?;
498                let id = unsafe { gcstore.func_ref_table.intern(f) };
499                gcstore
500                    .gc_object_data(self)?
501                    .write_u32(offset, id.into_raw())
502            }
503            Val::ContRef(_) => {
504                // TODO(#10248): Implement struct continuation reference field init handling
505                bail_bug!(
506                    "initializing continuation references in struct fields not yet supported"
507                );
508            }
509        }
510    }
511
512    pub fn write_val(
513        &self,
514        store: &mut AutoAssertNoGc,
515        ty: &StorageType,
516        offset: u32,
517        val: Val,
518    ) -> Result<()> {
519        let gcstore = store.require_gc_store_mut()?;
520        let data = gcstore.gc_object_data(self)?;
521        match val {
522            Val::I32(i) if ty.is_i8() => data.write_i8(offset, truncate_i32_to_i8(i)),
523            Val::I32(i) if ty.is_i16() => data.write_i16(offset, truncate_i32_to_i16(i)),
524            Val::I32(i) => data.write_i32(offset, i),
525            Val::I64(i) => data.write_i64(offset, i),
526            Val::F32(f) => data.write_u32(offset, f),
527            Val::F64(f) => data.write_u64(offset, f),
528            Val::V128(v) => data.write_v128(offset, v),
529
530            // For GC-managed references, we need to take care to run the
531            // appropriate barriers, even when we are writing null references
532            // into the struct.
533            //
534            // POD-read the old value into a local copy, run the GC write
535            // barrier on that local copy, and then POD-write the updated
536            // value back into the struct. This avoids transmuting the inner
537            // data, which would probably be fine, but this approach is
538            // Obviously Correct and should get us by for now. If LLVM isn't
539            // able to elide some of these unnecessary copies, and this
540            // method is ever hot enough, we can always come back and clean
541            // it up in the future.
542            Val::ExternRef(e) => {
543                let raw = data.read_u32(offset)?;
544                let mut gc_ref = VMGcRef::from_raw_u32(raw);
545                let e = match e {
546                    Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()),
547                    None => None,
548                };
549                let store = store.require_gc_store_mut()?;
550                store.write_gc_ref(&mut gc_ref, e.as_ref())?;
551                let data = store.gc_object_data(self)?;
552                data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()))
553            }
554            Val::AnyRef(a) => {
555                let raw = data.read_u32(offset)?;
556                let mut gc_ref = VMGcRef::from_raw_u32(raw);
557                let a = match a {
558                    Some(a) => Some(a.try_gc_ref(store)?.unchecked_copy()),
559                    None => None,
560                };
561                let store = store.require_gc_store_mut()?;
562                store.write_gc_ref(&mut gc_ref, a.as_ref())?;
563                let data = store.gc_object_data(self)?;
564                data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()))
565            }
566            Val::ExnRef(e) => {
567                let raw = data.read_u32(offset)?;
568                let mut gc_ref = VMGcRef::from_raw_u32(raw);
569                let e = match e {
570                    Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()),
571                    None => None,
572                };
573                let store = store.require_gc_store_mut()?;
574                store.write_gc_ref(&mut gc_ref, e.as_ref())?;
575                let data = store.gc_object_data(self)?;
576                data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()))
577            }
578
579            Val::FuncRef(f) => {
580                let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store)));
581                let gcstore = store.require_gc_store_mut()?;
582                let id = unsafe { gcstore.func_ref_table.intern(f) };
583                gcstore
584                    .gc_object_data(self)?
585                    .write_u32(offset, id.into_raw())
586            }
587            Val::ContRef(_) => {
588                // TODO(#10248): Implement struct continuation reference field handling
589                bail_bug!("setting continuation references in struct fields not yet supported");
590            }
591        }
592    }
593}
594
595/// A trait implemented by all objects allocated inside a GC heap.
596///
597/// # Safety
598///
599/// All implementations must:
600///
601/// * Be `repr(C)` or `repr(transparent)`
602///
603/// * Begin with a `VMGcHeader` as their first field
604///
605/// * Not have `Drop` implementations (aka, `std::mem::needs_drop::<Self>()`
606///   should return `false`).
607///
608/// * Be memory safe to transmute to from an arbitrary byte sequence (that is,
609///   it is okay if some bit patterns are invalid with regards to correctness,
610///   so long as these invalid bit patterns cannot lead to memory unsafety).
611pub unsafe trait GcHeapObject: Send + Sync {
612    /// Check whether the GC object with the given header is an instance of
613    /// `Self`.
614    fn is(header: &VMGcHeader) -> bool;
615}
616
617/// A GC reference to a heap object of concrete type `T`.
618///
619/// Create typed GC refs via `VMGcRef::into_typed` and `VMGcRef::as_typed`, as
620/// well as via their unchecked equivalents `VMGcRef::into_typed_unchecked` and
621/// `VMGcRef::as_typed_unchecked`.
622#[derive(Debug, PartialEq, Eq, Hash)]
623#[repr(transparent)]
624pub struct TypedGcRef<T> {
625    gc_ref: VMGcRef,
626    _phantom: marker::PhantomData<*mut T>,
627}
628
629impl<T> TypedGcRef<T>
630where
631    T: GcHeapObject,
632{
633    /// Clone this `VMGcRef`, running any GC barriers as necessary.
634    pub fn clone(&self, gc_store: &mut GcStore) -> Self {
635        Self {
636            gc_ref: gc_store.clone_gc_ref(&self.gc_ref),
637            _phantom: marker::PhantomData,
638        }
639    }
640
641    /// Explicitly drop this GC reference, running any GC barriers as necessary.
642    pub fn drop(self, gc_store: &mut GcStore) {
643        gc_store.drop_gc_ref(self.gc_ref);
644    }
645
646    /// Copy this GC reference without running the GC's clone barriers.
647    ///
648    /// Prefer calling `clone(&mut GcStore)` instead! This is mostly an internal
649    /// escape hatch for collector implementations.
650    ///
651    /// Failure to run GC barriers when they would otherwise be necessary can
652    /// lead to leaks, panics, and wrong results. It cannot lead to memory
653    /// unsafety, however.
654    pub fn unchecked_copy(&self) -> Self {
655        Self {
656            gc_ref: self.gc_ref.unchecked_copy(),
657            _phantom: marker::PhantomData,
658        }
659    }
660}
661
662impl<T> TypedGcRef<T> {
663    /// Get the untyped version of this GC reference.
664    pub fn as_untyped(&self) -> &VMGcRef {
665        &self.gc_ref
666    }
667}
668
669#[cfg(test)]
670mod tests {
671    use super::*;
672
673    #[test]
674    fn reserved_bits() {
675        let kind = VMGcKind::StructRef;
676        let ty = VMSharedTypeIndex::new(1234);
677        let mut header = VMGcHeader::from_kind_and_index(kind, ty);
678
679        assert_eq!(header.reserved_u26(), 0);
680        assert_eq!(header.kind(), kind);
681        assert_eq!(header.ty(), Some(ty));
682
683        header.set_reserved_u26(36);
684        assert_eq!(header.reserved_u26(), 36);
685        assert_eq!(header.kind(), kind);
686        assert_eq!(header.ty(), Some(ty));
687
688        let max = (1 << 26) - 1;
689        header.set_reserved_u26(max);
690        assert_eq!(header.reserved_u26(), max);
691        assert_eq!(header.kind(), kind);
692        assert_eq!(header.ty(), Some(ty));
693
694        header.set_reserved_u26(0);
695        assert_eq!(header.reserved_u26(), 0);
696        assert_eq!(header.kind(), kind);
697        assert_eq!(header.ty(), Some(ty));
698
699        let result = std::panic::catch_unwind(move || header.set_reserved_u26(max + 1));
700        assert!(result.is_err());
701    }
702}