Repository: bytecodealliance/wasm-tools
Pull request #1485 (2024-04-03 17:25:19)

Add `nullexnref` and `noexn` types to exception-handling
The latest overview has a `noexn` bottom type like the other hierarchies now.

https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md#exnref


Patch:
diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs
index bfc54ae1..85ef0aef 100644
--- a/crates/wasm-encoder/src/core/types.rs
+++ b/crates/wasm-encoder/src/core/types.rs
@@ -450,9 +450,12 @@ pub enum HeapType {
     /// The unboxed `i31` heap type.
     I31,
 
-    /// The abstract` exception` heap type.
+    /// The abstract `exception` heap type.
     Exn,
 
+    /// The abstract `noexn` heap type.
+    NoExn,
+
     /// A concrete Wasm-defined type at the given index.
     Concrete(u32),
 }
@@ -471,6 +474,7 @@ impl Encode for HeapType {
             HeapType::Array => sink.push(0x6A),
             HeapType::I31 => sink.push(0x6C),
             HeapType::Exn => sink.push(0x69),
+            HeapType::NoExn => sink.push(0x74),
             // Note that this is encoded as a signed type rather than unsigned
             // as it's decoded as an s33
             HeapType::Concrete(i) => i64::from(*i).encode(sink),
@@ -496,6 +500,7 @@ impl TryFrom<wasmparser::HeapType> for HeapType {
             wasmparser::HeapType::Array => HeapType::Array,
             wasmparser::HeapType::I31 => HeapType::I31,
             wasmparser::HeapType::Exn => HeapType::Exn,
+            wasmparser::HeapType::NoExn => HeapType::NoExn,
         })
     }
 }
diff --git a/crates/wasm-mutate/src/module.rs b/crates/wasm-mutate/src/module.rs
index 486c9690..87653dd7 100644
--- a/crates/wasm-mutate/src/module.rs
+++ b/crates/wasm-mutate/src/module.rs
@@ -91,6 +91,7 @@ pub fn map_ref_type(ref_ty: wasmparser::RefType) -> Result<RefType> {
             wasmparser::HeapType::Array => HeapType::Array,
             wasmparser::HeapType::I31 => HeapType::I31,
             wasmparser::HeapType::Exn => HeapType::Exn,
+            wasmparser::HeapType::NoExn => HeapType::NoExn,
             wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().unwrap()),
         },
     })
diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs
index 11fb368c..58b3a51e 100644
--- a/crates/wasm-mutate/src/mutators/translate.rs
+++ b/crates/wasm-mutate/src/mutators/translate.rs
@@ -212,6 +212,7 @@ pub fn heapty(t: &mut dyn Translator, ty: &wasmparser::HeapType) -> Result<HeapT
         wasmparser::HeapType::Array => Ok(HeapType::Array),
         wasmparser::HeapType::I31 => Ok(HeapType::I31),
         wasmparser::HeapType::Exn => Ok(HeapType::Exn),
+        wasmparser::HeapType::NoExn => Ok(HeapType::NoExn),
         wasmparser::HeapType::Concrete(i) => Ok(HeapType::Concrete(
             t.remap(Item::Type, i.as_module_index().unwrap())?,
         )),
diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs
index b76da6ad..3180c618 100644
--- a/crates/wasm-smith/src/core.rs
+++ b/crates/wasm-smith/src/core.rs
@@ -496,6 +496,8 @@ impl Module {
                 matches!(self.ty(b).composite_type, CompositeType::Func(_))
             }
 
+            (HT::NoExn, HT::Exn) => true,
+
             // Nothing else matches. (Avoid full wildcard matches so that
             // adding/modifying variants is easier in the future.)
             (HT::Concrete(_), _)
