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 self.inner.try_gc_ref(store)?;
470 Ok(HeapType::Exn.matches(ty))
471 }
472
473 pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> {
474 if !self.comes_from_same_store(store) {
475 bail!("function used with wrong store");
476 }
477 if self._matches_ty(store, ty)? {
478 Ok(())
479 } else {
480 let actual_ty = self._ty(store)?;
481 bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
482 }
483 }
484
485 pub fn fields<'a, T: 'static>(
495 &'a self,
496 store: impl Into<StoreContextMut<'a, T>>,
497 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
498 self._fields(store.into().0)
499 }
500
501 pub(crate) fn _fields<'a>(
502 &'a self,
503 store: &'a mut StoreOpaque,
504 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
505 assert!(self.comes_from_same_store(store));
506 let store = AutoAssertNoGc::new(store);
507
508 let gc_ref = self.inner.try_gc_ref(&store)?;
509 let header = store.require_gc_store()?.header(gc_ref)?;
510 debug_assert!(header.matches_kind(VMGcKind::ExnRef));
511
512 let index = match header.ty() {
513 Some(index) => index,
514 None => bail_bug!("exnrefs should have concrete types"),
515 };
516 let ty = match ExnType::from_shared_type_index(store.engine(), index) {
517 Some(ty) => ty,
518 None => bail_bug!("invalid exception type index"),
519 };
520 let len = ty.fields().len();
521
522 return Ok(Fields {
523 exnref: self,
524 store,
525 index: 0,
526 len,
527 });
528
529 struct Fields<'a, 'b> {
530 exnref: &'a ExnRef,
531 store: AutoAssertNoGc<'b>,
532 index: usize,
533 len: usize,
534 }
535
536 impl Iterator for Fields<'_, '_> {
537 type Item = Val;
538
539 #[inline]
540 fn next(&mut self) -> Option<Self::Item> {
541 let i = self.index;
542 debug_assert!(i <= self.len);
543 if i >= self.len {
544 return None;
545 }
546 self.index += 1;
547 self.exnref._field(&mut self.store, i).ok()
548 }
549
550 #[inline]
551 fn size_hint(&self) -> (usize, Option<usize>) {
552 let len = self.len - self.index;
553 (len, Some(len))
554 }
555 }
556
557 impl ExactSizeIterator for Fields<'_, '_> {
558 #[inline]
559 fn len(&self) -> usize {
560 self.len - self.index
561 }
562 }
563 }
564
565 fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
566 assert!(self.comes_from_same_store(&store));
567 let gc_ref = self.inner.try_gc_ref(store)?;
568 Ok(store.require_gc_store()?.header(gc_ref)?)
569 }
570
571 fn exnref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMExnRef> {
572 assert!(self.comes_from_same_store(&store));
573 let gc_ref = self.inner.try_gc_ref(store)?;
574 debug_assert!(self.header(store)?.matches_kind(VMGcKind::ExnRef));
575 Ok(gc_ref.as_exnref_unchecked())
576 }
577
578 fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result<Arc<GcStructLayout>> {
579 assert!(self.comes_from_same_store(&store));
580 let type_index = self.type_index(store)?;
581 super::gc_struct_layout(store.engine(), type_index)
582 }
583
584 fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result<FieldType> {
585 let ty = self._ty(store)?;
586 match ty.field(field) {
587 Some(f) => Ok(f),
588 None => {
589 let len = ty.fields().len();
590 bail!("cannot access field {field}: exn only has {len} fields")
591 }
592 }
593 }
594
595 pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result<Val> {
606 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
607 self._field(&mut store, index)
608 }
609
610 pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result<Val> {
611 assert!(self.comes_from_same_store(store));
612 let exnref = self.exnref(store)?.unchecked_copy();
613 let field_ty = self.field_ty(store, index)?;
614 let layout = self.layout(store)?;
615 exnref.read_field(store, &layout, field_ty.element_type(), index)
616 }
617
618 pub fn tag(&self, mut store: impl AsContextMut) -> Result<Tag> {
628 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
629 assert!(self.comes_from_same_store(&store));
630 let exnref = self.exnref(&store)?.unchecked_copy();
631 let (instance, index) = exnref.tag(&mut store)?;
632 match Tag::from_raw_indices(&*store, instance, index) {
633 Some(tag) => Ok(tag),
634 None => bail_bug!("invalid tag indices in exception object"),
635 }
636 }
637}
638
639unsafe impl WasmTy for Rooted<ExnRef> {
640 #[inline]
641 fn valtype() -> ValType {
642 ValType::Ref(RefType::new(false, HeapType::Exn))
643 }
644
645 #[inline]
646 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
647 self.comes_from_same_store(store)
648 }
649
650 #[inline]
651 fn dynamic_concrete_type_check(
652 &self,
653 store: &StoreOpaque,
654 _nullable: bool,
655 ty: &HeapType,
656 ) -> Result<()> {
657 self.ensure_matches_ty(store, ty)
658 }
659
660 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
661 self.wasm_ty_store(store, ptr, ValRaw::anyref)
662 }
663
664 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
665 Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
666 }
667}
668
669unsafe impl WasmTy for Option<Rooted<ExnRef>> {
670 #[inline]
671 fn valtype() -> ValType {
672 ValType::EXNREF
673 }
674
675 #[inline]
676 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
677 self.map_or(true, |x| x.comes_from_same_store(store))
678 }
679
680 #[inline]
681 fn dynamic_concrete_type_check(
682 &self,
683 store: &StoreOpaque,
684 nullable: bool,
685 ty: &HeapType,
686 ) -> Result<()> {
687 match self {
688 Some(a) => a.ensure_matches_ty(store, ty),
689 None => {
690 ensure!(
691 nullable,
692 "expected a non-null reference, but found a null reference"
693 );
694 Ok(())
695 }
696 }
697 }
698
699 #[inline]
700 fn is_vmgcref_and_points_to_object(&self) -> bool {
701 self.is_some()
702 }
703
704 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
705 <Rooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
706 }
707
708 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
709 <Rooted<ExnRef>>::wasm_ty_option_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
710 }
711}
712
713unsafe impl WasmTy for OwnedRooted<ExnRef> {
714 #[inline]
715 fn valtype() -> ValType {
716 ValType::Ref(RefType::new(false, HeapType::Exn))
717 }
718
719 #[inline]
720 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
721 self.comes_from_same_store(store)
722 }
723
724 #[inline]
725 fn dynamic_concrete_type_check(
726 &self,
727 store: &StoreOpaque,
728 _nullable: bool,
729 ty: &HeapType,
730 ) -> Result<()> {
731 self.ensure_matches_ty(store, ty)
732 }
733
734 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
735 self.wasm_ty_store(store, ptr, ValRaw::anyref)
736 }
737
738 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
739 Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
740 }
741}
742
743unsafe impl WasmTy for Option<OwnedRooted<ExnRef>> {
744 #[inline]
745 fn valtype() -> ValType {
746 ValType::EXNREF
747 }
748
749 #[inline]
750 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
751 self.as_ref()
752 .map_or(true, |x| x.comes_from_same_store(store))
753 }
754
755 #[inline]
756 fn dynamic_concrete_type_check(
757 &self,
758 store: &StoreOpaque,
759 nullable: bool,
760 ty: &HeapType,
761 ) -> Result<()> {
762 match self {
763 Some(a) => a.ensure_matches_ty(store, ty),
764 None => {
765 ensure!(
766 nullable,
767 "expected a non-null reference, but found a null reference"
768 );
769 Ok(())
770 }
771 }
772 }
773
774 #[inline]
775 fn is_vmgcref_and_points_to_object(&self) -> bool {
776 self.is_some()
777 }
778
779 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
780 <OwnedRooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
781 }
782
783 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
784 <OwnedRooted<ExnRef>>::wasm_ty_option_load(
785 store,
786 ptr.get_anyref(),
787 ExnRef::from_cloned_gc_ref,
788 )
789 }
790}