From 1ddd56d8b1b3687791e7fee75b1bcd0fac2805b4 Mon Sep 17 00:00:00 2001 From: Ryan Hunt Date: Tue, 26 Sep 2023 15:54:59 -0500 Subject: [PATCH] wast: Add exnref, try_table, and throw_ref These types and instructions are part of the exception handling rework tracked in (https://github.com/WebAssembly/exception-handling/issues/281). --- crates/wast/src/component/binary.rs | 3 ++ crates/wast/src/component/resolve.rs | 1 + crates/wast/src/core/binary.rs | 32 +++++++++++++++ crates/wast/src/core/expr.rs | 58 +++++++++++++++++++++++++++ crates/wast/src/core/resolve/names.rs | 14 +++++++ crates/wast/src/core/resolve/types.rs | 3 +- crates/wast/src/core/types.rs | 18 +++++++++ crates/wast/src/lib.rs | 2 + tests/local/exnref/exnref.wast | 5 +++ tests/local/exnref/throw_ref.wast | 6 +++ tests/local/exnref/try_table.wast | 53 ++++++++++++++++++++++++ tests/roundtrip.rs | 3 ++ 12 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 tests/local/exnref/exnref.wast create mode 100644 tests/local/exnref/throw_ref.wast create mode 100644 tests/local/exnref/try_table.wast diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index de12be2e8d..7edb4c2b16 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -599,6 +599,9 @@ impl From> for wasm_encoder::HeapType { match r { core::HeapType::Func => Self::Func, core::HeapType::Extern => Self::Extern, + core::HeapType::Exn => { + todo!("encoding of exceptions proposal types not yet implemented") + } core::HeapType::Index(Index::Num(i, _)) => Self::Indexed(i), core::HeapType::Index(_) => panic!("unresolved index"), core::HeapType::Any diff --git a/crates/wast/src/component/resolve.rs b/crates/wast/src/component/resolve.rs index c0df488fd8..3e3169aa6d 100644 --- a/crates/wast/src/component/resolve.rs +++ b/crates/wast/src/component/resolve.rs @@ -523,6 +523,7 @@ impl<'a> Resolver<'a> { ValType::Ref(r) => match &mut r.heap { core::HeapType::Func | core::HeapType::Extern + | core::HeapType::Exn | core::HeapType::Any | core::HeapType::Eq | core::HeapType::Array diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs index 01d3d64093..3852755c42 100644 --- a/crates/wast/src/core/binary.rs +++ b/crates/wast/src/core/binary.rs @@ -246,6 +246,7 @@ impl<'a> Encode for HeapType<'a> { match self { HeapType::Func => e.push(0x70), HeapType::Extern => e.push(0x6f), + HeapType::Exn => e.push(0x69), HeapType::Any => e.push(0x6e), HeapType::Eq => e.push(0x6d), HeapType::Struct => e.push(0x6b), @@ -277,6 +278,11 @@ impl<'a> Encode for RefType<'a> { nullable: true, heap: HeapType::Extern, } => e.push(0x6f), + // The 'exnref' binary abbreviation + RefType { + nullable: true, + heap: HeapType::Exn, + } => e.push(0x69), // The 'eqref' binary abbreviation RefType { nullable: true, @@ -1028,6 +1034,32 @@ impl Encode for Id<'_> { } } +impl<'a> Encode for TryTable<'a> { + fn encode(&self, dst: &mut Vec) { + self.block.encode(dst); + self.catches.encode(dst); + if let Some(catch_all) = &self.catch_all { + (1 as u8).encode(dst); + catch_all.encode(dst); + } else { + (0 as u8).encode(dst); + } + } +} + +impl<'a> Encode for TryTableCatch<'a> { + fn encode(&self, dst: &mut Vec) { + self.tag.encode(dst); + self.label.encode(dst); + } +} + +impl<'a> Encode for TryTableCatchAll<'a> { + fn encode(&self, dst: &mut Vec) { + self.label.encode(dst); + } +} + impl Encode for V128Const { fn encode(&self, dst: &mut Vec) { dst.extend_from_slice(&self.to_le_bytes()); diff --git a/crates/wast/src/core/expr.rs b/crates/wast/src/core/expr.rs index f89624e29b..0229fbf0c8 100644 --- a/crates/wast/src/core/expr.rs +++ b/crates/wast/src/core/expr.rs @@ -1149,6 +1149,10 @@ instructions! { Delegate(Index<'a>) : [0x18] : "delegate", CatchAll : [0x19] : "catch_all", + // Exception handling proposal extension for 'exnref' + ThrowRef : [0x0a] : "throw_ref", + TryTable(TryTable<'a>) : [0x1f] : "try_table", + // Relaxed SIMD proposal I8x16RelaxedSwizzle : [0xfd, 0x100]: "i8x16.relaxed_swizzle", I32x4RelaxedTruncF32x4S : [0xfd, 0x101]: "i32x4.relaxed_trunc_f32x4_s", @@ -1219,6 +1223,60 @@ impl<'a> Parse<'a> for BlockType<'a> { } } +#[derive(Debug)] +#[allow(missing_docs)] +pub struct TryTable<'a> { + pub block: Box>, + pub catches: Vec>, + pub catch_all: Option>, +} + +impl<'a> Parse<'a> for TryTable<'a> { + fn parse(parser: Parser<'a>) -> Result { + let block = parser.parse()?; + + let mut catches = Vec::new(); + while parser.peek2::()? { + catches.push(parser.parens(|p| { + p.parse::()?; + Ok(TryTableCatch { + tag: p.parse()?, + label: p.parse()?, + }) + })?); + } + + let mut catch_all = None; + if parser.peek2::()? { + catch_all = Some(parser.parens(|p| { + p.parse::()?; + Ok(TryTableCatchAll { + label: p.parse()?, + }) + })?); + } + + Ok(TryTable { + block, + catches, + catch_all, + }) + } +} + +#[derive(Debug)] +#[allow(missing_docs)] +pub struct TryTableCatch<'a> { + pub tag: Index<'a>, + pub label: Index<'a>, +} + +#[derive(Debug)] +#[allow(missing_docs)] +pub struct TryTableCatchAll<'a> { + pub label: Index<'a>, +} + /// Extra information associated with the func.bind instruction. #[derive(Debug)] #[allow(missing_docs)] diff --git a/crates/wast/src/core/resolve/names.rs b/crates/wast/src/core/resolve/names.rs index a82789d78d..4f252ad44b 100644 --- a/crates/wast/src/core/resolve/names.rs +++ b/crates/wast/src/core/resolve/names.rs @@ -521,6 +521,20 @@ impl<'a, 'b> ExprResolver<'a, 'b> { }); self.resolve_block_type(bt)?; } + TryTable(try_table) => { + self.blocks.push(ExprBlock { + label: try_table.block.label, + pushed_scope: false, + }); + self.resolve_block_type(&mut try_table.block)?; + for catch in &mut try_table.catches { + self.resolver.resolve(&mut catch.tag, Ns::Tag)?; + self.resolve_label(&mut catch.label)?; + } + if let Some(catch_all) = &mut try_table.catch_all { + self.resolve_label(&mut catch_all.label)?; + } + } // On `End` instructions we pop a label from the stack, and for both // `End` and `Else` instructions if they have labels listed we diff --git a/crates/wast/src/core/resolve/types.rs b/crates/wast/src/core/resolve/types.rs index aaf2988c18..bfb996b4b6 100644 --- a/crates/wast/src/core/resolve/types.rs +++ b/crates/wast/src/core/resolve/types.rs @@ -140,7 +140,8 @@ impl<'a> Expander<'a> { | Instruction::If(bt) | Instruction::Loop(bt) | Instruction::Let(LetType { block: bt, .. }) - | Instruction::Try(bt) => { + | Instruction::Try(bt) + | Instruction::TryTable(TryTable { block: bt, .. }) => { // No expansion necessary, a type reference is already here. // We'll verify that it's the same as the inline type, if any, // later. diff --git a/crates/wast/src/core/types.rs b/crates/wast/src/core/types.rs index e42e295de5..4bf69fe051 100644 --- a/crates/wast/src/core/types.rs +++ b/crates/wast/src/core/types.rs @@ -66,6 +66,8 @@ pub enum HeapType<'a> { /// A reference to any host value: externref. This is part of the reference /// types proposal. Extern, + /// A reference to a wasm exception. This is part of the exceptions proposal. + Exn, /// A reference to any reference value: anyref. This is part of the GC /// proposal. Any, @@ -98,6 +100,9 @@ impl<'a> Parse<'a> for HeapType<'a> { } else if l.peek::()? { parser.parse::()?; Ok(HeapType::Extern) + } else if l.peek::()? { + parser.parse::()?; + Ok(HeapType::Exn) } else if l.peek::()? { parser.parse::()?; Ok(HeapType::Any) @@ -134,6 +139,7 @@ impl<'a> Peek for HeapType<'a> { fn peek(cursor: Cursor<'_>) -> Result { Ok(kw::func::peek(cursor)? || kw::r#extern::peek(cursor)? + || kw::exn::peek(cursor)? || kw::any::peek(cursor)? || kw::eq::peek(cursor)? || kw::r#struct::peek(cursor)? @@ -174,6 +180,14 @@ impl<'a> RefType<'a> { } } + /// An `exnrefr` as an abbreviation for `(ref null exn)`. + pub fn exn() -> Self { + RefType { + nullable: true, + heap: HeapType::Exn, + } + } + /// An `anyref` as an abbreviation for `(ref null any)`. pub fn any() -> Self { RefType { @@ -251,6 +265,9 @@ impl<'a> Parse<'a> for RefType<'a> { } else if l.peek::()? { parser.parse::()?; Ok(RefType::r#extern()) + } else if l.peek::()? { + parser.parse::()?; + Ok(RefType::exn()) } else if l.peek::()? { parser.parse::()?; Ok(RefType::any()) @@ -306,6 +323,7 @@ impl<'a> Peek for RefType<'a> { Ok(kw::funcref::peek(cursor)? || /* legacy */ kw::anyfunc::peek(cursor)? || kw::externref::peek(cursor)? + || kw::exnref::peek(cursor)? || kw::anyref::peek(cursor)? || kw::eqref::peek(cursor)? || kw::structref::peek(cursor)? diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs index 220e955f53..34d5960f1f 100644 --- a/crates/wast/src/lib.rs +++ b/crates/wast/src/lib.rs @@ -404,6 +404,8 @@ pub mod kw { custom_keyword!(elem); custom_keyword!(end); custom_keyword!(tag); + custom_keyword!(exn); + custom_keyword!(exnref); custom_keyword!(export); custom_keyword!(r#extern = "extern"); custom_keyword!(externref); diff --git a/tests/local/exnref/exnref.wast b/tests/local/exnref/exnref.wast new file mode 100644 index 0000000000..421eca772e --- /dev/null +++ b/tests/local/exnref/exnref.wast @@ -0,0 +1,5 @@ +(module + (func (param exnref)) + (func (param (ref null exn))) + (func (param (ref exn))) +) diff --git a/tests/local/exnref/throw_ref.wast b/tests/local/exnref/throw_ref.wast new file mode 100644 index 0000000000..67ead8198d --- /dev/null +++ b/tests/local/exnref/throw_ref.wast @@ -0,0 +1,6 @@ +(module + (func (param exnref) + local.get 0 + throw_ref + ) +) diff --git a/tests/local/exnref/try_table.wast b/tests/local/exnref/try_table.wast new file mode 100644 index 0000000000..6df7302a70 --- /dev/null +++ b/tests/local/exnref/try_table.wast @@ -0,0 +1,53 @@ +(module + (tag $a (param i32)) + + (func + try_table + end + + try_table (result i32) + i32.const 0 + end + drop + + try_table (catch $a 0) + end + + try_table (catch $a 0) (catch $a 0) + end + + try_table (catch_all 0) + end + + try_table (catch $a 0) (catch_all 0) + end + + try_table (catch $a 0) (catch $a 0) (catch_all 0) + end + + try_table (result i32) (catch $a 0) + i32.const 0 + end + drop + + try_table (result i32) (catch $a 0) (catch $a 0) + i32.const 0 + end + drop + + try_table (result i32) (catch_all 0) + i32.const 0 + end + drop + + try_table (result i32) (catch $a 0) (catch_all 0) + i32.const 0 + end + drop + + try_table (result i32) (catch $a 0) (catch $a 0) (catch_all 0) + i32.const 0 + end + drop + ) +) diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index ecaf9a7d34..6d5eac7b4c 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -193,6 +193,9 @@ fn skip_validation(test: &Path) -> bool { "/proposals/gc/type-equivalence.wast", "/proposals/gc/type-rec.wast", "/proposals/gc/type-subtyping.wast", + "/exnref/exnref.wast", + "/exnref/throw_ref.wast", + "/exnref/try_table.wast", ]; let test_path = test.to_str().unwrap().replace("\\", "/"); // for windows paths if broken.iter().any(|x| test_path.contains(x)) {