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 pub fn externref() -> Self {
34 Self::from_kind_and_index(VMGcKind::ExternRef, VMSharedTypeIndex::reserved_value())
35 }
36
37 pub fn from_kind_and_index(kind: VMGcKind, ty: VMSharedTypeIndex) -> Self {
39 let kind = kind.as_u32();
40 Self { kind, ty }
41 }
42
43 pub fn kind(&self) -> VMGcKind {
45 VMGcKind::from_high_bits_of_u32(self.kind)
46 }
47
48 pub fn reserved_u26(&self) -> u32 {
53 self.kind & VMGcKind::UNUSED_MASK
54 }
55
56 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 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 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#[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 pub const I31_REF_DISCRIMINANT: u32 = 1;
153
154 pub fn from_raw_u32(raw: u32) -> Option<Self> {
164 Some(Self::from_raw_non_zero_u32(NonZeroU32::new(raw)?))
165 }
166
167 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 pub fn from_raw_non_zero_u32(raw: NonZeroU32) -> Self {
187 VMGcRef(raw)
188 }
189
190 #[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 pub fn unchecked_copy(&self) -> Self {
208 VMGcRef(self.0)
209 }
210
211 pub fn copy_i31(&self) -> Self {
215 assert!(self.is_i31());
216 self.unchecked_copy()
217 }
218
219 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 pub fn as_raw_non_zero_u32(&self) -> NonZeroU32 {
233 self.0
234 }
235
236 pub fn as_raw_u32(&self) -> u32 {
239 self.0.get()
240 }
241
242 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 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 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 #[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 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 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 #[inline]
358 pub fn is_i31(&self) -> bool {
359 let val = self.0.get();
360 (val & Self::I31_REF_DISCRIMINANT) != 0
361 }
362
363 #[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 #[inline]
376 pub fn unwrap_i31(&self) -> I31 {
377 self.as_i31().unwrap()
378 }
379
380 #[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 #[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 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 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 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 bail_bug!("setting continuation references in struct fields not yet supported");
590 }
591 }
592 }
593}
594
595pub unsafe trait GcHeapObject: Send + Sync {
612 fn is(header: &VMGcHeader) -> bool;
615}
616
617#[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 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 pub fn drop(self, gc_store: &mut GcStore) {
643 gc_store.drop_gc_ref(self.gc_ref);
644 }
645
646 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 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}