diff --git a/crates/c-api/src/val.rs b/crates/c-api/src/val.rs index 6d26e808df26..efc53cf52968 100644 --- a/crates/c-api/src/val.rs +++ b/crates/c-api/src/val.rs @@ -96,6 +96,7 @@ impl wasm_val_t { }, Val::AnyRef(_) => crate::abort("creating a wasm_val_t from an anyref"), Val::ExternRef(_) => crate::abort("creating a wasm_val_t from an externref"), + Val::ExnRef(_) => crate::abort("creating a wasm_val_t from an exnref"), Val::V128(_) => crate::abort("creating a wasm_val_t from a v128"), } } @@ -251,6 +252,7 @@ impl wasmtime_val_t { funcref: func.into(), }, }, + Val::ExnRef(_) => crate::abort("exnrefs not yet supported in C API"), Val::V128(val) => wasmtime_val_t { kind: crate::WASMTIME_V128, of: wasmtime_val_union { diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 50a24b560a3d..5b97adf87551 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -1514,6 +1514,9 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> { | WasmHeapType::ConcreteArray(_) | WasmHeapType::Struct | WasmHeapType::ConcreteStruct(_) + | WasmHeapType::Exn + | WasmHeapType::ConcreteExn(_) + | WasmHeapType::NoExn | WasmHeapType::None => { unreachable!() } @@ -1741,7 +1744,7 @@ impl<'module_environment> TargetEnvironment for FuncEnvironment<'module_environm fn reference_type(&self, wasm_ty: WasmHeapType) -> (ir::Type, bool) { let ty = crate::reference_type(wasm_ty, self.pointer_type()); let needs_stack_map = match wasm_ty.top() { - WasmHeapTopType::Extern | WasmHeapTopType::Any => true, + WasmHeapTopType::Extern | WasmHeapTopType::Any | WasmHeapTopType::Exn => true, WasmHeapTopType::Func => false, WasmHeapTopType::Cont => todo!(), // FIXME: #10248 stack switching support. }; @@ -1838,7 +1841,7 @@ impl FuncEnvironment<'_> { let heap_ty = table.ref_type.heap_type; match heap_ty.top() { // GC-managed types. - WasmHeapTopType::Any | WasmHeapTopType::Extern => { + WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => { let (src, flags) = table_data.prepare_table_addr(self, builder, index); gc::gc_compiler(self)?.translate_read_gc_reference( self, @@ -1872,7 +1875,7 @@ impl FuncEnvironment<'_> { let heap_ty = table.ref_type.heap_type; match heap_ty.top() { // GC-managed types. - WasmHeapTopType::Any | WasmHeapTopType::Extern => { + WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => { let (dst, flags) = table_data.prepare_table_addr(self, builder, index); gc::gc_compiler(self)?.translate_write_gc_reference( self, @@ -2254,7 +2257,9 @@ impl FuncEnvironment<'_> { Ok(match ht.top() { WasmHeapTopType::Func => pos.ins().iconst(self.pointer_type(), 0), // NB: null GC references don't need to be in stack maps. - WasmHeapTopType::Any | WasmHeapTopType::Extern => pos.ins().iconst(types::I32, 0), + WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => { + pos.ins().iconst(types::I32, 0) + } WasmHeapTopType::Cont => todo!(), // FIXME: #10248 stack switching support. }) } diff --git a/crates/cranelift/src/func_environ/gc/enabled.rs b/crates/cranelift/src/func_environ/gc/enabled.rs index 04674dae7aa9..c6bca5dff00f 100644 --- a/crates/cranelift/src/func_environ/gc/enabled.rs +++ b/crates/cranelift/src/func_environ/gc/enabled.rs @@ -117,8 +117,10 @@ fn read_field_at_addr( WasmValType::F64 => builder.ins().load(ir::types::F64, flags, addr, 0), WasmValType::V128 => builder.ins().load(ir::types::I8X16, flags, addr, 0), WasmValType::Ref(r) => match r.heap_type.top() { - WasmHeapTopType::Any | WasmHeapTopType::Extern => gc_compiler(func_env)? - .translate_read_gc_reference(func_env, builder, r, addr, flags)?, + WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => { + gc_compiler(func_env)? + .translate_read_gc_reference(func_env, builder, r, addr, flags)? + } WasmHeapTopType::Func => { let expected_ty = match r.heap_type { WasmHeapType::Func => ModuleInternedTypeIndex::reserved_value(), @@ -1042,6 +1044,8 @@ pub fn translate_ref_test( | WasmHeapType::NoFunc | WasmHeapType::Cont | WasmHeapType::NoCont + | WasmHeapType::Exn + | WasmHeapType::NoExn | WasmHeapType::I31 => unreachable!("handled top, bottom, and i31 types above"), // For these abstract but non-top and non-bottom types, we check the @@ -1057,7 +1061,9 @@ pub fn translate_ref_test( // TODO: This check should ideally be done inline, but we don't have a // good way to access the `TypeRegistry`'s supertypes arrays from Wasm // code at the moment. - WasmHeapType::ConcreteArray(ty) | WasmHeapType::ConcreteStruct(ty) => { + WasmHeapType::ConcreteArray(ty) + | WasmHeapType::ConcreteStruct(ty) + | WasmHeapType::ConcreteExn(ty) => { let expected_interned_ty = ty.unwrap_module_type_index(); let expected_shared_ty = func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty); @@ -1417,6 +1423,8 @@ impl FuncEnvironment<'_> { // Can only ever be `null`. WasmHeapType::NoExtern => false, + WasmHeapType::Exn | WasmHeapType::ConcreteExn(_) | WasmHeapType::NoExn => false, + // Wrong type hierarchy, and also funcrefs are not GC-managed // types. Should have been caught by the assertion at the start of // the function. diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 871c20bff916..2ec67f629368 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -212,7 +212,7 @@ fn wasm_call_signature( fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type { match wasm_ht.top() { WasmHeapTopType::Func => pointer_type, - WasmHeapTopType::Any | WasmHeapTopType::Extern => ir::types::I32, + WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => ir::types::I32, WasmHeapTopType::Cont => // TODO(10248) This is added in a follow-up PR { diff --git a/crates/environ/src/compile/module_environ.rs b/crates/environ/src/compile/module_environ.rs index b3a70737942c..f56b5c4c8594 100644 --- a/crates/environ/src/compile/module_environ.rs +++ b/crates/environ/src/compile/module_environ.rs @@ -1220,7 +1220,10 @@ impl ModuleTranslation<'_> { // initializer won't trap so we could continue processing // segments, but that's left as a future optimization if // necessary. - WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Cont => break, + WasmHeapTopType::Any + | WasmHeapTopType::Extern + | WasmHeapTopType::Cont + | WasmHeapTopType::Exn => break, } // Function indices can be optimized here, but fully general diff --git a/crates/environ/src/compile/module_types.rs b/crates/environ/src/compile/module_types.rs index 9109a9790e8c..d550ae0e8c28 100644 --- a/crates/environ/src/compile/module_types.rs +++ b/crates/environ/src/compile/module_types.rs @@ -441,6 +441,7 @@ where WasmCompositeInnerType::Func(_) => WasmHeapType::ConcreteFunc(index), WasmCompositeInnerType::Struct(_) => WasmHeapType::ConcreteStruct(index), WasmCompositeInnerType::Cont(_) => WasmHeapType::ConcreteCont(index), + WasmCompositeInnerType::Exn(_) => WasmHeapType::ConcreteExn(index), } } else if let Some((wasmparser_types, _)) = self.rec_group_context.as_ref() { let wasmparser_ty = &wasmparser_types[id].composite_type; @@ -481,6 +482,7 @@ where WasmCompositeInnerType::Func(_) => WasmHeapType::ConcreteFunc(index), WasmCompositeInnerType::Struct(_) => WasmHeapType::ConcreteStruct(index), WasmCompositeInnerType::Cont(_) => WasmHeapType::ConcreteCont(index), + WasmCompositeInnerType::Exn(_) => WasmHeapType::ConcreteExn(index), } } else if let Some((parser_types, rec_group)) = self.rec_group_context.as_ref() { let rec_group_index = interned.index() - self.types.types.len_types(); diff --git a/crates/environ/src/gc.rs b/crates/environ/src/gc.rs index 556071b24dcf..21af210d8b55 100644 --- a/crates/environ/src/gc.rs +++ b/crates/environ/src/gc.rs @@ -15,11 +15,11 @@ pub mod drc; #[cfg(feature = "gc-null")] pub mod null; -use crate::prelude::*; use crate::{ WasmArrayType, WasmCompositeInnerType, WasmCompositeType, WasmStorageType, WasmStructType, WasmValType, }; +use crate::{WasmExnType, prelude::*}; use core::alloc::Layout; /// Discriminant to check whether GC reference is an `i31ref` or not. @@ -119,17 +119,15 @@ fn common_array_layout( } } -/// Common code to define a GC struct's layout, given the size and alignment of -/// the collector's GC header and its expected offset of the array length field. +/// Shared layout code for structs and exception objects, which are +/// identical except for the tag field (present in +/// exceptions). Returns `(size, align, fields)`. #[cfg(any(feature = "gc-null", feature = "gc-drc"))] -fn common_struct_layout( - ty: &WasmStructType, +fn common_struct_or_exn_layout( + fields: &[crate::WasmFieldType], header_size: u32, header_align: u32, -) -> GcStructLayout { - assert!(header_size >= crate::VM_GC_HEADER_SIZE); - assert!(header_align >= crate::VM_GC_HEADER_ALIGN); - +) -> (u32, u32, Vec) { // Process each field, aligning it to its natural alignment. // // We don't try and do any fancy field reordering to minimize padding (yet?) @@ -138,11 +136,11 @@ fn common_struct_layout( // subtyping where we need a subtype's fields to be assigned the same // offsets as its supertype's fields. We can come back and improve things // here if we find that (a) isn't actually holding true in practice. + let mut size = header_size; let mut align = header_align; - let fields = ty - .fields + let fields = fields .iter() .map(|f| { let field_size = byte_size_of_wasm_ty_in_gc_heap(&f.element_type); @@ -157,6 +155,22 @@ fn common_struct_layout( let align_size_to = align; align_up(&mut size, &mut align, align_size_to); + (size, align, fields) +} + +/// Common code to define a GC struct's layout, given the size and alignment of +/// the collector's GC header and its expected offset of the array length field. +#[cfg(any(feature = "gc-null", feature = "gc-drc"))] +fn common_struct_layout( + ty: &WasmStructType, + header_size: u32, + header_align: u32, +) -> GcStructLayout { + assert!(header_size >= crate::VM_GC_HEADER_SIZE); + assert!(header_align >= crate::VM_GC_HEADER_ALIGN); + + let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align); + GcStructLayout { size, align, @@ -164,6 +178,30 @@ fn common_struct_layout( } } +/// Common code to define a GC exception object's layout, given the +/// size and alignment of the collector's GC header and its expected +/// offset of the array length field. +#[cfg(any(feature = "gc-null", feature = "gc-drc"))] +fn common_exn_layout(ty: &WasmExnType, header_size: u32, header_align: u32) -> GcExceptionLayout { + assert!(header_size >= crate::VM_GC_HEADER_SIZE); + assert!(header_align >= crate::VM_GC_HEADER_ALIGN); + + // Compute a struct layout, with extra header size for the + // `(instance_idx, tag_idx)` fields. + let tag_offset = header_size; + assert!(header_align >= 8); + let header_size = header_size + 2 * u32::try_from(core::mem::size_of::()).unwrap(); + + let (size, align, fields) = common_struct_or_exn_layout(&ty.fields, header_size, header_align); + + GcExceptionLayout { + size, + align, + tag_offset, + fields, + } +} + /// A trait for getting the layout of a Wasm GC struct or array inside a /// particular collector. pub trait GcTypeLayouts { @@ -186,6 +224,7 @@ pub trait GcTypeLayouts { WasmCompositeInnerType::Cont(_) => { unimplemented!("Stack switching feature not compatbile with GC, yet") } + WasmCompositeInnerType::Exn(ty) => Some(self.exn_layout(ty).into()), } } @@ -194,6 +233,9 @@ pub trait GcTypeLayouts { /// Get this collector's layout for the given struct type. fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout; + + /// Get this collector's layout for the given exception type. + fn exn_layout(&self, ty: &WasmExnType) -> GcExceptionLayout; } /// The layout of a GC-managed object. @@ -204,6 +246,9 @@ pub enum GcLayout { /// The layout of a GC-managed struct object. Struct(GcStructLayout), + + /// The layout of a GC-managed exception object. + Exception(GcExceptionLayout), } impl From for GcLayout { @@ -218,6 +263,12 @@ impl From for GcLayout { } } +impl From for GcLayout { + fn from(layout: GcExceptionLayout) -> Self { + Self::Exception(layout) + } +} + impl GcLayout { /// Get the underlying `GcStructLayout`, or panic. #[track_caller] @@ -236,6 +287,15 @@ impl GcLayout { _ => panic!("GcLayout::unwrap_array on non-array GC layout"), } } + + /// Get the underlying `GcExceptionLayout`, or panic. + #[track_caller] + pub fn unwrap_exception(&self) -> &GcExceptionLayout { + match self { + Self::Exception(e) => e, + _ => panic!("GcLayout::unwrap_exception on a non-exception GC layout"), + } + } } /// The layout of a GC-managed array. @@ -337,6 +397,41 @@ pub struct GcStructLayoutField { pub is_gc_ref: bool, } +/// The layout for a GC-managed exception object. +/// +/// This layout is only valid for use with the GC runtime that created it. It is +/// not valid to use one GC runtime's layout with another GC runtime, doing so +/// is memory safe but will lead to general incorrectness like panics and wrong +/// results. +/// +/// All offsets are from the start of the object; that is, the size of the GC +/// header (for example) is included in the offset. +#[derive(Clone, Debug)] +pub struct GcExceptionLayout { + /// The size (in bytes) of this struct. + pub size: u32, + + /// The alignment (in bytes) of this struct. + pub align: u32, + + /// The offset of the VMTagImport pointer. + pub tag_offset: u32, + + /// The fields of this exception object. The `i`th entry contains + /// information about the `i`th parameter in the associated tag + /// type. + pub fields: Vec, +} + +impl GcExceptionLayout { + /// Get a `core::alloc::Layout` for an exception object of this type. + pub fn layout(&self) -> Layout { + let size = usize::try_from(self.size).unwrap(); + let align = usize::try_from(self.align).unwrap(); + Layout::from_size_align(size, align).unwrap() + } +} + /// The kind of an object in a GC heap. /// /// Note that this type is accessed from Wasm JIT code. @@ -360,18 +455,19 @@ pub struct GcStructLayoutField { /// VMGcKind::EqRef`. /// /// Furthermore, this type only uses the highest 6 bits of its `u32` -/// representation, allowing the lower 27 bytes to be bitpacked with other stuff +/// representation, allowing the lower 26 bits to be bitpacked with other stuff /// as users see fit. #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[rustfmt::skip] #[expect(missing_docs, reason = "self-describing variants")] pub enum VMGcKind { - ExternRef = 0b01000 << 27, - AnyRef = 0b10000 << 27, - EqRef = 0b10100 << 27, - ArrayRef = 0b10101 << 27, - StructRef = 0b10110 << 27, + ExternRef = 0b010000 << 26, + AnyRef = 0b100000 << 26, + EqRef = 0b101000 << 26, + ArrayRef = 0b101010 << 26, + StructRef = 0b101100 << 26, + ExnRef = 0b000001 << 26, } /// The size of the `VMGcKind` in bytes. @@ -381,7 +477,7 @@ const _: () = assert!(VM_GC_KIND_SIZE as usize == core::mem::size_of:: impl VMGcKind { /// Mask this value with a `u32` to get just the bits that `VMGcKind` uses. - pub const MASK: u32 = 0b11111 << 27; + pub const MASK: u32 = 0b111111 << 26; /// Mask this value with a `u32` that potentially contains a `VMGcKind` to /// get the bits that `VMGcKind` doesn't use. @@ -404,6 +500,7 @@ impl VMGcKind { x if x == Self::EqRef.as_u32() => Self::EqRef, x if x == Self::ArrayRef.as_u32() => Self::ArrayRef, x if x == Self::StructRef.as_u32() => Self::StructRef, + x if x == Self::ExnRef.as_u32() => Self::ExnRef, _ => panic!("invalid `VMGcKind`: {masked:#032b}"), } } @@ -430,14 +527,16 @@ mod tests { #[test] fn kind_matches() { - let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef]; + let all = [ExternRef, AnyRef, EqRef, ArrayRef, StructRef, ExnRef]; for (sup, subs) in [ (ExternRef, vec![]), (AnyRef, vec![EqRef, ArrayRef, StructRef]), + // N.B.: exnref is not an eqref. (EqRef, vec![ArrayRef, StructRef]), (ArrayRef, vec![]), (StructRef, vec![]), + (ExnRef, vec![]), ] { assert!(sup.matches(sup)); for sub in &subs { diff --git a/crates/environ/src/gc/drc.rs b/crates/environ/src/gc/drc.rs index 23b556a36565..908ad7e43369 100644 --- a/crates/environ/src/gc/drc.rs +++ b/crates/environ/src/gc/drc.rs @@ -36,4 +36,8 @@ impl GcTypeLayouts for DrcTypeLayouts { fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout { common_struct_layout(ty, HEADER_SIZE, HEADER_ALIGN) } + + fn exn_layout(&self, ty: &WasmExnType) -> GcExceptionLayout { + common_exn_layout(ty, HEADER_SIZE, HEADER_ALIGN) + } } diff --git a/crates/environ/src/gc/null.rs b/crates/environ/src/gc/null.rs index e3e69bcb357e..15518e0211b0 100644 --- a/crates/environ/src/gc/null.rs +++ b/crates/environ/src/gc/null.rs @@ -27,4 +27,8 @@ impl GcTypeLayouts for NullTypeLayouts { fn struct_layout(&self, ty: &WasmStructType) -> GcStructLayout { common_struct_layout(ty, HEADER_SIZE, HEADER_ALIGN) } + + fn exn_layout(&self, ty: &WasmExnType) -> GcExceptionLayout { + common_exn_layout(ty, HEADER_SIZE, HEADER_ALIGN) + } } diff --git a/crates/environ/src/types.rs b/crates/environ/src/types.rs index bfe669e17a17..eb5a9730dc17 100644 --- a/crates/environ/src/types.rs +++ b/crates/environ/src/types.rs @@ -446,6 +446,11 @@ pub enum WasmHeapType { ConcreteFunc(EngineOrModuleTypeIndex), NoFunc, + // Exception types. + Exn, + ConcreteExn(EngineOrModuleTypeIndex), + NoExn, + // Continuation types. Cont, ConcreteCont(EngineOrModuleTypeIndex), @@ -470,6 +475,7 @@ impl From for WasmHeapType { WasmHeapTopType::Any => Self::Any, WasmHeapTopType::Func => Self::Func, WasmHeapTopType::Cont => Self::Cont, + WasmHeapTopType::Exn => Self::Exn, } } } @@ -482,6 +488,7 @@ impl From for WasmHeapType { WasmHeapBottomType::None => Self::None, WasmHeapBottomType::NoFunc => Self::NoFunc, WasmHeapBottomType::NoCont => Self::NoCont, + WasmHeapBottomType::NoExn => Self::NoExn, } } } @@ -504,6 +511,9 @@ impl fmt::Display for WasmHeapType { Self::ConcreteArray(i) => write!(f, "array {i}"), Self::Struct => write!(f, "struct"), Self::ConcreteStruct(i) => write!(f, "struct {i}"), + Self::Exn => write!(f, "exn"), + Self::ConcreteExn(i) => write!(f, "exn {i}"), + Self::NoExn => write!(f, "noexn"), Self::None => write!(f, "none"), } } @@ -542,9 +552,10 @@ impl WasmHeapType { #[inline] pub fn is_vmgcref_type(&self) -> bool { match self.top() { - // All `t <: (ref null any)` and `t <: (ref null extern)` are - // represented as `VMGcRef`s. - WasmHeapTopType::Any | WasmHeapTopType::Extern => true, + // All `t <: (ref null any)`, `t <: (ref null extern)`, + // and `t <: (ref null exn)` are represented as + // `VMGcRef`s. + WasmHeapTopType::Any | WasmHeapTopType::Extern | WasmHeapTopType::Exn => true, // All `t <: (ref null func)` are not. WasmHeapTopType::Func => false, @@ -582,6 +593,10 @@ impl WasmHeapType { WasmHeapTopType::Cont } + WasmHeapType::Exn | WasmHeapType::ConcreteExn(_) | WasmHeapType::NoExn => { + WasmHeapTopType::Exn + } + WasmHeapType::Any | WasmHeapType::Eq | WasmHeapType::I31 @@ -613,6 +628,10 @@ impl WasmHeapType { WasmHeapBottomType::NoCont } + WasmHeapType::Exn | WasmHeapType::ConcreteExn(_) | WasmHeapType::NoExn => { + WasmHeapBottomType::NoExn + } + WasmHeapType::Any | WasmHeapType::Eq | WasmHeapType::I31 @@ -634,6 +653,8 @@ pub enum WasmHeapTopType { Any, /// The common supertype of all function references. Func, + /// The common supertype of all exception references. + Exn, /// The common supertype of all continuation references. Cont, } @@ -647,6 +668,8 @@ pub enum WasmHeapBottomType { None, /// The common subtype of all function references. NoFunc, + /// The common subtype of all exception references. + NoExn, /// The common subtype of all continuation references. NoCont, } @@ -838,6 +861,79 @@ impl TypeTrace for WasmContType { } } +/// WebAssembly exception type. +/// +/// This "exception type" is not a Wasm language-level +/// concept. Instead, it denotes an *exception object signature* -- +/// the types of the payload values. +/// +/// In contrast, at the Wasm language level, exception objects are +/// associated with specific tags, and these tags refer to their +/// signatures (function types). However, tags are *nominal*: like +/// memories and tables, a separate instance of a tag exists for every +/// instance of the defining module, and these tag instances can be +/// imported and exported. At runtime we handle tags like we do +/// memories and tables, but these runtime instances do not exist in +/// the type system here. +/// +/// Because the Wasm type system does not have concrete `exn` types +/// (i.e., the heap-type lattice has only top `exn` and bottom +/// `noexn`), we are free to decide what we mean by "concrete type" +/// here. Thus, we define an "exception type" to refer to the +/// type-level *signature*. When a particular *exception object* is +/// created in a store, it can be associated with a particular *tag +/// instance* also in that store, and the compatibility is checked +/// (the tag's function type must match the function type in the +/// associated WasmExnType). +#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct WasmExnType { + /// The function type from which we get our signature. We hold + /// this directly so that we can efficiently derive a FuncType + /// without re-interning the field types. + pub func_ty: EngineOrModuleTypeIndex, + /// The fields (payload values) that make up this exception type. + /// + /// While we could obtain these by looking up the `func_ty` above, + /// we also need to be able to derive a GC object layout from this + /// type descriptor without referencing other type descriptors; so + /// we directly inline the information here. + pub fields: Box<[WasmFieldType]>, +} + +impl fmt::Display for WasmExnType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "(exn ({})", self.func_ty)?; + for ty in self.fields.iter() { + write!(f, " {ty}")?; + } + write!(f, ")") + } +} + +impl TypeTrace for WasmExnType { + fn trace(&self, func: &mut F) -> Result<(), E> + where + F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>, + { + func(self.func_ty)?; + for f in self.fields.iter() { + f.trace(func)?; + } + Ok(()) + } + + fn trace_mut(&mut self, func: &mut F) -> Result<(), E> + where + F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>, + { + func(&mut self.func_ty)?; + for f in self.fields.iter_mut() { + f.trace_mut(func)?; + } + Ok(()) + } +} + /// Represents storage types introduced in the GC spec for array and struct fields. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum WasmStorageType { @@ -1027,6 +1123,7 @@ pub enum WasmCompositeInnerType { Func(WasmFuncType), Struct(WasmStructType), Cont(WasmContType), + Exn(WasmExnType), } impl fmt::Display for WasmCompositeInnerType { @@ -1036,6 +1133,7 @@ impl fmt::Display for WasmCompositeInnerType { Self::Func(ty) => fmt::Display::fmt(ty, f), Self::Struct(ty) => fmt::Display::fmt(ty, f), Self::Cont(ty) => fmt::Display::fmt(ty, f), + Self::Exn(ty) => fmt::Display::fmt(ty, f), } } } @@ -1113,6 +1211,24 @@ impl WasmCompositeInnerType { pub fn unwrap_cont(&self) -> &WasmContType { self.as_cont().unwrap() } + + #[inline] + pub fn is_exn(&self) -> bool { + matches!(self, Self::Exn(_)) + } + + #[inline] + pub fn as_exn(&self) -> Option<&WasmExnType> { + match self { + Self::Exn(f) => Some(f), + _ => None, + } + } + + #[inline] + pub fn unwrap_exn(&self) -> &WasmExnType { + self.as_exn().unwrap() + } } impl TypeTrace for WasmCompositeType { @@ -1125,6 +1241,7 @@ impl TypeTrace for WasmCompositeType { WasmCompositeInnerType::Func(f) => f.trace(func), WasmCompositeInnerType::Struct(a) => a.trace(func), WasmCompositeInnerType::Cont(c) => c.trace(func), + WasmCompositeInnerType::Exn(e) => e.trace(func), } } @@ -1137,6 +1254,7 @@ impl TypeTrace for WasmCompositeType { WasmCompositeInnerType::Func(f) => f.trace_mut(func), WasmCompositeInnerType::Struct(a) => a.trace_mut(func), WasmCompositeInnerType::Cont(c) => c.trace_mut(func), + WasmCompositeInnerType::Exn(e) => e.trace_mut(func), } } } @@ -1256,6 +1374,26 @@ impl WasmSubType { assert!(!self.composite_type.shared); self.composite_type.inner.unwrap_cont() } + + #[inline] + pub fn is_exn(&self) -> bool { + self.composite_type.inner.is_exn() && !self.composite_type.shared + } + + #[inline] + pub fn as_exn(&self) -> Option<&WasmExnType> { + if self.composite_type.shared { + None + } else { + self.composite_type.inner.as_exn() + } + } + + #[inline] + pub fn unwrap_exn(&self) -> &WasmExnType { + assert!(!self.composite_type.shared); + self.composite_type.inner.unwrap_exn() + } } impl TypeTrace for WasmSubType { @@ -2260,9 +2398,8 @@ pub trait TypeConvert { wasmparser::AbstractHeapType::None => WasmHeapType::None, wasmparser::AbstractHeapType::Cont => WasmHeapType::Cont, wasmparser::AbstractHeapType::NoCont => WasmHeapType::NoCont, - wasmparser::AbstractHeapType::Exn | wasmparser::AbstractHeapType::NoExn => { - return Err(wasm_unsupported!("unsupported heap type {ty:?}")); - } + wasmparser::AbstractHeapType::Exn => WasmHeapType::Exn, + wasmparser::AbstractHeapType::NoExn => WasmHeapType::NoExn, }, _ => return Err(wasm_unsupported!("unsupported heap type {ty:?}")), }) diff --git a/crates/fuzzing/src/generators/value.rs b/crates/fuzzing/src/generators/value.rs index 516f733570a0..237652129373 100644 --- a/crates/fuzzing/src/generators/value.rs +++ b/crates/fuzzing/src/generators/value.rs @@ -17,6 +17,7 @@ pub enum DiffValue { FuncRef { null: bool }, ExternRef { null: bool }, AnyRef { null: bool }, + ExnRef { null: bool }, } impl DiffValue { @@ -30,6 +31,7 @@ impl DiffValue { DiffValue::FuncRef { .. } => DiffValueType::FuncRef, DiffValue::ExternRef { .. } => DiffValueType::ExternRef, DiffValue::AnyRef { .. } => DiffValueType::AnyRef, + DiffValue::ExnRef { .. } => DiffValueType::ExnRef, } } @@ -186,6 +188,7 @@ impl DiffValue { FuncRef => DiffValue::FuncRef { null: true }, ExternRef => DiffValue::ExternRef { null: true }, AnyRef => DiffValue::AnyRef { null: true }, + ExnRef => DiffValue::ExnRef { null: true }, }; arbitrary::Result::Ok(val) } @@ -232,6 +235,7 @@ impl Hash for DiffValue { DiffValue::ExternRef { null } => null.hash(state), DiffValue::FuncRef { null } => null.hash(state), DiffValue::AnyRef { null } => null.hash(state), + DiffValue::ExnRef { null } => null.hash(state), } } } @@ -285,6 +289,7 @@ pub enum DiffValueType { FuncRef, ExternRef, AnyRef, + ExnRef, } impl TryFrom for DiffValueType { @@ -303,6 +308,7 @@ impl TryFrom for DiffValueType { (true, HeapType::Any) => Ok(Self::AnyRef), (true, HeapType::I31) => Ok(Self::AnyRef), (true, HeapType::None) => Ok(Self::AnyRef), + (true, HeapType::Exn) => Ok(Self::ExnRef), _ => Err("non-null reference types are not supported yet"), }, } diff --git a/crates/fuzzing/src/oracles/diff_spec.rs b/crates/fuzzing/src/oracles/diff_spec.rs index c258ed0ed8a0..643e9cb3b44a 100644 --- a/crates/fuzzing/src/oracles/diff_spec.rs +++ b/crates/fuzzing/src/oracles/diff_spec.rs @@ -103,7 +103,10 @@ impl From<&DiffValue> for SpecValue { DiffValue::F32(n) => SpecValue::F32(n as i32), DiffValue::F64(n) => SpecValue::F64(n as i64), DiffValue::V128(n) => SpecValue::V128(n.to_le_bytes().to_vec()), - DiffValue::FuncRef { .. } | DiffValue::ExternRef { .. } | DiffValue::AnyRef { .. } => { + DiffValue::FuncRef { .. } + | DiffValue::ExternRef { .. } + | DiffValue::AnyRef { .. } + | DiffValue::ExnRef { .. } => { unimplemented!() } } diff --git a/crates/fuzzing/src/oracles/diff_v8.rs b/crates/fuzzing/src/oracles/diff_v8.rs index c0d393716fdd..7e0ed12e21ec 100644 --- a/crates/fuzzing/src/oracles/diff_v8.rs +++ b/crates/fuzzing/src/oracles/diff_v8.rs @@ -190,6 +190,7 @@ impl DiffInstance for V8Instance { // JS doesn't support v128 parameters DiffValue::V128(_) => return Ok(None), DiffValue::AnyRef { .. } => unimplemented!(), + DiffValue::ExnRef { .. } => unimplemented!(), }); } // JS doesn't support v128 return values @@ -313,6 +314,7 @@ fn get_diff_value( null: val.is_null(), }, DiffValueType::AnyRef => unimplemented!(), + DiffValueType::ExnRef => unimplemented!(), DiffValueType::V128 => unreachable!(), } } diff --git a/crates/fuzzing/src/oracles/diff_wasmi.rs b/crates/fuzzing/src/oracles/diff_wasmi.rs index 7f494afcaf4d..c591c218cdf8 100644 --- a/crates/fuzzing/src/oracles/diff_wasmi.rs +++ b/crates/fuzzing/src/oracles/diff_wasmi.rs @@ -194,6 +194,7 @@ impl From<&DiffValue> for wasmi::Val { WasmiValue::ExternRef(wasmi::ExternRef::null()) } DiffValue::AnyRef { .. } => unimplemented!(), + DiffValue::ExnRef { .. } => unimplemented!(), } } } diff --git a/crates/fuzzing/src/oracles/diff_wasmtime.rs b/crates/fuzzing/src/oracles/diff_wasmtime.rs index 98a396c5ab01..53c54b73a239 100644 --- a/crates/fuzzing/src/oracles/diff_wasmtime.rs +++ b/crates/fuzzing/src/oracles/diff_wasmtime.rs @@ -223,6 +223,10 @@ impl From<&DiffValue> for Val { assert!(null); Val::AnyRef(None) } + DiffValue::ExnRef { null } => { + assert!(null); + Val::ExnRef(None) + } } } } @@ -238,6 +242,7 @@ impl From for DiffValue { Val::ExternRef(r) => DiffValue::ExternRef { null: r.is_none() }, Val::FuncRef(r) => DiffValue::FuncRef { null: r.is_none() }, Val::AnyRef(r) => DiffValue::AnyRef { null: r.is_none() }, + Val::ExnRef(e) => DiffValue::ExnRef { null: e.is_none() }, } } } diff --git a/crates/wasmtime/src/runtime/coredump.rs b/crates/wasmtime/src/runtime/coredump.rs index 3a89275c6e90..517c2fbb241a 100644 --- a/crates/wasmtime/src/runtime/coredump.rs +++ b/crates/wasmtime/src/runtime/coredump.rs @@ -204,6 +204,12 @@ impl WasmCoreDump { Val::AnyRef(_) => { wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::ANY) } + Val::ExnRef(_) => { + wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::Abstract { + shared: false, + ty: wasm_encoder::AbstractHeapType::Exn, + }) + } }; globals.global( wasm_encoder::GlobalType { diff --git a/crates/wasmtime/src/runtime/externals/global.rs b/crates/wasmtime/src/runtime/externals/global.rs index 089aa3fb55bc..ecad1f3dd781 100644 --- a/crates/wasmtime/src/runtime/externals/global.rs +++ b/crates/wasmtime/src/runtime/externals/global.rs @@ -175,7 +175,9 @@ impl Global { | HeapType::Struct | HeapType::ConcreteStruct(_) | HeapType::Array - | HeapType::ConcreteArray(_) => definition + | HeapType::ConcreteArray(_) + | HeapType::Exn + | HeapType::ConcreteExn(_) => definition .as_gc_ref() .map(|r| { let r = store.unwrap_gc_store_mut().clone_gc_ref(r); @@ -183,6 +185,8 @@ impl Global { }) .into(), + HeapType::NoExn => Ref::Exn(None), + HeapType::None => Ref::Any(None), }; debug_assert!( @@ -242,6 +246,14 @@ impl Global { let new = new.as_ref(); definition.write_gc_ref(store.unwrap_gc_store_mut(), new); } + Val::ExnRef(e) => { + let new = match e { + None => None, + Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()), + }; + let new = new.as_ref(); + definition.write_gc_ref(store.unwrap_gc_store_mut(), new); + } } } Ok(()) diff --git a/crates/wasmtime/src/runtime/externals/tag.rs b/crates/wasmtime/src/runtime/externals/tag.rs index 3e09e6c9f808..62ee4a89f9eb 100644 --- a/crates/wasmtime/src/runtime/externals/tag.rs +++ b/crates/wasmtime/src/runtime/externals/tag.rs @@ -1,10 +1,15 @@ +use crate::Result; use crate::runtime::types::TagType; +use crate::trampoline::generate_tag_export; use crate::{ - AsContext, + AsContext, AsContextMut, store::{StoreInstanceId, StoreOpaque}, }; use wasmtime_environ::DefinedTagIndex; +#[cfg(feature = "gc")] +use crate::store::InstanceId; + /// A WebAssembly `tag`. #[derive(Copy, Clone, Debug)] #[repr(C)] // here for the C API in the future @@ -18,6 +23,19 @@ impl Tag { Tag { instance, index } } + /// Create a new tag instance from a given TagType. + /// + /// # Panics + /// + /// This function will panic when used with a [`Store`](`crate::Store`) + /// which has a [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) + /// (see also: [`Store::limiter_async`](`crate::Store::limiter_async`). + /// When using an async resource limiter, use [`Tag::new_async`] + /// instead. + pub fn new(mut store: impl AsContextMut, ty: &TagType) -> Result { + generate_tag_export(store.as_context_mut().0, ty) + } + /// Returns the underlying type of this `tag`. /// /// # Panics @@ -65,4 +83,30 @@ impl Tag { // then compare to see if they have the same definition a.instance == b.instance && a.index == b.index } + + /// Get the "index coordinates" for this `Tag`: the raw instance + /// ID and defined-tag index within that instance. This can be + /// used to "serialize" the tag as safe (tamper-proof, + /// bounds-checked) values, e.g. within the GC store for an + /// exception object. + #[cfg(feature = "gc")] + pub(crate) fn to_raw_indices(&self) -> (InstanceId, DefinedTagIndex) { + (self.instance.instance(), self.index) + } + + /// Create a new `Tag` from known raw indices as produced by + /// `to_raw_indices()`. + /// + /// # Panics + /// + /// Panics if the indices are out-of-bounds in the given store. + #[cfg(feature = "gc")] + pub(crate) fn from_raw_indices( + store: &StoreOpaque, + instance: InstanceId, + index: DefinedTagIndex, + ) -> Tag { + let instance = StoreInstanceId::new(store.id(), instance); + Tag { instance, index } + } } diff --git a/crates/wasmtime/src/runtime/gc/disabled.rs b/crates/wasmtime/src/runtime/gc/disabled.rs index a962df22eba5..439a9c502661 100644 --- a/crates/wasmtime/src/runtime/gc/disabled.rs +++ b/crates/wasmtime/src/runtime/gc/disabled.rs @@ -11,6 +11,7 @@ mod anyref; mod arrayref; mod eqref; +mod exnref; mod externref; mod i31; mod rooting; @@ -19,6 +20,7 @@ mod structref; pub use anyref::*; pub use arrayref::*; pub use eqref::*; +pub use exnref::*; pub use externref::*; pub use i31::*; pub use rooting::*; diff --git a/crates/wasmtime/src/runtime/gc/disabled/exnref.rs b/crates/wasmtime/src/runtime/gc/disabled/exnref.rs new file mode 100644 index 000000000000..1107f460aeef --- /dev/null +++ b/crates/wasmtime/src/runtime/gc/disabled/exnref.rs @@ -0,0 +1,70 @@ +//! `exnref` implementation stubs when GC is disabled. + +use crate::{ + AsContext, AsContextMut, ExnType, GcRefImpl, HeapType, Result, Rooted, Tag, Val, + store::{AutoAssertNoGc, StoreContextMut, StoreOpaque}, +}; + +/// Support for `ExnRefPre` disabled at compile time because the `gc` +/// cargo feature was not enabled. +pub enum ExnRefPre {} + +/// Support for `exnref` disabled at compile time because the `gc` +/// cargo feature was not enabled. +pub enum ExnRef {} + +impl GcRefImpl for ExnRef {} + +impl ExnRef { + pub unsafe fn from_raw(_store: impl AsContextMut, _raw: u32) -> Option> { + None + } + + pub(crate) fn _from_raw(_store: &mut AutoAssertNoGc, _raw: u32) -> Option> { + None + } + + pub unsafe fn to_raw(&self, _store: impl AsContextMut) -> Result { + Ok(0) + } + + pub(crate) unsafe fn _to_raw(&self, _store: &mut AutoAssertNoGc<'_>) -> Result { + Ok(0) + } + + pub fn ty(&self, _store: impl AsContext) -> Result { + match *self {} + } + + pub(crate) fn _ty(&self, _store: &StoreOpaque) -> Result { + match *self {} + } + + pub fn matches_ty(&self, _store: impl AsContext, _ty: &HeapType) -> Result { + match *self {} + } + + pub(crate) fn _matches_ty(&self, _store: &StoreOpaque, _ty: &HeapType) -> Result { + match *self {} + } + + pub fn tag(&self, _store: impl AsContextMut) -> Result { + match *self {} + } + + pub fn fields<'a, T: 'static>( + &self, + _store: impl Into>, + ) -> Result + 'a + '_> { + match *self {} + Ok([].into_iter()) + } + + pub fn field(&self, _store: impl AsContextMut, _index: usize) -> Result { + match *self {} + } + + pub fn set_field(&self, _store: impl AsContextMut, _index: usize, _value: Val) -> Result<()> { + match *self {} + } +} diff --git a/crates/wasmtime/src/runtime/gc/enabled.rs b/crates/wasmtime/src/runtime/gc/enabled.rs index 8694a0387dbf..5d74162d536b 100644 --- a/crates/wasmtime/src/runtime/gc/enabled.rs +++ b/crates/wasmtime/src/runtime/gc/enabled.rs @@ -4,6 +4,7 @@ mod anyref; mod arrayref; mod eqref; +mod exnref; mod externref; mod i31; mod rooting; @@ -12,6 +13,7 @@ mod structref; pub use anyref::*; pub use arrayref::*; pub use eqref::*; +pub use exnref::*; pub use externref::*; pub use i31::*; pub use rooting::*; diff --git a/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs b/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs index ed8cb102ca44..efc9aa21dc2d 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/arrayref.rs @@ -741,6 +741,7 @@ impl ArrayRef { match layout { GcLayout::Array(a) => Ok(a), GcLayout::Struct(_) => unreachable!(), + GcLayout::Exception(_) => unreachable!(), } } @@ -894,6 +895,9 @@ unsafe impl WasmTy for Rooted { | HeapType::Cont | HeapType::NoCont | HeapType::ConcreteCont(_) + | HeapType::Exn + | HeapType::NoExn + | HeapType::ConcreteExn(_) | HeapType::None => bail!( "type mismatch: expected `(ref {ty})`, got `(ref {})`", self._ty(store)?, @@ -991,6 +995,9 @@ unsafe impl WasmTy for ManuallyRooted { | HeapType::Cont | HeapType::NoCont | HeapType::ConcreteCont(_) + | HeapType::Exn + | HeapType::NoExn + | HeapType::ConcreteExn(_) | HeapType::None => bail!( "type mismatch: expected `(ref {ty})`, got `(ref {})`", self._ty(store)?, diff --git a/crates/wasmtime/src/runtime/gc/enabled/exnref.rs b/crates/wasmtime/src/runtime/gc/enabled/exnref.rs new file mode 100644 index 000000000000..3a146c447942 --- /dev/null +++ b/crates/wasmtime/src/runtime/gc/enabled/exnref.rs @@ -0,0 +1,786 @@ +//! Implementation of `exnref` in Wasmtime. + +use crate::runtime::vm::VMGcRef; +use crate::store::StoreId; +use crate::vm::{VMExnRef, VMGcHeader}; +use crate::{ + AsContext, AsContextMut, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, RefType, Result, + Rooted, Val, ValRaw, ValType, WasmTy, + store::{AutoAssertNoGc, StoreOpaque}, +}; +use crate::{ExnType, FieldType, GcHeapOutOfMemory, StoreContextMut, Tag, prelude::*}; +use core::mem; +use core::mem::MaybeUninit; +use wasmtime_environ::{GcExceptionLayout, GcLayout, VMGcKind, VMSharedTypeIndex}; + +/// An allocator for a particular Wasm GC exception type. +/// +/// Every `ExnRefPre` is associated with a particular +/// [`Store`][crate::Store] and a particular +/// [ExnType][crate::ExnType]. +/// +/// Reusing an allocator across many allocations amortizes some +/// per-type runtime overheads inside Wasmtime. An `ExnRefPre` is to +/// `ExnRef`s as an `InstancePre` is to `Instance`s. +/// +/// # Example +/// +/// ``` +/// use wasmtime::*; +/// +/// # fn foo() -> Result<()> { +/// let mut config = Config::new(); +/// config.wasm_function_references(true); +/// config.wasm_gc(true); +/// +/// let engine = Engine::new(&config)?; +/// let mut store = Store::new(&engine, ()); +/// +/// // Define a exn type. +/// let exn_ty = ExnType::new( +/// store.engine(), +/// [ValType::I32], +/// )?; +/// +/// // Create an allocator for the exn type. +/// let allocator = ExnRefPre::new(&mut store, exn_ty.clone()); +/// +/// // Create a tag instance to associate with our exception objects. +/// let tag = Tag::new(&mut store, &exn_ty.tag_type()).unwrap(); +/// +/// { +/// let mut scope = RootScope::new(&mut store); +/// +/// // Allocate a bunch of instances of our exception type using the same +/// // allocator! This is faster than creating a new allocator for each +/// // instance we want to allocate. +/// for i in 0..10 { +/// ExnRef::new(&mut scope, &allocator, &tag, &[Val::I32(i)])?; +/// } +/// } +/// # Ok(()) +/// # } +/// # foo().unwrap(); +/// ``` +pub struct ExnRefPre { + store_id: StoreId, + ty: ExnType, +} + +impl ExnRefPre { + /// Create a new `ExnRefPre` that is associated with the given store + /// and type. + pub fn new(mut store: impl AsContextMut, ty: ExnType) -> Self { + Self::_new(store.as_context_mut().0, ty) + } + + pub(crate) fn _new(store: &mut StoreOpaque, ty: ExnType) -> Self { + store.insert_gc_host_alloc_type(ty.registered_type().clone()); + let store_id = store.id(); + + ExnRefPre { store_id, ty } + } + + pub(crate) fn layout(&self) -> &GcExceptionLayout { + self.ty + .registered_type() + .layout() + .expect("exn types have a layout") + .unwrap_exception() + } + + pub(crate) fn type_index(&self) -> VMSharedTypeIndex { + self.ty.registered_type().index() + } +} + +/// An `exnref` GC reference. +/// +/// The `ExnRef` type represents WebAssembly `exnref` values. These +/// are references to exception objects created either by catching a +/// thrown exception in WebAssembly with a `catch_ref` clause of a +/// `try_table`, or by allocating via the host API. +/// +/// Note that you can also use `Rooted` and `ManuallyRooted` as +/// a type parameter with [`Func::typed`][crate::Func::typed]- and +/// [`Func::wrap`][crate::Func::wrap]-style APIs. +#[derive(Debug)] +#[repr(transparent)] +pub struct ExnRef { + pub(super) inner: GcRootIndex, +} + +unsafe impl GcRefImpl for ExnRef { + fn transmute_ref(index: &GcRootIndex) -> &Self { + // Safety: `ExnRef` is a newtype of a `GcRootIndex`. + let me: &Self = unsafe { mem::transmute(index) }; + + // Assert we really are just a newtype of a `GcRootIndex`. + assert!(matches!( + me, + Self { + inner: GcRootIndex { .. }, + } + )); + + me + } +} + +impl ExnRef { + /// Creates a new strongly-owned [`ExnRef`] from the raw value provided. + /// + /// This is intended to be used in conjunction with [`Func::new_unchecked`], + /// [`Func::call_unchecked`], and [`ValRaw`] with its `anyref` field. + /// + /// This function assumes that `raw` is an `exnref` value which is currently + /// rooted within the [`Store`]. + /// + /// # Unsafety + /// + /// This function is particularly `unsafe` because `raw` not only must be a + /// valid `exnref` value produced prior by [`ExnRef::to_raw`] but it must + /// also be correctly rooted within the store. When arguments are provided + /// to a callback with [`Func::new_unchecked`], for example, or returned via + /// [`Func::call_unchecked`], if a GC is performed within the store then + /// floating `exnref` values are not rooted and will be GC'd, meaning that + /// this function will no longer be safe to call with the values cleaned up. + /// This function must be invoked *before* possible GC operations can happen + /// (such as calling Wasm). + /// + /// When in doubt try to not use this. Instead use the safe Rust APIs of + /// [`TypedFunc`] and friends. + /// + /// [`Func::call_unchecked`]: crate::Func::call_unchecked + /// [`Func::new_unchecked`]: crate::Func::new_unchecked + /// [`Store`]: crate::Store + /// [`TypedFunc`]: crate::TypedFunc + /// [`ValRaw`]: crate::ValRaw + pub unsafe fn from_raw(mut store: impl AsContextMut, raw: u32) -> Option> { + let mut store = AutoAssertNoGc::new(store.as_context_mut().0); + Self::_from_raw(&mut store, raw) + } + + // (Not actually memory unsafe since we have indexed GC heaps.) + pub(crate) fn _from_raw(store: &mut AutoAssertNoGc, raw: u32) -> Option> { + let gc_ref = VMGcRef::from_raw_u32(raw)?; + let gc_ref = store.unwrap_gc_store_mut().clone_gc_ref(&gc_ref); + Some(Self::from_cloned_gc_ref(store, gc_ref)) + } + + /// Synchronously allocate a new exception object and get a + /// reference to it. + /// + /// # Automatic Garbage Collection + /// + /// If the GC heap is at capacity, and there isn't room for + /// allocating this new exception object, then this method will + /// automatically trigger a synchronous collection in an attempt + /// to free up space in the GC heap. + /// + /// # Errors + /// + /// If the given `fields` values' types do not match the field + /// types of the `allocator`'s exception type, an error is + /// returned. + /// + /// If the allocation cannot be satisfied because the GC heap is currently + /// out of memory, then a [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory] + /// error is returned. The allocation might succeed on a second attempt if + /// you drop some rooted GC references and try again. + /// + /// # Panics + /// + /// Panics if your engine is configured for async; use + /// [`ExnRef::new_async`][crate::ExnRef::new_async] to perform + /// synchronous allocation instead. + /// + /// Panics if the allocator, or any of the field values, is not associated + /// with the given store. + pub fn new( + mut store: impl AsContextMut, + allocator: &ExnRefPre, + tag: &Tag, + fields: &[Val], + ) -> Result> { + Self::_new(store.as_context_mut().0, allocator, tag, fields) + } + + pub(crate) fn _new( + store: &mut StoreOpaque, + allocator: &ExnRefPre, + tag: &Tag, + fields: &[Val], + ) -> Result> { + assert!( + !store.async_support(), + "use `ExnRef::new_async` with asynchronous stores" + ); + Self::type_check_tag_and_fields(store, allocator, tag, fields)?; + store.retry_after_gc((), |store, ()| { + Self::new_unchecked(store, allocator, tag, fields) + }) + } + + /// Asynchronously allocate a new exception object and get a + /// reference to it. + /// + /// # Automatic Garbage Collection + /// + /// If the GC heap is at capacity, and there isn't room for allocating this + /// new exn, then this method will automatically trigger a synchronous + /// collection in an attempt to free up space in the GC heap. + /// + /// # Errors + /// + /// If the given `fields` values' types do not match the field + /// types of the `allocator`'s exception type, an error is + /// returned. + /// + /// If the allocation cannot be satisfied because the GC heap is currently + /// out of memory, then a [`GcHeapOutOfMemory<()>`][crate::GcHeapOutOfMemory] + /// error is returned. The allocation might succeed on a second attempt if + /// you drop some rooted GC references and try again. + /// + /// # Panics + /// + /// Panics if your engine is not configured for async; use + /// [`ExnRef::new`][crate::ExnRef::new] to perform synchronous + /// allocation instead. + /// + /// Panics if the allocator, or any of the field values, is not associated + /// with the given store. + #[cfg(feature = "async")] + pub async fn new_async( + mut store: impl AsContextMut, + allocator: &ExnRefPre, + tag: &Tag, + fields: &[Val], + ) -> Result> { + Self::_new_async(store.as_context_mut().0, allocator, tag, fields).await + } + + #[cfg(feature = "async")] + pub(crate) async fn _new_async( + store: &mut StoreOpaque, + allocator: &ExnRefPre, + tag: &Tag, + fields: &[Val], + ) -> Result> { + assert!( + store.async_support(), + "use `ExnRef::new` with synchronous stores" + ); + Self::type_check_tag_and_fields(store, allocator, tag, fields)?; + store + .retry_after_gc_async((), |store, ()| { + Self::new_unchecked(store, allocator, tag, fields) + }) + .await + } + + /// Type check the tag instance and field values before allocating + /// a new exception object. + fn type_check_tag_and_fields( + store: &mut StoreOpaque, + allocator: &ExnRefPre, + tag: &Tag, + fields: &[Val], + ) -> Result<(), Error> { + assert!( + tag.comes_from_same_store(store), + "tag comes from the wrong store" + ); + ensure!( + tag.wasmtime_ty(store).signature.unwrap_engine_type_index() + == allocator.ty.tag_type().ty().type_index(), + "incorrect signature for tag when creating exception object" + ); + let expected_len = allocator.ty.fields().len(); + let actual_len = fields.len(); + ensure!( + actual_len == expected_len, + "expected {expected_len} fields, got {actual_len}" + ); + for (ty, val) in allocator.ty.fields().zip(fields) { + assert!( + val.comes_from_same_store(store), + "field value comes from the wrong store", + ); + let ty = ty.element_type().unpack(); + val.ensure_matches_ty(store, ty) + .context("field type mismatch")?; + } + Ok(()) + } + + /// Given that the field values have already been type checked, allocate a + /// new exn. + /// + /// Does not attempt GC+retry on OOM, that is the caller's responsibility. + fn new_unchecked( + store: &mut StoreOpaque, + allocator: &ExnRefPre, + tag: &Tag, + fields: &[Val], + ) -> Result> { + assert_eq!( + store.id(), + allocator.store_id, + "attempted to use a `ExnRefPre` with the wrong store" + ); + + // Allocate the exn and write each field value into the appropriate + // offset. + let exnref = store + .gc_store_mut()? + .alloc_uninit_exn(allocator.type_index(), &allocator.layout()) + .context("unrecoverable error when allocating new `exnref`")? + .map_err(|n| GcHeapOutOfMemory::new((), n))?; + + // From this point on, if we get any errors, then the exn is not + // fully initialized, so we need to eagerly deallocate it before the + // next GC where the collector might try to interpret one of the + // uninitialized fields as a GC reference. + let mut store = AutoAssertNoGc::new(store); + match (|| { + let (instance, index) = tag.to_raw_indices(); + exnref.initialize_tag(&mut store, allocator.layout(), instance, index)?; + for (index, (ty, val)) in allocator.ty.fields().zip(fields).enumerate() { + exnref.initialize_field( + &mut store, + allocator.layout(), + ty.element_type(), + index, + *val, + )?; + } + Ok(()) + })() { + Ok(()) => Ok(Rooted::new(&mut store, exnref.into())), + Err(e) => { + store.gc_store_mut()?.dealloc_uninit_exn(exnref); + Err(e) + } + } + } + + pub(crate) fn type_index(&self, store: &StoreOpaque) -> Result { + let gc_ref = self.inner.try_gc_ref(store)?; + let header = store.gc_store()?.header(gc_ref); + debug_assert!(header.kind().matches(VMGcKind::ExnRef)); + Ok(header.ty().expect("exnrefs should have concrete types")) + } + + /// Create a new `Rooted` from the given GC reference. + /// + /// `gc_ref` should point to a valid `exnref` and should belong to + /// the store's GC heap. Failure to uphold these invariants is + /// memory safe but will lead to general incorrectness such as + /// panics or wrong results. + pub(crate) fn from_cloned_gc_ref( + store: &mut AutoAssertNoGc<'_>, + gc_ref: VMGcRef, + ) -> Rooted { + debug_assert!( + store + .unwrap_gc_store() + .header(&gc_ref) + .kind() + .matches(VMGcKind::ExnRef) + ); + Rooted::new(store, gc_ref) + } + + #[inline] + pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool { + self.inner.comes_from_same_store(store) + } + + /// Converts this [`ExnRef`] to a raw value suitable to store within a + /// [`ValRaw`]. + /// + /// Returns an error if this `exnref` has been unrooted. + /// + /// # Unsafety + /// + /// Produces a raw value which is only safe to pass into a store if a GC + /// doesn't happen between when the value is produce and when it's passed + /// into the store. + /// + /// [`ValRaw`]: crate::ValRaw + pub unsafe fn to_raw(&self, mut store: impl AsContextMut) -> Result { + let mut store = AutoAssertNoGc::new(store.as_context_mut().0); + self._to_raw(&mut store) + } + + pub(crate) unsafe fn _to_raw(&self, store: &mut AutoAssertNoGc<'_>) -> Result { + let gc_ref = self.inner.try_clone_gc_ref(store)?; + let raw = if gc_ref.is_i31() { + gc_ref.as_raw_non_zero_u32() + } else { + store.gc_store_mut()?.expose_gc_ref_to_wasm(gc_ref) + }; + Ok(raw.get()) + } + + /// Get the type of this reference. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn ty(&self, store: impl AsContext) -> Result { + self._ty(store.as_context().0) + } + + pub(crate) fn _ty(&self, store: &StoreOpaque) -> Result { + assert!(self.comes_from_same_store(store)); + let index = self.type_index(store)?; + Ok(ExnType::from_shared_type_index(store.engine(), index)) + } + + /// Does this `exnref` match the given type? + /// + /// That is, is this object's type a subtype of the given type? + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn matches_ty(&self, store: impl AsContext, ty: &HeapType) -> Result { + self._matches_ty(store.as_context().0, ty) + } + + pub(crate) fn _matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result { + assert!(self.comes_from_same_store(store)); + Ok(HeapType::from(self._ty(store)?).matches(ty)) + } + + pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, ty: &HeapType) -> Result<()> { + if !self.comes_from_same_store(store) { + bail!("function used with wrong store"); + } + if self._matches_ty(store, ty)? { + Ok(()) + } else { + let actual_ty = self._ty(store)?; + bail!("type mismatch: expected `(ref {ty})`, found `(ref {actual_ty})`") + } + } + + /// Get the values of this exception object's fields. + /// + /// # Errors + /// + /// Return an error if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn fields<'a, T: 'static>( + &'a self, + store: impl Into>, + ) -> Result + 'a> { + self._fields(store.into().0) + } + + pub(crate) fn _fields<'a>( + &'a self, + store: &'a mut StoreOpaque, + ) -> Result + 'a> { + assert!(self.comes_from_same_store(store)); + let store = AutoAssertNoGc::new(store); + + let gc_ref = self.inner.try_gc_ref(&store)?; + let header = store.gc_store()?.header(gc_ref); + debug_assert!(header.kind().matches(VMGcKind::ExnRef)); + + let index = header.ty().expect("exnrefs should have concrete types"); + let ty = ExnType::from_shared_type_index(store.engine(), index); + let len = ty.fields().len(); + + return Ok(Fields { + exnref: self, + store, + index: 0, + len, + }); + + struct Fields<'a, 'b> { + exnref: &'a ExnRef, + store: AutoAssertNoGc<'b>, + index: usize, + len: usize, + } + + impl Iterator for Fields<'_, '_> { + type Item = Val; + + #[inline] + fn next(&mut self) -> Option { + let i = self.index; + debug_assert!(i <= self.len); + if i >= self.len { + return None; + } + self.index += 1; + Some(self.exnref._field(&mut self.store, i).unwrap()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.len - self.index; + (len, Some(len)) + } + } + + impl ExactSizeIterator for Fields<'_, '_> { + #[inline] + fn len(&self) -> usize { + self.len - self.index + } + } + } + + fn header<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMGcHeader> { + assert!(self.comes_from_same_store(&store)); + let gc_ref = self.inner.try_gc_ref(store)?; + Ok(store.gc_store()?.header(gc_ref)) + } + + fn exnref<'a>(&self, store: &'a AutoAssertNoGc<'_>) -> Result<&'a VMExnRef> { + assert!(self.comes_from_same_store(&store)); + let gc_ref = self.inner.try_gc_ref(store)?; + debug_assert!(self.header(store)?.kind().matches(VMGcKind::ExnRef)); + Ok(gc_ref.as_exnref_unchecked()) + } + + fn layout(&self, store: &AutoAssertNoGc<'_>) -> Result { + assert!(self.comes_from_same_store(&store)); + let type_index = self.type_index(store)?; + let layout = store + .engine() + .signatures() + .layout(type_index) + .expect("exn types should have GC layouts"); + match layout { + GcLayout::Struct(_) => unreachable!(), + GcLayout::Array(_) => unreachable!(), + GcLayout::Exception(e) => Ok(e), + } + } + + fn field_ty(&self, store: &StoreOpaque, field: usize) -> Result { + let ty = self._ty(store)?; + match ty.field(field) { + Some(f) => Ok(f), + None => { + let len = ty.fields().len(); + bail!("cannot access field {field}: exn only has {len} fields") + } + } + } + + /// Get this exception object's `index`th field. + /// + /// # Errors + /// + /// Returns an `Err(_)` if the index is out of bounds or this reference has + /// been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn field(&self, mut store: impl AsContextMut, index: usize) -> Result { + let mut store = AutoAssertNoGc::new(store.as_context_mut().0); + self._field(&mut store, index) + } + + pub(crate) fn _field(&self, store: &mut AutoAssertNoGc<'_>, index: usize) -> Result { + assert!(self.comes_from_same_store(store)); + let exnref = self.exnref(store)?.unchecked_copy(); + let field_ty = self.field_ty(store, index)?; + let layout = self.layout(store)?; + Ok(exnref.read_field(store, &layout, field_ty.element_type(), index)) + } + + /// Get this exception object's associated tag. + /// + /// # Errors + /// + /// Returns an `Err(_)` if this reference has been unrooted. + /// + /// # Panics + /// + /// Panics if this reference is associated with a different store. + pub fn tag(&self, mut store: impl AsContextMut) -> Result { + let mut store = AutoAssertNoGc::new(store.as_context_mut().0); + assert!(self.comes_from_same_store(&store)); + let exnref = self.exnref(&store)?.unchecked_copy(); + let layout = self.layout(&store)?; + let (instance, index) = exnref.tag(&mut store, &layout)?; + Ok(Tag::from_raw_indices(&*store, instance, index)) + } +} + +unsafe impl WasmTy for Rooted { + #[inline] + fn valtype() -> ValType { + ValType::Ref(RefType::new(false, HeapType::Exn)) + } + + #[inline] + fn compatible_with_store(&self, store: &StoreOpaque) -> bool { + self.comes_from_same_store(store) + } + + #[inline] + fn dynamic_concrete_type_check( + &self, + _store: &StoreOpaque, + _nullable: bool, + _ty: &HeapType, + ) -> Result<()> { + // Wasm can't specify a concrete exn type, so there are no + // dynamic checks. + Ok(()) + } + + fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + self.wasm_ty_store(store, ptr, ValRaw::anyref) + } + + unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref) + } +} + +unsafe impl WasmTy for Option> { + #[inline] + fn valtype() -> ValType { + ValType::EXNREF + } + + #[inline] + fn compatible_with_store(&self, store: &StoreOpaque) -> bool { + self.map_or(true, |x| x.comes_from_same_store(store)) + } + + #[inline] + fn dynamic_concrete_type_check( + &self, + store: &StoreOpaque, + nullable: bool, + ty: &HeapType, + ) -> Result<()> { + match self { + Some(a) => a.ensure_matches_ty(store, ty), + None => { + ensure!( + nullable, + "expected a non-null reference, but found a null reference" + ); + Ok(()) + } + } + } + + #[inline] + fn is_vmgcref_and_points_to_object(&self) -> bool { + self.is_some() + } + + fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + >::wasm_ty_option_store(self, store, ptr, ValRaw::anyref) + } + + unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + >::wasm_ty_option_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref) + } +} + +unsafe impl WasmTy for ManuallyRooted { + #[inline] + fn valtype() -> ValType { + ValType::Ref(RefType::new(false, HeapType::Exn)) + } + + #[inline] + fn compatible_with_store(&self, store: &StoreOpaque) -> bool { + self.comes_from_same_store(store) + } + + #[inline] + fn dynamic_concrete_type_check( + &self, + store: &StoreOpaque, + _nullable: bool, + ty: &HeapType, + ) -> Result<()> { + self.ensure_matches_ty(store, ty) + } + + fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + self.wasm_ty_store(store, ptr, ValRaw::anyref) + } + + unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + Self::wasm_ty_load(store, ptr.get_anyref(), ExnRef::from_cloned_gc_ref) + } +} + +unsafe impl WasmTy for Option> { + #[inline] + fn valtype() -> ValType { + ValType::EXNREF + } + + #[inline] + fn compatible_with_store(&self, store: &StoreOpaque) -> bool { + self.as_ref() + .map_or(true, |x| x.comes_from_same_store(store)) + } + + #[inline] + fn dynamic_concrete_type_check( + &self, + store: &StoreOpaque, + nullable: bool, + ty: &HeapType, + ) -> Result<()> { + match self { + Some(a) => a.ensure_matches_ty(store, ty), + None => { + ensure!( + nullable, + "expected a non-null reference, but found a null reference" + ); + Ok(()) + } + } + } + + #[inline] + fn is_vmgcref_and_points_to_object(&self) -> bool { + self.is_some() + } + + fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit) -> Result<()> { + >::wasm_ty_option_store(self, store, ptr, ValRaw::anyref) + } + + unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self { + >::wasm_ty_option_load( + store, + ptr.get_anyref(), + ExnRef::from_cloned_gc_ref, + ) + } +} diff --git a/crates/wasmtime/src/runtime/gc/enabled/structref.rs b/crates/wasmtime/src/runtime/gc/enabled/structref.rs index 10115b7e2143..8ae1f0ddb710 100644 --- a/crates/wasmtime/src/runtime/gc/enabled/structref.rs +++ b/crates/wasmtime/src/runtime/gc/enabled/structref.rs @@ -547,6 +547,7 @@ impl StructRef { match layout { GcLayout::Struct(s) => Ok(s), GcLayout::Array(_) => unreachable!(), + GcLayout::Exception(_) => unreachable!(), } } @@ -689,7 +690,10 @@ unsafe impl WasmTy for Rooted { | HeapType::None | HeapType::NoCont | HeapType::Cont - | HeapType::ConcreteCont(_) => bail!( + | HeapType::ConcreteCont(_) + | HeapType::NoExn + | HeapType::Exn + | HeapType::ConcreteExn(_) => bail!( "type mismatch: expected `(ref {ty})`, got `(ref {})`", self._ty(store)?, ), @@ -786,7 +790,10 @@ unsafe impl WasmTy for ManuallyRooted { | HeapType::None | HeapType::NoCont | HeapType::Cont - | HeapType::ConcreteCont(_) => bail!( + | HeapType::ConcreteCont(_) + | HeapType::NoExn + | HeapType::Exn + | HeapType::ConcreteExn(_) => bail!( "type mismatch: expected `(ref {ty})`, got `(ref {})`", self._ty(store)?, ), diff --git a/crates/wasmtime/src/runtime/instance.rs b/crates/wasmtime/src/runtime/instance.rs index c2f9bdac9bbd..bda64698aa06 100644 --- a/crates/wasmtime/src/runtime/instance.rs +++ b/crates/wasmtime/src/runtime/instance.rs @@ -543,7 +543,10 @@ impl Instance { self.get_export(store, name)?.into_tag() } - #[cfg(feature = "component-model")] + #[allow( + dead_code, + reason = "c-api crate does not yet support exnrefs and causes this method to be dead." + )] pub(crate) fn id(&self) -> InstanceId { self.id.instance() } diff --git a/crates/wasmtime/src/runtime/trampoline.rs b/crates/wasmtime/src/runtime/trampoline.rs index c6e818ceb150..c3b7e50f9d8a 100644 --- a/crates/wasmtime/src/runtime/trampoline.rs +++ b/crates/wasmtime/src/runtime/trampoline.rs @@ -4,6 +4,7 @@ mod func; mod global; mod memory; mod table; +mod tag; pub use self::func::*; pub use self::global::*; @@ -11,11 +12,12 @@ pub(crate) use memory::MemoryCreatorProxy; use self::memory::create_memory; use self::table::create_table; +use self::tag::create_tag; use crate::prelude::*; use crate::runtime::vm::SharedMemory; use crate::store::StoreOpaque; -use crate::{MemoryType, TableType}; -use wasmtime_environ::{MemoryIndex, TableIndex}; +use crate::{MemoryType, TableType, TagType}; +use wasmtime_environ::{MemoryIndex, TableIndex, TagIndex}; pub fn generate_memory_export( store: &mut StoreOpaque, @@ -36,3 +38,11 @@ pub fn generate_table_export(store: &mut StoreOpaque, t: &TableType) -> Result Result { + let id = store.id(); + let instance = create_tag(store, t)?; + Ok(store + .instance_mut(instance) + .get_exported_tag(id, TagIndex::from_u32(0))) +} diff --git a/crates/wasmtime/src/runtime/trampoline/global.rs b/crates/wasmtime/src/runtime/trampoline/global.rs index 979182ab34ad..3907f4c7eba7 100644 --- a/crates/wasmtime/src/runtime/trampoline/global.rs +++ b/crates/wasmtime/src/runtime/trampoline/global.rs @@ -62,6 +62,14 @@ pub fn generate_global_export( let new = new.as_ref(); global.write_gc_ref(store.gc_store_mut()?, new); } + Val::ExnRef(e) => { + let new = match e { + None => None, + Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()), + }; + let new = new.as_ref(); + global.write_gc_ref(store.gc_store_mut()?, new); + } } } diff --git a/crates/wasmtime/src/runtime/trampoline/tag.rs b/crates/wasmtime/src/runtime/trampoline/tag.rs new file mode 100644 index 000000000000..861ab7516e75 --- /dev/null +++ b/crates/wasmtime/src/runtime/trampoline/tag.rs @@ -0,0 +1,41 @@ +use crate::TagType; +use crate::prelude::*; +use crate::runtime::vm::{Imports, ModuleRuntimeInfo, OnDemandInstanceAllocator}; +use crate::store::{AllocateInstanceKind, InstanceId, StoreOpaque}; +use alloc::sync::Arc; +use wasmtime_environ::EngineOrModuleTypeIndex; +use wasmtime_environ::Tag; +use wasmtime_environ::{EntityIndex, Module, TypeTrace}; + +pub fn create_tag(store: &mut StoreOpaque, ty: &TagType) -> Result { + let mut module = Module::new(); + let func_ty = ty.ty().clone().into_registered_type(); + + debug_assert!( + func_ty.is_canonicalized_for_runtime_usage(), + "should be canonicalized for runtime usage: {func_ty:?}", + ); + + let tag_id = module.tags.push(Tag { + signature: EngineOrModuleTypeIndex::Engine(func_ty.index()), + }); + + module + .exports + .insert(String::new(), EntityIndex::Tag(tag_id)); + + let imports = Imports::default(); + + unsafe { + let allocator = + OnDemandInstanceAllocator::new(store.engine().config().mem_creator.clone(), 0, false); + let module = Arc::new(module); + store.allocate_instance( + AllocateInstanceKind::Dummy { + allocator: &allocator, + }, + &ModuleRuntimeInfo::bare_with_registered_type(module, Some(func_ty)), + imports, + ) + } +} diff --git a/crates/wasmtime/src/runtime/type_registry.rs b/crates/wasmtime/src/runtime/type_registry.rs index 03bd6ff02c06..91af6af31073 100644 --- a/crates/wasmtime/src/runtime/type_registry.rs +++ b/crates/wasmtime/src/runtime/type_registry.rs @@ -929,11 +929,18 @@ impl TypeRegistryInner { ), wasmtime_environ::WasmCompositeInnerType::Struct(s) => Some( gc_runtime - .expect("must have a GC runtime to register array types") + .expect("must have a GC runtime to register struct types") .layouts() .struct_layout(s) .into(), ), + wasmtime_environ::WasmCompositeInnerType::Exn(e) => Some( + gc_runtime + .expect("must have a GC runtime to register exception types") + .layouts() + .exn_layout(e) + .into(), + ), wasmtime_environ::WasmCompositeInnerType::Cont(_) => None, // FIXME: #10248 stack switching support. }; diff --git a/crates/wasmtime/src/runtime/types.rs b/crates/wasmtime/src/runtime/types.rs index 7e626dd9c6eb..5d512d0cb2e3 100644 --- a/crates/wasmtime/src/runtime/types.rs +++ b/crates/wasmtime/src/runtime/types.rs @@ -5,6 +5,7 @@ use crate::runtime::externals::Table as RuntimeTable; use crate::{AsContextMut, Extern, Func, Val}; use crate::{Engine, type_registry::RegisteredType}; use core::fmt::{self, Display, Write}; +use wasmtime_environ::WasmExnType; use wasmtime_environ::{ EngineOrModuleTypeIndex, EntityType, Global, IndexType, Limits, Memory, ModuleTypes, Table, Tag, TypeTrace, VMSharedTypeIndex, WasmArrayType, WasmCompositeInnerType, WasmCompositeType, @@ -164,6 +165,12 @@ impl ValType { /// The `nullcontref` type, aka. `(ref null nocont)`. pub const NULLCONTREF: Self = ValType::Ref(RefType::NULLCONTREF); + /// The `exnref` type, aka `(ref null exn)`. + pub const EXNREF: Self = ValType::Ref(RefType::EXNREF); + + /// The `nullexnref` type, aka `(ref null noexn)`. + pub const NULLEXNREF: Self = ValType::Ref(RefType::NULLEXNREF); + /// Returns true if `ValType` matches any of the numeric types. (e.g. `I32`, /// `I64`, `F32`, `F64`). #[inline] @@ -495,6 +502,18 @@ impl RefType { heap_type: HeapType::NoCont, }; + /// The `exnref` type, aka `(ref null exn)`. + pub const EXNREF: Self = RefType { + is_nullable: true, + heap_type: HeapType::Exn, + }; + + /// The `nullexnref` type, aka `(ref null noexn)`. + pub const NULLEXNREF: Self = RefType { + is_nullable: true, + heap_type: HeapType::NoExn, + }; + /// Construct a new reference type. pub fn new(is_nullable: bool, heap_type: HeapType) -> RefType { RefType { @@ -587,7 +606,8 @@ impl RefType { /// /// 1. Function types /// 2. External types -/// 3. Internal types +/// 3. Internal (struct and array) types +/// 4. Exception types /// /// Each hierarchy has a top type (the common supertype of which everything else /// in its hierarchy is a subtype of) and a bottom type (the common subtype of @@ -673,6 +693,21 @@ impl RefType { /// definitions. Once again, this is omitted from the above diagram for /// simplicity. /// +/// ## Exceptions +/// +/// The top of the exception types hierarchy is `exn`; the bottom is +/// `noexn`. At the WebAssembly level, there are no concrete types in +/// this hierachy. However, internally we do reify a heap type for +/// each tag, similar to how continuation objects work. +/// +/// ```text +/// exn +/// / | \ +/// (exn $t) ... +/// \ | / +/// noexn +/// ``` +/// /// # Subtyping and Equality /// /// `HeapType` does not implement `Eq`, because heap types have a subtyping @@ -759,6 +794,20 @@ pub enum HeapType { /// of `any` and `eq`) and supertypes of the `none` heap type. ConcreteStruct(StructType), + /// The abstract `exn` heap type represents a reference to any + /// kind of exception. + /// + /// This is a supertype of the internal concrete exception heap + /// types and the `noexn` heap type. + Exn, + + /// A concrete exception object with a specific tag. + /// + /// These are internal, not exposed at the Wasm level, but useful + /// in our implementation and host API. These are subtypes of + /// `exn` and supertypes of `noexn`. + ConcreteExn(ExnType), + /// A reference to a continuation of a specific, concrete type. /// /// These are subtypes of `cont` and supertypes of `nocont`. @@ -781,6 +830,11 @@ pub enum HeapType { /// This is the bottom type for the internal type hierarchy, and therefore /// `none` is a subtype of internal types. None, + + /// The `noexn` heap type represents the null exception object. + /// + /// This is the bottom type for the exception objects type hierarchy. + NoExn, } impl Display for HeapType { @@ -800,8 +854,11 @@ impl Display for HeapType { HeapType::ConcreteArray(ty) => write!(f, "(concrete array {:?})", ty.type_index()), HeapType::ConcreteStruct(ty) => write!(f, "(concrete struct {:?})", ty.type_index()), HeapType::ConcreteCont(ty) => write!(f, "(concrete cont {:?})", ty.type_index()), + HeapType::ConcreteExn(ty) => write!(f, "(concrete exn {:?})", ty.type_index()), HeapType::Cont => write!(f, "cont"), HeapType::NoCont => write!(f, "nocont"), + HeapType::Exn => write!(f, "exn"), + HeapType::NoExn => write!(f, "noexn"), } } } @@ -834,6 +891,13 @@ impl From for HeapType { } } +impl From for HeapType { + #[inline] + fn from(e: ExnType) -> Self { + HeapType::ConcreteExn(e) + } +} + impl HeapType { /// Is this the abstract `extern` heap type? pub fn is_extern(&self) -> bool { @@ -870,6 +934,16 @@ impl HeapType { matches!(self, HeapType::Cont) } + /// Is this the abstract `exn` heap type? + pub fn is_exn(&self) -> bool { + matches!(self, HeapType::Exn) + } + + /// Is this the abstract `noexn` heap type? + pub fn is_no_exn(&self) -> bool { + matches!(self, HeapType::NoExn) + } + /// Is this an abstract type? /// /// Types that are not abstract are concrete, user-defined types. @@ -888,6 +962,7 @@ impl HeapType { | HeapType::ConcreteArray(_) | HeapType::ConcreteStruct(_) | HeapType::ConcreteCont(_) + | HeapType::ConcreteExn(_) ) } @@ -975,6 +1050,21 @@ impl HeapType { self.as_concrete_struct().unwrap() } + /// Is this a concrete, user-defined exception type? + pub fn is_concrete_exn(&self) -> bool { + matches!(self, HeapType::ConcreteExn(_)) + } + + /// Get the underlying concrete, user-defined exception type, if any. + /// + /// Returns `None` if this is not a concrete exception type. + pub fn as_concrete_exn(&self) -> Option<&ExnType> { + match self { + HeapType::ConcreteExn(e) => Some(e), + _ => None, + } + } + /// Get the top type of this heap type's type hierarchy. /// /// The returned heap type is a supertype of all types in this heap type's @@ -996,6 +1086,8 @@ impl HeapType { | HeapType::None => HeapType::Any, HeapType::Cont | HeapType::ConcreteCont(_) | HeapType::NoCont => HeapType::Cont, + + HeapType::Exn | HeapType::ConcreteExn(_) | HeapType::NoExn => HeapType::Exn, } } @@ -1003,7 +1095,9 @@ impl HeapType { #[inline] pub fn is_top(&self) -> bool { match self { - HeapType::Any | HeapType::Extern | HeapType::Func | HeapType::Cont => true, + HeapType::Any | HeapType::Extern | HeapType::Func | HeapType::Cont | HeapType::Exn => { + true + } _ => false, } } @@ -1029,6 +1123,8 @@ impl HeapType { | HeapType::None => HeapType::None, HeapType::Cont | HeapType::ConcreteCont(_) | HeapType::NoCont => HeapType::NoCont, + + HeapType::Exn | HeapType::ConcreteExn(_) | HeapType::NoExn => HeapType::NoExn, } } @@ -1036,7 +1132,11 @@ impl HeapType { #[inline] pub fn is_bottom(&self) -> bool { match self { - HeapType::None | HeapType::NoExtern | HeapType::NoFunc | HeapType::NoCont => true, + HeapType::None + | HeapType::NoExtern + | HeapType::NoFunc + | HeapType::NoCont + | HeapType::NoExn => true, _ => false, } } @@ -1131,6 +1231,16 @@ impl HeapType { (HeapType::Any, HeapType::Any) => true, (HeapType::Any, _) => false, + + (HeapType::NoExn, HeapType::Exn | HeapType::ConcreteExn(_) | HeapType::NoExn) => true, + (HeapType::NoExn, _) => true, + + (HeapType::ConcreteExn(_), HeapType::Exn) => true, + (HeapType::ConcreteExn(a), HeapType::ConcreteExn(b)) => a.matches(b), + (HeapType::ConcreteExn(_), _) => false, + + (HeapType::Exn, HeapType::Exn) => true, + (HeapType::Exn, _) => false, } } @@ -1171,11 +1281,14 @@ impl HeapType { | HeapType::Struct | HeapType::Cont | HeapType::NoCont + | HeapType::Exn + | HeapType::NoExn | HeapType::None => true, HeapType::ConcreteFunc(ty) => ty.comes_from_same_engine(engine), HeapType::ConcreteArray(ty) => ty.comes_from_same_engine(engine), HeapType::ConcreteStruct(ty) => ty.comes_from_same_engine(engine), HeapType::ConcreteCont(ty) => ty.comes_from_same_engine(engine), + HeapType::ConcreteExn(ty) => ty.comes_from_same_engine(engine), } } @@ -1205,6 +1318,11 @@ impl HeapType { HeapType::ConcreteCont(c) => { WasmHeapType::ConcreteCont(EngineOrModuleTypeIndex::Engine(c.type_index())) } + HeapType::Exn => WasmHeapType::Exn, + HeapType::NoExn => WasmHeapType::NoExn, + HeapType::ConcreteExn(e) => { + WasmHeapType::ConcreteExn(EngineOrModuleTypeIndex::Engine(e.type_index())) + } } } @@ -1237,7 +1355,9 @@ impl HeapType { | WasmHeapType::ConcreteStruct(EngineOrModuleTypeIndex::Module(_)) | WasmHeapType::ConcreteStruct(EngineOrModuleTypeIndex::RecGroup(_)) | WasmHeapType::ConcreteCont(EngineOrModuleTypeIndex::Module(_)) - | WasmHeapType::ConcreteCont(EngineOrModuleTypeIndex::RecGroup(_)) => { + | WasmHeapType::ConcreteCont(EngineOrModuleTypeIndex::RecGroup(_)) + | WasmHeapType::ConcreteExn(EngineOrModuleTypeIndex::Module(_)) + | WasmHeapType::ConcreteExn(EngineOrModuleTypeIndex::RecGroup(_)) => { panic!("HeapType::from_wasm_type on non-canonicalized-for-runtime-usage heap type") } WasmHeapType::Cont => HeapType::Cont, @@ -1245,6 +1365,11 @@ impl HeapType { WasmHeapType::ConcreteCont(EngineOrModuleTypeIndex::Engine(idx)) => { HeapType::ConcreteCont(ContType::from_shared_type_index(engine, *idx)) } + WasmHeapType::Exn => HeapType::Exn, + WasmHeapType::NoExn => HeapType::NoExn, + WasmHeapType::ConcreteExn(EngineOrModuleTypeIndex::Engine(idx)) => { + HeapType::ConcreteExn(ExnType::from_shared_type_index(engine, *idx)) + } } } @@ -1254,6 +1379,7 @@ impl HeapType { HeapType::ConcreteFunc(f) => Some(&f.registered_type), HeapType::ConcreteArray(a) => Some(&a.registered_type), HeapType::ConcreteStruct(a) => Some(&a.registered_type), + HeapType::ConcreteExn(e) => Some(&e.registered_type), HeapType::Extern | HeapType::NoExtern @@ -1266,6 +1392,8 @@ impl HeapType { | HeapType::Struct | HeapType::Cont | HeapType::NoCont + | HeapType::Exn + | HeapType::NoExn | HeapType::None => None, } } @@ -1298,8 +1426,9 @@ impl HeapType { ConcreteArray(ty) => Some(ty.registered_type), ConcreteStruct(ty) => Some(ty.registered_type), ConcreteCont(ty) => Some(ty.registered_type), + ConcreteExn(ty) => Some(ty.registered_type), Extern | NoExtern | Func | NoFunc | Any | Eq | I31 | Array | Struct | Cont | NoCont - | None => Option::None, + | Exn | NoExn | None => Option::None, } } } @@ -2633,6 +2762,192 @@ impl ContType { } } +// Exception types + +/// A WebAssembly exception-object signature type. +/// +/// This type captures the *signature* of an exception object. Note +/// that the WebAssembly standard does not define concrete types in +/// the heap-type lattice between `exn` (any exception object -- the +/// top type) and `noexn` (the uninhabited bottom type). Wasmtime +/// defines concrete types based on the *signature* -- that is, the +/// function type that describes the signature of the exception +/// payload values -- rather than the tag. The tag is a per-instance +/// nominal entity (similar to a memory or a table) and is associated +/// only with particular exception *objects*. +#[derive(Debug, Clone, Hash)] +pub struct ExnType { + func_ty: FuncType, + registered_type: RegisteredType, +} + +impl fmt::Display for ExnType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "(exn {}", self.func_ty)?; + for field in self.fields() { + write!(f, " (field {field})")?; + } + write!(f, ")")?; + Ok(()) + } +} + +impl ExnType { + /// Create a new `ExnType`. + /// + /// This function creates a new exception object type with the + /// given signature, i.e., list of payload value types. This + /// signature implies a tag type, and when instantiated at + /// runtime, it must be associated with a tag of that type. + pub fn new(engine: &Engine, fields: impl IntoIterator) -> Result { + let fields = fields.into_iter().collect::>(); + + // First, construct/intern a FuncType: we need this to exist + // so we can hand out a TagType, and it also roots any nested registrations. + let func_ty = FuncType::new(engine, fields.clone(), []); + + Ok(Self::_new(engine, fields, func_ty)) + } + + /// Create a new `ExnType` from an existing `TagType`. + /// + /// This function creates a new exception object type with the + /// signature represented by the tag. The signature must have no + /// result values, i.e., must be of the form `(T1, T2, ...) -> + /// ()`. + pub fn from_tag_type(tag: &TagType) -> Result { + let func_ty = tag.ty(); + + // Check that the tag's signature type has no results. + ensure!( + func_ty.results().len() == 0, + "Cannot create an exception type from a tag type with results in the signature" + ); + + Ok(Self::_new( + tag.ty.engine(), + func_ty.params(), + func_ty.clone(), + )) + } + + fn _new( + engine: &Engine, + fields: impl IntoIterator, + func_ty: FuncType, + ) -> ExnType { + let fields = fields + .into_iter() + .map(|ty| { + assert!(ty.comes_from_same_engine(engine)); + WasmFieldType { + element_type: WasmStorageType::Val(ty.to_wasm_type()), + mutable: false, + } + }) + .collect(); + + let ty = RegisteredType::new( + engine, + WasmSubType { + is_final: true, + supertype: None, + composite_type: WasmCompositeType { + shared: false, + inner: WasmCompositeInnerType::Exn(WasmExnType { + func_ty: EngineOrModuleTypeIndex::Engine(func_ty.type_index()), + fields, + }), + }, + }, + ); + + Self { + func_ty, + registered_type: ty, + } + } + + /// Get the tag type that this exception type is associated with. + pub fn tag_type(&self) -> TagType { + TagType { + ty: self.func_ty.clone(), + } + } + + /// Get the `i`th field type. + /// + /// Returns `None` if `i` is out of bounds. + pub fn field(&self, i: usize) -> Option { + let engine = self.engine(); + self.as_wasm_exn_type() + .fields + .get(i) + .map(|ty| FieldType::from_wasm_field_type(engine, ty)) + } + + /// Returns the list of field types for this function. + #[inline] + pub fn fields(&self) -> impl ExactSizeIterator + '_ { + let engine = self.engine(); + self.as_wasm_exn_type() + .fields + .iter() + .map(|ty| FieldType::from_wasm_field_type(engine, ty)) + } + + /// Get the engine that this exception type is associated with. + pub fn engine(&self) -> &Engine { + self.registered_type.engine() + } + + pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool { + Engine::same(self.registered_type.engine(), engine) + } + + pub(crate) fn as_wasm_exn_type(&self) -> &WasmExnType { + self.registered_type().unwrap_exn() + } + + pub(crate) fn type_index(&self) -> VMSharedTypeIndex { + self.registered_type.index() + } + + /// Does this exception type match the other exception type? + /// + /// That is, is this exception type a subtype of the other exception type? + /// + /// # Panics + /// + /// Panics if either type is associated with a different engine from the + /// other. + pub fn matches(&self, other: &ExnType) -> bool { + assert!(self.comes_from_same_engine(other.engine())); + + // We have no concrete-exception-type subtyping; concrete + // exception types are only (mutually, trivially) subtypes if + // they are exactly equal. + self.type_index() == other.type_index() + } + + pub(crate) fn registered_type(&self) -> &RegisteredType { + &self.registered_type + } + + pub(crate) fn from_shared_type_index(engine: &Engine, index: VMSharedTypeIndex) -> ExnType { + let ty = RegisteredType::root(engine, index); + assert!(ty.is_exn()); + let func_ty = FuncType::from_shared_type_index( + engine, + ty.unwrap_exn().func_ty.unwrap_engine_type_index(), + ); + Self { + func_ty, + registered_type: ty, + } + } +} + // Global Types /// A WebAssembly global descriptor. @@ -2698,8 +3013,11 @@ impl GlobalType { /// A descriptor for a tag in a WebAssembly module. /// -/// This type describes an instance of a tag in a WebAssembly -/// module. Tags are local to an [`Instance`](crate::Instance). +/// Note that tags are local to an [`Instance`](crate::Instance), +/// i.e., are a runtime entity. However, a tag is associated with a +/// function type, and so has a kind of static type. This descriptor +/// is a thin wrapper around a `FuncType` representing the function +/// type of a tag. #[derive(Debug, Clone, Hash)] pub struct TagType { ty: FuncType, diff --git a/crates/wasmtime/src/runtime/types/matching.rs b/crates/wasmtime/src/runtime/types/matching.rs index 56ec057733f1..e9431c75dac9 100644 --- a/crates/wasmtime/src/runtime/types/matching.rs +++ b/crates/wasmtime/src/runtime/types/matching.rs @@ -190,7 +190,8 @@ fn match_heap( (H::ConcreteArray(actual), H::ConcreteArray(expected)) | (H::ConcreteFunc(actual), H::ConcreteFunc(expected)) | (H::ConcreteStruct(actual), H::ConcreteStruct(expected)) - | (H::ConcreteCont(actual), H::ConcreteCont(expected)) => { + | (H::ConcreteCont(actual), H::ConcreteCont(expected)) + | (H::ConcreteExn(actual), H::ConcreteExn(expected)) => { let actual = actual.unwrap_engine_type_index(); let expected = expected.unwrap_engine_type_index(); engine.signatures().is_subtype(actual, expected) @@ -260,6 +261,15 @@ fn match_heap( (_, H::NoCont) => false, (_, H::ConcreteCont(_)) => false, + (H::NoExn | H::ConcreteExn(_) | H::Exn, H::Exn) => true, + (_, H::Exn) => false, + + (H::NoExn, H::ConcreteExn(_)) => true, + (H::NoExn, H::NoExn) => true, + + (_, H::NoExn) => false, + (_, H::ConcreteExn(_)) => false, + (H::None, H::None) => true, (_, H::None) => false, }; diff --git a/crates/wasmtime/src/runtime/values.rs b/crates/wasmtime/src/runtime/values.rs index 7200ddfeacf3..f63bfd12f985 100644 --- a/crates/wasmtime/src/runtime/values.rs +++ b/crates/wasmtime/src/runtime/values.rs @@ -1,7 +1,7 @@ use crate::runtime::vm::TableElement; use crate::store::{AutoAssertNoGc, StoreOpaque}; use crate::{ - AnyRef, ArrayRef, AsContext, AsContextMut, ExternRef, Func, HeapType, RefType, Rooted, + AnyRef, ArrayRef, AsContext, AsContextMut, ExnRef, ExternRef, Func, HeapType, RefType, Rooted, RootedGcRefImpl, StructRef, V128, ValType, prelude::*, }; use core::ptr; @@ -47,6 +47,9 @@ pub enum Val { /// An internal reference. AnyRef(Option>), + + /// An exception reference. + ExnRef(Option>), } macro_rules! accessors { @@ -162,6 +165,8 @@ impl Val { )), Val::AnyRef(None) => ValType::NULLREF, Val::AnyRef(Some(a)) => ValType::Ref(RefType::new(false, a._ty(store)?)), + Val::ExnRef(None) => ValType::NULLEXNREF, + Val::ExnRef(Some(e)) => ValType::Ref(RefType::new(false, e._ty(store)?.into())), }) } @@ -191,6 +196,7 @@ impl Val { Ref::from(*e)._matches_ty(store, ref_ty)? } (Val::AnyRef(a), ValType::Ref(ref_ty)) => Ref::from(*a)._matches_ty(store, ref_ty)?, + (Val::ExnRef(e), ValType::Ref(ref_ty)) => Ref::from(*e)._matches_ty(store, ref_ty)?, (Val::I32(_), _) | (Val::I64(_), _) @@ -199,7 +205,8 @@ impl Val { | (Val::V128(_), _) | (Val::FuncRef(_), _) | (Val::ExternRef(_), _) - | (Val::AnyRef(_), _) => false, + | (Val::AnyRef(_), _) + | (Val::ExnRef(_), _) => false, }) } @@ -242,6 +249,10 @@ impl Val { None => 0, Some(e) => e.to_raw(store)?, })), + Val::ExnRef(e) => Ok(ValRaw::exnref(match e { + None => 0, + Some(e) => e.to_raw(store)?, + })), Val::FuncRef(f) => Ok(ValRaw::funcref(match f { Some(f) => f.to_raw(store), None => ptr::null_mut(), @@ -299,6 +310,11 @@ impl Val { AnyRef::_from_raw(store, raw.get_anyref()).into() } + HeapType::Exn | HeapType::ConcreteExn(_) => { + ExnRef::_from_raw(store, raw.get_exnref()).into() + } + HeapType::NoExn => Ref::Exn(None), + HeapType::None => Ref::Any(None), }; assert!( @@ -330,6 +346,7 @@ impl Val { Val::FuncRef(f) => Some(Ref::Func(f)), Val::ExternRef(e) => Some(Ref::Extern(e)), Val::AnyRef(a) => Some(Ref::Any(a)), + Val::ExnRef(e) => Some(Ref::Exn(e)), Val::I32(_) | Val::I64(_) | Val::F32(_) | Val::F64(_) | Val::V128(_) => None, } } @@ -396,6 +413,37 @@ impl Val { self.anyref().expect("expected anyref") } + /// Attempt to access the underlying `exnref` value of this `Val`. + /// + /// If this is not an `exnref`, then `None` is returned. + /// + /// If this is a null `exnref`, then `Some(None)` is returned. + /// + /// If this is a non-null `exnref`, then `Some(Some(..))` is returned. + #[inline] + pub fn exnref(&self) -> Option>> { + match self { + Val::ExnRef(None) => Some(None), + Val::ExnRef(Some(e)) => Some(Some(e)), + _ => None, + } + } + + /// Returns the underlying `exnref` value of this `Val`, panicking if it's the + /// wrong type. + /// + /// If this is a null `exnref`, then `None` is returned. + /// + /// If this is a non-null `exnref`, then `Some(..)` is returned. + /// + /// # Panics + /// + /// Panics if `self` is not a (nullable) `exnref`. + #[inline] + pub fn unwrap_exnref(&self) -> Option<&Rooted> { + self.exnref().expect("expected exnref") + } + /// Attempt to access the underlying `funcref` value of this `Val`. /// /// If this is not an `funcref`, then `None` is returned. @@ -439,6 +487,9 @@ impl Val { Val::AnyRef(Some(a)) => a.comes_from_same_store(store), Val::AnyRef(None) => true, + Val::ExnRef(Some(e)) => e.comes_from_same_store(store), + Val::ExnRef(None) => true, + // Integers, floats, and vectors have no association with any // particular store, so they're always considered as "yes I came // from that store", @@ -482,6 +533,7 @@ impl From for Val { Ref::Extern(e) => Val::ExternRef(e), Ref::Func(f) => Val::FuncRef(f), Ref::Any(a) => Val::AnyRef(a), + Ref::Exn(e) => Val::ExnRef(e), } } } @@ -542,6 +594,20 @@ impl From>> for Val { } } +impl From> for Val { + #[inline] + fn from(val: Rooted) -> Val { + Val::ExnRef(Some(val)) + } +} + +impl From>> for Val { + #[inline] + fn from(val: Option>) -> Val { + Val::ExnRef(val) + } +} + impl From for Val { #[inline] fn from(val: Func) -> Val { @@ -654,6 +720,14 @@ pub enum Ref { /// Unlike `externref`, Wasm guests can directly allocate `anyref`s, and /// does not need to rely on the host to do that. Any(Option>), + + /// An exception-object reference. + /// + /// The `ExnRef` type represents WebAssembly `exnref` + /// values. These are references to exception objects as caught by + /// `catch_ref` clauses on `try_table` instructions, or as + /// allocated via the host API. + Exn(Option>), } impl From for Ref { @@ -726,6 +800,20 @@ impl From>> for Ref { } } +impl From> for Ref { + #[inline] + fn from(e: Rooted) -> Ref { + Ref::Exn(Some(e)) + } +} + +impl From>> for Ref { + #[inline] + fn from(e: Option>) -> Ref { + Ref::Exn(e) + } +} + impl Ref { /// Create a null reference to the given heap type. #[inline] @@ -742,8 +830,10 @@ impl Ref { #[inline] pub fn is_null(&self) -> bool { match self { - Ref::Any(None) | Ref::Extern(None) | Ref::Func(None) => true, - Ref::Any(Some(_)) | Ref::Extern(Some(_)) | Ref::Func(Some(_)) => false, + Ref::Any(None) | Ref::Extern(None) | Ref::Func(None) | Ref::Exn(None) => true, + Ref::Any(Some(_)) | Ref::Extern(Some(_)) | Ref::Func(Some(_)) | Ref::Exn(Some(_)) => { + false + } } } @@ -820,6 +910,39 @@ impl Ref { self.as_any().expect("Ref::unwrap_any on non-any reference") } + /// Is this an `exn` reference? + #[inline] + pub fn is_exn(&self) -> bool { + matches!(self, Ref::Exn(_)) + } + + /// Get the underlying `exn` reference, if any. + /// + /// Returns `None` if this `Ref` is not an `exn` reference, eg it is a + /// `func` reference. + /// + /// Returns `Some(None)` if this `Ref` is a null `exn` reference. + /// + /// Returns `Some(Some(_))` if this `Ref` is a non-null `exn` reference. + #[inline] + pub fn as_exn(&self) -> Option>> { + match self { + Ref::Exn(e) => Some(e.as_ref()), + _ => None, + } + } + + /// Get the underlying `exn` reference, panicking if this is a different + /// kind of reference. + /// + /// Returns `None` if this `Ref` is a null `exn` reference. + /// + /// Returns `Some(_)` if this `Ref` is a non-null `exn` reference. + #[inline] + pub fn unwrap_exn(&self) -> Option<&Rooted> { + self.as_exn().expect("Ref::unwrap_exn on non-exn reference") + } + /// Is this a `func` reference? #[inline] pub fn is_func(&self) -> bool { @@ -883,6 +1006,9 @@ impl Ref { Ref::Any(None) => HeapType::None, Ref::Any(Some(a)) => a._ty(store)?, + + Ref::Exn(None) => HeapType::None, + Ref::Exn(Some(e)) => e._ty(store)?.into(), }, )) } @@ -938,6 +1064,13 @@ impl Ref { | HeapType::Eq, ) => true, (Ref::Any(_), _) => false, + + (Ref::Exn(_), HeapType::Exn) => true, + (Ref::Exn(None), HeapType::NoExn | HeapType::ConcreteExn(_)) => true, + (Ref::Exn(Some(e)), HeapType::ConcreteExn(_)) => { + e._matches_ty(store, &ty.heap_type())? + } + (Ref::Exn(_), _) => false, }) } @@ -964,6 +1097,8 @@ impl Ref { Ref::Extern(None) => true, Ref::Any(Some(a)) => a.comes_from_same_store(store), Ref::Any(None) => true, + Ref::Exn(Some(e)) => e.comes_from_same_store(store), + Ref::Exn(None) => true, } } @@ -1011,6 +1146,17 @@ impl Ref { } }, + (Ref::Exn(e), HeapType::Exn) => match e { + None => { + assert!(ty.is_nullable()); + Ok(TableElement::GcRef(None)) + } + Some(e) => { + let gc_ref = e.try_clone_gc_ref(&mut store)?; + Ok(TableElement::GcRef(Some(gc_ref))) + } + }, + _ => unreachable!("checked that the value matches the type above"), } } diff --git a/crates/wasmtime/src/runtime/vm/gc.rs b/crates/wasmtime/src/runtime/vm/gc.rs index 83e5db301214..1f9e7349ba3c 100644 --- a/crates/wasmtime/src/runtime/vm/gc.rs +++ b/crates/wasmtime/src/runtime/vm/gc.rs @@ -25,7 +25,9 @@ use crate::runtime::vm::{GcHeapAllocationIndex, VMMemoryDefinition}; use core::any::Any; use core::mem::MaybeUninit; use core::{alloc::Layout, num::NonZeroU32}; -use wasmtime_environ::{GcArrayLayout, GcStructLayout, VMGcKind, VMSharedTypeIndex}; +use wasmtime_environ::{ + GcArrayLayout, GcExceptionLayout, GcStructLayout, VMGcKind, VMSharedTypeIndex, +}; /// GC-related data that is one-to-one with a `wasmtime::Store`. /// @@ -268,4 +270,24 @@ impl GcStore { pub fn array_len(&self, arrayref: &VMArrayRef) -> u32 { self.gc_heap.array_len(arrayref) } + + /// Allocate an uninitialized exception object with the given type + /// index. + /// + /// This does NOT check that the index is currently allocated in the types + /// registry or that the layout matches the index's type. Failure to uphold + /// those invariants is memory safe, but will lead to general incorrectness + /// such as panics and wrong results. + pub fn alloc_uninit_exn( + &mut self, + ty: VMSharedTypeIndex, + layout: &GcExceptionLayout, + ) -> Result> { + self.gc_heap.alloc_uninit_exn(ty, layout) + } + + /// Deallocate an uninitialized exception object. + pub fn dealloc_uninit_exn(&mut self, exnref: VMExnRef) { + self.gc_heap.dealloc_uninit_exn(exnref); + } } diff --git a/crates/wasmtime/src/runtime/vm/gc/disabled.rs b/crates/wasmtime/src/runtime/vm/gc/disabled.rs index 28953ae31b15..59c3f1f60ee4 100644 --- a/crates/wasmtime/src/runtime/vm/gc/disabled.rs +++ b/crates/wasmtime/src/runtime/vm/gc/disabled.rs @@ -6,6 +6,8 @@ pub enum VMStructRef {} pub enum VMArrayRef {} +pub enum VMExnRef {} + pub struct VMGcObjectData { _inner: VMStructRef, _phantom: core::marker::PhantomData<[u8]>, diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled.rs b/crates/wasmtime/src/runtime/vm/gc/enabled.rs index 6d221d91772a..f4542ed25af1 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled.rs @@ -2,6 +2,7 @@ mod arrayref; mod data; +mod exnref; mod externref; #[cfg(feature = "gc-drc")] mod free_list; @@ -9,6 +10,7 @@ mod structref; pub use arrayref::*; pub use data::*; +pub use exnref::*; pub use externref::*; pub use structref::*; diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/arrayref.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/arrayref.rs index d6df315c3e82..ca29e6243629 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/arrayref.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/arrayref.rs @@ -1,6 +1,6 @@ use super::{truncate_i32_to_i8, truncate_i32_to_i16}; use crate::{ - AnyRef, ExternRef, Func, HeapType, RootedGcRefImpl, StorageType, Val, ValType, + AnyRef, ExnRef, ExternRef, Func, HeapType, RootedGcRefImpl, StorageType, Val, ValType, prelude::*, runtime::vm::{GcHeap, GcStore, VMGcRef}, store::{AutoAssertNoGc, StoreOpaque}, @@ -164,6 +164,10 @@ impl VMArrayRef { let raw = data.read_u32(offset); Val::AnyRef(AnyRef::_from_raw(store, raw)) } + HeapType::Exn => { + let raw = data.read_u32(offset); + Val::ExnRef(ExnRef::_from_raw(store, raw)) + } HeapType::Func => { let func_ref_id = data.read_u32(offset); let func_ref_id = FuncRefTableId::from_raw(func_ref_id); @@ -246,6 +250,17 @@ impl VMArrayRef { let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); } + Val::ExnRef(e) => { + let raw = data.read_u32(offset); + let mut gc_ref = VMGcRef::from_raw_u32(raw); + let e = match e { + Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()), + None => None, + }; + store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref()); + let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); + } Val::FuncRef(f) => { let func_ref = match f { @@ -349,6 +364,16 @@ impl VMArrayRef { .gc_object_data(self.as_gc_ref()) .write_u32(offset, x); } + Val::ExnRef(x) => { + let x = match x { + None => 0, + Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), + }; + store + .gc_store_mut()? + .gc_object_data(self.as_gc_ref()) + .write_u32(offset, x); + } Val::FuncRef(f) => { let func_ref = match f { diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs index 2fa094146d14..0da49ef9fb54 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/drc.rs @@ -45,7 +45,7 @@ //! use super::free_list::FreeList; -use super::{VMArrayRef, VMStructRef}; +use super::{VMArrayRef, VMExnRef, VMStructRef}; use crate::hash_map::HashMap; use crate::hash_set::HashSet; use crate::runtime::vm::{ @@ -64,7 +64,8 @@ use core::{ }; use wasmtime_environ::drc::{ARRAY_LENGTH_OFFSET, DrcTypeLayouts}; use wasmtime_environ::{ - GcArrayLayout, GcLayout, GcStructLayout, GcTypeLayouts, VMGcKind, VMSharedTypeIndex, + GcArrayLayout, GcExceptionLayout, GcLayout, GcStructLayout, GcTypeLayouts, VMGcKind, + VMSharedTypeIndex, }; #[expect(clippy::cast_possible_truncation, reason = "known to not overflow")] @@ -296,6 +297,13 @@ impl DrcHeap { .filter_map(|f| if f.is_gc_ref { Some(f.offset) } else { None }) .collect(), }, + GcLayout::Exception(e) => TraceInfo::Struct { + gc_ref_offsets: e + .fields + .iter() + .filter_map(|f| if f.is_gc_ref { Some(f.offset) } else { None }) + .collect(), + }, }; let old_entry = self.trace_infos.insert(ty, info); @@ -574,19 +582,19 @@ impl VMDrcHeader { /// Is this object in the over-approximated stack roots list? #[inline] fn is_in_over_approximated_stack_roots(&self) -> bool { - self.header.reserved_u27() & wasmtime_environ::drc::HEADER_IN_OVER_APPROX_LIST_BIT != 0 + self.header.reserved_u26() & wasmtime_environ::drc::HEADER_IN_OVER_APPROX_LIST_BIT != 0 } /// Set whether this object is in the over-approximated stack roots list. #[inline] fn set_in_over_approximated_stack_roots_bit(&mut self, bit: bool) { - let reserved = self.header.reserved_u27(); + let reserved = self.header.reserved_u26(); let new_reserved = if bit { reserved | wasmtime_environ::drc::HEADER_IN_OVER_APPROX_LIST_BIT } else { reserved & !wasmtime_environ::drc::HEADER_IN_OVER_APPROX_LIST_BIT }; - self.header.set_reserved_u27(new_reserved); + self.header.set_reserved_u26(new_reserved); } /// Get the next object after this one in the over-approximated-stack-roots @@ -610,7 +618,7 @@ impl VMDrcHeader { /// Is this object marked? #[inline] fn is_marked(&self) -> bool { - self.header.reserved_u27() & wasmtime_environ::drc::HEADER_MARK_BIT != 0 + self.header.reserved_u26() & wasmtime_environ::drc::HEADER_MARK_BIT != 0 } /// Mark this object. @@ -619,9 +627,9 @@ impl VMDrcHeader { /// have returned `false` before this call was made). #[inline] fn set_marked(&mut self) { - let reserved = self.header.reserved_u27(); + let reserved = self.header.reserved_u26(); self.header - .set_reserved_u27(reserved | wasmtime_environ::drc::HEADER_MARK_BIT); + .set_reserved_u26(reserved | wasmtime_environ::drc::HEADER_MARK_BIT); } /// Clear the mark bit for this object. @@ -631,9 +639,9 @@ impl VMDrcHeader { #[inline] fn clear_marked(&mut self) -> bool { if self.is_marked() { - let reserved = self.header.reserved_u27(); + let reserved = self.header.reserved_u26(); self.header - .set_reserved_u27(reserved & !wasmtime_environ::drc::HEADER_MARK_BIT); + .set_reserved_u26(reserved & !wasmtime_environ::drc::HEADER_MARK_BIT); debug_assert!(!self.is_marked()); true } else { @@ -809,7 +817,7 @@ unsafe impl GcHeap for DrcHeap { fn alloc_raw(&mut self, header: VMGcHeader, layout: Layout) -> Result> { debug_assert!(layout.size() >= core::mem::size_of::()); debug_assert!(layout.align() >= core::mem::align_of::()); - debug_assert_eq!(header.reserved_u27(), 0); + debug_assert_eq!(header.reserved_u26(), 0); // We must have trace info for every GC type that we allocate in this // heap. The only kinds of GC objects we allocate that do not have an @@ -888,6 +896,26 @@ unsafe impl GcHeap for DrcHeap { .length } + fn alloc_uninit_exn( + &mut self, + ty: VMSharedTypeIndex, + layout: &GcExceptionLayout, + ) -> Result> { + let gc_ref = match self.alloc_raw( + VMGcHeader::from_kind_and_index(VMGcKind::ExnRef, ty), + layout.layout(), + )? { + Err(n) => return Ok(Err(n)), + Ok(gc_ref) => gc_ref, + }; + + Ok(Ok(gc_ref.into_exnref_unchecked())) + } + + fn dealloc_uninit_exn(&mut self, exnref: VMExnRef) { + self.dealloc(exnref.into()); + } + fn gc<'a>( &'a mut self, roots: GcRootsIter<'a>, diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/exnref.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/exnref.rs new file mode 100644 index 000000000000..418cbf3621ab --- /dev/null +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/exnref.rs @@ -0,0 +1,211 @@ +use super::structref::{initialize_field_impl, read_field_impl}; +use crate::{ + StorageType, Val, + prelude::*, + runtime::vm::{GcHeap, GcStore, VMGcRef}, + store::{AutoAssertNoGc, InstanceId}, +}; +use core::fmt; +use wasmtime_environ::{DefinedTagIndex, GcExceptionLayout, VMGcKind}; + +/// A `VMGcRef` that we know points to an `exn`. +/// +/// Create a `VMExnRef` via `VMGcRef::into_exnref` and +/// `VMGcRef::as_exnref`, or their untyped equivalents +/// `VMGcRef::into_exnref_unchecked` and `VMGcRef::as_exnref_unchecked`. +/// +/// Note: This is not a `TypedGcRef<_>` because each collector can have a +/// different concrete representation of `exnref` that they allocate inside +/// their heaps. +#[derive(Debug, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct VMExnRef(VMGcRef); + +impl fmt::Pointer for VMExnRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&self.0, f) + } +} + +impl From for VMGcRef { + #[inline] + fn from(x: VMExnRef) -> Self { + x.0 + } +} + +impl VMGcRef { + /// Is this `VMGcRef` pointing to an `exn`? + pub fn is_exnref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool { + if self.is_i31() { + return false; + } + + let header = gc_heap.header(&self); + header.kind().matches(VMGcKind::ExnRef) + } + + /// Create a new `VMExnRef` from the given `gc_ref`. + /// + /// If this is not a GC reference to an `exnref`, `Err(self)` is + /// returned. + pub fn into_exnref(self, gc_heap: &impl GcHeap) -> Result { + if self.is_exnref(gc_heap) { + Ok(self.into_exnref_unchecked()) + } else { + Err(self) + } + } + + /// Create a new `VMExnRef` from `self` without actually checking that + /// `self` is an `exnref`. + /// + /// This method does not check that `self` is actually an `exnref`, but + /// it should be. Failure to uphold this invariant is memory safe but will + /// result in general incorrectness down the line such as panics or wrong + /// results. + #[inline] + pub fn into_exnref_unchecked(self) -> VMExnRef { + debug_assert!(!self.is_i31()); + VMExnRef(self) + } + + /// Get this GC reference as an `exnref` reference, if it actually is an + /// `exnref` reference. + pub fn as_exnref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> Option<&VMExnRef> { + if self.is_exnref(gc_heap) { + Some(self.as_exnref_unchecked()) + } else { + None + } + } + + /// Get this GC reference as an `exnref` reference without checking if it + /// actually is an `exnref` reference. + /// + /// Calling this method on a non-`exnref` reference is memory safe, but + /// will lead to general incorrectness like panics and wrong results. + pub fn as_exnref_unchecked(&self) -> &VMExnRef { + debug_assert!(!self.is_i31()); + let ptr = self as *const VMGcRef; + let ret = unsafe { &*ptr.cast() }; + assert!(matches!(ret, VMExnRef(VMGcRef { .. }))); + ret + } +} + +impl VMExnRef { + /// Get the underlying `VMGcRef`. + pub fn as_gc_ref(&self) -> &VMGcRef { + &self.0 + } + + /// Clone this `VMExnRef`, running any GC barriers as necessary. + pub fn clone(&self, gc_store: &mut GcStore) -> Self { + Self(gc_store.clone_gc_ref(&self.0)) + } + + /// Explicitly drop this `exnref`, running GC drop barriers as necessary. + pub fn drop(self, gc_store: &mut GcStore) { + gc_store.drop_gc_ref(self.0); + } + + /// Copy this `VMExnRef` without running the GC's clone barriers. + /// + /// Prefer calling `clone(&mut GcStore)` instead! This is mostly an internal + /// escape hatch for collector implementations. + /// + /// Failure to run GC barriers when they would otherwise be necessary can + /// lead to leaks, panics, and wrong results. It cannot lead to memory + /// unsafety, however. + pub fn unchecked_copy(&self) -> Self { + Self(self.0.unchecked_copy()) + } + + /// Read a field of the given `StorageType` into a `Val`. + /// + /// `i8` and `i16` fields are zero-extended into `Val::I32(_)`s. + /// + /// Does not check that the field is actually of type `ty`. That is the + /// caller's responsibility. Failure to do so is memory safe, but will lead + /// to general incorrectness such as panics and wrong results. + /// + /// Panics on out-of-bounds accesses. + pub fn read_field( + &self, + store: &mut AutoAssertNoGc, + layout: &GcExceptionLayout, + ty: &StorageType, + field: usize, + ) -> Val { + let offset = layout.fields[field].offset; + read_field_impl(self.as_gc_ref(), store, ty, offset) + } + + /// Initialize a field in this exnref that is currently uninitialized. + /// + /// Calling this method on an exnref that has already had the + /// associated field initialized will result in GC bugs. These are + /// memory safe but will lead to generally incorrect behavior such + /// as panics, leaks, and incorrect results. + /// + /// Does not check that `val` matches `ty`, nor that the field is actually + /// of type `ty`. Checking those things is the caller's responsibility. + /// Failure to do so is memory safe, but will lead to general incorrectness + /// such as panics and wrong results. + /// + /// Returns an error if `val` is a GC reference that has since been + /// unrooted. + /// + /// Panics on out-of-bounds accesses. + pub fn initialize_field( + &self, + store: &mut AutoAssertNoGc, + layout: &GcExceptionLayout, + ty: &StorageType, + field: usize, + val: Val, + ) -> Result<()> { + debug_assert!(val._matches_ty(&store, &ty.unpack())?); + let offset = layout.fields[field].offset; + initialize_field_impl(self.as_gc_ref(), store, ty, offset, val) + } + + /// Initialize the tag referenced by this exception object. + pub fn initialize_tag( + &self, + store: &mut AutoAssertNoGc, + layout: &GcExceptionLayout, + instance: InstanceId, + tag: DefinedTagIndex, + ) -> Result<()> { + store + .gc_store_mut()? + .gc_object_data(&self.0) + .write_u32(layout.tag_offset, instance.as_u32()); + store + .gc_store_mut()? + .gc_object_data(&self.0) + .write_u32(layout.tag_offset + 4, tag.as_u32()); + Ok(()) + } + + /// Get the tag referenced by this exception object. + pub fn tag( + &self, + store: &mut AutoAssertNoGc, + layout: &GcExceptionLayout, + ) -> Result<(InstanceId, DefinedTagIndex)> { + let instance = store + .gc_store_mut()? + .gc_object_data(&self.0) + .read_u32(layout.tag_offset); + let instance = InstanceId::from_u32(instance); + let tag = store + .gc_store_mut()? + .gc_object_data(&self.0) + .read_u32(layout.tag_offset + 4); + let tag = DefinedTagIndex::from_u32(tag); + Ok((instance, tag)) + } +} diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/null.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/null.rs index f93e9c4e4fe2..2506bd081c65 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/null.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/null.rs @@ -17,7 +17,7 @@ use crate::{ use core::ptr::NonNull; use core::{alloc::Layout, any::Any, num::NonZeroU32}; use wasmtime_environ::{ - GcArrayLayout, GcStructLayout, GcTypeLayouts, VMGcKind, VMSharedTypeIndex, + GcArrayLayout, GcExceptionLayout, GcStructLayout, GcTypeLayouts, VMGcKind, VMSharedTypeIndex, null::NullTypeLayouts, }; @@ -168,8 +168,8 @@ impl NullHeap { let aligned = NonZeroU32::new(aligned).unwrap(); let gc_ref = VMGcRef::from_heap_index(aligned).unwrap(); - debug_assert_eq!(header.reserved_u27(), 0); - header.set_reserved_u27(size); + debug_assert_eq!(header.reserved_u26(), 0); + header.set_reserved_u26(size); *self.header_mut(&gc_ref) = header; Ok(Ok(gc_ref)) @@ -270,7 +270,7 @@ unsafe impl GcHeap for NullHeap { } fn object_size(&self, gc_ref: &VMGcRef) -> usize { - let size = self.header(gc_ref).reserved_u27(); + let size = self.header(gc_ref).reserved_u26(); usize::try_from(size).unwrap() } @@ -326,6 +326,20 @@ unsafe impl GcHeap for NullHeap { self.index(arrayref).length } + fn alloc_uninit_exn( + &mut self, + ty: VMSharedTypeIndex, + layout: &GcExceptionLayout, + ) -> Result> { + self.alloc( + VMGcHeader::from_kind_and_index(VMGcKind::ExnRef, ty), + layout.layout(), + ) + .map(|r| r.map(|r| r.into_exnref_unchecked())) + } + + fn dealloc_uninit_exn(&mut self, _exnref: VMExnRef) {} + fn gc<'a>( &'a mut self, _roots: GcRootsIter<'a>, diff --git a/crates/wasmtime/src/runtime/vm/gc/enabled/structref.rs b/crates/wasmtime/src/runtime/vm/gc/enabled/structref.rs index 96bb867f50db..a2980c4e8b0d 100644 --- a/crates/wasmtime/src/runtime/vm/gc/enabled/structref.rs +++ b/crates/wasmtime/src/runtime/vm/gc/enabled/structref.rs @@ -1,6 +1,6 @@ use super::{truncate_i32_to_i8, truncate_i32_to_i16}; use crate::{ - AnyRef, ExternRef, Func, HeapType, RootedGcRefImpl, StorageType, Val, ValType, + AnyRef, ExnRef, ExternRef, Func, HeapType, RootedGcRefImpl, StorageType, Val, ValType, prelude::*, runtime::vm::{GcHeap, GcStore, VMGcRef}, store::AutoAssertNoGc, @@ -140,38 +140,7 @@ impl VMStructRef { field: usize, ) -> Val { let offset = layout.fields[field].offset; - let data = store.unwrap_gc_store_mut().gc_object_data(self.as_gc_ref()); - match ty { - StorageType::I8 => Val::I32(data.read_u8(offset).into()), - StorageType::I16 => Val::I32(data.read_u16(offset).into()), - StorageType::ValType(ValType::I32) => Val::I32(data.read_i32(offset)), - StorageType::ValType(ValType::I64) => Val::I64(data.read_i64(offset)), - StorageType::ValType(ValType::F32) => Val::F32(data.read_u32(offset)), - StorageType::ValType(ValType::F64) => Val::F64(data.read_u64(offset)), - StorageType::ValType(ValType::V128) => Val::V128(data.read_v128(offset)), - StorageType::ValType(ValType::Ref(r)) => match r.heap_type().top() { - HeapType::Extern => { - let raw = data.read_u32(offset); - Val::ExternRef(ExternRef::_from_raw(store, raw)) - } - HeapType::Any => { - let raw = data.read_u32(offset); - Val::AnyRef(AnyRef::_from_raw(store, raw)) - } - HeapType::Func => { - let func_ref_id = data.read_u32(offset); - let func_ref_id = FuncRefTableId::from_raw(func_ref_id); - let func_ref = store - .unwrap_gc_store() - .func_ref_table - .get_untyped(func_ref_id); - Val::FuncRef(unsafe { - func_ref.map(|p| Func::from_vm_func_ref(store.id(), p.as_non_null())) - }) - } - otherwise => unreachable!("not a top type: {otherwise:?}"), - }, - } + read_field_impl(self.as_gc_ref(), store, ty, offset) } /// Write the given value into this struct at the given offset. @@ -240,6 +209,17 @@ impl VMStructRef { let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); } + Val::ExnRef(e) => { + let raw = data.read_u32(offset); + let mut gc_ref = VMGcRef::from_raw_u32(raw); + let e = match e { + Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()), + None => None, + }; + store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref()); + let data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref()); + data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32())); + } Val::FuncRef(f) => { let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store))); @@ -287,69 +267,137 @@ impl VMStructRef { ) -> Result<()> { debug_assert!(val._matches_ty(&store, &ty.unpack())?); let offset = layout.fields[field].offset; - match val { - Val::I32(i) if ty.is_i8() => store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_i8(offset, truncate_i32_to_i8(i)), - Val::I32(i) if ty.is_i16() => store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_i16(offset, truncate_i32_to_i16(i)), - Val::I32(i) => store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_i32(offset, i), - Val::I64(i) => store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_i64(offset, i), - Val::F32(f) => store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_u32(offset, f), - Val::F64(f) => store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_u64(offset, f), - Val::V128(v) => store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_v128(offset, v), + initialize_field_impl(self.as_gc_ref(), store, ty, offset, val) + } +} - // NB: We don't need to do a write barrier when initializing a - // field, because there is nothing being overwritten. Therefore, we - // just the clone barrier. - Val::ExternRef(x) => { - let x = match x { - None => 0, - Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), - }; - store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_u32(offset, x); +/// Read a field from a GC object at a given offset. +/// +/// This factored-out function allows a shared implementation for both +/// structs (this module) and exception objects. +pub(crate) fn read_field_impl( + gc_ref: &VMGcRef, + store: &mut AutoAssertNoGc, + ty: &StorageType, + offset: u32, +) -> Val { + let data = store.unwrap_gc_store_mut().gc_object_data(gc_ref); + match ty { + StorageType::I8 => Val::I32(data.read_u8(offset).into()), + StorageType::I16 => Val::I32(data.read_u16(offset).into()), + StorageType::ValType(ValType::I32) => Val::I32(data.read_i32(offset)), + StorageType::ValType(ValType::I64) => Val::I64(data.read_i64(offset)), + StorageType::ValType(ValType::F32) => Val::F32(data.read_u32(offset)), + StorageType::ValType(ValType::F64) => Val::F64(data.read_u64(offset)), + StorageType::ValType(ValType::V128) => Val::V128(data.read_v128(offset)), + StorageType::ValType(ValType::Ref(r)) => match r.heap_type().top() { + HeapType::Extern => { + let raw = data.read_u32(offset); + Val::ExternRef(ExternRef::_from_raw(store, raw)) } - Val::AnyRef(x) => { - let x = match x { - None => 0, - Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), - }; - store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_u32(offset, x); + HeapType::Any => { + let raw = data.read_u32(offset); + Val::AnyRef(AnyRef::_from_raw(store, raw)) } - - Val::FuncRef(f) => { - let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store))); - let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(f) }; - store - .gc_store_mut()? - .gc_object_data(self.as_gc_ref()) - .write_u32(offset, id.into_raw()); + HeapType::Exn => { + let raw = data.read_u32(offset); + Val::ExnRef(ExnRef::_from_raw(store, raw)) + } + HeapType::Func => { + let func_ref_id = data.read_u32(offset); + let func_ref_id = FuncRefTableId::from_raw(func_ref_id); + let func_ref = store + .unwrap_gc_store() + .func_ref_table + .get_untyped(func_ref_id); + Val::FuncRef(unsafe { + func_ref.map(|p| Func::from_vm_func_ref(store.id(), p.as_non_null())) + }) } + otherwise => unreachable!("not a top type: {otherwise:?}"), + }, + } +} + +pub(crate) fn initialize_field_impl( + gc_ref: &VMGcRef, + store: &mut AutoAssertNoGc, + ty: &StorageType, + offset: u32, + val: Val, +) -> Result<()> { + match val { + Val::I32(i) if ty.is_i8() => store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_i8(offset, truncate_i32_to_i8(i)), + Val::I32(i) if ty.is_i16() => store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_i16(offset, truncate_i32_to_i16(i)), + Val::I32(i) => store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_i32(offset, i), + Val::I64(i) => store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_i64(offset, i), + Val::F32(f) => store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_u32(offset, f), + Val::F64(f) => store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_u64(offset, f), + Val::V128(v) => store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_v128(offset, v), + + // NB: We don't need to do a write barrier when initializing a + // field, because there is nothing being overwritten. Therefore, we + // just the clone barrier. + Val::ExternRef(x) => { + let x = match x { + None => 0, + Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), + }; + store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_u32(offset, x); + } + Val::AnyRef(x) => { + let x = match x { + None => 0, + Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), + }; + store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_u32(offset, x); + } + Val::ExnRef(x) => { + let x = match x { + None => 0, + Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(), + }; + store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_u32(offset, x); + } + + Val::FuncRef(f) => { + let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store))); + let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(f) }; + store + .gc_store_mut()? + .gc_object_data(gc_ref) + .write_u32(offset, id.into_raw()); } - Ok(()) } + Ok(()) } diff --git a/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs b/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs index e0d7d3fe83e2..54f45dc588f9 100644 --- a/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs +++ b/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs @@ -72,36 +72,36 @@ impl VMGcHeader { VMGcKind::from_high_bits_of_u32(self.kind) } - /// Get the reserved 27 bits in this header. + /// Get the reserved 26 bits in this header. /// /// These are bits are reserved for `GcRuntime` implementations to make use /// of however they see fit. - pub fn reserved_u27(&self) -> u32 { + pub fn reserved_u26(&self) -> u32 { self.kind & VMGcKind::UNUSED_MASK } - /// Set the 27-bit reserved value. + /// Set the 26-bit reserved value. /// /// # Panics /// /// Panics if the given `value` has any of the upper 6 bits set. - pub fn set_reserved_u27(&mut self, value: u32) { + pub fn set_reserved_u26(&mut self, value: u32) { assert!( VMGcKind::value_fits_in_unused_bits(value), - "VMGcHeader::set_reserved_u27 with value using more than 27 bits: \ + "VMGcHeader::set_reserved_u26 with value using more than 26 bits: \ {value:#034b} ({value}, {value:#010x})" ); self.kind &= VMGcKind::MASK; self.kind |= value; } - /// Set the 27-bit reserved value. + /// Set the 26-bit reserved value. /// /// # Safety /// - /// The given `value` must only use the lower 27 bits; its upper 5 bits must + /// The given `value` must only use the lower 26 bits; its upper 6 bits must /// be unset. - pub unsafe fn unchecked_set_reserved_u27(&mut self, value: u32) { + pub unsafe fn unchecked_set_reserved_u26(&mut self, value: u32) { debug_assert_eq!(value & VMGcKind::MASK, 0); self.kind &= VMGcKind::MASK; self.kind |= value; @@ -498,27 +498,27 @@ mod tests { let ty = VMSharedTypeIndex::new(1234); let mut header = VMGcHeader::from_kind_and_index(kind, ty); - assert_eq!(header.reserved_u27(), 0); + assert_eq!(header.reserved_u26(), 0); assert_eq!(header.kind(), kind); assert_eq!(header.ty(), Some(ty)); - header.set_reserved_u27(36); - assert_eq!(header.reserved_u27(), 36); + header.set_reserved_u26(36); + assert_eq!(header.reserved_u26(), 36); assert_eq!(header.kind(), kind); assert_eq!(header.ty(), Some(ty)); - let max = (1 << 27) - 1; - header.set_reserved_u27(max); - assert_eq!(header.reserved_u27(), max); + let max = (1 << 26) - 1; + header.set_reserved_u26(max); + assert_eq!(header.reserved_u26(), max); assert_eq!(header.kind(), kind); assert_eq!(header.ty(), Some(ty)); - header.set_reserved_u27(0); - assert_eq!(header.reserved_u27(), 0); + header.set_reserved_u26(0); + assert_eq!(header.reserved_u26(), 0); assert_eq!(header.kind(), kind); assert_eq!(header.ty(), Some(ty)); - let result = std::panic::catch_unwind(move || header.set_reserved_u27(max + 1)); + let result = std::panic::catch_unwind(move || header.set_reserved_u26(max + 1)); assert!(result.is_err()); } } diff --git a/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs b/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs index 57e62b1fb1c5..65ff869193a6 100644 --- a/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs +++ b/crates/wasmtime/src/runtime/vm/gc/gc_runtime.rs @@ -9,7 +9,11 @@ use crate::vm::VMMemoryDefinition; use core::ptr::NonNull; use core::slice; use core::{alloc::Layout, any::Any, marker, mem, ops::Range, ptr}; -use wasmtime_environ::{GcArrayLayout, GcStructLayout, GcTypeLayouts, VMSharedTypeIndex}; +use wasmtime_environ::{ + GcArrayLayout, GcExceptionLayout, GcStructLayout, GcTypeLayouts, VMSharedTypeIndex, +}; + +use super::VMExnRef; /// Trait for integrating a garbage collector with the runtime. /// @@ -196,7 +200,7 @@ pub unsafe trait GcHeap: 'static + Send + Sync { /// /// Return values: /// - /// * `Ok(Some(_))`: The allocation was successful. + /// * `Ok(Ok(_))`: The allocation was successful. /// /// * `Ok(Err(n))`: There is currently not enough available space for this /// allocation of size `n`. The caller should either grow the heap or run @@ -253,7 +257,7 @@ pub unsafe trait GcHeap: 'static + Send + Sync { /// /// Return values: /// - /// * `Ok(Some(_))`: The allocation was successful. + /// * `Ok(Ok(_))`: The allocation was successful. /// /// * `Ok(Err(n))`: There is currently not enough available space for this /// allocation of size `n`. The caller should either grow the heap or run @@ -278,7 +282,7 @@ pub unsafe trait GcHeap: 'static + Send + Sync { /// /// Return values: /// - /// * `Ok(Some(_))`: The allocation was successful. + /// * `Ok(Ok(_))`: The allocation was successful. /// /// * `Ok(Err(n))`: There is currently not enough available space for this /// allocation of size `n`. The caller should either grow the heap or run @@ -338,6 +342,46 @@ pub unsafe trait GcHeap: 'static + Send + Sync { /// or incorrect results. fn array_len(&self, arrayref: &VMArrayRef) -> u32; + /// Allocate a GC-managed exception object of the given type and + /// layout, with the given tag. + /// + /// The exception object's fields are left uninitialized. It is + /// the caller's responsibility to initialize them before exposing + /// the object to Wasm or triggering a GC. + /// + /// The `ty` and `layout` must match, and the tag's function type + /// must have a matching signature to the exception layout's. + /// + /// Failure to do either of the above is memory safe, but may result in + /// general failures such as panics or incorrect results. + /// + /// Return values: + /// + /// * `Ok(Ok(_))`: The allocation was successful. + /// + /// * `Ok(Err(n))`: There is currently not enough available space for this + /// allocation of size `n`. The caller should either grow the heap or run + /// a collection to reclaim space, and then try allocating again. + /// + /// * `Err(_)`: The collector cannot satisfy this allocation request, and + /// would not be able to even after the caller were to trigger a + /// collection. This could be because, for example, the requested + /// allocation is larger than this collector's implementation limit for + /// object size. + fn alloc_uninit_exn( + &mut self, + ty: VMSharedTypeIndex, + layout: &GcExceptionLayout, + ) -> Result>; + + /// Deallocate an uninitialized, GC-managed exception object. + /// + /// This is useful for if initialization of the struct's fields fails, so + /// that the struct's allocation can be eagerly reclaimed, and so that the + /// collector doesn't attempt to treat any of the uninitialized fields as + /// valid GC references, or something like that. + fn dealloc_uninit_exn(&mut self, exnref: VMExnRef); + //////////////////////////////////////////////////////////////////////////// // Garbage Collection Methods diff --git a/crates/wasmtime/src/runtime/vm/instance.rs b/crates/wasmtime/src/runtime/vm/instance.rs index 7245567b4a7d..2e9b06fb6e5d 100644 --- a/crates/wasmtime/src/runtime/vm/instance.rs +++ b/crates/wasmtime/src/runtime/vm/instance.rs @@ -673,7 +673,12 @@ impl Instance { } } - fn get_exported_tag(&self, store: StoreId, index: TagIndex) -> crate::Tag { + /// Get an exported tag by index. + /// + /// # Panics + /// + /// Panics if the index is out-of-range. + pub fn get_exported_tag(&self, store: StoreId, index: TagIndex) -> crate::Tag { let (id, def_index) = if let Some(def_index) = self.env_module().defined_tag_index(index) { (self.id, def_index) } else { @@ -999,7 +1004,7 @@ impl Instance { VMGcRef::from_raw_u32(raw.get_externref()) }), )?, - WasmHeapTopType::Any => table.init_gc_refs( + WasmHeapTopType::Any | WasmHeapTopType::Exn => table.init_gc_refs( dst, exprs.iter().map(|expr| unsafe { let raw = const_evaluator diff --git a/crates/wasmtime/src/runtime/vm/instance/allocator.rs b/crates/wasmtime/src/runtime/vm/instance/allocator.rs index 38b52ef3eea6..95f59c7c7fa8 100644 --- a/crates/wasmtime/src/runtime/vm/instance/allocator.rs +++ b/crates/wasmtime/src/runtime/vm/instance/allocator.rs @@ -634,6 +634,16 @@ fn initialize_tables( table.init_gc_refs(0, items)?; } + WasmHeapTopType::Exn => { + let (gc_store, instance) = + store.gc_store_and_instance_mut(context.instance)?; + let table = instance.get_defined_table(table); + let gc_ref = VMGcRef::from_raw_u32(raw.get_anyref()); + let items = (0..table.size()) + .map(|_| gc_ref.as_ref().map(|r| gc_store.clone_gc_ref(r))); + table.init_gc_refs(0, items)?; + } + WasmHeapTopType::Func => { let table = store .instance_mut(context.instance) diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs index 69bbbd729d12..31d51cc1071b 100644 --- a/crates/wasmtime/src/runtime/vm/libcalls.rs +++ b/crates/wasmtime/src/runtime/vm/libcalls.rs @@ -564,7 +564,7 @@ unsafe fn gc_alloc_raw( .expect("should have engine type index for module type index"); let mut header = VMGcHeader::from_kind_and_index(kind, shared_type_index); - header.set_reserved_u27(kind_and_reserved & VMGcKind::UNUSED_MASK); + header.set_reserved_u26(kind_and_reserved & VMGcKind::UNUSED_MASK); let size = usize::try_from(size).unwrap(); let align = usize::try_from(align).unwrap(); diff --git a/crates/wasmtime/src/runtime/vm/table.rs b/crates/wasmtime/src/runtime/vm/table.rs index 433d27352a41..25c8495c4f23 100644 --- a/crates/wasmtime/src/runtime/vm/table.rs +++ b/crates/wasmtime/src/runtime/vm/table.rs @@ -376,6 +376,7 @@ pub(crate) fn wasm_to_table_type(ty: WasmRefType) -> TableElementType { WasmHeapTopType::Func => TableElementType::Func, WasmHeapTopType::Any | WasmHeapTopType::Extern => TableElementType::GcRef, WasmHeapTopType::Cont => TableElementType::Cont, + WasmHeapTopType::Exn => TableElementType::GcRef, } } diff --git a/crates/wasmtime/src/runtime/vm/vmcontext.rs b/crates/wasmtime/src/runtime/vm/vmcontext.rs index 86d692cc0c04..67aa41e20830 100644 --- a/crates/wasmtime/src/runtime/vm/vmcontext.rs +++ b/crates/wasmtime/src/runtime/vm/vmcontext.rs @@ -567,6 +567,10 @@ impl VMGlobalDefinition { } WasmHeapTopType::Func => *global.as_func_ref_mut() = raw.get_funcref().cast(), WasmHeapTopType::Cont => *global.as_func_ref_mut() = raw.get_funcref().cast(), // TODO(#10248): temporary hack. + WasmHeapTopType::Exn => { + let r = VMGcRef::from_raw_u32(raw.get_exnref()); + global.init_gc_ref(store.gc_store_mut()?, r.as_ref()) + } }, } Ok(global) @@ -599,6 +603,12 @@ impl VMGlobalDefinition { None => 0, } }), + WasmHeapTopType::Exn => ValRaw::exnref({ + match self.as_gc_ref() { + Some(r) => store.gc_store_mut()?.clone_gc_ref(r).as_raw_u32(), + None => 0, + } + }), WasmHeapTopType::Func => ValRaw::funcref(self.as_func_ref().cast()), WasmHeapTopType::Cont => todo!(), // FIXME: #10248 stack switching support. }, @@ -1357,6 +1367,17 @@ pub union ValRaw { /// /// This value is always stored in a little-endian format. anyref: u32, + + /// A WebAssembly `exnref` value (or one of its subtypes). + /// + /// The payload here is a compressed pointer value which is + /// runtime-defined. This is one of the main points of unsafety about the + /// `ValRaw` type as the validity of the pointer here is not easily verified + /// and must be preserved by carefully calling the correct functions + /// throughout the runtime. + /// + /// This value is always stored in a little-endian format. + exnref: u32, } // The `ValRaw` type is matched as `wasmtime_val_raw_t` in the C API so these @@ -1394,6 +1415,7 @@ impl fmt::Debug for ValRaw { .field("funcref", &self.funcref) .field("externref", &Hex(self.externref)) .field("anyref", &Hex(self.anyref)) + .field("exnref", &Hex(self.exnref)) .finish() } } @@ -1401,11 +1423,12 @@ impl fmt::Debug for ValRaw { impl ValRaw { /// Create a null reference that is compatible with any of - /// `{any,extern,func}ref`. + /// `{any,extern,func,exn}ref`. pub fn null() -> ValRaw { unsafe { let raw = mem::MaybeUninit::::zeroed().assume_init(); debug_assert_eq!(raw.get_anyref(), 0); + debug_assert_eq!(raw.get_exnref(), 0); debug_assert_eq!(raw.get_externref(), 0); debug_assert_eq!(raw.get_funcref(), ptr::null_mut()); raw @@ -1490,6 +1513,13 @@ impl ValRaw { ValRaw { anyref: r.to_le() } } + /// Creates a WebAssembly `exnref` value + #[inline] + pub fn exnref(r: u32) -> ValRaw { + assert!(cfg!(feature = "gc") || r == 0); + ValRaw { exnref: r.to_le() } + } + /// Gets the WebAssembly `i32` value #[inline] pub fn get_i32(&self) -> i32 { @@ -1553,6 +1583,14 @@ impl ValRaw { assert!(cfg!(feature = "gc") || anyref == 0); anyref } + + /// Gets the WebAssembly `exnref` value + #[inline] + pub fn get_exnref(&self) -> u32 { + let exnref = u32::from_le(unsafe { self.exnref }); + assert!(cfg!(feature = "gc") || exnref == 0); + exnref + } } /// An "opaque" version of `VMContext` which must be explicitly casted to a diff --git a/crates/wasmtime/src/runtime/wave/core.rs b/crates/wasmtime/src/runtime/wave/core.rs index f330fe05fe68..338ce4b2d888 100644 --- a/crates/wasmtime/src/runtime/wave/core.rs +++ b/crates/wasmtime/src/runtime/wave/core.rs @@ -39,6 +39,7 @@ impl WasmValue for crate::Val { Self::FuncRef(_) => WasmTypeKind::Unsupported, Self::ExternRef(_) => WasmTypeKind::Unsupported, Self::AnyRef(_) => WasmTypeKind::Unsupported, + Self::ExnRef(_) => WasmTypeKind::Unsupported, } } diff --git a/src/commands/run.rs b/src/commands/run.rs index e4e13d23b7ad..9a5c032e15f3 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -719,6 +719,8 @@ impl RunCommand { Val::FuncRef(Some(_)) => println!(""), Val::AnyRef(None) => println!(""), Val::AnyRef(Some(_)) => println!(""), + Val::ExnRef(None) => println!(""), + Val::ExnRef(Some(_)) => println!(""), } } diff --git a/tests/all/exnrefs.rs b/tests/all/exnrefs.rs new file mode 100644 index 000000000000..e1586242b793 --- /dev/null +++ b/tests/all/exnrefs.rs @@ -0,0 +1,85 @@ +use super::gc_store; +use wasmtime::*; + +#[test] +fn tag_objects() -> Result<()> { + let mut store = gc_store()?; + let engine = store.engine(); + + let func_ty = FuncType::new(&engine, [ValType::I32, ValType::I64], []); + let tag_ty = TagType::new(func_ty); + + let tag = Tag::new(&mut store, &tag_ty).unwrap(); + + assert!(tag.ty(&store).ty().matches(tag_ty.ty())); + + let tag2 = Tag::new(&mut store, &tag_ty).unwrap(); + + assert!(!Tag::eq(&tag, &tag2, &store)); + + Ok(()) +} + +#[test] +fn exn_types() -> Result<()> { + let mut store = gc_store()?; + let engine = store.engine(); + + let func_ty = FuncType::new(&engine, [ValType::I32, ValType::I64], []); + let tag_ty = TagType::new(func_ty); + + let tag = Tag::new(&mut store, &tag_ty).unwrap(); + + assert!(tag.ty(&store).ty().matches(tag_ty.ty())); + + let tag2 = Tag::new(&mut store, &tag_ty).unwrap(); + + assert!(!Tag::eq(&tag, &tag2, &store)); + + let exntype = ExnType::from_tag_type(&tag_ty).unwrap(); + let exntype2 = ExnType::new(store.engine(), [ValType::I32, ValType::I64]).unwrap(); + + assert!(exntype.matches(&exntype2)); + assert!(exntype.tag_type().ty().matches(&tag_ty.ty())); + + Ok(()) +} + +#[test] +fn exn_objects() -> Result<()> { + let mut store = gc_store()?; + let exntype = ExnType::new(store.engine(), [ValType::I32, ValType::I64]).unwrap(); + + // Create a tag instance to associate with our exception objects. + let tag = Tag::new(&mut store, &exntype.tag_type()).unwrap(); + + // Create an allocator for the exn type. + let allocator = ExnRefPre::new(&mut store, exntype); + + { + let mut scope = RootScope::new(&mut store); + + for i in 0..10 { + ExnRef::new( + &mut scope, + &allocator, + &tag, + &[Val::I32(i), Val::I64(i64::MAX)], + )?; + } + + let obj = ExnRef::new( + &mut scope, + &allocator, + &tag, + &[Val::I32(42), Val::I64(i64::MIN)], + )?; + + assert_eq!(obj.fields(&mut scope)?.len(), 2); + assert_eq!(obj.field(&mut scope, 0)?.unwrap_i32(), 42); + assert_eq!(obj.field(&mut scope, 1)?.unwrap_i64(), i64::MIN); + assert!(Tag::eq(&obj.tag(&mut scope)?, &tag, &scope)); + } + + Ok(()) +} diff --git a/tests/all/main.rs b/tests/all/main.rs index 5676647b9b08..e7eadbac3e15 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -13,6 +13,7 @@ mod custom_code_memory; mod debug; mod defaults; mod epoch_interruption; +mod exnrefs; mod externals; mod fuel; mod func; diff --git a/tests/disas/gc/null/array-new.wat b/tests/disas/gc/null/array-new.wat index fa1cc19ce17b..dc79a568cdcd 100644 --- a/tests/disas/gc/null/array-new.wat +++ b/tests/disas/gc/null/array-new.wat @@ -32,8 +32,8 @@ ;; v62 = iconst.i32 3 ;; v63 = ishl v3, v62 ; v62 = 3 ;; @0022 v10 = uadd_overflow_trap v5, v63, user18 ; v5 = 16 -;; @0022 v12 = iconst.i32 -134217728 -;; @0022 v13 = band v10, v12 ; v12 = -134217728 +;; @0022 v12 = iconst.i32 -67108864 +;; @0022 v13 = band v10, v12 ; v12 = -67108864 ;; @0022 trapnz v13, user18 ;; @0022 v15 = load.i64 notrap aligned readonly v0+32 ;; @0022 v16 = load.i32 notrap aligned v15