1use crate::runtime::vm::VMGcRef;
4use crate::store::{Asyncness, StoreId, StoreResourceLimiter};
5#[cfg(feature = "async")]
6use crate::vm::VMStore;
7use crate::vm::{self, VMExnRef, VMGcHeader};
8use crate::{
9 AsContext, AsContextMut, GcRefImpl, GcRootIndex, HeapType, OwnedRooted, RefType, Rooted, Val,
10 ValRaw, ValType, WasmTy,
11 store::{AutoAssertNoGc, StoreOpaque},
12};
13use crate::{ExnType, FieldType, GcHeapOutOfMemory, StoreContextMut, Tag, bail_bug, prelude::*};
14use alloc::sync::Arc;
15use core::mem;
16use core::mem::MaybeUninit;
17use wasmtime_environ::{GcStructLayout, VMGcKind, VMSharedTypeIndex};
18
19pub struct ExnRefPre {
69 store_id: StoreId,
70 ty: ExnType,
71}
72
73impl ExnRefPre {
74 pub fn new(mut store: impl AsContextMut, ty: ExnType) -> Self {
82 Self::_new(store.as_context_mut().0, ty)
83 }
84
85 pub(crate) fn _new(store: &mut StoreOpaque, ty: ExnType) -> Self {
86 store.insert_gc_host_alloc_type(ty.registered_type().clone());
87 let store_id = store.id();
88 ExnRefPre { store_id, ty }
89 }
90
91 pub(crate) fn layout(&self) -> &GcStructLayout {
92 self.ty
93 .registered_type()
94 .layout()
95 .expect("exn types have a layout")
96 .unwrap_struct()
97 }
98
99 pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
100 self.ty.registered_type().index()
101 }
102}
103
104#[derive(Debug)]
115#[repr(transparent)]
116pub struct ExnRef {
117 pub(super) inner: GcRootIndex,
118}
119
120unsafe impl GcRefImpl for ExnRef {
121 fn transmute_ref(index: &GcRootIndex) -> &Self {
122 let me: &Self = unsafe { mem::transmute(index) };
124
125 assert!(matches!(
127 me,
128 Self {
129 inner: GcRootIndex { .. },
130 }
131 ));
132
133 me
134 }
135}
136
137impl ExnRef {
138 pub fn from_raw(mut store: impl AsContextMut, raw: u32) -> Option<Rooted<Self>> {
169 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
170 Self::_from_raw(&mut store, raw)
171 }
172
173 pub(crate) fn _from_raw(store: &mut AutoAssertNoGc, raw: u32) -> Option<Rooted<Self>> {
175 let gc_ref = VMGcRef::from_raw_u32(raw)?;
176 let gc_ref = store.clone_gc_ref(&gc_ref);
177 Some(Self::from_cloned_gc_ref(store, gc_ref))
178 }
179
180 pub fn new(
210 mut store: impl AsContextMut,
211 allocator: &ExnRefPre,
212 tag: &Tag,
213 fields: &[Val],
214 ) -> Result<Rooted<ExnRef>> {
215 let (mut limiter, store) = store
216 .as_context_mut()
217 .0
218 .validate_sync_resource_limiter_and_store_opaque()?;
219 vm::assert_ready(Self::_new_async(
220 store,
221 limiter.as_mut(),
222 allocator,
223 tag,
224 fields,
225 Asyncness::No,
226 ))
227 }
228
229 #[cfg(feature = "async")]
254 pub async fn new_async(
255 mut store: impl AsContextMut,
256 allocator: &ExnRefPre,
257 tag: &Tag,
258 fields: &[Val],
259 ) -> Result<Rooted<ExnRef>> {
260 let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
261 Self::_new_async(
262 store,
263 limiter.as_mut(),
264 allocator,
265 tag,
266 fields,
267 Asyncness::Yes,
268 )
269 .await
270 }
271
272 pub(crate) async fn _new_async(
273 store: &mut StoreOpaque,
274 limiter: Option<&mut StoreResourceLimiter<'_>>,
275 allocator: &ExnRefPre,
276 tag: &Tag,
277 fields: &[Val],
278 asyncness: Asyncness,
279 ) -> Result<Rooted<ExnRef>> {
280 Self::type_check_tag_and_fields(store, allocator, tag, fields)?;
281 store
282 .retry_after_gc_async(limiter, (), asyncness, |store, ()| {
283 Self::new_unchecked(store, allocator, tag, fields)
284 })
285 .await
286 }
287
288 fn type_check_tag_and_fields(
291 store: &mut StoreOpaque,
292 allocator: &ExnRefPre,
293 tag: &Tag,
294 fields: &[Val],
295 ) -> Result<(), Error> {
296 assert!(
297 tag.comes_from_same_store(store),
298 "tag comes from the wrong store"
299 );
300 ensure!(
301 tag.wasmtime_ty(store).signature.unwrap_engine_type_index()
302 == allocator.ty.tag_type().ty().type_index(),
303 "incorrect signature for tag when creating exception object"
304 );
305 let expected_len = allocator.ty.fields().len();
306 let actual_len = fields.len();
307 ensure!(
308 actual_len == expected_len,
309 "expected {expected_len} fields, got {actual_len}"
310 );
311 for (ty, val) in allocator.ty.fields().zip(fields) {
312 assert!(
313 val.comes_from_same_store(store),
314 "field value comes from the wrong store",
315 );
316 let ty = ty.element_type().unpack();
317 val.ensure_matches_ty(store, ty)
318 .context("field type mismatch")?;
319 }
320 Ok(())
321 }
322
323 fn new_unchecked(
328 store: &mut StoreOpaque,
329 allocator: &ExnRefPre,
330 tag: &Tag,
331 fields: &[Val],
332 ) -> Result<Rooted<ExnRef>> {
333 assert_eq!(
334 store.id(),
335 allocator.store_id,
336 "attempted to use a `ExnRefPre` with the wrong store"
337 );
338
339 let exnref = store
342 .require_gc_store_mut()?
343 .alloc_uninit_exn(allocator.type_index(), &allocator.layout())
344 .context("unrecoverable error when allocating new `exnref`")?
345 .map_err(|n| GcHeapOutOfMemory::new((), n))?;
346
347 let mut store = AutoAssertNoGc::new(store);
352 match (|| {
353 let (instance, index) = tag.to_raw_indices();
354 exnref.initialize_tag(&mut store, instance, index)?;
355 for (index, (ty, val)) in allocator.ty.fields().zip(fields).enumerate() {
356 exnref.initialize_field(
357 &mut store,
358 allocator.layout(),
359 ty.element_type(),
360 index,
361 *val,
362 )?;
363 }
364 Ok(())
365 })() {
366 Ok(()) => Ok(Rooted::new(&mut store, exnref.into())),
367 Err(e) => {
368 store.require_gc_store_mut()?.dealloc_uninit_exn(exnref)?;
369 Err(e)
370 }
371 }
372 }
373
374 pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result<VMSharedTypeIndex> {
375 let gc_ref = self.inner.try_gc_ref(store)?;
376 let header = store.require_gc_store()?.header(gc_ref)?;
377 debug_assert!(header.matches_kind(VMGcKind::ExnRef));
378 match header.ty() {
379 Some(ty) => Ok(ty),
380 None => bail_bug!("exnrefs should have concrete types"),
381 }
382 }
383
384 pub(crate) fn from_cloned_gc_ref(
391 store: &mut AutoAssertNoGc<'_>,
392 gc_ref: VMGcRef,
393 ) -> Rooted<Self> {
394 debug_assert!(
395 store
396 .unwrap_gc_store()
397 .kind(&gc_ref)
398 .unwrap()
399 .matches(VMGcKind::ExnRef)
400 );
401 Rooted::new(store, gc_ref)
402 }
403
404 #[inline]
405 pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
406 self.inner.comes_from_same_store(store)
407 }
408
409 pub fn to_raw(&self, mut store: impl AsContextMut) -> Result<u32> {
422 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
423 self._to_raw(&mut store)
424 }
425
426 pub(crate) fn _to_raw(&self, store: &mut AutoAssertNoGc<'_>) -> Result<u32> {
427 self.inner.expose_gc_ref_to_wasm(store).map(|r| r.get())
428 }
429
430 pub fn ty(&self, store: impl AsContext) -> Result<ExnType> {
440 self._ty(store.as_context().0)
441 }
442
443 pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result<ExnType> {
444 assert!(self.comes_from_same_store(store));
445 let index = self.type_index(store)?;
446 match ExnType::from_shared_type_index(store.engine(), index) {
447 Some(ty) => Ok(ty),
448 None => bail_bug!("invalid exception type index"),
449 }
450 }
451
452 pub fn matches_ty(&self, store: impl AsContext, ty: &HeapType) -> Result<bool> {
464 self._matches_ty(store.as_context().0, ty)
465 }
466
467 pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<bool> {
468 assert!(self.comes_from_same_store(store));
469 Ok(HeapType::from(self._ty(store)?).matches(ty))
470 }
471
472 pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> {
473 if !self.comes_from_same_store(store) {
474 bail!("function used with wrong store");
475 }
476 if self._matches_ty(store, ty)? {
477 Ok(())
478 } else {
479 let actual_ty = self._ty(store)?;
480 bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
481 }
482 }
483
484 pub fn fields<'a, T: 'static>(
494 &'a self,
495 store: impl Into<StoreContextMut<'a, T>>,
496 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
497 self._fields(store.into().0)
498 }
499
500 pub(crate) fn _fields<'a>(
501 &'a self,
502 store: &'a mut StoreOpaque,
503 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
504 assert!(self.comes_from_same_store(store));
505 let store = AutoAssertNoGc::new(store);
506
507 let gc_ref = self.inner.try_gc_ref(&store)?;
508 let header = store.require_gc_store()?.header(gc_ref)?;
509 debug_assert!(header.matches_kind(VMGcKind::ExnRef));
510
511 let index = match header.ty() {
512 Some(index) => index,
513 None => bail_bug!("exnrefs should have concrete types"),
514 };
515 let ty = match ExnType::from_shared_type_index(store.engine(), index) {
516 Some(ty) => ty,
517 None => bail_bug!("invalid exception type index"),
518 };
519 let len = ty.fields().len();
520
521 return Ok(Fields {
522 exnref: self,
523 store,
524 index: 0,
525 len,
526 });
527
528 struct Fields<'a, 'b> {
529 exnref: &'a ExnRef,
530 store: AutoAssertNoGc<'b>,
531 index: usize,
532 len: usize,
533 }
534
535 impl Iterator for Fields<'_, '_> {
536 type Item = Val;
537
538 #[inline]
539 fn next(&mut self) -> Option<Self::Item> {
540 let i = self.index;
541 debug_assert!(i <= self.len);
542 if i >= self.len {
543 return None;
544 }
545 self.index += 1;
546 self.exnref._field(&mut self.store, i).ok()
547 }
548
549 #[inline]
550 fn size_hint(&self) -> (usize, Option<usize>) {
551 let len = self.len - self.index;
552 (len, Some(len))
553 }
554 }
555
556 impl ExactSizeIterator for Fields<'_, '_> {
557 #[inline]
558 fn len(&self) -> usize {
559 self.len - self.index
560 }
561 }
562 }
563
564 fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
565 assert!(self.comes_from_same_store(&store));
566 let gc_ref = self.inner.try_gc_ref(store)?;
567 Ok(store.require_gc_store()?.header(gc_ref)?)
568 }
569
570 fn exnref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMExnRef> {
571 assert!(self.comes_from_same_store(&store));
572 let gc_ref = self.inner.try_gc_ref(store)?;
573 debug_assert!(self.header(store)?.matches_kind(VMGcKind::ExnRef));
574 Ok(gc_ref.as_exnref_unchecked())
575 }
576
577 fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result<Arc<GcStructLayout>> {
578 assert!(self.comes_from_same_store(&store));
579 let type_index = self.type_index(store)?;
580 super::gc_struct_layout(store.engine(), type_index)
581 }
582
583 fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result<FieldType> {
584 let ty = self._ty(store)?;
585 match ty.field(field) {
586 Some(f) => Ok(f),
587 None => {
588 let len = ty.fields().len();
589 bail!("cannot access field {field}: exn only has {len} fields")
590 }
591 }
592 }
593
594 pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result<Val> {
605 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
606 self._field(&mut store, index)
607 }
608
609 pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result<Val> {
610 assert!(self.comes_from_same_store(store));
611 let exnref = self.exnref(store)?.unchecked_copy();
612 let field_ty = self.field_ty(store, index)?;
613 let layout = self.layout(store)?;
614 exnref.read_field(store, &layout, field_ty.element_type(), index)
615 }
616
617 pub fn tag(&self, mut store: impl AsContextMut) -> Result<Tag> {
627 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
628 assert!(self.comes_from_same_store(&store));
629 let exnref = self.exnref(&store)?.unchecked_copy();
630 let (instance, index) = exnref.tag(&mut store)?;
631 match Tag::from_raw_indices(&*store, instance, index) {
632 Some(tag) => Ok(tag),
633 None => bail_bug!("invalid tag indices in exception object"),
634 }
635 }
636}
637
638unsafe impl WasmTy for Rooted<ExnRef> {
639 #[inline]
640 fn valtype() -> ValType {
641 ValType::Ref(RefType::new(false, HeapType::Exn))
642 }
643
644 #[inline]
645 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
646 self.comes_from_same_store(store)
647 }
648
649 #[inline]
650 fn dynamic_concrete_type_check(
651 &self,
652 _store: &StoreOpaque,
653 _nullable: bool,
654 _ty: &HeapType,
655 ) -> Result<()> {
656 Ok(())
659 }
660
661 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
662 self.wasm_ty_store(store, ptr, ValRaw::anyref)
663 }
664
665 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
666 Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
667 }
668}
669
670unsafe impl WasmTy for Option<Rooted<ExnRef>> {
671 #[inline]
672 fn valtype() -> ValType {
673 ValType::EXNREF
674 }
675
676 #[inline]
677 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
678 self.map_or(true, |x| x.comes_from_same_store(store))
679 }
680
681 #[inline]
682 fn dynamic_concrete_type_check(
683 &self,
684 store: &StoreOpaque,
685 nullable: bool,
686 ty: &HeapType,
687 ) -> Result<()> {
688 match self {
689 Some(a) => a.ensure_matches_ty(store, ty),
690 None => {
691 ensure!(
692 nullable,
693 "expected a non-null reference, but found a null reference"
694 );
695 Ok(())
696 }
697 }
698 }
699
700 #[inline]
701 fn is_vmgcref_and_points_to_object(&self) -> bool {
702 self.is_some()
703 }
704
705 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
706 <Rooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
707 }
708
709 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
710 <Rooted<ExnRef>>::wasm_ty_option_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
711 }
712}
713
714unsafe impl WasmTy for OwnedRooted<ExnRef> {
715 #[inline]
716 fn valtype() -> ValType {
717 ValType::Ref(RefType::new(false, HeapType::Exn))
718 }
719
720 #[inline]
721 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
722 self.comes_from_same_store(store)
723 }
724
725 #[inline]
726 fn dynamic_concrete_type_check(
727 &self,
728 store: &StoreOpaque,
729 _nullable: bool,
730 ty: &HeapType,
731 ) -> Result<()> {
732 self.ensure_matches_ty(store, ty)
733 }
734
735 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
736 self.wasm_ty_store(store, ptr, ValRaw::anyref)
737 }
738
739 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
740 Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
741 }
742}
743
744unsafe impl WasmTy for Option<OwnedRooted<ExnRef>> {
745 #[inline]
746 fn valtype() -> ValType {
747 ValType::EXNREF
748 }
749
750 #[inline]
751 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
752 self.as_ref()
753 .map_or(true, |x| x.comes_from_same_store(store))
754 }
755
756 #[inline]
757 fn dynamic_concrete_type_check(
758 &self,
759 store: &StoreOpaque,
760 nullable: bool,
761 ty: &HeapType,
762 ) -> Result<()> {
763 match self {
764 Some(a) => a.ensure_matches_ty(store, ty),
765 None => {
766 ensure!(
767 nullable,
768 "expected a non-null reference, but found a null reference"
769 );
770 Ok(())
771 }
772 }
773 }
774
775 #[inline]
776 fn is_vmgcref_and_points_to_object(&self) -> bool {
777 self.is_some()
778 }
779
780 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
781 <OwnedRooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
782 }
783
784 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
785 <OwnedRooted<ExnRef>>::wasm_ty_option_load(
786 store,
787 ptr.get_anyref(),
788 ExnRef::from_cloned_gc_ref,
789 )
790 }
791}