@@ -508,11 +510,9 @@ impl Module {
             | (HT::Eq, _)
             | (HT::Struct, _)
             | (HT::Array, _)
-            | (HT::I31, _) => false,
-
-            // TODO: `exn` probably will be its own type hierarchy and will
-            // probably get `noexn` as well.
-            (HT::Exn, _) => false,
+            | (HT::I31, _)
+            | (HT::Exn, _)
+            | (HT::NoExn, _) => false,
         }
     }
 
@@ -781,7 +781,7 @@ impl Module {
             HT::Extern => {
                 choices.push(HT::NoExtern);
             }
-            HT::Exn | HT::None | HT::NoExtern | HT::NoFunc => {}
+            HT::Exn | HT::NoExn | HT::None | HT::NoExtern | HT::NoFunc => {}
         }
         Ok(*u.choose(&choices)?)
     }
@@ -860,6 +860,9 @@ impl Module {
                 choices.extend(self.func_types.iter().copied().map(HT::Concrete));
                 choices.push(HT::Func);
             }
+            HT::NoExn => {
+                choices.push(HT::Exn);
+            }
             HT::Concrete(mut idx) => {
                 match &self
                     .types
@@ -1648,6 +1651,7 @@ impl Module {
                 wasmparser::HeapType::Array => HeapType::Array,
                 wasmparser::HeapType::I31 => HeapType::I31,
                 wasmparser::HeapType::Exn => HeapType::Exn,
+                wasmparser::HeapType::NoExn => HeapType::NoExn,
             }
         }
 
diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs
index 6ba9096d..0dfcbdc1 100644
--- a/crates/wasmparser/src/readers/core/types.rs
+++ b/crates/wasmparser/src/readers/core/types.rs
@@ -878,6 +878,8 @@ impl std::fmt::Debug for RefType {
             (false, HeapType::Func) => write!(f, "(ref func)"),
             (true, HeapType::Exn) => write!(f, "exnref"),
             (false, HeapType::Exn) => write!(f, "(ref exn)"),
+            (true, HeapType::NoExn) => write!(f, "nullexnref"),
+            (false, HeapType::NoExn) => write!(f, "(ref noexn)"),
             (true, HeapType::Concrete(idx)) => write!(f, "(ref null {idx})"),
             (false, HeapType::Concrete(idx)) => write!(f, "(ref {idx})"),
         }
@@ -928,6 +930,7 @@ impl RefType {
     const EXTERN_ABSTYPE: u32 = 0b0011 << 18;
     const NOEXTERN_ABSTYPE: u32 = 0b0010 << 18;
     const EXN_ABSTYPE: u32 = 0b0001 << 18;
+    const NOEXN_ABSTYPE: u32 = 0b1110 << 18;
     const NONE_ABSTYPE: u32 = 0b0000 << 18;
 
     // The `index` is valid only when `concrete == 1`.
@@ -972,6 +975,10 @@ impl RefType {
     /// `exnref`.
     pub const EXNREF: Self = RefType::EXN.nullable();
 
+    /// A nullable reference to a noexn object aka `(ref null noexn)` aka
+    /// `nullexnref`.
+    pub const NULLEXNREF: Self = RefType::NOEXN.nullable();
+
     /// A non-nullable untyped function reference aka `(ref func)`.
     pub const FUNC: Self = RefType::from_u32(Self::FUNC_ABSTYPE);
 
@@ -1005,6 +1012,9 @@ impl RefType {
     /// A non-nullable reference to an exn object aka `(ref exn)`.
     pub const EXN: Self = RefType::from_u32(Self::EXN_ABSTYPE);
 
+    /// A non-nullable reference to a noexn object aka `(ref noexn)`.
+    pub const NOEXN: Self = RefType::from_u32(Self::NOEXN_ABSTYPE);
+
     const fn can_represent_type_index(index: u32) -> bool {
         index & Self::INDEX_MASK == index
     }
@@ -1045,6 +1055,7 @@ impl RefType {
                         | Self::NOEXTERN_ABSTYPE
                         | Self::NONE_ABSTYPE
                         | Self::EXN_ABSTYPE
+                        | Self::NOEXN_ABSTYPE
                 )
         );
 
@@ -1083,6 +1094,7 @@ impl RefType {
             HeapType::Array => Some(Self::from_u32(nullable32 | Self::ARRAY_ABSTYPE)),
             HeapType::I31 => Some(Self::from_u32(nullable32 | Self::I31_ABSTYPE)),
             HeapType::Exn => Some(Self::from_u32(nullable32 | Self::EXN_ABSTYPE)),
+            HeapType::NoExn => Some(Self::from_u32(nullable32 | Self::NOEXN_ABSTYPE)),
         }
     }
 
@@ -1179,6 +1191,7 @@ impl RefType {
                 Self::ARRAY_ABSTYPE => HeapType::Array,
                 Self::I31_ABSTYPE => HeapType::I31,
                 Self::EXN_ABSTYPE => HeapType::Exn,
+                Self::NOEXN_ABSTYPE => HeapType::NoExn,
                 _ => unreachable!(),
             }
         }
