1#![cfg(feature = "gc")]
3
4use crate::runtime::vm::VMGcRef;
5use crate::store::{Asyncness, StoreId};
6#[cfg(feature = "async")]
7use crate::vm::VMStore;
8use crate::vm::{self, VMGcHeader, VMStructRef};
9use crate::{AnyRef, FieldType};
10use crate::{
11 AsContext, AsContextMut, EqRef, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType,
12 OwnedRooted, RefType, Rooted, StructType, Val, ValRaw, ValType, WasmTy, bail_bug,
13 prelude::*,
14 store::{AutoAssertNoGc, StoreContextMut, StoreOpaque, StoreResourceLimiter},
15};
16use alloc::sync::Arc;
17use core::mem::{self, MaybeUninit};
18use wasmtime_environ::{GcStructLayout, VMGcKind, VMSharedTypeIndex};
19
20pub struct StructRefPre {
66 store_id: StoreId,
67 ty: StructType,
68}
69
70impl StructRefPre {
71 pub fn new(mut store: impl AsContextMut, ty: StructType) -> Self {
79 Self::_new(store.as_context_mut().0, ty)
80 }
81
82 pub(crate) fn _new(store: &mut StoreOpaque, ty: StructType) -> Self {
83 store.insert_gc_host_alloc_type(ty.registered_type().clone());
84 let store_id = store.id();
85 StructRefPre { store_id, ty }
86 }
87
88 pub(crate) fn layout(&self) -> &GcStructLayout {
89 self.ty
90 .registered_type()
91 .layout()
92 .expect("struct types have a layout")
93 .unwrap_struct()
94 }
95
96 pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
97 self.ty.registered_type().index()
98 }
99}
100
101#[derive(Debug)]
160#[repr(transparent)]
161pub struct StructRef {
162 pub(super) inner: GcRootIndex,
163}
164
165unsafe impl GcRefImpl for StructRef {
166 fn transmute_ref(index: &GcRootIndex) -> &Self {
167 let me: &Self = unsafe { mem::transmute(index) };
169
170 assert!(matches!(
172 me,
173 Self {
174 inner: GcRootIndex { .. },
175 }
176 ));
177
178 me
179 }
180}
181
182impl Rooted<StructRef> {
183 #[inline]
185 pub fn to_anyref(self) -> Rooted<AnyRef> {
186 self.unchecked_cast()
187 }
188
189 #[inline]
191 pub fn to_eqref(self) -> Rooted<EqRef> {
192 self.unchecked_cast()
193 }
194}
195
196impl OwnedRooted<StructRef> {
197 #[inline]
199 pub fn to_anyref(self) -> OwnedRooted<AnyRef> {
200 self.unchecked_cast()
201 }
202
203 #[inline]
205 pub fn to_eqref(self) -> OwnedRooted<EqRef> {
206 self.unchecked_cast()
207 }
208}
209
210impl StructRef {
211 pub fn new(
239 mut store: impl AsContextMut,
240 allocator: &StructRefPre,
241 fields: &[Val],
242 ) -> Result<Rooted<StructRef>> {
243 let (mut limiter, store) = store
244 .as_context_mut()
245 .0
246 .validate_sync_resource_limiter_and_store_opaque()?;
247 vm::assert_ready(Self::_new_async(
248 store,
249 limiter.as_mut(),
250 allocator,
251 fields,
252 Asyncness::No,
253 ))
254 }
255
256 #[cfg(feature = "async")]
279 pub async fn new_async(
280 mut store: impl AsContextMut,
281 allocator: &StructRefPre,
282 fields: &[Val],
283 ) -> Result<Rooted<StructRef>> {
284 let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
285 Self::_new_async(store, limiter.as_mut(), allocator, fields, Asyncness::Yes).await
286 }
287
288 pub(crate) async fn _new_async(
289 store: &mut StoreOpaque,
290 limiter: Option<&mut StoreResourceLimiter<'_>>,
291 allocator: &StructRefPre,
292 fields: &[Val],
293 asyncness: Asyncness,
294 ) -> Result<Rooted<StructRef>> {
295 Self::type_check_fields(store, allocator, fields)?;
296 store
297 .retry_after_gc_async(limiter, (), asyncness, |store, ()| {
298 Self::new_unchecked(store, allocator, fields)
299 })
300 .await
301 }
302
303 fn type_check_fields(
305 store: &mut StoreOpaque,
306 allocator: &StructRefPre,
307 fields: &[Val],
308 ) -> Result<(), Error> {
309 let expected_len = allocator.ty.fields().len();
310 let actual_len = fields.len();
311 ensure!(
312 actual_len == expected_len,
313 "expected {expected_len} fields, got {actual_len}"
314 );
315 for (ty, val) in allocator.ty.fields().zip(fields) {
316 assert!(
317 val.comes_from_same_store(store),
318 "field value comes from the wrong store",
319 );
320 let ty = ty.element_type().unpack();
321 val.ensure_matches_ty(store, ty)
322 .context("field type mismatch")?;
323 }
324 Ok(())
325 }
326
327 fn new_unchecked(
332 store: &mut StoreOpaque,
333 allocator: &StructRefPre,
334 fields: &[Val],
335 ) -> Result<Rooted<StructRef>> {
336 assert_eq!(
337 store.id(),
338 allocator.store_id,
339 "attempted to use a `StructRefPre` with the wrong store"
340 );
341
342 let structref = store
345 .require_gc_store_mut()?
346 .alloc_uninit_struct(allocator.type_index(), &allocator.layout())
347 .context("unrecoverable error when allocating new `structref`")?
348 .map_err(|n| GcHeapOutOfMemory::new((), n))?;
349
350 let mut store = AutoAssertNoGc::new(store);
355 match (|| {
356 for (index, (ty, val)) in allocator.ty.fields().zip(fields).enumerate() {
357 structref.initialize_field(
358 &mut store,
359 allocator.layout(),
360 ty.element_type(),
361 index,
362 *val,
363 )?;
364 }
365 Ok(())
366 })() {
367 Ok(()) => Ok(Rooted::new(&mut store, structref.into())),
368 Err(e) => {
369 store
370 .require_gc_store_mut()?
371 .dealloc_uninit_struct(structref)?;
372 Err(e)
373 }
374 }
375 }
376
377 #[inline]
378 pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
379 self.inner.comes_from_same_store(store)
380 }
381
382 pub fn ty(&self, store: impl AsContext) -> Result<StructType> {
392 self._ty(store.as_context().0)
393 }
394
395 pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result<StructType> {
396 assert!(self.comes_from_same_store(store));
397 let index = self.type_index(store)?;
398 match StructType::from_shared_type_index(store.engine(), index) {
399 Some(ty) => Ok(ty),
400 None => bail_bug!("invalid struct type index"),
401 }
402 }
403
404 pub fn matches_ty(&self, store: impl AsContext, ty: &StructType) -> Result<bool> {
417 self._matches_ty(store.as_context().0, ty)
418 }
419
420 pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &StructType) -> Result<bool> {
421 assert!(self.comes_from_same_store(store));
422 Ok(self._ty(store)?.matches(ty))
423 }
424
425 pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &StructType) -> Result<()> {
426 if !self.comes_from_same_store(store) {
427 bail!("function used with wrong store");
428 }
429 if self._matches_ty(store, ty)? {
430 Ok(())
431 } else {
432 let actual_ty = self._ty(store)?;
433 bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
434 }
435 }
436
437 pub fn fields<'a, T: 'static>(
450 &'a self,
451 store: impl Into<StoreContextMut<'a, T>>,
452 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
453 self._fields(store.into().0)
454 }
455
456 pub(crate) fn _fields<'a>(
457 &'a self,
458 store: &'a mut StoreOpaque,
459 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
460 assert!(self.comes_from_same_store(store));
461 let store = AutoAssertNoGc::new(store);
462
463 let gc_ref = self.inner.try_gc_ref(&store)?;
464 let header = store.require_gc_store()?.header(gc_ref)?;
465 debug_assert!(header.matches_kind(VMGcKind::StructRef));
466
467 let index = match header.ty() {
468 Some(index) => index,
469 None => bail_bug!("structrefs should have concrete types"),
470 };
471 let ty = match StructType::from_shared_type_index(store.engine(), index) {
472 Some(ty) => ty,
473 None => bail_bug!("invalid struct type index"),
474 };
475 let len = ty.fields().len();
476
477 return Ok(Fields {
478 structref: self,
479 store,
480 index: 0,
481 len,
482 });
483
484 struct Fields<'a, 'b> {
485 structref: &'a StructRef,
486 store: AutoAssertNoGc<'b>,
487 index: usize,
488 len: usize,
489 }
490
491 impl Iterator for Fields<'_, '_> {
492 type Item = Val;
493
494 #[inline]
495 fn next(&mut self) -> Option<Self::Item> {
496 let i = self.index;
497 debug_assert!(i <= self.len);
498 if i >= self.len {
499 return None;
500 }
501 self.index += 1;
502 self.structref._field(&mut self.store, i).ok()
503 }
504
505 #[inline]
506 fn size_hint(&self) -> (usize, Option<usize>) {
507 let len = self.len - self.index;
508 (len, Some(len))
509 }
510 }
511
512 impl ExactSizeIterator for Fields<'_, '_> {
513 #[inline]
514 fn len(&self) -> usize {
515 self.len - self.index
516 }
517 }
518 }
519
520 fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
521 assert!(self.comes_from_same_store(&store));
522 let gc_ref = self.inner.try_gc_ref(store)?;
523 Ok(store.require_gc_store()?.header(gc_ref)?)
524 }
525
526 fn structref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMStructRef> {
527 assert!(self.comes_from_same_store(&store));
528 let gc_ref = self.inner.try_gc_ref(store)?;
529 debug_assert!(self.header(store)?.matches_kind(VMGcKind::StructRef));
530 Ok(gc_ref.as_structref_unchecked())
531 }
532
533 fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result<Arc<GcStructLayout>> {
534 assert!(self.comes_from_same_store(&store));
535 let type_index = self.type_index(store)?;
536 super::gc_struct_layout(store.engine(), type_index)
537 }
538
539 fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result<FieldType> {
540 let ty = self._ty(store)?;
541 match ty.field(field) {
542 Some(f) => Ok(f),
543 None => {
544 let len = ty.fields().len();
545 bail!("cannot access field {field}: struct only has {len} fields")
546 }
547 }
548 }
549
550 pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result<Val> {
564 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
565 self._field(&mut store, index)
566 }
567
568 pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result<Val> {
569 assert!(self.comes_from_same_store(store));
570 let structref = self.structref(store)?.unchecked_copy();
571 let field_ty = self.field_ty(store, index)?;
572 let layout = self.layout(store)?;
573 structref.read_field(store, &layout, field_ty.element_type(), index)
574 }
575
576 pub fn set_field(&self, mut store: impl AsContextMut, index: usize, value: Val) -> Result<()> {
596 self._set_field(store.as_context_mut().0, index, value)
597 }
598
599 pub(crate) fn _set_field(
600 &self,
601 store: &mut StoreOpaque,
602 index: usize,
603 value: Val,
604 ) -> Result<()> {
605 assert!(self.comes_from_same_store(store));
606 let mut store = AutoAssertNoGc::new(store);
607
608 let field_ty = self.field_ty(&store, index)?;
609 ensure!(
610 field_ty.mutability().is_var(),
611 "cannot set field {index}: field is not mutable"
612 );
613
614 value
615 .ensure_matches_ty(&store, &field_ty.element_type().unpack())
616 .with_context(|| format!("cannot set field {index}: type mismatch"))?;
617
618 let layout = self.layout(&store)?;
619 let structref = self.structref(&store)?.unchecked_copy();
620
621 structref.write_field(&mut store, &layout, field_ty.element_type(), index, value)
622 }
623
624 pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result<VMSharedTypeIndex> {
625 let gc_ref = self.inner.try_gc_ref(store)?;
626 let header = store.require_gc_store()?.header(gc_ref)?;
627 debug_assert!(header.matches_kind(VMGcKind::StructRef));
628 match header.ty() {
629 Some(ty) => Ok(ty),
630 None => bail_bug!("structrefs should have concrete types"),
631 }
632 }
633
634 pub(crate) fn from_cloned_gc_ref(
640 store: &mut AutoAssertNoGc<'_>,
641 gc_ref: VMGcRef,
642 ) -> Rooted<Self> {
643 debug_assert!(gc_ref.is_structref(&*store.unwrap_gc_store().gc_heap));
644 Rooted::new(store, gc_ref)
645 }
646}
647
648unsafe impl WasmTy for Rooted<StructRef> {
649 #[inline]
650 fn valtype() -> ValType {
651 ValType::Ref(RefType::new(false, HeapType::Struct))
652 }
653
654 #[inline]
655 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
656 self.comes_from_same_store(store)
657 }
658
659 #[inline]
660 fn dynamic_concrete_type_check(
661 &self,
662 store: &StoreOpaque,
663 _nullable: bool,
664 ty: &HeapType,
665 ) -> Result<()> {
666 match ty {
667 HeapType::Any | HeapType::Eq | HeapType::Struct => Ok(()),
668 HeapType::ConcreteStruct(ty) => self.ensure_matches_ty(store, ty),
669
670 HeapType::Extern
671 | HeapType::NoExtern
672 | HeapType::Func
673 | HeapType::ConcreteFunc(_)
674 | HeapType::NoFunc
675 | HeapType::I31
676 | HeapType::Array
677 | HeapType::ConcreteArray(_)
678 | HeapType::None
679 | HeapType::NoCont
680 | HeapType::Cont
681 | HeapType::ConcreteCont(_)
682 | HeapType::NoExn
683 | HeapType::Exn
684 | HeapType::ConcreteExn(_) => bail!(
685 "type mismatch: expected `(ref {ty})`, got `(ref {})`",
686 self._ty(store)?,
687 ),
688 }
689 }
690
691 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
692 self.wasm_ty_store(store, ptr, ValRaw::anyref)
693 }
694
695 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
696 Self::wasm_ty_load(store, ptr.get_anyref(), StructRef::from_cloned_gc_ref)
697 }
698}
699
700unsafe impl WasmTy for Option<Rooted<StructRef>> {
701 #[inline]
702 fn valtype() -> ValType {
703 ValType::STRUCTREF
704 }
705
706 #[inline]
707 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
708 self.map_or(true, |x| x.comes_from_same_store(store))
709 }
710
711 #[inline]
712 fn dynamic_concrete_type_check(
713 &self,
714 store: &StoreOpaque,
715 nullable: bool,
716 ty: &HeapType,
717 ) -> Result<()> {
718 match self {
719 Some(s) => Rooted::<StructRef>::dynamic_concrete_type_check(s, store, nullable, ty),
720 None => {
721 ensure!(
722 nullable,
723 "expected a non-null reference, but found a null reference"
724 );
725 Ok(())
726 }
727 }
728 }
729
730 #[inline]
731 fn is_vmgcref_and_points_to_object(&self) -> bool {
732 self.is_some()
733 }
734
735 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
736 <Rooted<StructRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
737 }
738
739 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
740 <Rooted<StructRef>>::wasm_ty_option_load(
741 store,
742 ptr.get_anyref(),
743 StructRef::from_cloned_gc_ref,
744 )
745 }
746}
747
748unsafe impl WasmTy for OwnedRooted<StructRef> {
749 #[inline]
750 fn valtype() -> ValType {
751 ValType::Ref(RefType::new(false, HeapType::Struct))
752 }
753
754 #[inline]
755 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
756 self.comes_from_same_store(store)
757 }
758
759 #[inline]
760 fn dynamic_concrete_type_check(
761 &self,
762 store: &StoreOpaque,
763 _: bool,
764 ty: &HeapType,
765 ) -> Result<()> {
766 match ty {
767 HeapType::Any | HeapType::Eq | HeapType::Struct => Ok(()),
768 HeapType::ConcreteStruct(ty) => self.ensure_matches_ty(store, ty),
769
770 HeapType::Extern
771 | HeapType::NoExtern
772 | HeapType::Func
773 | HeapType::ConcreteFunc(_)
774 | HeapType::NoFunc
775 | HeapType::I31
776 | HeapType::Array
777 | HeapType::ConcreteArray(_)
778 | HeapType::None
779 | HeapType::NoCont
780 | HeapType::Cont
781 | HeapType::ConcreteCont(_)
782 | HeapType::NoExn
783 | HeapType::Exn
784 | HeapType::ConcreteExn(_) => bail!(
785 "type mismatch: expected `(ref {ty})`, got `(ref {})`",
786 self._ty(store)?,
787 ),
788 }
789 }
790
791 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
792 self.wasm_ty_store(store, ptr, ValRaw::anyref)
793 }
794
795 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
796 Self::wasm_ty_load(store, ptr.get_anyref(), StructRef::from_cloned_gc_ref)
797 }
798}
799
800unsafe impl WasmTy for Option<OwnedRooted<StructRef>> {
801 #[inline]
802 fn valtype() -> ValType {
803 ValType::STRUCTREF
804 }
805
806 #[inline]
807 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
808 self.as_ref()
809 .map_or(true, |x| x.comes_from_same_store(store))
810 }
811
812 #[inline]
813 fn dynamic_concrete_type_check(
814 &self,
815 store: &StoreOpaque,
816 nullable: bool,
817 ty: &HeapType,
818 ) -> Result<()> {
819 match self {
820 Some(s) => {
821 OwnedRooted::<StructRef>::dynamic_concrete_type_check(s, store, nullable, ty)
822 }
823 None => {
824 ensure!(
825 nullable,
826 "expected a non-null reference, but found a null reference"
827 );
828 Ok(())
829 }
830 }
831 }
832
833 #[inline]
834 fn is_vmgcref_and_points_to_object(&self) -> bool {
835 self.is_some()
836 }
837
838 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
839 <OwnedRooted<StructRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
840 }
841
842 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
843 <OwnedRooted<StructRef>>::wasm_ty_option_load(
844 store,
845 ptr.get_anyref(),
846 StructRef::from_cloned_gc_ref,
847 )
848 }
849}