From 7dc8054d2dc5980587676d0c7fc97f117abd184f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 8 Jan 2024 18:03:35 -0600 Subject: [PATCH] Update the exception-handling validator implementation (#1333) * 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. * Update error message * Restore old text format Continue to parse the old exception opcodes both in the text and binary format. Additionally print them too. Don't validate them, however, and additionally don't restore the sugared paren-style parsing of the old text format. This should enable the ability to print out old binaries and reassemble them but will likely render handwritten examples un-workable due to the removal of the parentheses-based form. * No tests need skipping at this time * Fix a bit of mixing old and new syntax --- crates/wasm-encoder/src/core/code.rs | 55 +++- 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 | 4 +- crates/wasmparser/src/lib.rs | 9 +- 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 | 162 +++++----- crates/wasmparser/src/validator/types.rs | 5 + crates/wasmprinter/src/lib.rs | 11 +- crates/wasmprinter/src/operator.rs | 118 ++++--- crates/wast/src/core/binary.rs | 8 +- crates/wast/src/core/expr.rs | 156 +-------- crates/wast/src/core/resolve/names.rs | 15 +- 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 -- .../print-deprecated-exceptions.wat} | 31 +- .../print-deprecated-exceptions.wat.stdout} | 7 + tests/cli/print-deprecated-exceptions2.wat | 47 +++ .../print-deprecated-exceptions2.wat.stdout} | 0 tests/local/exnref/try_table.wast | 95 ------ tests/local/try.wast | 55 ---- tests/local/try.wat | 15 - tests/roundtrip.rs | 24 +- .../local/exnref/exnref.wast/0.print | 7 + .../local/exnref/throw_ref.wast/0.print | 7 + .../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, 1165 insertions(+), 686 deletions(-) delete mode 100644 tests/cli/dump/try-delegate.wat delete mode 100644 tests/cli/dump/try-delegate.wat.stdout rename tests/{local/exception-handling.wast => cli/print-deprecated-exceptions.wat} (59%) rename tests/{snapshots/local/exception-handling.wast/0.print => cli/print-deprecated-exceptions.wat.stdout} (87%) create mode 100644 tests/cli/print-deprecated-exceptions2.wat rename tests/{snapshots/local/try.wat.print => cli/print-deprecated-exceptions2.wat.stdout} (100%) delete mode 100644 tests/local/exnref/try_table.wast delete mode 100644 tests/local/try.wast delete mode 100644 tests/local/try.wat create mode 100644 tests/snapshots/local/exnref/exnref.wast/0.print create mode 100644 tests/snapshots/local/exnref/throw_ref.wast/0.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 851a807cc7..888f14b577 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), @@ -333,7 +329,15 @@ pub enum Instruction<'a> { ty: u32, table: u32, }, + TryTable(BlockType, Cow<'a, [Catch]>), Throw(u32), + ThrowRef, + + // Deprecated exception-handling instructions + Try(BlockType), + Delegate(u32), + Catch(u32), + CatchAll, Rethrow(u32), // Parametric instructions. @@ -1013,6 +1017,9 @@ impl Encode for Instruction<'_> { sink.push(0x09); l.encode(sink); } + Instruction::ThrowRef => { + sink.push(0x0A); + } Instruction::End => sink.push(0x0B), Instruction::Br(l) => { sink.push(0x0C); @@ -1079,6 +1086,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); @@ -3114,6 +3127,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 9a9ba874c1..ec8e408c77 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())?, )), @@ -374,6 +375,7 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result (*$arg); (map $arg:ident from_type_nullable) => (*$arg); (map $arg:ident to_type_nullable) => (*$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 @@ -386,6 +388,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, @@ -409,6 +412,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 b31e1112af..56250386b8 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 { @@ -1306,114 +1301,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(()) } @@ -1863,40 +1813,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 5cbc94eaa9..032402fed1 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 @@ -770,6 +770,7 @@ impl<'a> BinaryReader<'a> { 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()?), @@ -799,6 +800,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 e10f5e9d3d..6b3aed5c49 100644 --- a/crates/wasmparser/src/lib.rs +++ b/crates/wasmparser/src/lib.rs @@ -129,10 +129,15 @@ macro_rules! for_each_operator { @mvp Loop { blockty: $crate::BlockType } => visit_loop @mvp If { blockty: $crate::BlockType } => visit_if @mvp Else => visit_else + @exceptions TryTable { try_table: $crate::TryTable } => visit_try_table + @exceptions Throw { tag_index: u32 } => visit_throw + @exceptions ThrowRef => visit_throw_ref + // Deprecated old instructions from the exceptions proposal @exceptions Try { blockty: $crate::BlockType } => visit_try @exceptions Catch { tag_index: u32 } => visit_catch - @exceptions Throw { tag_index: u32 } => visit_throw @exceptions Rethrow { relative_depth: u32 } => visit_rethrow + @exceptions Delegate { relative_depth: u32 } => visit_delegate + @exceptions CatchAll => visit_catch_all @mvp End => visit_end @mvp Br { relative_depth: u32 } => visit_br @mvp BrIf { relative_depth: u32 } => visit_br_if @@ -142,8 +147,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 eda7f93fa6..6845426df8 100644 --- a/crates/wasmparser/src/readers/core/types.rs +++ b/crates/wasmparser/src/readers/core/types.rs @@ -738,6 +738,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 @@ -829,6 +832,8 @@ impl ValType { // 0011 = extern // 0010 = noextern // +// 0001 = exn +// // 0000 = none // ``` #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -857,6 +862,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})"), } @@ -906,6 +913,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`. @@ -919,6 +927,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); @@ -949,6 +961,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 } @@ -988,6 +1003,7 @@ impl RefType { | Self::EXTERN_ABSTYPE | Self::NOEXTERN_ABSTYPE | Self::NONE_ABSTYPE + | Self::EXN_ABSTYPE ) ); @@ -1025,6 +1041,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)), } } @@ -1120,6 +1137,7 @@ impl RefType { Self::STRUCT_ABSTYPE => HeapType::Struct, Self::ARRAY_ABSTYPE => HeapType::Array, Self::I31_ABSTYPE => HeapType::I31, + Self::EXN_ABSTYPE => HeapType::Exn, _ => unreachable!(), } } @@ -1140,6 +1158,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)", @@ -1151,6 +1170,7 @@ impl RefType { (false, HeapType::Struct) => "(ref struct)", (false, HeapType::Array) => "(ref array)", (false, HeapType::I31) => "(ref i31)", + (false, HeapType::Exn) => "(ref exn)", } } } @@ -1231,13 +1251,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, } } @@ -1282,9 +1307,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"), } } @@ -1303,6 +1327,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(); @@ -1357,6 +1382,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..23c1899be4 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("exception refs not supported without the exception handling feature") + } + } } } } diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs index a539de5e2d..700adac3b6 100644 --- a/crates/wasmparser/src/validator/core.rs +++ b/crates/wasmparser/src/validator/core.rs @@ -981,7 +981,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 eb285c31b8..a8cd9c04de 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -23,10 +23,10 @@ // the various methods here. use crate::{ - limits::MAX_WASM_FUNCTION_LOCALS, ArrayType, BinaryReaderError, BlockType, BrTable, + limits::MAX_WASM_FUNCTION_LOCALS, ArrayType, BinaryReaderError, BlockType, BrTable, Catch, CompositeType, FieldType, FuncType, HeapType, Ieee32, Ieee64, MemArg, RefType, Result, - StorageType, StructType, SubType, UnpackedIndex, ValType, VisitOperator, WasmFeatures, - WasmModuleResources, V128, + StorageType, StructType, SubType, TryTable, UnpackedIndex, ValType, VisitOperator, + WasmFeatures, WasmModuleResources, V128, }; use std::ops::{Deref, DerefMut}; @@ -118,19 +118,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> { @@ -1295,34 +1283,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.params() { - 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.params(); + 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.params().iter().copied(); + 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 { @@ -1340,49 +1368,25 @@ 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_try(&mut self, _: BlockType) -> Self::Output { + bail!(self.offset, "unimplemented validation of deprecated opcode") + } + fn visit_catch(&mut self, _: u32) -> Self::Output { + bail!(self.offset, "unimplemented validation of deprecated opcode") + } + fn visit_rethrow(&mut self, _: u32) -> Self::Output { + bail!(self.offset, "unimplemented validation of deprecated opcode") + } + fn visit_delegate(&mut self, _: u32) -> Self::Output { + bail!(self.offset, "unimplemented validation of deprecated opcode") } 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(()) + bail!(self.offset, "unimplemented validation of deprecated opcode") } 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 e011f84223..0832471a4c 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,7 +1192,7 @@ impl Printer { } // Exiting a block prints `end` at the previous indentation - // level. `delegate` also ends a block like `end` for `try`. + // level. operator::OpKind::End | operator::OpKind::Delegate if op_printer.printer.nesting > nesting_start => { @@ -2446,7 +2451,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 3d17480e1e..ff7c252513 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,41 +43,34 @@ 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; } } // Label is out of scope so remove it from the stack. - OpKind::End => { + OpKind::End | OpKind::Delegate => { self.label_indices.pop(); } } } 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 = 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 has_name = if let Some(name) = self - .state - .core - .label_names - .get(&(self.state.core.funcs, self.label)) - { + 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(()) } @@ -305,6 +292,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)] @@ -343,11 +372,12 @@ macro_rules! define_visit { (kind Loop) => (OpKind::BlockStart); (kind If) => (OpKind::BlockStart); (kind Try) => (OpKind::BlockStart); - (kind Else) => (OpKind::BlockMid); + (kind TryTable) => (OpKind::BlockStart); (kind Catch) => (OpKind::BlockMid); (kind CatchAll) => (OpKind::BlockMid); - (kind End) => (OpKind::End); (kind Delegate) => (OpKind::Delegate); + (kind Else) => (OpKind::BlockMid); + (kind End) => (OpKind::End); (kind $other:tt) => (OpKind::Normal); // How to print the payload of an instruction. There are a number of @@ -1004,12 +1034,6 @@ 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 Throw) => ("throw"); - (name Rethrow) => ("rethrow"); - (name Delegate) => ("delegate"); - (name CatchAll) => ("catch_all"); (name I8x16RelaxedSwizzle) => ("i8x16.relaxed_swizzle"); (name I32x4RelaxedTruncF32x4S) => ("i32x4.relaxed_trunc_f32x4_s"); (name I32x4RelaxedTruncF32x4U) => ("i32x4.relaxed_trunc_f32x4_u"); @@ -1061,6 +1085,14 @@ macro_rules! define_visit { (name RefI31) => ("ref.i31"); (name I31GetS) => ("i31.get_s"); (name I31GetU) => ("i31.get_u"); + (name TryTable) => ("try_table"); + (name Throw) => ("throw"); + (name ThrowRef) => ("throw_ref"); + (name Rethrow) => ("rethrow"); + (name Try) => ("try"); + (name Catch) => ("catch"); + (name CatchAll) => ("catch_all"); + (name Delegate) => ("delegate"); } impl<'a> VisitOperator<'a> for PrintOperator<'_, '_> { diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs index c45a0a59fd..68facd6431 100644 --- a/crates/wast/src/core/binary.rs +++ b/crates/wast/src/core/binary.rs @@ -933,6 +933,7 @@ fn find_names<'a>( | 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 +970,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 +1026,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..489ac205af 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,17 +1011,17 @@ instructions! { F64x2PromoteLowF32x4 : [0xfd, 95] : "f64x2.promote_low_f32x4", // Exception handling proposal + ThrowRef : [0x0a] : "throw_ref", + TryTable(TryTable<'a>) : [0x1f] : "try_table", + Throw(Index<'a>) : [0x08] : "throw", + + // Deprecated exception handling optocdes 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", - // Relaxed SIMD proposal I8x16RelaxedSwizzle : [0xfd, 0x100]: "i8x16.relaxed_swizzle", I32x4RelaxedTruncF32x4S : [0xfd, 0x101]: "i32x4.relaxed_trunc_f32x4_s", @@ -1235,10 +1104,11 @@ impl<'a> Parse<'a> for TryTable<'a> { let block = parser.parse()?; let mut catches = Vec::new(); - while parser.peek2::()? - || parser.peek2::()? - || parser.peek2::()? - || parser.peek2::()? + while parser.peek::()? + && (parser.peek2::()? + || parser.peek2::()? + || parser.peek2::()? + || parser.peek2::()?) { catches.push(parser.parens(|p| { let kind = if parser.peek::()? { diff --git a/crates/wast/src/core/resolve/names.rs b/crates/wast/src/core/resolve/names.rs index acd84e177c..05894e9a1e 100644 --- a/crates/wast/src/core/resolve/names.rs +++ b/crates/wast/src/core/resolve/names.rs @@ -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 @@ -580,15 +580,14 @@ impl<'a, 'b> ExprResolver<'a, 'b> { self.resolve_label(&mut i.default)?; } - Throw(i) => { + Throw(i) | Catch(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. diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs index 9ebdaf661f..a672335caa 100644 --- a/crates/wit-component/src/gc.rs +++ b/crates/wit-component/src/gc.rs @@ -495,7 +495,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()), } } @@ -1042,6 +1043,7 @@ macro_rules! define_visit { (mark_live $self:ident $arg:ident field_index) => {}; (mark_live $self:ident $arg:ident from_type_nullable) => {}; (mark_live $self:ident $arg:ident to_type_nullable) => {}; + (mark_live $self:ident $arg:ident try_table) => {unimplemented!();}; } impl<'a> VisitOperator<'a> for Module<'a> { @@ -1130,6 +1132,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())) } @@ -1186,6 +1189,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()))); @@ -1238,12 +1245,17 @@ macro_rules! define_encode { (map $self:ident $arg:ident field_index) => {$arg}; (map $self:ident $arg:ident from_type_nullable) => {$arg}; (map $self:ident $arg:ident to_type_nullable) => {$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 d801299f9d..f00ef17a4d 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 d78029e7da..7ada35a5ed 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/cli/print-deprecated-exceptions.wat similarity index 59% rename from tests/local/exception-handling.wast rename to tests/cli/print-deprecated-exceptions.wat index 092a2dfee8..2b942e2c68 100644 --- a/tests/local/exception-handling.wast +++ b/tests/cli/print-deprecated-exceptions.wat @@ -1,4 +1,5 @@ -;; --enable-exceptions --enable-multi-value +;; RUN: print % + (module (type (func (param i32 i64))) (type (func (param i32))) @@ -39,25 +40,11 @@ drop drop ) + (func $mix-old-and-new + try_table + try + catch_all + end + end + ) ) - -(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/snapshots/local/exception-handling.wast/0.print b/tests/cli/print-deprecated-exceptions.wat.stdout similarity index 87% rename from tests/snapshots/local/exception-handling.wast/0.print rename to tests/cli/print-deprecated-exceptions.wat.stdout index 52d4eb2d95..f3cb45edb8 100644 --- a/tests/snapshots/local/exception-handling.wast/0.print +++ b/tests/cli/print-deprecated-exceptions.wat.stdout @@ -39,5 +39,12 @@ drop drop ) + (func $mix-old-and-new (;3;) (type 2) + try_table ;; label = @1 + try ;; label = @2 + catch_all + end + end + ) (tag (;1;) (type 1) (param i32)) ) \ No newline at end of file diff --git a/tests/cli/print-deprecated-exceptions2.wat b/tests/cli/print-deprecated-exceptions2.wat new file mode 100644 index 0000000000..c2dba24040 --- /dev/null +++ b/tests/cli/print-deprecated-exceptions2.wat @@ -0,0 +1,47 @@ +;; RUN: print % + +(module $m + (tag) + (func + try + end + ) + (func + try + catch 0 + end + ) + (func + try + catch 0 + rethrow 0 + end + ) + (func + try + catch_all + rethrow 0 + end + ) + (func + try + catch 0 + catch_all + rethrow 0 + end + ) + (func + try + try + delegate 0 + catch 0 + end + ) + (func (result i32) + try (result i32) + i32.const 42 + catch 0 + i32.const 42 + end + ) +) diff --git a/tests/snapshots/local/try.wat.print b/tests/cli/print-deprecated-exceptions2.wat.stdout similarity index 100% rename from tests/snapshots/local/try.wat.print rename to tests/cli/print-deprecated-exceptions2.wat.stdout 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 e2a964ab74..ced12df2ff 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -141,22 +141,7 @@ fn skip_test(test: &Path, contents: &[u8]) -> bool { false } -fn skip_validation(test: &Path) -> bool { - let broken = &[ - "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; - } - +fn skip_validation(_test: &Path) -> bool { false } @@ -670,7 +655,8 @@ fn error_matches(error: &str, message: &str) -> bool { { return error.contains("expected ") || error.contains("constant out of range") - || error.contains("extra tokens remaining"); + || error.contains("extra tokens remaining") + || error.contains("unimplemented validation of deprecated opcode"); } if message == "illegal character" { @@ -818,5 +804,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/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/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