@@ -1200,6 +1213,7 @@ impl RefType {
             (true, HeapType::Array) => "arrayref",
             (true, HeapType::I31) => "i31ref",
             (true, HeapType::Exn) => "exnref",
+            (true, HeapType::NoExn) => "nullexnref",
             (false, HeapType::Func) => "(ref func)",
             (false, HeapType::Extern) => "(ref extern)",
             (false, HeapType::Concrete(_)) => "(ref $type)",
@@ -1212,6 +1226,7 @@ impl RefType {
             (false, HeapType::Array) => "(ref array)",
             (false, HeapType::I31) => "(ref i31)",
             (false, HeapType::Exn) => "(ref exn)",
+            (false, HeapType::NoExn) => "(ref noexn)",
         }
     }
 }
@@ -1297,13 +1312,20 @@ pub enum HeapType {
     ///
     /// Introduced in the exception-handling proposal.
     Exn,
+
+    /// The abstract `noexn` heap type.
+    ///
+    /// The common subtype (a.k.a. bottom) of all exception types.
+    ///
+    /// Introduced in the exception-handling proposal.
+    NoExn,
 }
 
 impl ValType {
     pub(crate) fn is_valtype_byte(byte: u8) -> bool {
         match byte {
             0x7F | 0x7E | 0x7D | 0x7C | 0x7B | 0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72
-            | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C | 0x69 => true,
+            | 0x74 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C | 0x69 => true,
             _ => false,
         }
     }
@@ -1348,8 +1370,8 @@ impl<'a> FromReader<'a> for ValType {
                 reader.position += 1;
                 Ok(ValType::V128)
             }
-            0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C
-            | 0x69 => Ok(ValType::Ref(reader.read()?)),
+            0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 | 0x73 | 0x74 | 0x6D | 0x6B | 0x6A
+            | 0x6C | 0x69 => Ok(ValType::Ref(reader.read()?)),
             _ => bail!(reader.original_position(), "invalid value type"),
         }
     }
