From 5e1035c8a78cdf24edb070c7573c389bb3cb73bd Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 12 Dec 2023 11:57:09 -0800 Subject: [PATCH] Update the exception-handling validator implementation This commit updates the implementation of the exception-handling proposal to the latest version of the proposal. This was already implemented in the text format but all other crates needed updating. The changes were: * A new `exn` heap type is added for `exnref`. Currently this isn't a subtype with anything else, and this probably isn't correct but it's at least conservative for now. * The `try`, `delegate`, `catch`, `catch_all`, and `rethrow` instructions were all removed as they're no longer present. * New `try_table` and `throw_ref` instructions were added. * Support for the name section subsection for tags has been added along with printing tag names in the text format. * All exception-handling spec tests are now re-enabled. --- crates/wasm-encoder/src/core/code.rs | 67 ++-- crates/wasm-encoder/src/core/names.rs | 8 + crates/wasm-encoder/src/core/types.rs | 13 + crates/wasm-metadata/src/lib.rs | 3 +- crates/wasm-mutate/src/module.rs | 1 + crates/wasm-mutate/src/mutators/translate.rs | 7 + crates/wasm-smith/src/core/code_builder.rs | 188 ++++------- crates/wasmparser/src/binary_reader.rs | 9 +- crates/wasmparser/src/lib.rs | 7 +- crates/wasmparser/src/limits.rs | 1 + crates/wasmparser/src/readers/core/names.rs | 3 + .../wasmparser/src/readers/core/operators.rs | 59 +++- crates/wasmparser/src/readers/core/types.rs | 37 ++- crates/wasmparser/src/validator.rs | 9 + crates/wasmparser/src/validator/core.rs | 3 +- crates/wasmparser/src/validator/operators.rs | 153 +++++---- crates/wasmparser/src/validator/types.rs | 5 + crates/wasmprinter/src/lib.rs | 15 +- crates/wasmprinter/src/operator.rs | 112 ++++--- crates/wast/src/core/binary.rs | 9 +- crates/wast/src/core/expr.rs | 144 +-------- crates/wast/src/core/resolve/names.rs | 22 +- crates/wast/src/core/resolve/types.rs | 1 - crates/wit-component/src/gc.rs | 14 +- .../components/link-initialize/component.wat | 18 +- .../tests/components/link/component.wat | 18 +- fuzz/src/roundtrip.rs | 3 +- src/bin/wasm-tools/demangle.rs | 1 + src/bin/wasm-tools/dump.rs | 1 + tests/cli/dump/try-delegate.wat | 10 - tests/cli/dump/try-delegate.wat.stdout | 26 -- tests/local/exception-handling.wast | 63 ---- tests/local/exnref/try_table.wast | 95 ------ tests/local/try.wast | 55 ---- tests/local/try.wat | 15 - tests/roundtrip.rs | 13 +- .../local/exception-handling.wast/0.print | 43 --- .../local/exnref/exnref.wast/0.print | 7 + .../local/exnref/throw_ref.wast/0.print | 7 + tests/snapshots/local/try.wat.print | 47 --- .../exception-handling/imports.wast/0.print | 2 +- .../exception-handling/ref_null.wast/0.print | 20 ++ .../exception-handling/tag.wast/0.print | 2 +- .../exception-handling/tag.wast/2.print | 4 +- .../exception-handling/throw.wast/0.print | 77 +++++ .../exception-handling/throw_ref.wast/0.print | 132 ++++++++ .../exception-handling/try_table.wast/0.print | 9 + .../exception-handling/try_table.wast/2.print | 296 ++++++++++++++++++ .../try_table.wast/40.print | 23 ++ .../try_table.wast/44.print | 32 ++ 50 files changed, 1057 insertions(+), 852 deletions(-) delete mode 100644 tests/cli/dump/try-delegate.wat delete mode 100644 tests/cli/dump/try-delegate.wat.stdout delete mode 100644 tests/local/exception-handling.wast delete mode 100644 tests/local/exnref/try_table.wast delete mode 100644 tests/local/try.wast delete mode 100644 tests/local/try.wat delete mode 100644 tests/snapshots/local/exception-handling.wast/0.print create mode 100644 tests/snapshots/local/exnref/exnref.wast/0.print create mode 100644 tests/snapshots/local/exnref/throw_ref.wast/0.print delete mode 100644 tests/snapshots/local/try.wat.print create mode 100644 tests/snapshots/testsuite/proposals/exception-handling/ref_null.wast/0.print create mode 100644 tests/snapshots/testsuite/proposals/exception-handling/throw.wast/0.print create mode 100644 tests/snapshots/testsuite/proposals/exception-handling/throw_ref.wast/0.print create mode 100644 tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/0.print create mode 100644 tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/2.print create mode 100644 tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/40.print create mode 100644 tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/44.print diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs index e77707515a..899362d8bc 100644 --- a/crates/wasm-encoder/src/core/code.rs +++ b/crates/wasm-encoder/src/core/code.rs @@ -310,10 +310,6 @@ pub enum Instruction<'a> { Loop(BlockType), If(BlockType), Else, - Try(BlockType), - Delegate(u32), - Catch(u32), - CatchAll, End, Br(u32), BrIf(u32), @@ -327,8 +323,9 @@ pub enum Instruction<'a> { ReturnCallRef(u32), ReturnCall(u32), ReturnCallIndirect { ty: u32, table: u32 }, + TryTable(BlockType, Cow<'a, [Catch]>), Throw(u32), - Rethrow(u32), + ThrowRef, // Parametric instructions. Drop, @@ -916,21 +913,12 @@ impl Encode for Instruction<'_> { bt.encode(sink); } Instruction::Else => sink.push(0x05), - Instruction::Try(bt) => { - sink.push(0x06); - bt.encode(sink); - } - Instruction::Catch(t) => { - sink.push(0x07); - t.encode(sink); - } Instruction::Throw(t) => { sink.push(0x08); t.encode(sink); } - Instruction::Rethrow(l) => { - sink.push(0x09); - l.encode(sink); + Instruction::ThrowRef => { + sink.push(0x0A); } Instruction::End => sink.push(0x0B), Instruction::Br(l) => { @@ -982,13 +970,6 @@ impl Encode for Instruction<'_> { ty.encode(sink); table.encode(sink); } - Instruction::Delegate(l) => { - sink.push(0x18); - l.encode(sink); - } - Instruction::CatchAll => { - sink.push(0x19); - } // Parametric instructions. Instruction::Drop => sink.push(0x1A), @@ -998,6 +979,12 @@ impl Encode for Instruction<'_> { [ty].encode(sink); } + Instruction::TryTable(ty, ref catches) => { + sink.push(0x1f); + ty.encode(sink); + catches.encode(sink); + } + // Variable instructions. Instruction::LocalGet(l) => { sink.push(0x20); @@ -2975,6 +2962,40 @@ impl Encode for Instruction<'_> { } } +#[derive(Clone, Debug)] +#[allow(missing_docs)] +pub enum Catch { + One { tag: u32, label: u32 }, + OneRef { tag: u32, label: u32 }, + All { label: u32 }, + AllRef { label: u32 }, +} + +impl Encode for Catch { + fn encode(&self, sink: &mut Vec) { + match self { + Catch::One { tag, label } => { + sink.push(0x00); + tag.encode(sink); + label.encode(sink); + } + Catch::OneRef { tag, label } => { + sink.push(0x01); + tag.encode(sink); + label.encode(sink); + } + Catch::All { label } => { + sink.push(0x02); + label.encode(sink); + } + Catch::AllRef { label } => { + sink.push(0x03); + label.encode(sink); + } + } + } +} + /// A constant expression. /// /// Usable in contexts such as offsets or initializers. diff --git a/crates/wasm-encoder/src/core/names.rs b/crates/wasm-encoder/src/core/names.rs index f3b2fc3783..587ae6a792 100644 --- a/crates/wasm-encoder/src/core/names.rs +++ b/crates/wasm-encoder/src/core/names.rs @@ -154,6 +154,14 @@ impl NameSection { names.encode(&mut self.bytes); } + /// Appends a subsection for the names of all tags in this wasm module. + /// + /// This section should come after the data name subsection (if present). + pub fn tag(&mut self, names: &NameMap) { + self.subsection_header(Subsection::Tag, names.size()); + names.encode(&mut self.bytes); + } + /// Appends a subsection for the names of fields within types in this /// wasm module. /// diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs index 8f47527d85..ab4d659425 100644 --- a/crates/wasm-encoder/src/core/types.rs +++ b/crates/wasm-encoder/src/core/types.rs @@ -255,6 +255,8 @@ impl ValType { pub const FUNCREF: ValType = ValType::Ref(RefType::FUNCREF); /// Alias for the `externref` type in WebAssembly pub const EXTERNREF: ValType = ValType::Ref(RefType::EXTERNREF); + /// Alias for the `exnref` type in WebAssembly + pub const EXNREF: ValType = ValType::Ref(RefType::EXNREF); } impl Encode for StorageType { @@ -304,6 +306,12 @@ impl RefType { nullable: true, heap_type: HeapType::Extern, }; + + /// Alias for the `exnref` type in WebAssembly + pub const EXNREF: RefType = RefType { + nullable: true, + heap_type: HeapType::Exn, + }; } impl Encode for RefType { @@ -393,6 +401,9 @@ pub enum HeapType { /// The unboxed `i31` heap type. I31, + /// The abstract` exception` heap type. + Exn, + /// A concrete Wasm-defined type at the given index. Concrete(u32), } @@ -410,6 +421,7 @@ impl Encode for HeapType { HeapType::Struct => sink.push(0x6B), HeapType::Array => sink.push(0x6A), HeapType::I31 => sink.push(0x6C), + HeapType::Exn => sink.push(0x69), // 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), @@ -434,6 +446,7 @@ impl TryFrom for HeapType { wasmparser::HeapType::Struct => HeapType::Struct, wasmparser::HeapType::Array => HeapType::Array, wasmparser::HeapType::I31 => HeapType::I31, + wasmparser::HeapType::Exn => HeapType::Exn, }) } } diff --git a/crates/wasm-metadata/src/lib.rs b/crates/wasm-metadata/src/lib.rs index 53f8ec0801..537851bede 100644 --- a/crates/wasm-metadata/src/lib.rs +++ b/crates/wasm-metadata/src/lib.rs @@ -639,7 +639,8 @@ impl<'a> ModuleNames<'a> { wasmparser::Name::Memory(m) => section.memories(&name_map(&m)?), wasmparser::Name::Global(m) => section.globals(&name_map(&m)?), wasmparser::Name::Element(m) => section.elements(&name_map(&m)?), - wasmparser::Name::Data(m) => section.types(&name_map(&m)?), + wasmparser::Name::Data(m) => section.data(&name_map(&m)?), + wasmparser::Name::Tag(m) => section.tags(&name_map(&m)?), wasmparser::Name::Unknown { .. } => {} // wasm-encoder doesn't support it } } diff --git a/crates/wasm-mutate/src/module.rs b/crates/wasm-mutate/src/module.rs index 536a596d54..486c9690d2 100644 --- a/crates/wasm-mutate/src/module.rs +++ b/crates/wasm-mutate/src/module.rs @@ -90,6 +90,7 @@ pub fn map_ref_type(ref_ty: wasmparser::RefType) -> Result { wasmparser::HeapType::Struct => HeapType::Struct, wasmparser::HeapType::Array => HeapType::Array, wasmparser::HeapType::I31 => HeapType::I31, + wasmparser::HeapType::Exn => HeapType::Exn, 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 50c97eceae..555d1c6903 100644 --- a/crates/wasm-mutate/src/mutators/translate.rs +++ b/crates/wasm-mutate/src/mutators/translate.rs @@ -210,6 +210,7 @@ pub fn heapty(t: &mut dyn Translator, ty: &wasmparser::HeapType) -> Result Ok(HeapType::Struct), wasmparser::HeapType::Array => Ok(HeapType::Array), wasmparser::HeapType::I31 => Ok(HeapType::I31), + wasmparser::HeapType::Exn => Ok(HeapType::Exn), wasmparser::HeapType::Concrete(i) => Ok(HeapType::Concrete( t.remap(Item::Type, i.as_module_index().unwrap())?, )), @@ -362,6 +363,7 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result ($arg); (map $arg:ident lane) => (*$arg); (map $arg:ident lanes) => (*$arg); + (map $arg:ident try_table) => ($arg); // This case takes the arguments of a wasmparser instruction and creates // a wasm-encoder instruction. There are a few special cases for where @@ -374,6 +376,7 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result (I::F32Const(f32::from_bits($arg.bits()))); (build F64Const $arg:ident) => (I::F64Const(f64::from_bits($arg.bits()))); (build V128Const $arg:ident) => (I::V128Const($arg.i128())); + (build TryTable $table:ident) => (unimplemented_try_table()); (build $op:ident $arg:ident) => (I::$op($arg)); (build CallIndirect $ty:ident $table:ident $_:ident) => (I::CallIndirect { ty: $ty, @@ -391,6 +394,10 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result wasm_encoder::Instruction<'static> { + unimplemented!() +} + pub fn block_type(t: &mut dyn Translator, ty: &wasmparser::BlockType) -> Result { match ty { wasmparser::BlockType::Empty => Ok(BlockType::Empty), diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 28f0d49f33..1ad475936e 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -7,7 +7,7 @@ use arbitrary::{Result, Unstructured}; use std::collections::{BTreeMap, BTreeSet}; use std::convert::TryFrom; use std::rc::Rc; -use wasm_encoder::{BlockType, ConstExpr, ExportKind, GlobalType, MemArg, RefType}; +use wasm_encoder::{BlockType, Catch, ConstExpr, ExportKind, GlobalType, MemArg, RefType}; mod no_traps; macro_rules! instructions { @@ -94,10 +94,7 @@ instructions! { (None, nop, Control, 800), (None, block, Control), (None, r#loop, Control), - (Some(try_valid), r#try, Control), - (Some(delegate_valid), delegate, Control), - (Some(catch_valid), catch, Control), - (Some(catch_all_valid), catch_all, Control), + (Some(try_table_valid), try_table, Control), (Some(if_valid), r#if, Control), (Some(else_valid), r#else, Control), (Some(end_valid), end, Control), @@ -110,7 +107,7 @@ instructions! { (Some(return_call_valid), return_call, Control), (Some(return_call_indirect_valid), return_call_indirect, Control), (Some(throw_valid), throw, Control, 850), - (Some(rethrow_valid), rethrow, Control), + (Some(throw_ref_valid), throw_ref, Control, 850), // Parametric instructions. (Some(drop_valid), drop, Parametric, 990), (Some(select_valid), select, Parametric), @@ -662,9 +659,7 @@ enum ControlKind { Block, If, Loop, - Try, - Catch, - CatchAll, + TryTable, } enum Float { @@ -1310,114 +1305,69 @@ fn block( } #[inline] -fn try_valid(module: &Module, _: &mut CodeBuilder) -> bool { +fn try_table_valid(module: &Module, _: &mut CodeBuilder) -> bool { module.config.exceptions_enabled() } -fn r#try( +fn try_table<'m>( u: &mut Unstructured, module: &Module, - builder: &mut CodeBuilder, + builder: &'m mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { let block_ty = builder.arbitrary_block_type(u, module)?; - let (params, results) = module.params_results(&block_ty); - let height = builder.allocs.operands.len() - params.len(); - builder.allocs.controls.push(Control { - kind: ControlKind::Try, - params, - results, - height, - }); - instructions.push(Instruction::Try(block_ty)); - Ok(()) -} -#[inline] -fn delegate_valid(module: &Module, builder: &mut CodeBuilder) -> bool { - let control_kind = builder.allocs.controls.last().unwrap().kind; - // delegate is only valid if end could be used in a try control frame - module.config.exceptions_enabled() - && control_kind == ControlKind::Try - && end_valid(module, builder) -} + let mut catch_options: Vec) -> Result + 'm>> = + Vec::new(); -fn delegate( - u: &mut Unstructured, - _: &Module, - builder: &mut CodeBuilder, - instructions: &mut Vec, -) -> Result<()> { - // There will always be at least the function's return frame and try - // control frame if we are emitting delegate - let n = builder.allocs.controls.iter().count(); - debug_assert!(n >= 2); - // Delegate must target an outer control from the try block, and is - // encoded with relative depth from the outer control - let target_relative_from_last = u.int_in_range(1..=n - 1)?; - let target_relative_from_outer = target_relative_from_last - 1; - // Delegate ends the try block - builder.allocs.controls.pop(); - instructions.push(Instruction::Delegate(target_relative_from_outer as u32)); - Ok(()) -} + for (i, ctrl) in builder.allocs.controls.iter().rev().enumerate() { + let i = i as u32; -#[inline] -fn catch_valid(module: &Module, builder: &mut CodeBuilder) -> bool { - let control_kind = builder.allocs.controls.last().unwrap().kind; - // catch is only valid if end could be used in a try or catch (not - // catch_all) control frame. There must also be a tag that we can catch. - module.config.exceptions_enabled() - && (control_kind == ControlKind::Try || control_kind == ControlKind::Catch) - && end_valid(module, builder) - && module.tags.len() > 0 -} + let label_types = ctrl.label_types(); + if label_types.is_empty() { + catch_options.push(Box::new(move |_| Ok(Catch::All { label: i }))); + } + if label_types == [ValType::EXNREF] { + catch_options.push(Box::new(move |_| Ok(Catch::AllRef { label: i }))); + } -fn catch( - u: &mut Unstructured, - module: &Module, - builder: &mut CodeBuilder, - instructions: &mut Vec, -) -> Result<()> { - let tag_idx = u.int_in_range(0..=(module.tags.len() - 1))?; - let tag_type = &module.tags[tag_idx]; - let control = builder.allocs.controls.pop().unwrap(); - // Pop the results for the previous try or catch - builder.pop_operands(&control.results); - // Push the params of the tag we're catching - builder.push_operands(&tag_type.func_type.params); - builder.allocs.controls.push(Control { - kind: ControlKind::Catch, - ..control - }); - instructions.push(Instruction::Catch(tag_idx as u32)); - Ok(()) -} + if let Some(tags) = builder.allocs.tags.get(label_types) { + catch_options.push(Box::new(move |u| { + Ok(Catch::One { + tag: *u.choose(&tags)?, + label: i, + }) + })); + } -#[inline] -fn catch_all_valid(module: &Module, builder: &mut CodeBuilder) -> bool { - let control_kind = builder.allocs.controls.last().unwrap().kind; - // catch_all is only valid if end could be used in a try or catch (not - // catch_all) control frame. - module.config.exceptions_enabled() - && (control_kind == ControlKind::Try || control_kind == ControlKind::Catch) - && end_valid(module, builder) -} + let mut label_types_with_exnref = label_types.to_vec(); + label_types_with_exnref.push(ValType::EXNREF); + if let Some(tags) = builder.allocs.tags.get(&label_types_with_exnref) { + catch_options.push(Box::new(move |u| { + Ok(Catch::OneRef { + tag: *u.choose(&tags)?, + label: i, + }) + })); + } + } -fn catch_all( - _: &mut Unstructured, - _: &Module, - builder: &mut CodeBuilder, - instructions: &mut Vec, -) -> Result<()> { - let control = builder.allocs.controls.pop().unwrap(); - // Pop the results for the previous try or catch - builder.pop_operands(&control.results); + let mut catches = Vec::new(); + if catch_options.len() > 0 { + for _ in 0..u.int_in_range(0..=10)? { + catches.push(u.choose(&mut catch_options)?(u)?); + } + } + + let (params, results) = module.params_results(&block_ty); + let height = builder.allocs.operands.len() - params.len(); builder.allocs.controls.push(Control { - kind: ControlKind::CatchAll, - ..control + kind: ControlKind::TryTable, + params, + results, + height, }); - instructions.push(Instruction::CatchAll); + instructions.push(Instruction::TryTable(block_ty, catches.into())); Ok(()) } @@ -1867,40 +1817,18 @@ fn throw( } #[inline] -fn rethrow_valid(module: &Module, builder: &mut CodeBuilder) -> bool { - // There must be a catch or catch_all control on the stack - module.config.exceptions_enabled() - && builder - .allocs - .controls - .iter() - .any(|l| l.kind == ControlKind::Catch || l.kind == ControlKind::CatchAll) +fn throw_ref_valid(module: &Module, builder: &mut CodeBuilder) -> bool { + module.config.exceptions_enabled() && builder.types_on_stack(&[ValType::EXNREF]) } -fn rethrow( - u: &mut Unstructured, - _: &Module, +fn throw_ref( + _u: &mut Unstructured, + _module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let n = builder - .allocs - .controls - .iter() - .filter(|l| l.kind == ControlKind::Catch || l.kind == ControlKind::CatchAll) - .count(); - debug_assert!(n > 0); - let i = u.int_in_range(0..=n - 1)?; - let (target, _) = builder - .allocs - .controls - .iter() - .rev() - .enumerate() - .filter(|(_, l)| l.kind == ControlKind::Catch || l.kind == ControlKind::CatchAll) - .nth(i) - .unwrap(); - instructions.push(Instruction::Rethrow(target as u32)); + builder.pop_operands(&[ValType::EXNREF]); + instructions.push(Instruction::ThrowRef); Ok(()) } diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs index 50dd3fb801..22c64e3b66 100644 --- a/crates/wasmparser/src/binary_reader.rs +++ b/crates/wasmparser/src/binary_reader.rs @@ -681,7 +681,7 @@ impl<'a> BinaryReader<'a> { Ok(self.buffer[self.position]) } - fn read_block_type(&mut self) -> Result { + pub(crate) fn read_block_type(&mut self) -> Result { let b = self.peek()?; // Check for empty block @@ -766,10 +766,8 @@ impl<'a> BinaryReader<'a> { 0x03 => visitor.visit_loop(self.read_block_type()?), 0x04 => visitor.visit_if(self.read_block_type()?), 0x05 => visitor.visit_else(), - 0x06 => visitor.visit_try(self.read_block_type()?), - 0x07 => visitor.visit_catch(self.read_var_u32()?), 0x08 => visitor.visit_throw(self.read_var_u32()?), - 0x09 => visitor.visit_rethrow(self.read_var_u32()?), + 0x0a => visitor.visit_throw_ref(), 0x0b => visitor.visit_end(), 0x0c => visitor.visit_br(self.read_var_u32()?), 0x0d => visitor.visit_br_if(self.read_var_u32()?), @@ -785,8 +783,6 @@ impl<'a> BinaryReader<'a> { 0x13 => visitor.visit_return_call_indirect(self.read_var_u32()?, self.read_var_u32()?), 0x14 => visitor.visit_call_ref(self.read()?), 0x15 => visitor.visit_return_call_ref(self.read()?), - 0x18 => visitor.visit_delegate(self.read_var_u32()?), - 0x19 => visitor.visit_catch_all(), 0x1a => visitor.visit_drop(), 0x1b => visitor.visit_select(), 0x1c => { @@ -799,6 +795,7 @@ impl<'a> BinaryReader<'a> { } visitor.visit_typed_select(self.read()?) } + 0x1f => visitor.visit_try_table(self.read()?), 0x20 => visitor.visit_local_get(self.read_var_u32()?), 0x21 => visitor.visit_local_set(self.read_var_u32()?), diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs index 9e8fd9ed5a..3cd1ca59c7 100644 --- a/crates/wasmparser/src/lib.rs +++ b/crates/wasmparser/src/lib.rs @@ -129,10 +129,9 @@ macro_rules! for_each_operator { @mvp Loop { blockty: $crate::BlockType } => visit_loop @mvp If { blockty: $crate::BlockType } => visit_if @mvp Else => visit_else - @exceptions Try { blockty: $crate::BlockType } => visit_try - @exceptions Catch { tag_index: u32 } => visit_catch + @exceptions TryTable { try_table: $crate::TryTable } => visit_try_table @exceptions Throw { tag_index: u32 } => visit_throw - @exceptions Rethrow { relative_depth: u32 } => visit_rethrow + @exceptions ThrowRef => visit_throw_ref @mvp End => visit_end @mvp Br { relative_depth: u32 } => visit_br @mvp BrIf { relative_depth: u32 } => visit_br_if @@ -142,8 +141,6 @@ macro_rules! for_each_operator { @mvp CallIndirect { type_index: u32, table_index: u32, table_byte: u8 } => visit_call_indirect @tail_call ReturnCall { function_index: u32 } => visit_return_call @tail_call ReturnCallIndirect { type_index: u32, table_index: u32 } => visit_return_call_indirect - @exceptions Delegate { relative_depth: u32 } => visit_delegate - @exceptions CatchAll => visit_catch_all @mvp Drop => visit_drop @mvp Select => visit_select @reference_types TypedSelect { ty: $crate::ValType } => visit_typed_select diff --git a/crates/wasmparser/src/limits.rs b/crates/wasmparser/src/limits.rs index 55acf53e49..9cebf27bc1 100644 --- a/crates/wasmparser/src/limits.rs +++ b/crates/wasmparser/src/limits.rs @@ -36,6 +36,7 @@ pub const MAX_WASM_MEMORIES: usize = 100; pub const MAX_WASM_TAGS: usize = 1_000_000; pub const MAX_WASM_BR_TABLE_SIZE: usize = MAX_WASM_FUNCTION_SIZE; pub const MAX_WASM_STRUCT_FIELDS: usize = 10_000; +pub const MAX_WASM_CATCHES: usize = 10_000; // Component-related limits pub const MAX_WASM_MODULE_SIZE: usize = 1024 * 1024 * 1024; //= 1 GiB diff --git a/crates/wasmparser/src/readers/core/names.rs b/crates/wasmparser/src/readers/core/names.rs index aa8a11dde2..b47824272d 100644 --- a/crates/wasmparser/src/readers/core/names.rs +++ b/crates/wasmparser/src/readers/core/names.rs @@ -101,6 +101,8 @@ pub enum Name<'a> { Element(NameMap<'a>), /// The name is for the data segments. Data(NameMap<'a>), + /// The name is for tags. + Tag(NameMap<'a>), /// An unknown [name subsection](https://webassembly.github.io/spec/core/appendix/custom.html#subsections). Unknown { /// The identifier for this subsection. @@ -143,6 +145,7 @@ impl<'a> Subsection<'a> for Name<'a> { 7 => Name::Global(NameMap::new(data, offset)?), 8 => Name::Element(NameMap::new(data, offset)?), 9 => Name::Data(NameMap::new(data, offset)?), + 11 => Name::Tag(NameMap::new(data, offset)?), ty => Name::Unknown { ty, data, diff --git a/crates/wasmparser/src/readers/core/operators.rs b/crates/wasmparser/src/readers/core/operators.rs index d1312c259f..004982e7ab 100644 --- a/crates/wasmparser/src/readers/core/operators.rs +++ b/crates/wasmparser/src/readers/core/operators.rs @@ -13,7 +13,8 @@ * limitations under the License. */ -use crate::{BinaryReader, BinaryReaderError, Result, ValType}; +use crate::limits::MAX_WASM_CATCHES; +use crate::{BinaryReader, BinaryReaderError, FromReader, Result, ValType}; /// Represents a block type. #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -352,3 +353,59 @@ impl<'a, V: VisitOperator<'a> + ?Sized> VisitOperator<'a> for Box { } for_each_operator!(define_visit_operator_delegate); } + +/// A `try_table` entries representation. +#[derive(Clone, Debug)] +pub struct TryTable { + /// The block type describing the try block itself. + pub ty: BlockType, + /// Outer blocks which will receive exceptions. + pub catches: Vec, +} + +/// Catch clauses that can be specified in [`TryTable`]. +#[derive(Copy, Clone, Debug)] +#[allow(missing_docs)] +pub enum Catch { + /// Equivalent of `catch` + One { tag: u32, label: u32 }, + /// Equivalent of `catch_ref` + OneRef { tag: u32, label: u32 }, + /// Equivalent of `catch_all` + All { label: u32 }, + /// Equivalent of `catch_all_ref` + AllRef { label: u32 }, +} + +impl<'a> FromReader<'a> for TryTable { + fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + let ty = reader.read_block_type()?; + let catches = reader + .read_iter(MAX_WASM_CATCHES, "catches")? + .collect::>()?; + Ok(TryTable { ty, catches }) + } +} + +impl<'a> FromReader<'a> for Catch { + fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + Ok(match reader.read_u8()? { + 0x00 => Catch::One { + tag: reader.read_var_u32()?, + label: reader.read_var_u32()?, + }, + 0x01 => Catch::OneRef { + tag: reader.read_var_u32()?, + label: reader.read_var_u32()?, + }, + 0x02 => Catch::All { + label: reader.read_var_u32()?, + }, + 0x03 => Catch::AllRef { + label: reader.read_var_u32()?, + }, + + x => return reader.invalid_leading_byte(x, "catch"), + }) + } +} diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs index 6ebe3d485d..47d25e297c 100644 --- a/crates/wasmparser/src/readers/core/types.rs +++ b/crates/wasmparser/src/readers/core/types.rs @@ -680,6 +680,9 @@ impl ValType { /// Alias for the wasm `externref` type. pub const EXTERNREF: ValType = ValType::Ref(RefType::EXTERNREF); + /// Alias for the wasm `exnref` type. + pub const EXNREF: ValType = ValType::Ref(RefType::EXNREF); + /// Returns whether this value type is a "reference type". /// /// Only reference types are allowed in tables, for example, and with some @@ -763,6 +766,8 @@ impl ValType { // 0011 = extern // 0010 = noextern // +// 0001 = exn +// // 0000 = none // ``` #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -791,6 +796,8 @@ impl std::fmt::Debug for RefType { (false, HeapType::Extern) => write!(f, "(ref extern)"), (true, HeapType::Func) => write!(f, "funcref"), (false, HeapType::Func) => write!(f, "(ref func)"), + (true, HeapType::Exn) => write!(f, "exnref"), + (false, HeapType::Exn) => write!(f, "(ref exn)"), (true, HeapType::Concrete(idx)) => write!(f, "(ref null {idx})"), (false, HeapType::Concrete(idx)) => write!(f, "(ref {idx})"), } @@ -840,6 +847,7 @@ impl RefType { const NOFUNC_ABSTYPE: u32 = 0b0100 << 18; const EXTERN_ABSTYPE: u32 = 0b0011 << 18; const NOEXTERN_ABSTYPE: u32 = 0b0010 << 18; + const EXN_ABSTYPE: u32 = 0b0001 << 18; const NONE_ABSTYPE: u32 = 0b0000 << 18; // The `index` is valid only when `concrete == 1`. @@ -853,6 +861,10 @@ impl RefType { /// `externref`. pub const EXTERNREF: Self = RefType::EXTERN.nullable(); + /// A nullable reference to an exception object aka `(ref null exn)` aka + /// `exnref`. + pub const EXNREF: Self = RefType::EXN.nullable(); + /// A non-nullable untyped function reference aka `(ref func)`. pub const FUNC: Self = RefType::from_u32(Self::FUNC_ABSTYPE); @@ -883,6 +895,9 @@ impl RefType { /// A non-nullable reference to an i31 object aka `(ref i31)`. pub const I31: Self = RefType::from_u32(Self::I31_ABSTYPE); + /// A non-nullable reference to an exn object aka `(ref exn)`. + pub const EXN: Self = RefType::from_u32(Self::EXN_ABSTYPE); + const fn can_represent_type_index(index: u32) -> bool { index & Self::INDEX_MASK == index } @@ -922,6 +937,7 @@ impl RefType { | Self::EXTERN_ABSTYPE | Self::NOEXTERN_ABSTYPE | Self::NONE_ABSTYPE + | Self::EXN_ABSTYPE ) ); @@ -959,6 +975,7 @@ impl RefType { HeapType::Struct => Some(Self::from_u32(nullable32 | Self::STRUCT_ABSTYPE)), 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)), } } @@ -1039,6 +1056,7 @@ impl RefType { Self::STRUCT_ABSTYPE => HeapType::Struct, Self::ARRAY_ABSTYPE => HeapType::Array, Self::I31_ABSTYPE => HeapType::I31, + Self::EXN_ABSTYPE => HeapType::Exn, _ => unreachable!(), } } @@ -1059,6 +1077,7 @@ impl RefType { (true, HeapType::Struct) => "structref", (true, HeapType::Array) => "arrayref", (true, HeapType::I31) => "i31ref", + (true, HeapType::Exn) => "exnref", (false, HeapType::Func) => "(ref func)", (false, HeapType::Extern) => "(ref extern)", (false, HeapType::Concrete(_)) => "(ref $type)", @@ -1070,6 +1089,7 @@ impl RefType { (false, HeapType::Struct) => "(ref struct)", (false, HeapType::Array) => "(ref array)", (false, HeapType::I31) => "(ref i31)", + (false, HeapType::Exn) => "(ref exn)", } } } @@ -1150,13 +1170,18 @@ pub enum HeapType { /// /// Introduced in the GC proposal. I31, + + /// The abstraction `exception` heap type. + /// + /// Introduced in the exception-handling proposal. + Exn, } 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 => true, + | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C | 0x69 => true, _ => false, } } @@ -1201,9 +1226,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 => { - Ok(ValType::Ref(reader.read()?)) - } + 0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C + | 0x69 => Ok(ValType::Ref(reader.read()?)), _ => bail!(reader.original_position(), "invalid value type"), } } @@ -1222,6 +1246,7 @@ impl<'a> FromReader<'a> for RefType { 0x6B => Ok(RefType::STRUCT.nullable()), 0x6A => Ok(RefType::ARRAY.nullable()), 0x6C => Ok(RefType::I31.nullable()), + 0x69 => Ok(RefType::EXN.nullable()), byte @ (0x63 | 0x64) => { let nullable = byte == 0x63; let pos = reader.original_position(); @@ -1276,6 +1301,10 @@ impl<'a> FromReader<'a> for HeapType { reader.position += 1; Ok(HeapType::I31) } + 0x69 => { + reader.position += 1; + Ok(HeapType::Exn) + } _ => { 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 e6309354eb..915c900aed 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -352,6 +352,15 @@ impl WasmFeatures { Err("heap types not supported without the gc feature") } } + + // These types were added in the exception-handling proposal. + (HeapType::Exn, _) => { + if self.exceptions { + Ok(()) + } else { + Err("heap types not supported without the gc feature") + } + } } } } diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs index 25fb16282f..ca27c3c920 100644 --- a/crates/wasmparser/src/validator/core.rs +++ b/crates/wasmparser/src/validator/core.rs @@ -961,7 +961,8 @@ impl Module { | HeapType::Eq | HeapType::Struct | HeapType::Array - | HeapType::I31 => return Ok(()), + | HeapType::I31 + | HeapType::Exn => return Ok(()), HeapType::Concrete(type_index) => type_index, }; match type_index { diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs index 9f6a73697d..7c6657cb65 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -23,9 +23,9 @@ // the various methods here. use crate::{ - limits::MAX_WASM_FUNCTION_LOCALS, BinaryReaderError, BlockType, BrTable, HeapType, Ieee32, - Ieee64, MemArg, RefType, Result, UnpackedIndex, ValType, VisitOperator, WasmFeatures, - WasmFuncType, WasmModuleResources, V128, + limits::MAX_WASM_FUNCTION_LOCALS, BinaryReaderError, BlockType, BrTable, Catch, HeapType, + Ieee32, Ieee64, MemArg, RefType, Result, TryTable, UnpackedIndex, ValType, VisitOperator, + WasmFeatures, WasmFuncType, WasmModuleResources, V128, }; use std::ops::{Deref, DerefMut}; @@ -117,19 +117,7 @@ pub enum FrameKind { /// # Note /// /// This belongs to the Wasm exception handling proposal. - Try, - /// A Wasm `catch` control block. - /// - /// # Note - /// - /// This belongs to the Wasm exception handling proposal. - Catch, - /// A Wasm `catch_all` control block. - /// - /// # Note - /// - /// This belongs to the Wasm exception handling proposal. - CatchAll, + TryTable, } struct OperatorValidatorTemp<'validator, 'resources, T> { @@ -1195,34 +1183,74 @@ where self.push_ctrl(FrameKind::Else, frame.block_type)?; Ok(()) } - fn visit_try(&mut self, mut ty: BlockType) -> Self::Output { - self.check_block_type(&mut ty)?; - for ty in self.params(ty)?.rev() { + fn visit_try_table(&mut self, mut ty: TryTable) -> Self::Output { + self.check_block_type(&mut ty.ty)?; + for ty in self.params(ty.ty)?.rev() { self.pop_operand(Some(ty))?; } - self.push_ctrl(FrameKind::Try, ty)?; - Ok(()) - } - fn visit_catch(&mut self, index: u32) -> Self::Output { - let frame = self.pop_ctrl()?; - if frame.kind != FrameKind::Try && frame.kind != FrameKind::Catch { - bail!(self.offset, "catch found outside of an `try` block"); - } - // Start a new frame and push `exnref` value. - let height = self.operands.len(); - let init_height = self.inits.len(); - self.control.push(Frame { - kind: FrameKind::Catch, - block_type: frame.block_type, - height, - unreachable: false, - init_height, - }); - // Push exception argument types. - let ty = self.tag_at(index)?; - for ty in ty.inputs() { - self.push_operand(ty)?; + for catch in ty.catches { + match catch { + Catch::One { tag, label } => { + let tag = self.tag_at(tag)?; + let (ty, kind) = self.jump(label)?; + let params = tag.inputs(); + let types = self.label_types(ty, kind)?; + if params.len() != types.len() { + bail!( + self.offset, + "type mismatch: catch label must have same number of types as tag" + ); + } + for (expected, actual) in types.zip(params) { + self.push_operand(actual)?; + self.pop_operand(Some(expected))?; + } + } + Catch::OneRef { tag, label } => { + let tag = self.tag_at(tag)?; + let (ty, kind) = self.jump(label)?; + let params = tag.inputs(); + let types = self.label_types(ty, kind)?; + if params.len() + 1 != types.len() { + bail!( + self.offset, + "type mismatch: catch_ref label must have one \ + more type than tag types", + ); + } + for (expected, actual) in types.zip(params.chain([ValType::EXNREF])) { + self.push_operand(actual)?; + self.pop_operand(Some(expected))?; + } + } + + Catch::All { label } => { + let (ty, kind) = self.jump(label)?; + if self.label_types(ty, kind)?.len() != 0 { + bail!( + self.offset, + "type mismatch: catch_all label must have no result types" + ); + } + } + + Catch::AllRef { label } => { + let (ty, kind) = self.jump(label)?; + let mut types = self.label_types(ty, kind)?; + match (types.next(), types.next()) { + (Some(ValType::EXNREF), None) => {} + _ => { + bail!( + self.offset, + "type mismatch: catch_all_ref label must have \ + one exnref result type" + ); + } + } + } + } } + self.push_ctrl(FrameKind::TryTable, ty.ty)?; Ok(()) } fn visit_throw(&mut self, index: u32) -> Self::Output { @@ -1240,50 +1268,11 @@ where self.unreachable()?; Ok(()) } - fn visit_rethrow(&mut self, relative_depth: u32) -> Self::Output { - // This is not a jump, but we need to check that the `rethrow` - // targets an actual `catch` to get the exception. - let (_, kind) = self.jump(relative_depth)?; - if kind != FrameKind::Catch && kind != FrameKind::CatchAll { - bail!( - self.offset, - "invalid rethrow label: target was not a `catch` block" - ); - } + fn visit_throw_ref(&mut self) -> Self::Output { + self.pop_operand(Some(ValType::EXNREF))?; self.unreachable()?; Ok(()) } - fn visit_delegate(&mut self, relative_depth: u32) -> Self::Output { - let frame = self.pop_ctrl()?; - if frame.kind != FrameKind::Try { - bail!(self.offset, "delegate found outside of an `try` block"); - } - // This operation is not a jump, but we need to check the - // depth for validity - let _ = self.jump(relative_depth)?; - for ty in self.results(frame.block_type)? { - self.push_operand(ty)?; - } - Ok(()) - } - fn visit_catch_all(&mut self) -> Self::Output { - let frame = self.pop_ctrl()?; - if frame.kind == FrameKind::CatchAll { - bail!(self.offset, "only one catch_all allowed per `try` block"); - } else if frame.kind != FrameKind::Try && frame.kind != FrameKind::Catch { - bail!(self.offset, "catch_all found outside of a `try` block"); - } - let height = self.operands.len(); - let init_height = self.inits.len(); - self.control.push(Frame { - kind: FrameKind::CatchAll, - block_type: frame.block_type, - height, - unreachable: false, - init_height, - }); - Ok(()) - } fn visit_end(&mut self) -> Self::Output { let mut frame = self.pop_ctrl()?; diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index a893acb64e..acab322d17 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -2708,6 +2708,10 @@ impl TypeList { | (HT::Struct, _) | (HT::Array, _) | (HT::I31, _) => false, + + // TODO: this probably isn't right, this is probably related to some + // gc type. + (HT::Exn, _) => false, } } @@ -2745,6 +2749,7 @@ impl TypeList { | HeapType::Array | HeapType::I31 | HeapType::None => HeapType::Any, + HeapType::Exn => HeapType::Exn, } } diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index 670346372d..dffc5fc8ec 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -68,6 +68,7 @@ struct CoreState { local_names: HashMap<(u32, u32), Naming>, label_names: HashMap<(u32, u32), Naming>, type_names: HashMap, + tag_names: HashMap, table_names: HashMap, memory_names: HashMap, global_names: HashMap, @@ -579,6 +580,7 @@ impl Printer { Name::Global(n) => name_map(&mut state.core.global_names, n, "global")?, Name::Element(n) => name_map(&mut state.core.element_names, n, "elem")?, Name::Data(n) => name_map(&mut state.core.data_names, n, "data")?, + Name::Tag(n) => name_map(&mut state.core.tag_names, n, "tag")?, Name::Unknown { .. } => (), } } @@ -886,6 +888,7 @@ impl Printer { RefType::EQ => self.result.push_str("eqref"), RefType::STRUCT => self.result.push_str("structref"), RefType::ARRAY => self.result.push_str("arrayref"), + RefType::EXN => self.result.push_str("exnref"), _ => { self.result.push_str("(ref null "); self.print_heaptype(ty.heap_type())?; @@ -912,6 +915,7 @@ impl Printer { HeapType::Struct => self.result.push_str("struct"), HeapType::Array => self.result.push_str("array"), HeapType::I31 => self.result.push_str("i31"), + HeapType::Exn => self.result.push_str("exn"), HeapType::Concrete(i) => self .result .push_str(&format!("{}", i.as_module_index().unwrap())), @@ -996,7 +1000,8 @@ impl Printer { fn print_tag_type(&mut self, state: &State, ty: &TagType, index: bool) -> Result<()> { self.start_group("tag "); if index { - write!(self.result, "(;{};) ", state.core.tags)?; + self.print_name(&state.core.tag_names, state.core.tags)?; + self.result.push(' '); } self.print_core_functype_idx(state, ty.func_type_idx, None)?; Ok(()) @@ -1187,10 +1192,8 @@ impl Printer { } // Exiting a block prints `end` at the previous indentation - // level. `delegate` also ends a block like `end` for `try`. - operator::OpKind::End | operator::OpKind::Delegate - if op_printer.printer.nesting > nesting_start => - { + // level. + operator::OpKind::End if op_printer.printer.nesting > nesting_start => { op_printer.printer.nesting -= 1; op_printer.printer.newline(offset); } @@ -2446,7 +2449,7 @@ impl Printer { } ExternalKind::Tag => { self.start_group("core tag "); - write!(self.result, "(;{};)", state.core.tags)?; + self.print_name(&state.core.tag_names, state.core.tags)?; self.end_group(); state.core.tags += 1; } diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs index 131bf26f42..7dbb37a98d 100644 --- a/crates/wasmprinter/src/operator.rs +++ b/crates/wasmprinter/src/operator.rs @@ -1,7 +1,7 @@ use super::{Printer, State}; use anyhow::{anyhow, bail, Result}; use std::fmt::Write; -use wasmparser::{BlockType, BrTable, MemArg, RefType, VisitOperator}; +use wasmparser::{BlockType, BrTable, Catch, MemArg, RefType, TryTable, VisitOperator}; pub struct PrintOperator<'a, 'b> { pub(super) printer: &'a mut Printer, @@ -43,7 +43,7 @@ impl<'a, 'b> PrintOperator<'a, 'b> { // The previous label is being defined at the same depth as the // latest label, meaning it's overwriting its entry. - OpKind::BlockMid | OpKind::Delegate => { + OpKind::BlockMid => { if let Some(last) = self.label_indices.last_mut() { *last = self.label - 1; } @@ -57,27 +57,20 @@ impl<'a, 'b> PrintOperator<'a, 'b> { } fn blockty(&mut self, ty: BlockType) -> Result<()> { - // This boolean is a little unfortunate. The block type is a payload on - // all instructions so printing prints the instruction name, then a - // space, then calls this method. We're guaranteed to print something - // here, but it's a bit confusing as to when depending on whether this - // block is named or not. If this block is named then we'll print the - // name and then optionally the type. If the block isn't named then - // we'll print the type and then a comment with a pseudo-name. The type - // may or may not print something. - // - // Add all that up and the best way I can use to keep track of this and - // not emit trailing whitespace at the end of a line is this boolean. - let mut preceding_space = true; - - let has_name = if let Some(name) = self - .state - .core - .label_names - .get(&(self.state.core.funcs, self.label)) - { + let has_name = self.blockty_without_label_comment(ty)?; + self.maybe_blockty_label_comment(has_name) + } + + fn blockty_without_label_comment(&mut self, ty: BlockType) -> Result { + // Trim the trailing space, if any. + if self.result().ends_with(" ") { + self.result().pop(); + } + + let key = (self.state.core.funcs, self.label); + let has_name = if let Some(name) = self.state.core.label_names.get(&key) { + self.printer.result.push_str(" "); name.write(&mut self.printer.result); - preceding_space = false; true } else { false @@ -85,29 +78,23 @@ impl<'a, 'b> PrintOperator<'a, 'b> { match ty { BlockType::Empty => {} BlockType::Type(t) => { - if !preceding_space { - self.push_str(" "); - } - self.push_str("(result "); + self.push_str(" (result "); self.printer.print_valtype(t)?; self.push_str(")"); - preceding_space = false; } BlockType::FuncType(idx) => { - if !preceding_space { - self.push_str(" "); - } + self.push_str(" "); self.printer .print_core_functype_idx(self.state, idx, None)?; - preceding_space = false; } } + Ok(has_name) + } + fn maybe_blockty_label_comment(&mut self, has_name: bool) -> Result<()> { if !has_name { let depth = self.cur_depth(); - if !preceding_space { - self.push_str(" "); - } + self.push_str(" "); // Note that 1 is added to the current depth here since if a block // type is being printed then a block is being created which will // increase the label depth of the block itself. @@ -123,7 +110,7 @@ impl<'a, 'b> PrintOperator<'a, 'b> { } fn tag_index(&mut self, index: u32) -> Result<()> { - write!(self.result(), "{index}")?; + self.printer.print_idx(&self.state.core.tag_names, index)?; Ok(()) } @@ -263,6 +250,48 @@ impl<'a, 'b> PrintOperator<'a, 'b> { } Ok(()) } + + fn try_table(&mut self, table: TryTable) -> Result<()> { + let has_name = self.blockty_without_label_comment(table.ty)?; + + // Nesting has already been incremented but labels for catch start above + // this `try_table` not at the `try_table`. Temporarily decrement this + // nesting count and increase it below after printing catch clauses. + self.printer.nesting -= 1; + + for catch in table.catches { + self.result().push(' '); + match catch { + Catch::One { tag, label } => { + self.printer.start_group("catch "); + self.tag_index(tag)?; + self.result().push(' '); + self.relative_depth(label)?; + self.printer.end_group(); + } + Catch::OneRef { tag, label } => { + self.printer.start_group("catch_ref "); + self.tag_index(tag)?; + self.result().push(' '); + self.relative_depth(label)?; + self.printer.end_group(); + } + Catch::All { label } => { + self.printer.start_group("catch_all "); + self.relative_depth(label)?; + self.printer.end_group(); + } + Catch::AllRef { label } => { + self.printer.start_group("catch_all_ref "); + self.relative_depth(label)?; + self.printer.end_group(); + } + } + } + self.printer.nesting += 1; + self.maybe_blockty_label_comment(has_name)?; + Ok(()) + } } #[derive(PartialEq, Copy, Clone)] @@ -270,7 +299,6 @@ pub enum OpKind { BlockStart, BlockMid, End, - Delegate, Normal, } @@ -300,12 +328,9 @@ macro_rules! define_visit { (kind Block) => (OpKind::BlockStart); (kind Loop) => (OpKind::BlockStart); (kind If) => (OpKind::BlockStart); - (kind Try) => (OpKind::BlockStart); + (kind TryTable) => (OpKind::BlockStart); (kind Else) => (OpKind::BlockMid); - (kind Catch) => (OpKind::BlockMid); - (kind CatchAll) => (OpKind::BlockMid); (kind End) => (OpKind::End); - (kind Delegate) => (OpKind::Delegate); (kind $other:tt) => (OpKind::Normal); // How to print the payload of an instruction. There are a number of @@ -961,12 +986,9 @@ macro_rules! define_visit { (name F64x2ConvertLowI32x4U) => ("f64x2.convert_low_i32x4_u"); (name F32x4DemoteF64x2Zero) => ("f32x4.demote_f64x2_zero"); (name F64x2PromoteLowF32x4) => ("f64x2.promote_low_f32x4"); - (name Try) => ("try"); - (name Catch) => ("catch"); + (name TryTable) => ("try_table"); (name Throw) => ("throw"); - (name Rethrow) => ("rethrow"); - (name Delegate) => ("delegate"); - (name CatchAll) => ("catch_all"); + (name ThrowRef) => ("throw_ref"); (name I8x16RelaxedSwizzle) => ("i8x16.relaxed_swizzle"); (name I32x4RelaxedTruncF32x4S) => ("i32x4.relaxed_trunc_f32x4_s"); (name I32x4RelaxedTruncF32x4U) => ("i32x4.relaxed_trunc_f32x4_u"); diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs index c45a0a59fd..bd9e89a657 100644 --- a/crates/wast/src/core/binary.rs +++ b/crates/wast/src/core/binary.rs @@ -932,7 +932,7 @@ fn find_names<'a>( Instruction::If(block) | Instruction::Block(block) | Instruction::Loop(block) - | Instruction::Try(block) + | Instruction::TryTable(TryTable { block, .. }) | Instruction::Let(LetType { block, .. }) => { if let Some(name) = get_name(&block.label, &block.label_name) { label_names.push((label_idx, name)); @@ -969,7 +969,8 @@ impl Names<'_> { && self.types.is_empty() && self.data.is_empty() && self.elems.is_empty() - // NB: specifically don't check tags/modules/instances since they're + && self.tags.is_empty() + // NB: specifically don't check modules/instances since they're // not encoded for now. } } @@ -1024,6 +1025,10 @@ impl Encode for Names<'_> { self.data.encode(&mut tmp); subsec(9, &mut tmp); } + if self.tags.len() > 0 { + self.tags.encode(&mut tmp); + subsec(11, &mut tmp); + } } } diff --git a/crates/wast/src/core/expr.rs b/crates/wast/src/core/expr.rs index 78e2694e9a..1fdbd5cc3d 100644 --- a/crates/wast/src/core/expr.rs +++ b/crates/wast/src/core/expr.rs @@ -89,13 +89,6 @@ enum Level<'a> { /// which don't correspond to terminating instructions, we're just in a /// nested block. IfArm, - - /// Similar to `If` but for `Try` statements, which has simpler parsing - /// state to track. - Try(Try<'a>), - - /// Similar to `IfArm` but for `(do ...)` and `(catch ...)` blocks. - TryArm, } /// Possible states of "what is currently being parsed?" in an `if` expression. @@ -112,21 +105,6 @@ enum If<'a> { Else, } -/// Possible state of "what should be parsed next?" in a `try` expression. -enum Try<'a> { - /// Next thing to parse is the `do` block. - Do(Instruction<'a>), - /// Next thing to parse is `catch`/`catch_all`, or `delegate`. - CatchOrDelegate, - /// Next thing to parse is a `catch` block or `catch_all`. - Catch, - /// Finished parsing like the `End` case, but does not push `end` opcode. - Delegate, - /// This `try` statement has finished parsing and if anything remains it's a - /// syntax error. - End, -} - impl<'a> ExpressionParser<'a> { fn parse(&mut self, parser: Parser<'a>) -> Result<()> { // Here we parse instructions in a loop, and we do not recursively @@ -141,7 +119,7 @@ impl<'a> ExpressionParser<'a> { // As a small ease-of-life adjustment here, if we're parsing inside // of an `if block then we require that all sub-components are // s-expressions surrounded by `(` and `)`, so verify that here. - if let Some(Level::If(_)) | Some(Level::Try(_)) = self.stack.last() { + if let Some(Level::If(_)) = self.stack.last() { if !parser.is_empty() && !parser.peek::()? { return Err(parser.error("expected `(`")); } @@ -167,18 +145,14 @@ impl<'a> ExpressionParser<'a> { if self.handle_if_lparen(parser)? { continue; } - // Second, we handle `try` parsing, which is simpler than - // `if` but more complicated than, e.g., `block`. - if self.handle_try_lparen(parser)? { - continue; - } match parser.parse()? { // If block/loop show up then we just need to be sure to // push an `end` instruction whenever the `)` token is // seen i @ Instruction::Block(_) | i @ Instruction::Loop(_) - | i @ Instruction::Let(_) => { + | i @ Instruction::Let(_) + | i @ Instruction::TryTable(_) => { self.instrs.push(i); self.stack.push(Level::EndWith(Instruction::End(None))); } @@ -190,12 +164,6 @@ impl<'a> ExpressionParser<'a> { self.stack.push(Level::If(If::Clause(i))); } - // Parsing a `try` is easier than `if` but we also push - // a `Try` scope to handle the required nested blocks. - i @ Instruction::Try(_) => { - self.stack.push(Level::Try(Try::Do(i))); - } - // Anything else means that we're parsing a nested form // such as `(i32.add ...)` which means that the // instruction we parsed will be coming at the end. @@ -209,7 +177,6 @@ impl<'a> ExpressionParser<'a> { Paren::Right => match self.stack.pop().unwrap() { Level::EndWith(i) => self.instrs.push(i), Level::IfArm => {} - Level::TryArm => {} // If an `if` statement hasn't parsed the clause or `then` // block, then that's an error because there weren't enough @@ -221,17 +188,6 @@ impl<'a> ExpressionParser<'a> { Level::If(_) => { self.instrs.push(Instruction::End(None)); } - - // The `do` clause is required in a `try` statement, so - // we will signal that error here. Otherwise, terminate with - // an `end` or `delegate` instruction. - Level::Try(Try::Do(_)) => { - return Err(parser.error("previous `try` had no `do`")); - } - Level::Try(Try::Delegate) => {} - Level::Try(_) => { - self.instrs.push(Instruction::End(None)); - } }, } } @@ -331,93 +287,6 @@ impl<'a> ExpressionParser<'a> { If::Else => Err(parser.error("unexpected token: too many payloads inside of `(if)`")), } } - - /// Handles parsing of a `try` statement. A `try` statement is simpler - /// than an `if` as the syntactic form is: - /// - /// ```wat - /// (try (do $do) (catch $tag $catch)) - /// ``` - /// - /// where the `do` and `catch` keywords are mandatory, even for an empty - /// $do or $catch. - /// - /// Returns `true` if the rest of the arm above should be skipped, or - /// `false` if we should parse the next item as an instruction (because we - /// didn't handle the lparen here). - fn handle_try_lparen(&mut self, parser: Parser<'a>) -> Result { - // Only execute the code below if there's a `Try` listed last. - let i = match self.stack.last_mut() { - Some(Level::Try(i)) => i, - _ => return Ok(false), - }; - - // Try statements must start with a `do` block. - if let Try::Do(try_instr) = i { - let instr = mem::replace(try_instr, Instruction::End(None)); - self.instrs.push(instr); - if parser.parse::>()?.is_some() { - // The state is advanced here only if the parse succeeds in - // order to strictly require the keyword. - *i = Try::CatchOrDelegate; - self.stack.push(Level::TryArm); - return Ok(true); - } - // We return here and continue parsing instead of raising an error - // immediately because the missing keyword will be caught more - // generally in the `Paren::Right` case in `parse`. - return Ok(false); - } - - // After a try's `do`, there are several possible kinds of handlers. - if let Try::CatchOrDelegate = i { - // `catch` may be followed by more `catch`s or `catch_all`. - if parser.parse::>()?.is_some() { - let evt = parser.parse::>()?; - self.instrs.push(Instruction::Catch(evt)); - *i = Try::Catch; - self.stack.push(Level::TryArm); - return Ok(true); - } - // `catch_all` can only come at the end and has no argument. - if parser.parse::>()?.is_some() { - self.instrs.push(Instruction::CatchAll); - *i = Try::End; - self.stack.push(Level::TryArm); - return Ok(true); - } - // `delegate` has an index, and also ends the block like `end`. - if parser.parse::>()?.is_some() { - let depth = parser.parse::>()?; - self.instrs.push(Instruction::Delegate(depth)); - *i = Try::Delegate; - match self.paren(parser)? { - Paren::Left | Paren::None => return Ok(false), - Paren::Right => return Ok(true), - } - } - return Err(parser.error("expected a `catch`, `catch_all`, or `delegate`")); - } - - if let Try::Catch = i { - if parser.parse::>()?.is_some() { - let evt = parser.parse::>()?; - self.instrs.push(Instruction::Catch(evt)); - *i = Try::Catch; - self.stack.push(Level::TryArm); - return Ok(true); - } - if parser.parse::>()?.is_some() { - self.instrs.push(Instruction::CatchAll); - *i = Try::End; - self.stack.push(Level::TryArm); - return Ok(true); - } - return Err(parser.error("unexpected items after `catch`")); - } - - Err(parser.error("unexpected token: too many payloads inside of `(try)`")) - } } // TODO: document this obscenity @@ -1142,14 +1011,7 @@ instructions! { F64x2PromoteLowF32x4 : [0xfd, 95] : "f64x2.promote_low_f32x4", // Exception handling proposal - Try(Box>) : [0x06] : "try", - Catch(Index<'a>) : [0x07] : "catch", Throw(Index<'a>) : [0x08] : "throw", - Rethrow(Index<'a>) : [0x09] : "rethrow", - 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", diff --git a/crates/wast/src/core/resolve/names.rs b/crates/wast/src/core/resolve/names.rs index acd84e177c..4d0e36f6ad 100644 --- a/crates/wast/src/core/resolve/names.rs +++ b/crates/wast/src/core/resolve/names.rs @@ -514,7 +514,7 @@ impl<'a, 'b> ExprResolver<'a, 'b> { self.resolve_block_type(&mut t.block)?; } - Block(bt) | If(bt) | Loop(bt) | Try(bt) => { + Block(bt) | If(bt) | Loop(bt) => { self.blocks.push(ExprBlock { label: bt.label, pushed_scope: false, @@ -522,10 +522,6 @@ 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 { if let Some(tag) = catch.kind.tag_index_mut() { @@ -533,6 +529,10 @@ impl<'a, 'b> ExprResolver<'a, 'b> { } self.resolve_label(&mut catch.label)?; } + self.blocks.push(ExprBlock { + label: try_table.block.label, + pushed_scope: false, + }); } // On `End` instructions we pop a label from the stack, and for both @@ -583,18 +583,6 @@ impl<'a, 'b> ExprResolver<'a, 'b> { Throw(i) => { self.resolver.resolve(i, Ns::Tag)?; } - Rethrow(i) => { - self.resolve_label(i)?; - } - Catch(i) => { - self.resolver.resolve(i, Ns::Tag)?; - } - Delegate(i) => { - // Since a delegate starts counting one layer out from the - // current try-delegate block, we pop before we resolve labels. - self.blocks.pop(); - self.resolve_label(i)?; - } Select(s) => { if let Some(list) = &mut s.tys { diff --git a/crates/wast/src/core/resolve/types.rs b/crates/wast/src/core/resolve/types.rs index bfb996b4b6..730f9934b5 100644 --- a/crates/wast/src/core/resolve/types.rs +++ b/crates/wast/src/core/resolve/types.rs @@ -140,7 +140,6 @@ impl<'a> Expander<'a> { | Instruction::If(bt) | Instruction::Loop(bt) | Instruction::Let(LetType { block: 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, diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs index 9db50319b1..725d9b9c18 100644 --- a/crates/wit-component/src/gc.rs +++ b/crates/wit-component/src/gc.rs @@ -491,7 +491,8 @@ impl<'a> Module<'a> { | HeapType::Eq | HeapType::Struct | HeapType::Array - | HeapType::I31 => {} + | HeapType::I31 + | HeapType::Exn => {} HeapType::Concrete(i) => self.ty(i.as_module_index().unwrap()), } } @@ -1026,6 +1027,7 @@ macro_rules! define_visit { (mark_live $self:ident $arg:ident targets) => {}; (mark_live $self:ident $arg:ident data_index) => {}; (mark_live $self:ident $arg:ident elem_index) => {}; + (mark_live $self:ident $arg:ident try_table) => {unimplemented!();}; } impl<'a> VisitOperator<'a> for Module<'a> { @@ -1114,6 +1116,7 @@ impl Encoder { HeapType::Struct => wasm_encoder::HeapType::Struct, HeapType::Array => wasm_encoder::HeapType::Array, HeapType::I31 => wasm_encoder::HeapType::I31, + HeapType::Exn => wasm_encoder::HeapType::Exn, HeapType::Concrete(idx) => { wasm_encoder::HeapType::Concrete(self.types.remap(idx.as_module_index().unwrap())) } @@ -1170,6 +1173,10 @@ macro_rules! define_encode { let _ = $mem_byte; MemoryGrow($mem) }); + (mk TryTable $try_table:ident) => ({ + let _ = $try_table; + unimplemented_try_table() + }); (mk I32Const $v:ident) => (I32Const($v)); (mk I64Const $v:ident) => (I64Const($v)); (mk F32Const $v:ident) => (F32Const(f32::from_bits($v.bits()))); @@ -1210,12 +1217,17 @@ macro_rules! define_encode { (map $self:ident $arg:ident table_byte) => {$arg}; (map $self:ident $arg:ident mem_byte) => {$arg}; (map $self:ident $arg:ident value) => {$arg}; + (map $self:ident $arg:ident try_table) => {$arg}; (map $self:ident $arg:ident targets) => (( $arg.targets().map(|i| i.unwrap()).collect::>().into(), $arg.default(), )); } +fn unimplemented_try_table() -> wasm_encoder::Instruction<'static> { + unimplemented!() +} + impl<'a> VisitOperator<'a> for Encoder { type Output = (); diff --git a/crates/wit-component/tests/components/link-initialize/component.wat b/crates/wit-component/tests/components/link-initialize/component.wat index dbbf3dae00..41be124706 100644 --- a/crates/wit-component/tests/components/link-initialize/component.wat +++ b/crates/wit-component/tests/components/link-initialize/component.wat @@ -69,7 +69,7 @@ (mem-info (memory 4 4)) (needed "c") ) - (type $.data (;0;) (func)) + (type (;0;) (func)) (type (;1;) (func (param i32) (result i32))) (import "env" "memory" (memory (;0;) 1)) (import "env" "__indirect_function_table" (table (;0;) 0 funcref)) @@ -77,11 +77,11 @@ (import "env" "__memory_base" (global $__memory_base (;1;) i32)) (import "env" "__table_base" (global $__table_base (;2;) i32)) (import "env" "malloc" (func $malloc (;0;) (type 1))) - (import "env" "abort" (func $abort (;1;) (type $.data))) + (import "env" "abort" (func $abort (;1;) (type 0))) (import "GOT.mem" "um" (global $um (;3;) (mut i32))) (import "test:test/test" "bar" (func $bar (;2;) (type 1))) - (func $_initialize (;3;) (type $.data)) - (func $__wasm_apply_data_relocs (;4;) (type $.data)) + (func $_initialize (;3;) (type 0)) + (func $__wasm_apply_data_relocs (;4;) (type 0)) (func $foo (;5;) (type 1) (param i32) (result i32) global.get $__stack_pointer i32.const 16 @@ -112,24 +112,24 @@ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs)) (export "foo" (func $foo)) (export "well" (global 4)) - (data (;0;) (global.get $__memory_base) "\04\00\00\00") + (data $.data (;0;) (global.get $__memory_base) "\04\00\00\00") ) (core module (;3;) (@dylink.0 (mem-info (memory 20 4)) (needed "foo") ) - (type $.data (;0;) (func (param i32) (result i32))) + (type (;0;) (func (param i32) (result i32))) (type (;1;) (func)) (import "env" "memory" (memory (;0;) 1)) (import "env" "__indirect_function_table" (table (;0;) 0 funcref)) (import "env" "__memory_base" (global $__memory_base (;0;) i32)) (import "env" "__table_base" (global $__table_base (;1;) i32)) - (import "env" "foo" (func $foo (;0;) (type $.data))) + (import "env" "foo" (func $foo (;0;) (type 0))) (import "GOT.mem" "well" (global $well (;2;) (mut i32))) (func $_initialize (;1;) (type 1)) (func $__wasm_apply_data_relocs (;2;) (type 1)) - (func $bar (;3;) (type $.data) (param i32) (result i32) + (func $bar (;3;) (type 0) (param i32) (result i32) local.get 0 call $foo global.get $well @@ -141,7 +141,7 @@ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs)) (export "test:test/test#bar" (func $bar)) (export "um" (global 3)) - (data (;0;) (global.get $__memory_base) "\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00\05\00\00\00") + (data $.data (;0;) (global.get $__memory_base) "\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00\05\00\00\00") ) (core module (;4;) (type (;0;) (func)) diff --git a/crates/wit-component/tests/components/link/component.wat b/crates/wit-component/tests/components/link/component.wat index b35218f14e..3b503cb4aa 100644 --- a/crates/wit-component/tests/components/link/component.wat +++ b/crates/wit-component/tests/components/link/component.wat @@ -69,7 +69,7 @@ (mem-info (memory 4 4)) (needed "c") ) - (type $.data (;0;) (func)) + (type (;0;) (func)) (type (;1;) (func (param i32) (result i32))) (import "env" "memory" (memory (;0;) 1)) (import "env" "__indirect_function_table" (table (;0;) 0 funcref)) @@ -77,11 +77,11 @@ (import "env" "__memory_base" (global $__memory_base (;1;) i32)) (import "env" "__table_base" (global $__table_base (;2;) i32)) (import "env" "malloc" (func $malloc (;0;) (type 1))) - (import "env" "abort" (func $abort (;1;) (type $.data))) + (import "env" "abort" (func $abort (;1;) (type 0))) (import "GOT.mem" "um" (global $um (;3;) (mut i32))) (import "test:test/test" "bar" (func $bar (;2;) (type 1))) - (func $__wasm_call_ctors (;3;) (type $.data)) - (func $__wasm_apply_data_relocs (;4;) (type $.data)) + (func $__wasm_call_ctors (;3;) (type 0)) + (func $__wasm_apply_data_relocs (;4;) (type 0)) (func $foo (;5;) (type 1) (param i32) (result i32) global.get $__stack_pointer i32.const 16 @@ -112,24 +112,24 @@ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs)) (export "foo" (func $foo)) (export "well" (global 4)) - (data (;0;) (global.get $__memory_base) "\04\00\00\00") + (data $.data (;0;) (global.get $__memory_base) "\04\00\00\00") ) (core module (;3;) (@dylink.0 (mem-info (memory 20 4)) (needed "foo") ) - (type $.data (;0;) (func (param i32) (result i32))) + (type (;0;) (func (param i32) (result i32))) (type (;1;) (func)) (import "env" "memory" (memory (;0;) 1)) (import "env" "__indirect_function_table" (table (;0;) 0 funcref)) (import "env" "__memory_base" (global $__memory_base (;0;) i32)) (import "env" "__table_base" (global $__table_base (;1;) i32)) - (import "env" "foo" (func $foo (;0;) (type $.data))) + (import "env" "foo" (func $foo (;0;) (type 0))) (import "GOT.mem" "well" (global $well (;2;) (mut i32))) (func $__wasm_call_ctors (;1;) (type 1)) (func $__wasm_apply_data_relocs (;2;) (type 1)) - (func $bar (;3;) (type $.data) (param i32) (result i32) + (func $bar (;3;) (type 0) (param i32) (result i32) local.get 0 call $foo global.get $well @@ -141,7 +141,7 @@ (export "__wasm_apply_data_relocs" (func $__wasm_apply_data_relocs)) (export "test:test/test#bar" (func $bar)) (export "um" (global 3)) - (data (;0;) (global.get $__memory_base) "\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00\05\00\00\00") + (data $.data (;0;) (global.get $__memory_base) "\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00\05\00\00\00") ) (core module (;4;) (type (;0;) (func)) diff --git a/fuzz/src/roundtrip.rs b/fuzz/src/roundtrip.rs index 70c6daf7a8..221193bbf3 100644 --- a/fuzz/src/roundtrip.rs +++ b/fuzz/src/roundtrip.rs @@ -76,7 +76,8 @@ fn validate_name_section(wasm: &[u8]) -> wasmparser::Result<()> { | Name::Memory(n) | Name::Global(n) | Name::Element(n) - | Name::Data(n) => { + | Name::Data(n) + | Name::Tag(n) => { for name in n { name?; } diff --git a/src/bin/wasm-tools/demangle.rs b/src/bin/wasm-tools/demangle.rs index 262ec3122c..297e299ccb 100644 --- a/src/bin/wasm-tools/demangle.rs +++ b/src/bin/wasm-tools/demangle.rs @@ -73,6 +73,7 @@ impl Opts { Name::Data(names) => new_section.data(&self.name_map(names)?), Name::Local(names) => new_section.locals(&self.indirect_name_map(names)?), Name::Label(names) => new_section.labels(&self.indirect_name_map(names)?), + Name::Tag(names) => new_section.tags(&self.name_map(names)?), Name::Unknown { .. } => bail!("unknown name section"), } } diff --git a/src/bin/wasm-tools/dump.rs b/src/bin/wasm-tools/dump.rs index 01175a752f..9223056160 100644 --- a/src/bin/wasm-tools/dump.rs +++ b/src/bin/wasm-tools/dump.rs @@ -550,6 +550,7 @@ impl<'a> Dump<'a> { Name::Global(n) => self.print_name_map("global", n)?, Name::Element(n) => self.print_name_map("element", n)?, Name::Data(n) => self.print_name_map("data", n)?, + Name::Tag(n) => self.print_name_map("tag", n)?, Name::Unknown { ty, range, .. } => { write!(self.state, "unknown names: {}", ty)?; self.print(range.start)?; diff --git a/tests/cli/dump/try-delegate.wat b/tests/cli/dump/try-delegate.wat deleted file mode 100644 index 85738bafb8..0000000000 --- a/tests/cli/dump/try-delegate.wat +++ /dev/null @@ -1,10 +0,0 @@ -;; RUN: dump % - -(module - (func - try $l - try - delegate $l - end - ) -) diff --git a/tests/cli/dump/try-delegate.wat.stdout b/tests/cli/dump/try-delegate.wat.stdout deleted file mode 100644 index 5503e0d4f7..0000000000 --- a/tests/cli/dump/try-delegate.wat.stdout +++ /dev/null @@ -1,26 +0,0 @@ - 0x0 | 00 61 73 6d | version 1 (Module) - | 01 00 00 00 - 0x8 | 01 04 | type section - 0xa | 01 | 1 count - 0xb | 60 00 00 | [type 0] RecGroup { inner: Implicit(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }) } - 0xe | 03 02 | func section - 0x10 | 01 | 1 count - 0x11 | 00 | [func 0] type 0 - 0x12 | 0a 0b | code section - 0x14 | 01 | 1 count -============== func 0 ==================== - 0x15 | 09 | size of function - 0x16 | 00 | 0 local blocks - 0x17 | 06 40 | try blockty:Empty - 0x19 | 06 40 | try blockty:Empty - 0x1b | 18 00 | delegate relative_depth:0 - 0x1d | 0b | end - 0x1e | 0b | end - 0x1f | 00 0d | custom section - 0x21 | 04 6e 61 6d | name: "name" - | 65 - 0x26 | 03 06 | label section - 0x28 | 01 | 1 count - 0x29 | 00 | function 0 label name section - 0x2a | 01 | 1 count - 0x2b | 00 01 6c | Naming { index: 0, name: "l" } diff --git a/tests/local/exception-handling.wast b/tests/local/exception-handling.wast deleted file mode 100644 index 092a2dfee8..0000000000 --- a/tests/local/exception-handling.wast +++ /dev/null @@ -1,63 +0,0 @@ -;; --enable-exceptions --enable-multi-value -(module - (type (func (param i32 i64))) - (type (func (param i32))) - (tag (import "m" "t") (type 0)) - (tag (type 1)) - (func $check-throw - i32.const 1 - i64.const 2 - throw 0 - ) - (func $check-try-catch-rethrow - try (result i32 i64) - call $check-throw - unreachable - catch 0 - ;; the exception arguments are on the stack at this point - catch 1 - i64.const 2 - catch_all - rethrow 0 - end - drop - drop - ) - (func $try-with-params - i32.const 0 - try (param i32) (result i32 i64) - i32.popcnt - drop - call $check-throw - unreachable - catch 1 - i64.const 2 - catch_all - i32.const 0 - i64.const 2 - end - drop - drop - ) -) - -(assert_invalid - (module - (type (func)) - (func throw 0)) - "unknown tag 0: tag index out of bounds") - -(assert_invalid - (module - (func try catch_all catch_all end)) - "only one catch_all allowed per `try` block") - -(assert_invalid - (module - (func try catch_all catch 0 end)) - "catch found outside of an `try` block") - -(assert_invalid - (module - (func block try catch_all rethrow 1 end end)) - "target was not a `catch` block") diff --git a/tests/local/exnref/try_table.wast b/tests/local/exnref/try_table.wast deleted file mode 100644 index ade6a0a520..0000000000 --- a/tests/local/exnref/try_table.wast +++ /dev/null @@ -1,95 +0,0 @@ -(module - (tag $a (param i32)) - - (func - (; empty try_table ;) - try_table - end - - (; try_table with result ;) - try_table (result i32) - i32.const 0 - end - drop - - (; try_table can catches ;) - 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 can have results and catches ;) - 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 - - (; mixes of catch, catch_ref, catch_all, and catch_all_ref ;) - - try_table (catch_ref $a 0) - end - - try_table (catch_ref $a 0) (catch_ref $a 0) - end - - try_table (catch $a 0) (catch_ref $a 0) - end - - try_table (catch_ref $a 0) (catch_ref $a 0) - end - - try_table (catch_ref $a 0) (catch $a 0) - end - - try_table (catch_all 0) - end - - try_table (catch_all_ref 0) - end - - try_table (catch_ref $a 0) (catch_all_ref 0) - end - - try_table (catch_ref $a 0) (catch_ref $a 0) (catch_all_ref 0) - end - - try_table (catch $a 0) (catch_ref $a 0) (catch_all_ref 0) - end - - try_table (catch_ref $a 0) (catch_ref $a 0) (catch_all_ref 0) - end - - try_table (catch_ref $a 0) (catch $a 0) (catch_all_ref 0) - end - ) -) diff --git a/tests/local/try.wast b/tests/local/try.wast deleted file mode 100644 index e4ffa23d48..0000000000 --- a/tests/local/try.wast +++ /dev/null @@ -1,55 +0,0 @@ -;; --enable-exceptions - -(assert_malformed - (module quote - "(func (try))" - ) - "previous `try` had no `do`") - -(assert_malformed - (module quote - "(func (try (catch $exn)))" - ) - "previous `try` had no `do`") - -(assert_malformed - (module quote - "(func (try (unreachable) (catch $exn)))" - ) - "previous `try` had no `do`") - -(assert_malformed - (module quote - "(func (try (do) (unreachable)))" - ) - "expected a `catch`, `catch_all`, or `delegate`") - -(assert_malformed - (module quote - "(func (try (do) (catch_all) (unreachable)))" - ) - "too many payloads inside of `(try)`") - -(assert_malformed - (module quote - "(func (try (do) (catch $exn) drop))" - ) - "expected `(`") - -(assert_malformed - (module quote - "(func (try (do) (catch $exn) (drop)))" - ) - "unexpected items after `catch`") - -(assert_malformed - (module quote - "(func (try (do) (delegate 0) (drop)))" - ) - "too many payloads inside of `(try)`") - -(assert_malformed - (module quote - "(func (try $l (do) (delegate $l)))" - ) - "unknown label") diff --git a/tests/local/try.wat b/tests/local/try.wat deleted file mode 100644 index c91f1ad639..0000000000 --- a/tests/local/try.wat +++ /dev/null @@ -1,15 +0,0 @@ -;; --enable-exceptions - -(module $m - (type (func)) - (tag $exn (type 0)) - (func (try (do))) - (func (try (do) (catch $exn))) - (func (try (do) (catch $exn rethrow 0))) - (func (try (do) (catch_all rethrow 0))) - (func (try (do) (catch $exn) (catch_all rethrow 0))) - (func (try (do (try (do) (delegate 0))) (catch $exn))) - (func (result i32) - (try (result i32) - (do (i32.const 42)) - (catch $exn (i32.const 42))))) diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index a22f37f806..cb5d04314a 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -157,15 +157,6 @@ fn skip_validation(test: &Path) -> bool { "proposals/gc/ref_eq.wast", "proposals/gc/ref_test.wast", "proposals/gc/struct.wast", - "exnref/exnref.wast", - "exnref/throw_ref.wast", - "exnref/try_table.wast", - "exception-handling/ref_null.wast", - "exception-handling/throw.wast", - "exception-handling/throw_ref.wast", - "exception-handling/try_catch.wast", - "exception-handling/try_delegate.wast", - "exception-handling/try_table.wast", ]; if broken.iter().any(|x| test.ends_with(x)) { return true; @@ -832,5 +823,9 @@ fn error_matches(error: &str, message: &str) -> bool { return error.starts_with("unknown operator") || error.starts_with("unexpected token"); } + if message.starts_with("type mismatch") { + return error.starts_with("type mismatch"); + } + return false; } diff --git a/tests/snapshots/local/exception-handling.wast/0.print b/tests/snapshots/local/exception-handling.wast/0.print deleted file mode 100644 index 52d4eb2d95..0000000000 --- a/tests/snapshots/local/exception-handling.wast/0.print +++ /dev/null @@ -1,43 +0,0 @@ -(module - (type (;0;) (func (param i32 i64))) - (type (;1;) (func (param i32))) - (type (;2;) (func)) - (type (;3;) (func (result i32 i64))) - (type (;4;) (func (param i32) (result i32 i64))) - (import "m" "t" (tag (;0;) (type 0) (param i32 i64))) - (func $check-throw (;0;) (type 2) - i32.const 1 - i64.const 2 - throw 0 - ) - (func $check-try-catch-rethrow (;1;) (type 2) - try (type 3) (result i32 i64) ;; label = @1 - call $check-throw - unreachable - catch 0 - catch 1 - i64.const 2 - catch_all - rethrow 0 (;@1;) - end - drop - drop - ) - (func $try-with-params (;2;) (type 2) - i32.const 0 - try (type 4) (param i32) (result i32 i64) ;; label = @1 - i32.popcnt - drop - call $check-throw - unreachable - catch 1 - i64.const 2 - catch_all - i32.const 0 - i64.const 2 - end - drop - drop - ) - (tag (;1;) (type 1) (param i32)) -) \ No newline at end of file diff --git a/tests/snapshots/local/exnref/exnref.wast/0.print b/tests/snapshots/local/exnref/exnref.wast/0.print new file mode 100644 index 0000000000..a8e59be1ac --- /dev/null +++ b/tests/snapshots/local/exnref/exnref.wast/0.print @@ -0,0 +1,7 @@ +(module + (type (;0;) (func (param exnref))) + (type (;1;) (func (param (ref exn)))) + (func (;0;) (type 0) (param exnref)) + (func (;1;) (type 0) (param exnref)) + (func (;2;) (type 1) (param (ref exn))) +) \ No newline at end of file diff --git a/tests/snapshots/local/exnref/throw_ref.wast/0.print b/tests/snapshots/local/exnref/throw_ref.wast/0.print new file mode 100644 index 0000000000..631e15285a --- /dev/null +++ b/tests/snapshots/local/exnref/throw_ref.wast/0.print @@ -0,0 +1,7 @@ +(module + (type (;0;) (func (param exnref))) + (func (;0;) (type 0) (param exnref) + local.get 0 + throw_ref + ) +) \ No newline at end of file diff --git a/tests/snapshots/local/try.wat.print b/tests/snapshots/local/try.wat.print deleted file mode 100644 index e2accdca33..0000000000 --- a/tests/snapshots/local/try.wat.print +++ /dev/null @@ -1,47 +0,0 @@ -(module $m - (type (;0;) (func)) - (type (;1;) (func (result i32))) - (func (;0;) (type 0) - try ;; label = @1 - end - ) - (func (;1;) (type 0) - try ;; label = @1 - catch 0 - end - ) - (func (;2;) (type 0) - try ;; label = @1 - catch 0 - rethrow 0 (;@1;) - end - ) - (func (;3;) (type 0) - try ;; label = @1 - catch_all - rethrow 0 (;@1;) - end - ) - (func (;4;) (type 0) - try ;; label = @1 - catch 0 - catch_all - rethrow 0 (;@1;) - end - ) - (func (;5;) (type 0) - try ;; label = @1 - try ;; label = @2 - delegate 0 (;@2;) - catch 0 - end - ) - (func (;6;) (type 1) (result i32) - try (result i32) ;; label = @1 - i32.const 42 - catch 0 - i32.const 42 - end - ) - (tag (;0;) (type 0)) -) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/imports.wast/0.print b/tests/snapshots/testsuite/proposals/exception-handling/imports.wast/0.print index b149a05a8a..7a7038a3d1 100644 --- a/tests/snapshots/testsuite/proposals/exception-handling/imports.wast/0.print +++ b/tests/snapshots/testsuite/proposals/exception-handling/imports.wast/0.print @@ -25,7 +25,7 @@ (table (;1;) 10 20 funcref) (memory (;0;) 2) (tag (;0;) (type 0)) - (tag (;1;) (type 1) (param i32)) + (tag $tag-i32 (;1;) (type 1) (param i32)) (tag (;2;) (type 2) (param f32)) (global (;0;) i32 i32.const 55) (global (;1;) f32 f32.const 0x1.6p+5 (;=44;)) diff --git a/tests/snapshots/testsuite/proposals/exception-handling/ref_null.wast/0.print b/tests/snapshots/testsuite/proposals/exception-handling/ref_null.wast/0.print new file mode 100644 index 0000000000..20e88ebd72 --- /dev/null +++ b/tests/snapshots/testsuite/proposals/exception-handling/ref_null.wast/0.print @@ -0,0 +1,20 @@ +(module + (type (;0;) (func (result externref))) + (type (;1;) (func (result exnref))) + (type (;2;) (func (result funcref))) + (func (;0;) (type 0) (result externref) + ref.null extern + ) + (func (;1;) (type 1) (result exnref) + ref.null exn + ) + (func (;2;) (type 2) (result funcref) + ref.null func + ) + (global (;0;) externref ref.null extern) + (global (;1;) exnref ref.null exn) + (global (;2;) funcref ref.null func) + (export "externref" (func 0)) + (export "exnref" (func 1)) + (export "funcref" (func 2)) +) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/tag.wast/0.print b/tests/snapshots/testsuite/proposals/exception-handling/tag.wast/0.print index 36049d8d37..26e3a246a7 100644 --- a/tests/snapshots/testsuite/proposals/exception-handling/tag.wast/0.print +++ b/tests/snapshots/testsuite/proposals/exception-handling/tag.wast/0.print @@ -5,7 +5,7 @@ (tag (;0;) (type 0)) (tag (;1;) (type 1) (param i32)) (tag (;2;) (type 1) (param i32)) - (tag (;3;) (type 2) (param i32 f32)) + (tag $t3 (;3;) (type 2) (param i32 f32)) (export "t2" (tag 2)) (export "t3" (tag 3)) ) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/tag.wast/2.print b/tests/snapshots/testsuite/proposals/exception-handling/tag.wast/2.print index d9f5a1d55a..039051e3e3 100644 --- a/tests/snapshots/testsuite/proposals/exception-handling/tag.wast/2.print +++ b/tests/snapshots/testsuite/proposals/exception-handling/tag.wast/2.print @@ -1,6 +1,6 @@ (module (type (;0;) (func (param i32))) (type (;1;) (func (param i32 f32))) - (import "test" "t2" (tag (;0;) (type 0) (param i32))) - (import "test" "t3" (tag (;1;) (type 1) (param i32 f32))) + (import "test" "t2" (tag $t0 (;0;) (type 0) (param i32))) + (import "test" "t3" (tag $t1 (;1;) (type 1) (param i32 f32))) ) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/throw.wast/0.print b/tests/snapshots/testsuite/proposals/exception-handling/throw.wast/0.print new file mode 100644 index 0000000000..144f1456fd --- /dev/null +++ b/tests/snapshots/testsuite/proposals/exception-handling/throw.wast/0.print @@ -0,0 +1,77 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (type (;2;) (func (param f32))) + (type (;3;) (func (param i64))) + (type (;4;) (func (param f64))) + (type (;5;) (func (param i32 i32))) + (type (;6;) (func (param i32) (result i32))) + (type (;7;) (func (result i32 i32))) + (func $throw-if (;0;) (type 6) (param i32) (result i32) + local.get 0 + i32.const 0 + i32.ne + if ;; label = @1 + throw $e0 + end + i32.const 0 + ) + (func (;1;) (type 2) (param f32) + local.get 0 + throw $e-f32 + ) + (func (;2;) (type 3) (param i64) + local.get 0 + throw $e-i64 + ) + (func (;3;) (type 4) (param f64) + local.get 0 + throw $e-f64 + ) + (func (;4;) (type 0) + throw $e0 + throw $e-i32 + ) + (func (;5;) (type 0) + block (result i32) ;; label = @1 + throw $e0 + end + throw $e-i32 + ) + (func $throw-1-2 (;6;) (type 0) + i32.const 1 + i32.const 2 + throw $e-i32-i32 + ) + (func (;7;) (type 0) + block $h (type 7) (result i32 i32) + try_table (catch $e-i32-i32 $h) ;; label = @2 + call $throw-1-2 + end + return + end + i32.const 2 + i32.ne + if ;; label = @1 + unreachable + end + i32.const 1 + i32.ne + if ;; label = @1 + unreachable + end + ) + (tag $e0 (;0;) (type 0)) + (tag $e-i32 (;1;) (type 1) (param i32)) + (tag $e-f32 (;2;) (type 2) (param f32)) + (tag $e-i64 (;3;) (type 3) (param i64)) + (tag $e-f64 (;4;) (type 4) (param f64)) + (tag $e-i32-i32 (;5;) (type 5) (param i32 i32)) + (export "throw-if" (func $throw-if)) + (export "throw-param-f32" (func 1)) + (export "throw-param-i64" (func 2)) + (export "throw-param-f64" (func 3)) + (export "throw-polymorphic" (func 4)) + (export "throw-polymorphic-block" (func 5)) + (export "test-throw-1-2" (func 7)) +) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/throw_ref.wast/0.print b/tests/snapshots/testsuite/proposals/exception-handling/throw_ref.wast/0.print new file mode 100644 index 0000000000..60b430aadd --- /dev/null +++ b/tests/snapshots/testsuite/proposals/exception-handling/throw_ref.wast/0.print @@ -0,0 +1,132 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32) (result i32))) + (type (;2;) (func (param exnref))) + (func (;0;) (type 0) + block $h (result exnref) + try_table (catch_ref $e0 $h) ;; label = @2 + throw $e0 + end + unreachable + end + throw_ref + ) + (func (;1;) (type 1) (param i32) (result i32) + block $h (result exnref) + try_table (result i32) (catch_ref $e0 $h) ;; label = @2 + throw $e0 + end + return + end + local.get 0 + i32.eqz + if (type 2) (param exnref) ;; label = @1 + throw_ref + else + drop + end + i32.const 23 + ) + (func (;2;) (type 0) + block $h (result exnref) + try_table (result exnref) (catch_all_ref $h) ;; label = @2 + throw $e0 + end + end + throw_ref + ) + (func (;3;) (type 1) (param i32) (result i32) + block $h (result exnref) + try_table (result i32) (catch_all_ref $h) ;; label = @2 + throw $e0 + end + return + end + local.get 0 + i32.eqz + if (type 2) (param exnref) ;; label = @1 + throw_ref + else + drop + end + i32.const 23 + ) + (func (;4;) (type 1) (param i32) (result i32) + (local $exn1 exnref) (local $exn2 exnref) + block $h1 (result exnref) + try_table (result i32) (catch_ref $e1 $h1) ;; label = @2 + throw $e1 + end + return + end + local.set $exn1 + block $h2 (result exnref) + try_table (result i32) (catch_ref $e0 $h2) ;; label = @2 + throw $e0 + end + return + end + local.set $exn2 + local.get 0 + i32.const 0 + i32.eq + if ;; label = @1 + local.get $exn1 + throw_ref + end + local.get 0 + i32.const 1 + i32.eq + if ;; label = @1 + local.get $exn2 + throw_ref + end + i32.const 23 + ) + (func (;5;) (type 1) (param i32) (result i32) + (local $e exnref) + block $h1 (result exnref) + try_table (result i32) (catch_ref $e0 $h1) ;; label = @2 + throw $e0 + end + return + end + local.set $e + block $h2 (result exnref) + try_table (result i32) (catch_ref $e0 $h2) ;; label = @2 + local.get 0 + i32.eqz + if ;; label = @3 + local.get $e + throw_ref + end + i32.const 42 + end + return + end + drop + i32.const 23 + ) + (func (;6;) (type 0) + (local $e exnref) + block $h (result exnref) + try_table (result f64) (catch_ref $e0 $h) ;; label = @2 + throw $e0 + end + unreachable + end + local.set $e + i32.const 1 + local.get $e + throw_ref + ) + (tag $e0 (;0;) (type 0)) + (tag $e1 (;1;) (type 0)) + (export "catch-throw_ref-0" (func 0)) + (export "catch-throw_ref-1" (func 1)) + (export "catchall-throw_ref-0" (func 2)) + (export "catchall-throw_ref-1" (func 3)) + (export "throw_ref-nested" (func 4)) + (export "throw_ref-recatch" (func 5)) + (export "throw_ref-stack-polymorphism" (func 6)) +) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/0.print b/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/0.print new file mode 100644 index 0000000000..139d36d2e6 --- /dev/null +++ b/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/0.print @@ -0,0 +1,9 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + throw $e0 + ) + (tag $e0 (;0;) (type 0)) + (export "e0" (tag 0)) + (export "throw" (func 0)) +) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/2.print b/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/2.print new file mode 100644 index 0000000000..7dcb8da478 --- /dev/null +++ b/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/2.print @@ -0,0 +1,296 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (type (;2;) (func (param f32))) + (type (;3;) (func (param i64))) + (type (;4;) (func (param f64))) + (type (;5;) (func (param i32) (result i32))) + (type (;6;) (func (param i32 i32) (result i32))) + (type (;7;) (func (param f32) (result f32))) + (type (;8;) (func (param i64) (result i64))) + (type (;9;) (func (param f64) (result f64))) + (type (;10;) (func (result i32 exnref))) + (type (;11;) (func (result f32 exnref))) + (type (;12;) (func (result i64 exnref))) + (type (;13;) (func (result f64 exnref))) + (type (;14;) (func (result i32))) + (import "test" "e0" (tag $imported-e0 (;0;) (type 0))) + (import "test" "throw" (func $imported-throw (;0;) (type 0))) + (func $throw-if (;1;) (type 5) (param i32) (result i32) + local.get 0 + i32.const 0 + i32.ne + if ;; label = @1 + throw $e0 + end + i32.const 0 + ) + (func (;2;) (type 5) (param i32) (result i32) + block $h + try_table (result i32) (catch $e0 $h) ;; label = @2 + local.get 0 + i32.eqz + if ;; label = @3 + throw $e0 + else + end + i32.const 42 + end + return + end + i32.const 23 + ) + (func (;3;) (type 0) + block $h + try_table (catch_all $h) ;; label = @2 + unreachable + end + return + end + ) + (func $div (;4;) (type 6) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.div_u + ) + (func (;5;) (type 6) (param i32 i32) (result i32) + block $h + try_table (result i32) (catch_all $h) ;; label = @2 + local.get 0 + local.get 1 + call $div + end + return + end + i32.const 11 + ) + (func (;6;) (type 5) (param i32) (result i32) + block $h1 + try_table (result i32) (catch $e1 $h1) ;; label = @2 + block $h0 + try_table (result i32) (catch $e0 $h0) ;; label = @4 + local.get 0 + i32.eqz + if ;; label = @5 + throw $e0 + else + local.get 0 + i32.const 1 + i32.eq + if ;; label = @6 + throw $e1 + else + throw $e2 + end + end + i32.const 2 + end + br 1 (;@2;) + end + i32.const 3 + end + return + end + i32.const 4 + ) + (func (;7;) (type 5) (param i32) (result i32) + block $h0 + block $h1 + try_table (result i32) (catch $e0 $h0) (catch $e1 $h1) ;; label = @3 + local.get 0 + i32.eqz + if ;; label = @4 + throw $e0 + else + local.get 0 + i32.const 1 + i32.eq + if ;; label = @5 + throw $e1 + else + throw $e2 + end + end + i32.const 2 + end + return + end + i32.const 4 + return + end + i32.const 3 + ) + (func (;8;) (type 5) (param i32) (result i32) + block $h (result i32) + try_table (result i32) (catch $e-i32 $h) ;; label = @2 + local.get 0 + throw $e-i32 + i32.const 2 + end + return + end + return + ) + (func (;9;) (type 7) (param f32) (result f32) + block $h (result f32) + try_table (result f32) (catch $e-f32 $h) ;; label = @2 + local.get 0 + throw $e-f32 + f32.const 0x0p+0 (;=0;) + end + return + end + return + ) + (func (;10;) (type 8) (param i64) (result i64) + block $h (result i64) + try_table (result i64) (catch $e-i64 $h) ;; label = @2 + local.get 0 + throw $e-i64 + i64.const 2 + end + return + end + return + ) + (func (;11;) (type 9) (param f64) (result f64) + block $h (result f64) + try_table (result f64) (catch $e-f64 $h) ;; label = @2 + local.get 0 + throw $e-f64 + f64.const 0x0p+0 (;=0;) + end + return + end + return + ) + (func (;12;) (type 5) (param i32) (result i32) + block $h (type 10) (result i32 exnref) + try_table (result i32) (catch_ref $e-i32 $h) ;; label = @2 + local.get 0 + throw $e-i32 + i32.const 2 + end + return + end + drop + return + ) + (func (;13;) (type 7) (param f32) (result f32) + block $h (type 11) (result f32 exnref) + try_table (result f32) (catch_ref $e-f32 $h) ;; label = @2 + local.get 0 + throw $e-f32 + f32.const 0x0p+0 (;=0;) + end + return + end + drop + return + ) + (func (;14;) (type 8) (param i64) (result i64) + block $h (type 12) (result i64 exnref) + try_table (result i64) (catch_ref $e-i64 $h) ;; label = @2 + local.get 0 + throw $e-i64 + i64.const 2 + end + return + end + drop + return + ) + (func (;15;) (type 9) (param f64) (result f64) + block $h (type 13) (result f64 exnref) + try_table (result f64) (catch_ref $e-f64 $h) ;; label = @2 + local.get 0 + throw $e-f64 + f64.const 0x0p+0 (;=0;) + end + return + end + drop + return + ) + (func $throw-param-i32 (;16;) (type 1) (param i32) + local.get 0 + throw $e-i32 + ) + (func (;17;) (type 5) (param i32) (result i32) + block $h (result i32) + try_table (result i32) (catch $e-i32 $h) ;; label = @2 + i32.const 0 + local.get 0 + call $throw-param-i32 + end + return + end + ) + (func (;18;) (type 14) (result i32) + block $h + try_table (result i32) (catch $imported-e0 $h) ;; label = @2 + i32.const 1 + call $imported-throw + end + return + end + i32.const 2 + ) + (func (;19;) (type 5) (param i32) (result i32) + block $h + try_table (result i32) (catch $e0 $h) ;; label = @2 + try_table (result i32) ;; label = @3 + local.get 0 + call $throw-if + end + end + return + end + i32.const 1 + ) + (func $throw-void (;20;) (type 0) + throw $e0 + ) + (func (;21;) (type 0) + block $h + try_table (catch $e0 $h) ;; label = @2 + return_call $throw-void + end + end + ) + (func (;22;) (type 0) + block $h + try_table (catch $e0 $h) ;; label = @2 + i32.const 0 + return_call_indirect (type 0) + end + end + ) + (table (;0;) 1 1 funcref) + (tag $e0 (;1;) (type 0)) + (tag $e1 (;2;) (type 0)) + (tag $e2 (;3;) (type 0)) + (tag $e-i32 (;4;) (type 1) (param i32)) + (tag $e-f32 (;5;) (type 2) (param f32)) + (tag $e-i64 (;6;) (type 3) (param i64)) + (tag $e-f64 (;7;) (type 4) (param f64)) + (export "simple-throw-catch" (func 2)) + (export "unreachable-not-caught" (func 3)) + (export "trap-in-callee" (func 5)) + (export "catch-complex-1" (func 6)) + (export "catch-complex-2" (func 7)) + (export "throw-catch-param-i32" (func 8)) + (export "throw-catch-param-f32" (func 9)) + (export "throw-catch-param-i64" (func 10)) + (export "throw-catch-param-f64" (func 11)) + (export "throw-catch_ref-param-i32" (func 12)) + (export "throw-catch_ref-param-f32" (func 13)) + (export "throw-catch_ref-param-i64" (func 14)) + (export "throw-catch_ref-param-f64" (func 15)) + (export "catch-param-i32" (func 17)) + (export "catch-imported" (func 18)) + (export "catchless-try" (func 19)) + (export "return-call-in-try-catch" (func 21)) + (export "return-call-indirect-in-try-catch" (func 22)) + (elem (;0;) (i32.const 0) func $throw-void) +) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/40.print b/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/40.print new file mode 100644 index 0000000000..f818e2ede8 --- /dev/null +++ b/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/40.print @@ -0,0 +1,23 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (import "test" "throw" (func $imported-throw (;0;) (type 0))) + (func (;1;) (type 1) (result i32) + block $h + try_table (result i32) (catch_all $h) ;; label = @2 + block $h0 + try_table (result i32) (catch $e0 $h0) ;; label = @4 + i32.const 1 + call $imported-throw + end + return + end + i32.const 2 + end + return + end + i32.const 3 + ) + (tag $e0 (;0;) (type 0)) + (export "imported-mismatch" (func 1)) +) \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/44.print b/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/44.print new file mode 100644 index 0000000000..59c03b2717 --- /dev/null +++ b/tests/snapshots/testsuite/proposals/exception-handling/try_table.wast/44.print @@ -0,0 +1,32 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (result exnref))) + (func (;0;) (type 0) + try_table (catch $e 0 (;@0;)) (catch $e 0 (;@0;)) ;; label = @1 + end + ) + (func (;1;) (type 0) + try_table (catch_all 0 (;@0;)) (catch $e 0 (;@0;)) ;; label = @1 + end + ) + (func (;2;) (type 0) + try_table (catch_all 0 (;@0;)) (catch_all 0 (;@0;)) ;; label = @1 + end + ) + (func (;3;) (type 1) (result exnref) + try_table (catch_ref $e 0 (;@0;)) (catch_ref $e 0 (;@0;)) ;; label = @1 + end + unreachable + ) + (func (;4;) (type 1) (result exnref) + try_table (catch_all_ref 0 (;@0;)) (catch_ref $e 0 (;@0;)) ;; label = @1 + end + unreachable + ) + (func (;5;) (type 1) (result exnref) + try_table (catch_all_ref 0 (;@0;)) (catch_all_ref 0 (;@0;)) ;; label = @1 + end + unreachable + ) + (tag $e (;0;) (type 0)) +) \ No newline at end of file