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, prelude::*};
14use alloc::sync::Arc;
15use core::mem;
16use core::mem::MaybeUninit;
17use wasmtime_environ::{GcLayout, 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.kind().matches(VMGcKind::ExnRef));
378 Ok(header.ty().expect("exnrefs should have concrete types"))
379 }
380
381 pub(crate) fn from_cloned_gc_ref(
388 store: &mut AutoAssertNoGc<'_>,
389 gc_ref: VMGcRef,
390 ) -> Rooted<Self> {
391 debug_assert!(
392 store
393 .unwrap_gc_store()
394 .kind(&gc_ref)
395 .unwrap()
396 .matches(VMGcKind::ExnRef)
397 );
398 Rooted::new(store, gc_ref)
399 }
400
401 #[inline]
402 pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
403 self.inner.comes_from_same_store(store)
404 }
405
406 pub fn to_raw(&self, mut store: impl AsContextMut) -> Result<u32> {
419 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
420 self._to_raw(&mut store)
421 }
422
423 pub(crate) fn _to_raw(&self, store: &mut AutoAssertNoGc<'_>) -> Result<u32> {
424 self.inner.expose_gc_ref_to_wasm(store).map(|r| r.get())
425 }
426
427 pub fn ty(&self, store: impl AsContext) -> Result<ExnType> {
437 self._ty(store.as_context().0)
438 }
439
440 pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result<ExnType> {
441 assert!(self.comes_from_same_store(store));
442 let index = self.type_index(store)?;
443 Ok(ExnType::from_shared_type_index(store.engine(), index))
444 }
445
446 pub fn matches_ty(&self, store: impl AsContext, ty: &HeapType) -> Result<bool> {
458 self._matches_ty(store.as_context().0, ty)
459 }
460
461 pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<bool> {
462 assert!(self.comes_from_same_store(store));
463 Ok(HeapType::from(self._ty(store)?).matches(ty))
464 }
465
466 pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> {
467 if !self.comes_from_same_store(store) {
468 bail!("function used with wrong store");
469 }
470 if self._matches_ty(store, ty)? {
471 Ok(())
472 } else {
473 let actual_ty = self._ty(store)?;
474 bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
475 }
476 }
477
478 pub fn fields<'a, T: 'static>(
488 &'a self,
489 store: impl Into<StoreContextMut<'a, T>>,
490 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
491 self._fields(store.into().0)
492 }
493
494 pub(crate) fn _fields<'a>(
495 &'a self,
496 store: &'a mut StoreOpaque,
497 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
498 assert!(self.comes_from_same_store(store));
499 let store = AutoAssertNoGc::new(store);
500
501 let gc_ref = self.inner.try_gc_ref(&store)?;
502 let header = store.require_gc_store()?.header(gc_ref)?;
503 debug_assert!(header.kind().matches(VMGcKind::ExnRef));
504
505 let index = header.ty().expect("exnrefs should have concrete types");
506 let ty = ExnType::from_shared_type_index(store.engine(), index);
507 let len = ty.fields().len();
508
509 return Ok(Fields {
510 exnref: self,
511 store,
512 index: 0,
513 len,
514 });
515
516 struct Fields<'a, 'b> {
517 exnref: &'a ExnRef,
518 store: AutoAssertNoGc<'b>,
519 index: usize,
520 len: usize,
521 }
522
523 impl Iterator for Fields<'_, '_> {
524 type Item = Val;
525
526 #[inline]
527 fn next(&mut self) -> Option<Self::Item> {
528 let i = self.index;
529 debug_assert!(i <= self.len);
530 if i >= self.len {
531 return None;
532 }
533 self.index += 1;
534 self.exnref._field(&mut self.store, i).ok()
535 }
536
537 #[inline]
538 fn size_hint(&self) -> (usize, Option<usize>) {
539 let len = self.len - self.index;
540 (len, Some(len))
541 }
542 }
543
544 impl ExactSizeIterator for Fields<'_, '_> {
545 #[inline]
546 fn len(&self) -> usize {
547 self.len - self.index
548 }
549 }
550 }
551
552 fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
553 assert!(self.comes_from_same_store(&store));
554 let gc_ref = self.inner.try_gc_ref(store)?;
555 Ok(store.require_gc_store()?.header(gc_ref)?)
556 }
557
558 fn exnref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMExnRef> {
559 assert!(self.comes_from_same_store(&store));
560 let gc_ref = self.inner.try_gc_ref(store)?;
561 debug_assert!(self.header(store)?.kind().matches(VMGcKind::ExnRef));
562 Ok(gc_ref.as_exnref_unchecked())
563 }
564
565 fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result<Arc<GcStructLayout>> {
566 assert!(self.comes_from_same_store(&store));
567 let type_index = self.type_index(store)?;
568 let layout = store
569 .engine()
570 .signatures()
571 .layout(type_index)
572 .expect("exn types should have GC layouts");
573 match layout {
574 GcLayout::Struct(s) => Ok(s),
575 GcLayout::Array(_) => unreachable!(),
576 }
577 }
578
579 fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result<FieldType> {
580 let ty = self._ty(store)?;
581 match ty.field(field) {
582 Some(f) => Ok(f),
583 None => {
584 let len = ty.fields().len();
585 bail!("cannot access field {field}: exn only has {len} fields")
586 }
587 }
588 }
589
590 pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result<Val> {
601 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
602 self._field(&mut store, index)
603 }
604
605 pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result<Val> {
606 assert!(self.comes_from_same_store(store));
607 let exnref = self.exnref(store)?.unchecked_copy();
608 let field_ty = self.field_ty(store, index)?;
609 let layout = self.layout(store)?;
610 exnref.read_field(store, &layout, field_ty.element_type(), index)
611 }
612
613 pub fn tag(&self, mut store: impl AsContextMut) -> Result<Tag> {
623 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
624 assert!(self.comes_from_same_store(&store));
625 let exnref = self.exnref(&store)?.unchecked_copy();
626 let (instance, index) = exnref.tag(&mut store)?;
627 Ok(Tag::from_raw_indices(&*store, instance, index))
628 }
629}
630
631unsafe impl WasmTy for Rooted<ExnRef> {
632 #[inline]
633 fn valtype() -> ValType {
634 ValType::Ref(RefType::new(false, HeapType::Exn))
635 }
636
637 #[inline]
638 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
639 self.comes_from_same_store(store)
640 }
641
642 #[inline]
643 fn dynamic_concrete_type_check(
644 &self,
645 _store: &StoreOpaque,
646 _nullable: bool,
647 _ty: &HeapType,
648 ) -> Result<()> {
649 Ok(())
652 }
653
654 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
655 self.wasm_ty_store(store, ptr, ValRaw::anyref)
656 }
657
658 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
659 Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
660 }
661}
662
663unsafe impl WasmTy for Option<Rooted<ExnRef>> {
664 #[inline]
665 fn valtype() -> ValType {
666 ValType::EXNREF
667 }
668
669 #[inline]
670 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
671 self.map_or(true, |x| x.comes_from_same_store(store))
672 }
673
674 #[inline]
675 fn dynamic_concrete_type_check(
676 &self,
677 store: &StoreOpaque,
678 nullable: bool,
679 ty: &HeapType,
680 ) -> Result<()> {
681 match self {
682 Some(a) => a.ensure_matches_ty(store, ty),
683 None => {
684 ensure!(
685 nullable,
686 "expected a non-null reference, but found a null reference"
687 );
688 Ok(())
689 }
690 }
691 }
692
693 #[inline]
694 fn is_vmgcref_and_points_to_object(&self) -> bool {
695 self.is_some()
696 }
697
698 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
699 <Rooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
700 }
701
702 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
703 <Rooted<ExnRef>>::wasm_ty_option_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
704 }
705}
706
707unsafe impl WasmTy for OwnedRooted<ExnRef> {
708 #[inline]
709 fn valtype() -> ValType {
710 ValType::Ref(RefType::new(false, HeapType::Exn))
711 }
712
713 #[inline]
714 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
715 self.comes_from_same_store(store)
716 }
717
718 #[inline]
719 fn dynamic_concrete_type_check(
720 &self,
721 store: &StoreOpaque,
722 _nullable: bool,
723 ty: &HeapType,
724 ) -> Result<()> {
725 self.ensure_matches_ty(store, ty)
726 }
727
728 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
729 self.wasm_ty_store(store, ptr, ValRaw::anyref)
730 }
731
732 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
733 Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
734 }
735}
736
737unsafe impl WasmTy for Option<OwnedRooted<ExnRef>> {
738 #[inline]
739 fn valtype() -> ValType {
740 ValType::EXNREF
741 }
742
743 #[inline]
744 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
745 self.as_ref()
746 .map_or(true, |x| x.comes_from_same_store(store))
747 }
748
749 #[inline]
750 fn dynamic_concrete_type_check(
751 &self,
752 store: &StoreOpaque,
753 nullable: bool,
754 ty: &HeapType,
755 ) -> Result<()> {
756 match self {
757 Some(a) => a.ensure_matches_ty(store, ty),
758 None => {
759 ensure!(
760 nullable,
761 "expected a non-null reference, but found a null reference"
762 );
763 Ok(())
764 }
765 }
766 }
767
768 #[inline]
769 fn is_vmgcref_and_points_to_object(&self) -> bool {
770 self.is_some()
771 }
772
773 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
774 <OwnedRooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
775 }
776
777 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
778 <OwnedRooted<ExnRef>>::wasm_ty_option_load(
779 store,
780 ptr.get_anyref(),
781 ExnRef::from_cloned_gc_ref,
782 )
783 }
784}