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 {
77 Self::_new(store.as_context_mut().0, ty)
78 }
79
80 pub(crate) fn _new(store: &mut StoreOpaque, ty: ExnType) -> Self {
81 store.insert_gc_host_alloc_type(ty.registered_type().clone());
82 let store_id = store.id();
83 ExnRefPre { store_id, ty }
84 }
85
86 pub(crate) fn layout(&self) -> &GcStructLayout {
87 self.ty
88 .registered_type()
89 .layout()
90 .expect("exn types have a layout")
91 .unwrap_struct()
92 }
93
94 pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
95 self.ty.registered_type().index()
96 }
97}
98
99#[derive(Debug)]
110#[repr(transparent)]
111pub struct ExnRef {
112 pub(super) inner: GcRootIndex,
113}
114
115unsafe impl GcRefImpl for ExnRef {
116 fn transmute_ref(index: &GcRootIndex) -> &Self {
117 let me: &Self = unsafe { mem::transmute(index) };
119
120 assert!(matches!(
122 me,
123 Self {
124 inner: GcRootIndex { .. },
125 }
126 ));
127
128 me
129 }
130}
131
132impl ExnRef {
133 pub fn from_raw(mut store: impl AsContextMut, raw: u32) -> Option<Rooted<Self>> {
164 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
165 Self::_from_raw(&mut store, raw)
166 }
167
168 pub(crate) fn _from_raw(store: &mut AutoAssertNoGc, raw: u32) -> Option<Rooted<Self>> {
170 let gc_ref = VMGcRef::from_raw_u32(raw)?;
171 let gc_ref = store.clone_gc_ref(&gc_ref);
172 Some(Self::from_cloned_gc_ref(store, gc_ref))
173 }
174
175 pub fn new(
205 mut store: impl AsContextMut,
206 allocator: &ExnRefPre,
207 tag: &Tag,
208 fields: &[Val],
209 ) -> Result<Rooted<ExnRef>> {
210 let (mut limiter, store) = store
211 .as_context_mut()
212 .0
213 .validate_sync_resource_limiter_and_store_opaque()?;
214 vm::assert_ready(Self::_new_async(
215 store,
216 limiter.as_mut(),
217 allocator,
218 tag,
219 fields,
220 Asyncness::No,
221 ))
222 }
223
224 #[cfg(feature = "async")]
249 pub async fn new_async(
250 mut store: impl AsContextMut,
251 allocator: &ExnRefPre,
252 tag: &Tag,
253 fields: &[Val],
254 ) -> Result<Rooted<ExnRef>> {
255 let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
256 Self::_new_async(
257 store,
258 limiter.as_mut(),
259 allocator,
260 tag,
261 fields,
262 Asyncness::Yes,
263 )
264 .await
265 }
266
267 pub(crate) async fn _new_async(
268 store: &mut StoreOpaque,
269 limiter: Option<&mut StoreResourceLimiter<'_>>,
270 allocator: &ExnRefPre,
271 tag: &Tag,
272 fields: &[Val],
273 asyncness: Asyncness,
274 ) -> Result<Rooted<ExnRef>> {
275 Self::type_check_tag_and_fields(store, allocator, tag, fields)?;
276 store
277 .retry_after_gc_async(limiter, (), asyncness, |store, ()| {
278 Self::new_unchecked(store, allocator, tag, fields)
279 })
280 .await
281 }
282
283 fn type_check_tag_and_fields(
286 store: &mut StoreOpaque,
287 allocator: &ExnRefPre,
288 tag: &Tag,
289 fields: &[Val],
290 ) -> Result<(), Error> {
291 assert!(
292 tag.comes_from_same_store(store),
293 "tag comes from the wrong store"
294 );
295 ensure!(
296 tag.wasmtime_ty(store).signature.unwrap_engine_type_index()
297 == allocator.ty.tag_type().ty().type_index(),
298 "incorrect signature for tag when creating exception object"
299 );
300 let expected_len = allocator.ty.fields().len();
301 let actual_len = fields.len();
302 ensure!(
303 actual_len == expected_len,
304 "expected {expected_len} fields, got {actual_len}"
305 );
306 for (ty, val) in allocator.ty.fields().zip(fields) {
307 assert!(
308 val.comes_from_same_store(store),
309 "field value comes from the wrong store",
310 );
311 let ty = ty.element_type().unpack();
312 val.ensure_matches_ty(store, ty)
313 .context("field type mismatch")?;
314 }
315 Ok(())
316 }
317
318 fn new_unchecked(
323 store: &mut StoreOpaque,
324 allocator: &ExnRefPre,
325 tag: &Tag,
326 fields: &[Val],
327 ) -> Result<Rooted<ExnRef>> {
328 assert_eq!(
329 store.id(),
330 allocator.store_id,
331 "attempted to use a `ExnRefPre` with the wrong store"
332 );
333
334 let exnref = store
337 .require_gc_store_mut()?
338 .alloc_uninit_exn(allocator.type_index(), &allocator.layout())
339 .context("unrecoverable error when allocating new `exnref`")?
340 .map_err(|n| GcHeapOutOfMemory::new((), n))?;
341
342 let mut store = AutoAssertNoGc::new(store);
347 match (|| {
348 let (instance, index) = tag.to_raw_indices();
349 exnref.initialize_tag(&mut store, instance, index)?;
350 for (index, (ty, val)) in allocator.ty.fields().zip(fields).enumerate() {
351 exnref.initialize_field(
352 &mut store,
353 allocator.layout(),
354 ty.element_type(),
355 index,
356 *val,
357 )?;
358 }
359 Ok(())
360 })() {
361 Ok(()) => Ok(Rooted::new(&mut store, exnref.into())),
362 Err(e) => {
363 store.require_gc_store_mut()?.dealloc_uninit_exn(exnref)?;
364 Err(e)
365 }
366 }
367 }
368
369 pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result<VMSharedTypeIndex> {
370 let gc_ref = self.inner.try_gc_ref(store)?;
371 let header = store.require_gc_store()?.header(gc_ref)?;
372 debug_assert!(header.kind().matches(VMGcKind::ExnRef));
373 Ok(header.ty().expect("exnrefs should have concrete types"))
374 }
375
376 pub(crate) fn from_cloned_gc_ref(
383 store: &mut AutoAssertNoGc<'_>,
384 gc_ref: VMGcRef,
385 ) -> Rooted<Self> {
386 debug_assert!(
387 store
388 .unwrap_gc_store()
389 .kind(&gc_ref)
390 .unwrap()
391 .matches(VMGcKind::ExnRef)
392 );
393 Rooted::new(store, gc_ref)
394 }
395
396 #[inline]
397 pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
398 self.inner.comes_from_same_store(store)
399 }
400
401 pub fn to_raw(&self, mut store: impl AsContextMut) -> Result<u32> {
414 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
415 self._to_raw(&mut store)
416 }
417
418 pub(crate) fn _to_raw(&self, store: &mut AutoAssertNoGc<'_>) -> Result<u32> {
419 let gc_ref = self.inner.try_clone_gc_ref(store)?;
420 let raw = if gc_ref.is_i31() {
421 gc_ref.as_raw_non_zero_u32()
422 } else {
423 store
424 .require_gc_store_mut()?
425 .expose_gc_ref_to_wasm(gc_ref)?
426 };
427 Ok(raw.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 Ok(ExnType::from_shared_type_index(store.engine(), index))
447 }
448
449 pub fn matches_ty(&self, store: impl AsContext, ty: &HeapType) -> Result<bool> {
461 self._matches_ty(store.as_context().0, ty)
462 }
463
464 pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<bool> {
465 assert!(self.comes_from_same_store(store));
466 Ok(HeapType::from(self._ty(store)?).matches(ty))
467 }
468
469 pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> {
470 if !self.comes_from_same_store(store) {
471 bail!("function used with wrong store");
472 }
473 if self._matches_ty(store, ty)? {
474 Ok(())
475 } else {
476 let actual_ty = self._ty(store)?;
477 bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`")
478 }
479 }
480
481 pub fn fields<'a, T: 'static>(
491 &'a self,
492 store: impl Into<StoreContextMut<'a, T>>,
493 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
494 self._fields(store.into().0)
495 }
496
497 pub(crate) fn _fields<'a>(
498 &'a self,
499 store: &'a mut StoreOpaque,
500 ) -> Result<impl ExactSizeIterator<Item = Val> + 'a> {
501 assert!(self.comes_from_same_store(store));
502 let store = AutoAssertNoGc::new(store);
503
504 let gc_ref = self.inner.try_gc_ref(&store)?;
505 let header = store.require_gc_store()?.header(gc_ref)?;
506 debug_assert!(header.kind().matches(VMGcKind::ExnRef));
507
508 let index = header.ty().expect("exnrefs should have concrete types");
509 let ty = ExnType::from_shared_type_index(store.engine(), index);
510 let len = ty.fields().len();
511
512 return Ok(Fields {
513 exnref: self,
514 store,
515 index: 0,
516 len,
517 });
518
519 struct Fields<'a, 'b> {
520 exnref: &'a ExnRef,
521 store: AutoAssertNoGc<'b>,
522 index: usize,
523 len: usize,
524 }
525
526 impl Iterator for Fields<'_, '_> {
527 type Item = Val;
528
529 #[inline]
530 fn next(&mut self) -> Option<Self::Item> {
531 let i = self.index;
532 debug_assert!(i <= self.len);
533 if i >= self.len {
534 return None;
535 }
536 self.index += 1;
537 self.exnref._field(&mut self.store, i).ok()
538 }
539
540 #[inline]
541 fn size_hint(&self) -> (usize, Option<usize>) {
542 let len = self.len - self.index;
543 (len, Some(len))
544 }
545 }
546
547 impl ExactSizeIterator for Fields<'_, '_> {
548 #[inline]
549 fn len(&self) -> usize {
550 self.len - self.index
551 }
552 }
553 }
554
555 fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> {
556 assert!(self.comes_from_same_store(&store));
557 let gc_ref = self.inner.try_gc_ref(store)?;
558 Ok(store.require_gc_store()?.header(gc_ref)?)
559 }
560
561 fn exnref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMExnRef> {
562 assert!(self.comes_from_same_store(&store));
563 let gc_ref = self.inner.try_gc_ref(store)?;
564 debug_assert!(self.header(store)?.kind().matches(VMGcKind::ExnRef));
565 Ok(gc_ref.as_exnref_unchecked())
566 }
567
568 fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result<Arc<GcStructLayout>> {
569 assert!(self.comes_from_same_store(&store));
570 let type_index = self.type_index(store)?;
571 let layout = store
572 .engine()
573 .signatures()
574 .layout(type_index)
575 .expect("exn types should have GC layouts");
576 match layout {
577 GcLayout::Struct(s) => Ok(s),
578 GcLayout::Array(_) => unreachable!(),
579 }
580 }
581
582 fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result<FieldType> {
583 let ty = self._ty(store)?;
584 match ty.field(field) {
585 Some(f) => Ok(f),
586 None => {
587 let len = ty.fields().len();
588 bail!("cannot access field {field}: exn only has {len} fields")
589 }
590 }
591 }
592
593 pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result<Val> {
604 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
605 self._field(&mut store, index)
606 }
607
608 pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result<Val> {
609 assert!(self.comes_from_same_store(store));
610 let exnref = self.exnref(store)?.unchecked_copy();
611 let field_ty = self.field_ty(store, index)?;
612 let layout = self.layout(store)?;
613 exnref.read_field(store, &layout, field_ty.element_type(), index)
614 }
615
616 pub fn tag(&self, mut store: impl AsContextMut) -> Result<Tag> {
626 let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
627 assert!(self.comes_from_same_store(&store));
628 let exnref = self.exnref(&store)?.unchecked_copy();
629 let (instance, index) = exnref.tag(&mut store)?;
630 Ok(Tag::from_raw_indices(&*store, instance, index))
631 }
632}
633
634unsafe impl WasmTy for Rooted<ExnRef> {
635 #[inline]
636 fn valtype() -> ValType {
637 ValType::Ref(RefType::new(false, HeapType::Exn))
638 }
639
640 #[inline]
641 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
642 self.comes_from_same_store(store)
643 }
644
645 #[inline]
646 fn dynamic_concrete_type_check(
647 &self,
648 _store: &StoreOpaque,
649 _nullable: bool,
650 _ty: &HeapType,
651 ) -> Result<()> {
652 Ok(())
655 }
656
657 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
658 self.wasm_ty_store(store, ptr, ValRaw::anyref)
659 }
660
661 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
662 Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
663 }
664}
665
666unsafe impl WasmTy for Option<Rooted<ExnRef>> {
667 #[inline]
668 fn valtype() -> ValType {
669 ValType::EXNREF
670 }
671
672 #[inline]
673 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
674 self.map_or(true, |x| x.comes_from_same_store(store))
675 }
676
677 #[inline]
678 fn dynamic_concrete_type_check(
679 &self,
680 store: &StoreOpaque,
681 nullable: bool,
682 ty: &HeapType,
683 ) -> Result<()> {
684 match self {
685 Some(a) => a.ensure_matches_ty(store, ty),
686 None => {
687 ensure!(
688 nullable,
689 "expected a non-null reference, but found a null reference"
690 );
691 Ok(())
692 }
693 }
694 }
695
696 #[inline]
697 fn is_vmgcref_and_points_to_object(&self) -> bool {
698 self.is_some()
699 }
700
701 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
702 <Rooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
703 }
704
705 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
706 <Rooted<ExnRef>>::wasm_ty_option_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
707 }
708}
709
710unsafe impl WasmTy for OwnedRooted<ExnRef> {
711 #[inline]
712 fn valtype() -> ValType {
713 ValType::Ref(RefType::new(false, HeapType::Exn))
714 }
715
716 #[inline]
717 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
718 self.comes_from_same_store(store)
719 }
720
721 #[inline]
722 fn dynamic_concrete_type_check(
723 &self,
724 store: &StoreOpaque,
725 _nullable: bool,
726 ty: &HeapType,
727 ) -> Result<()> {
728 self.ensure_matches_ty(store, ty)
729 }
730
731 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
732 self.wasm_ty_store(store, ptr, ValRaw::anyref)
733 }
734
735 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
736 Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref)
737 }
738}
739
740unsafe impl WasmTy for Option<OwnedRooted<ExnRef>> {
741 #[inline]
742 fn valtype() -> ValType {
743 ValType::EXNREF
744 }
745
746 #[inline]
747 fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
748 self.as_ref()
749 .map_or(true, |x| x.comes_from_same_store(store))
750 }
751
752 #[inline]
753 fn dynamic_concrete_type_check(
754 &self,
755 store: &StoreOpaque,
756 nullable: bool,
757 ty: &HeapType,
758 ) -> Result<()> {
759 match self {
760 Some(a) => a.ensure_matches_ty(store, ty),
761 None => {
762 ensure!(
763 nullable,
764 "expected a non-null reference, but found a null reference"
765 );
766 Ok(())
767 }
768 }
769 }
770
771 #[inline]
772 fn is_vmgcref_and_points_to_object(&self) -> bool {
773 self.is_some()
774 }
775
776 fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
777 <OwnedRooted<ExnRef>>::wasm_ty_option_store(self, store, ptr, ValRaw::anyref)
778 }
779
780 unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
781 <OwnedRooted<ExnRef>>::wasm_ty_option_load(
782 store,
783 ptr.get_anyref(),
784 ExnRef::from_cloned_gc_ref,
785 )
786 }
787}