@@ -1369,6 +1391,7 @@ impl<'a> FromReader<'a> for RefType {
             0x6A => Ok(RefType::ARRAY.nullable()),
             0x6C => Ok(RefType::I31.nullable()),
             0x69 => Ok(RefType::EXN.nullable()),
+            0x74 => Ok(RefType::NOEXN.nullable()),
             byte @ (0x63 | 0x64) => {
                 let nullable = byte == 0x63;
                 let pos = reader.original_position();
@@ -1427,6 +1450,10 @@ impl<'a> FromReader<'a> for HeapType {
                 reader.position += 1;
                 Ok(HeapType::Exn)
             }
+            0x74 => {
+                reader.position += 1;
+                Ok(HeapType::NoExn)
+            }
             _ => {
                 let idx = match u32::try_from(reader.read_var_s33()?) {
                     Ok(idx) => idx,
diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs
index 5e7833e7..47e429cd 100644
--- a/crates/wasmparser/src/validator.rs
+++ b/crates/wasmparser/src/validator.rs
@@ -358,7 +358,7 @@ impl WasmFeatures {
             }
 
             // These types were added in the exception-handling proposal.
-            (HeapType::Exn, _) => {
+            (HeapType::Exn | HeapType::NoExn, _) => {
                 if self.exceptions {
                     Ok(())
                 } else {
diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs
index 6fc077c1..c0f983de 100644
--- a/crates/wasmparser/src/validator/core.rs
+++ b/crates/wasmparser/src/validator/core.rs
@@ -982,7 +982,8 @@ impl Module {
             | HeapType::Struct
             | HeapType::Array
             | HeapType::I31
-            | HeapType::Exn => return Ok(()),
+            | HeapType::Exn
+            | HeapType::NoExn => return Ok(()),
             HeapType::Concrete(type_index) => type_index,
         };
         match type_index {
diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs
index 5f8791e0..d82ba55d 100644
--- a/crates/wasmparser/src/validator/types.rs
+++ b/crates/wasmparser/src/validator/types.rs
@@ -2695,6 +2695,8 @@ impl TypeList {
                 matches!(subtype(b_group, b).composite_type, CompositeType::Func(_))
             }
 
+            (HT::NoExn, HT::Exn) => true,
+
             // Nothing else matches. (Avoid full wildcard matches so that
             // adding/modifying variants is easier in the future.)
             (HT::Concrete(_), _)
@@ -2707,11 +2709,9 @@ impl TypeList {
             | (HT::Eq, _)
             | (HT::Struct, _)
             | (HT::Array, _)
-            | (HT::I31, _) => false,
-
-            // TODO: this probably isn't right, this is probably related to some
-            // gc type.
-            (HT::Exn, _) => false,
+            | (HT::I31, _)
+            | (HT::Exn, _)
+            | (HT::NoExn, _) => false,
         }
     }
 
@@ -2749,7 +2749,7 @@ impl TypeList {
             | HeapType::Array
             | HeapType::I31
             | HeapType::None => HeapType::Any,
-            HeapType::Exn => HeapType::Exn,
+            HeapType::Exn | HeapType::NoExn => HeapType::Exn,
         }
     }
 
diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs
index 26962f39..514530f9 100644
--- a/crates/wasmprinter/src/lib.rs
+++ b/crates/wasmprinter/src/lib.rs
@@ -969,6 +969,7 @@ impl Printer {
                 RefType::STRUCT => self.result.push_str("structref"),
                 RefType::ARRAY => self.result.push_str("arrayref"),
                 RefType::EXN => self.result.push_str("exnref"),
+                RefType::NOEXN => self.result.push_str("nullexnref"),
                 _ => {
                     self.result.push_str("(ref null ");
                     self.print_heaptype(state, ty.heap_type())?;
@@ -996,6 +997,7 @@ impl Printer {
             HeapType::Array => self.result.push_str("array"),
             HeapType::I31 => self.result.push_str("i31"),
             HeapType::Exn => self.result.push_str("exn"),
+            HeapType::NoExn => self.result.push_str("noexn"),
             HeapType::Concrete(i) => {
                 self.print_idx(&state.core.type_names, i.as_module_index().unwrap())?;
             }
diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs
index 6ee11e02..2629146c 100644
--- a/crates/wast/src/component/binary.rs
+++ b/crates/wast/src/component/binary.rs
@@ -597,7 +597,7 @@ impl From<core::HeapType<'_>> for wasm_encoder::HeapType {
         match r {
             core::HeapType::Func => Self::Func,
             core::HeapType::Extern => Self::Extern,
-            core::HeapType::Exn => {
+            core::HeapType::Exn | core::HeapType::NoExn => {
                 todo!("encoding of exceptions proposal types not yet implemented")
             }
             core::HeapType::Concrete(Index::Num(i, _)) => Self::Concrete(i),
diff --git a/crates/wast/src/component/resolve.rs b/crates/wast/src/component/resolve.rs
index c0122ef7..0f33df56 100644
--- a/crates/wast/src/component/resolve.rs
+++ b/crates/wast/src/component/resolve.rs
@@ -531,7 +531,8 @@ impl<'a> Resolver<'a> {
                         | core::HeapType::Struct
                         | core::HeapType::None
                         | core::HeapType::NoFunc
-                        | core::HeapType::NoExtern => {}
+                        | core::HeapType::NoExtern
+                        | core::HeapType::NoExn => {}
                         core::HeapType::Concrete(id) => {
                             self.resolve_ns(id, Ns::Type)?;
                         }
diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs
index 8531e81e..37e7d640 100644
--- a/crates/wast/src/core/binary.rs
+++ b/crates/wast/src/core/binary.rs
@@ -295,6 +295,7 @@ impl<'a> Encode for HeapType<'a> {
             HeapType::I31 => e.push(0x6c),
             HeapType::NoFunc => e.push(0x73),
             HeapType::NoExtern => e.push(0x72),
+            HeapType::NoExn => e.push(0x74),
             HeapType::None => e.push(0x71),
             // Note that this is encoded as a signed leb128 so be sure to cast
             // to an i64 first
@@ -349,6 +350,11 @@ impl<'a> Encode for RefType<'a> {
                 nullable: true,
                 heap: HeapType::NoExtern,
             } => e.push(0x72),
+            // The 'nullexnref' binary abbreviation
+            RefType {
+                nullable: true,
+                heap: HeapType::NoExn,
+            } => e.push(0x74),
             // The 'nullref' binary abbreviation
             RefType {
                 nullable: true,
diff --git a/crates/wast/src/core/types.rs b/crates/wast/src/core/types.rs
index ebae2e42..3cb1930d 100644
--- a/crates/wast/src/core/types.rs
+++ b/crates/wast/src/core/types.rs
@@ -86,6 +86,8 @@ pub enum HeapType<'a> {
     NoExtern,
     /// The bottom type of the anyref hierarchy. Part of the GC proposal.
     None,
+    /// The bottom type of the exnref hierarchy. Part of the exceptions proposal.
+    NoExn,
     /// A reference to a concrete function, struct, or array type defined by
     /// Wasm: `ref T`. This is part of the function references and GC proposals.
     Concrete(Index<'a>),
@@ -124,6 +126,9 @@ impl<'a> Parse<'a> for HeapType<'a> {
         } else if l.peek::<kw::noextern>()? {
             parser.parse::<kw::noextern>()?;
             Ok(HeapType::NoExtern)
+        } else if l.peek::<kw::noexn>()? {
+            parser.parse::<kw::noexn>()?;
+            Ok(HeapType::NoExn)
         } else if l.peek::<kw::none>()? {
             parser.parse::<kw::none>()?;
             Ok(HeapType::None)
@@ -147,6 +152,7 @@ impl<'a> Peek for HeapType<'a> {
             || kw::i31::peek(cursor)?
             || kw::nofunc::peek(cursor)?
             || kw::noextern::peek(cursor)?
+            || kw::noexn::peek(cursor)?
             || kw::none::peek(cursor)?
             || (LParen::peek(cursor)? && kw::r#type::peek2(cursor)?))
     }
@@ -251,6 +257,14 @@ impl<'a> RefType<'a> {
             heap: HeapType::None,
         }
     }
+
+    /// A `nullexnref` as an abbreviation for `(ref null noexn)`.
+    pub fn nullexnref() -> Self {
+        RefType {
+            nullable: true,
+            heap: HeapType::NoExn,
+        }
+    }
 }
 
 impl<'a> Parse<'a> for RefType<'a> {
@@ -286,6 +300,9 @@ impl<'a> Parse<'a> for RefType<'a> {
         } else if l.peek::<kw::nullexternref>()? {
             parser.parse::<kw::nullexternref>()?;
             Ok(RefType::nullexternref())
+        } else if l.peek::<kw::nullexnref>()? {
+            parser.parse::<kw::nullexnref>()?;
+            Ok(RefType::nullexnref())
         } else if l.peek::<kw::nullref>()? {
             parser.parse::<kw::nullref>()?;
             Ok(RefType::nullref())
@@ -327,6 +344,7 @@ impl<'a> Peek for RefType<'a> {
             || kw::i31ref::peek(cursor)?
             || kw::nullfuncref::peek(cursor)?
             || kw::nullexternref::peek(cursor)?
+            || kw::nullexnref::peek(cursor)?
             || kw::nullref::peek(cursor)?
             || (LParen::peek(cursor)? && kw::r#ref::peek2(cursor)?))
     }
diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs
index bb165741..e12f9bda 100644
--- a/crates/wast/src/lib.rs
+++ b/crates/wast/src/lib.rs
@@ -447,10 +447,12 @@ pub mod kw {
     custom_keyword!(nan_canonical = "nan:canonical");
     custom_keyword!(nofunc);
     custom_keyword!(noextern);
+    custom_keyword!(noexn);
     custom_keyword!(none);
     custom_keyword!(null);
     custom_keyword!(nullfuncref);
     custom_keyword!(nullexternref);
+    custom_keyword!(nullexnref);
     custom_keyword!(nullref);
     custom_keyword!(offset);
     custom_keyword!(outer);
diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs
index 5aacf8eb..b0fc67c0 100644
--- a/crates/wit-component/src/gc.rs
+++ b/crates/wit-component/src/gc.rs
@@ -496,7 +496,8 @@ impl<'a> Module<'a> {
             | HeapType::Struct
             | HeapType::Array
             | HeapType::I31
-            | HeapType::Exn => {}
+            | HeapType::Exn
+            | HeapType::NoExn => {}
             HeapType::Concrete(i) => self.ty(i.as_module_index().unwrap()),
         }
     }
@@ -1134,6 +1135,7 @@ impl Encoder {
             HeapType::Array => wasm_encoder::HeapType::Array,
             HeapType::I31 => wasm_encoder::HeapType::I31,
             HeapType::Exn => wasm_encoder::HeapType::Exn,
+            HeapType::NoExn => wasm_encoder::HeapType::NoExn,
             HeapType::Concrete(idx) => {
                 wasm_encoder::HeapType::Concrete(self.types.remap(idx.as_module_index().unwrap()))
             }


Test patch:
diff --git a/tests/local/exnref/exnref.wast b/tests/local/exnref/exnref.wast
index 421eca77..e0b51e16 100644
--- a/tests/local/exnref/exnref.wast
+++ b/tests/local/exnref/exnref.wast
@@ -2,4 +2,7 @@
   (func (param exnref))
   (func (param (ref null exn)))
   (func (param (ref exn)))
+  (func (param nullexnref))
+  (func (param (ref null noexn)))
+  (func (param (ref noexn)))
 )
diff --git a/tests/snapshots/local/exnref/exnref.wast/0.print b/tests/snapshots/local/exnref/exnref.wast/0.print
index 263d3868..168b0797 100644
--- a/tests/snapshots/local/exnref/exnref.wast/0.print
+++ b/tests/snapshots/local/exnref/exnref.wast/0.print
@@ -1,7 +1,12 @@
 (module
   (type (;0;) (func (param exnref)))
   (type (;1;) (func (param (ref exn))))
+  (type (;2;) (func (param nullexnref)))
+  (type (;3;) (func (param (ref noexn))))
   (func (;0;) (type 0) (param exnref))
   (func (;1;) (type 0) (param exnref))
   (func (;2;) (type 1) (param (ref exn)))
+  (func (;3;) (type 2) (param nullexnref))
+  (func (;4;) (type 2) (param nullexnref))
+  (func (;5;) (type 3) (param (ref noexn)))
 )