Repository: bytecodealliance/wasm-tools
Pull request #1670 (2024-07-12 18:11:08)

Gate component model functions with multiple returns
This commit is an implementation of WebAssembly/component-model#368 in this repository. The intention here is along the lines of a few other recent restrictions which is to avoid outright removing support and instead stage the changes a bit. To that end a new feature flag is added and multi-return functions are now gated behind this feature flag. Tests are updated to avoid using it and otherwise the actual removal of supporting code is deferred to a later date.


Patch:
diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs
index be5e1464..c1257363 100644
--- a/crates/wasmparser/src/features.rs
+++ b/crates/wasmparser/src/features.rs
@@ -148,13 +148,15 @@ define_wasm_features! {
         pub component_model_nested_names: COMPONENT_MODEL_NESTED_NAMES(1 << 22) = false;
         /// Support for more than 32 flags per-type in the component model.
         pub component_model_more_flags: COMPONENT_MODEL_MORE_FLAGS(1 << 23) = false;
+        /// Support for multiple return values in a component model function.
+        pub component_model_multiple_returns: COMPONENT_MODEL_MULTIPLE_RETURNS(1 << 24) = false;
         /// The WebAssembly legacy exception handling proposal (phase 1)
         ///
         /// # Note
         ///
         /// Support this feature as long as all leading browsers also support it
         /// https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md
-        pub legacy_exceptions: LEGACY_EXCEPTIONS(1 << 24) = false;
+        pub legacy_exceptions: LEGACY_EXCEPTIONS(1 << 25) = false;
     }
 }
 
diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs
index 56658af7..405a03c5 100644
--- a/crates/wasmparser/src/validator/component.rs
+++ b/crates/wasmparser/src/validator/component.rs
@@ -352,7 +352,7 @@ impl ComponentState {
                 types.push(ty).into()
             }
             crate::ComponentType::Func(ty) => {
-                let ty = current(components).create_function_type(ty, types, offset)?;
+                let ty = current(components).create_function_type(ty, types, features, offset)?;
                 types.push(ty).into()
             }
             crate::ComponentType::Component(decls) => {
@@ -1661,10 +1661,19 @@ impl ComponentState {
         &self,
         ty: crate::ComponentFuncType,
         types: &TypeList,
+        features: &WasmFeatures,
         offset: usize,
     ) -> Result<ComponentFuncType> {
         let mut info = TypeInfo::new();
 
+        if ty.results.type_count() > 1 && !features.component_model_multiple_returns() {
+            bail!(
+                offset,
+                "multiple returns on a function is now a gated feature \
+                 -- https://github.com/WebAssembly/component-model/pull/368"
+            );
+        }
+
         let mut set = Set::default();
         #[cfg(not(feature = "no-hash-maps"))] // TODO: remove when unified map type is available
         set.reserve(core::cmp::max(ty.params.len(), ty.results.type_count()));
diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs
index be539e9e..27f85db1 100644
--- a/crates/wit-parser/src/resolve.rs
+++ b/crates/wit-parser/src/resolve.rs
@@ -575,7 +575,10 @@ impl Resolve {
         let mut moved_types = Vec::new();
         for (id, mut ty) in types {
             let new_id = match type_map.get(&id).copied() {
-                Some(id) => id,
+                Some(id) => {
+                    update_stability(&ty.stability, &mut self.types[id].stability)?;
+                    id
+                }
                 None => {
                     log::debug!("moving type {:?}", ty.name);
                     moved_types.push(id);
@@ -590,7 +593,10 @@ impl Resolve {
         let mut moved_interfaces = Vec::new();
         for (id, mut iface) in interfaces {
             let new_id = match interface_map.get(&id).copied() {
-                Some(id) => id,
+                Some(id) => {
+                    update_stability(&iface.stability, &mut self.interfaces[id].stability)?;
+                    id
+                }
                 None => {
                     log::debug!("moving interface {:?}", iface.name);
                     moved_interfaces.push(id);
@@ -605,7 +611,10 @@ impl Resolve {
         let mut moved_worlds = Vec::new();
         for (id, mut world) in worlds {
             let new_id = match world_map.get(&id).copied() {
-                Some(id) => id,
+                Some(id) => {
+                    update_stability(&world.stability, &mut self.worlds[id].stability)?;
+                    id
+                }
                 None => {
                     log::debug!("moving world {}", world.name);
                     moved_worlds.push(id);
@@ -2345,10 +2354,25 @@ impl Remap {
                     ))
                 }
             }
-            key => {
-                let prev = items.insert(key.clone(), item.1.clone());
-                if let Some(prev) = prev {
-                    assert_eq!(prev, item.1.clone());
+            key @ WorldKey::Interface(_) => {
+                let prev = items.entry(key.clone()).or_insert(item.1.clone());
+                match (&item.1, prev) {
+                    (
+                        WorldItem::Interface {
+                            id: aid,
+                            stability: astability,
+                        },
+                        WorldItem::Interface {
+                            id: bid,
+                            stability: bstability,
+                        },
+                    ) => {
+                        assert_eq!(*aid, *bid);
+                        update_stability(astability, bstability)?;
+                    }
+                    (WorldItem::Interface { .. }, _) => unreachable!(),
+                    (WorldItem::Function(_), _) => unreachable!(),
+                    (WorldItem::Type(_), _) => unreachable!(),
                 }
             }
         };
@@ -2680,6 +2704,29 @@ impl<'a> MergeMap<'a> {
     }
 }
 
+/// Updates stability annotations when merging `from` into `into`.
+///
+/// This is done to keep up-to-date stability information if possible.
+/// Components for example don't carry stability information but WIT does so
+/// this tries to move from "unknown" to stable/unstable if possible.
+fn update_stability(from: &Stability, into: &mut Stability) -> Result<()> {
+    // If `from` is unknown or the two stability annotations are equal then
+    // there's nothing to do here.
+    if from == into || from.is_unknown() {
+        return Ok(());
+    }
+    // Otherwise if `into` is unknown then inherit the stability listed in
+    // `from`.
+    if into.is_unknown() {
+        *into = from.clone();
+        return Ok(());
+    }
+
+    // Failing all that this means that the two attributes are different so
+    // generate an error.
+    bail!("mismatch in stability attributes")
+}
+
 #[cfg(test)]
 mod tests {
     use crate::Resolve;


Test patch:
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/adapt-old.wit b/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/adapt-old.wit
index a9f798e8..e359426c 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/adapt-old.wit
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/adapt-old.wit
@@ -1,5 +1,5 @@
 world adapt-old {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/component.wat b/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/component.wat
index 416088a1..5cd3ae5e 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/component.wat
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/component.wat
@@ -1,8 +1,9 @@
 (component
   (type (;0;)
     (instance
-      (type (;0;) (func (result "a" u32) (result "b" u32)))
-      (export (;0;) "get-two" (func (type 0)))
+      (type (;0;) (tuple u32 u32))
+      (type (;1;) (func (result 0)))
+      (export (;0;) "get-two" (func (type 1)))
     )
   )
   (import "new" (instance (;0;) (type 0)))
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/component.wit.print b/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/component.wit.print
index b2c96248..806cb9e1 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/component.wit.print
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-adapt-realloc/component.wit.print
@@ -2,6 +2,6 @@ package root:component;
 
 world root {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/adapt-old.wit b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/adapt-old.wit
index a9f798e8..e359426c 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/adapt-old.wit
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/adapt-old.wit
@@ -1,5 +1,5 @@
 world adapt-old {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/component.wat b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/component.wat
index 3463de3b..6346d6d2 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/component.wat
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/component.wat
@@ -1,8 +1,9 @@
 (component
   (type (;0;)
     (instance
-      (type (;0;) (func (result "a" u32) (result "b" u32)))
-      (export (;0;) "get-two" (func (type 0)))
+      (type (;0;) (tuple u32 u32))
+      (type (;1;) (func (result 0)))
+      (export (;0;) "get-two" (func (type 1)))
     )
   )
   (import "new" (instance (;0;) (type 0)))
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/component.wit.print b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/component.wit.print
index b2c96248..806cb9e1 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/component.wit.print
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc-no-state/component.wit.print
@@ -2,6 +2,6 @@ package root:component;
 
 world root {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/adapt-old.wit b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/adapt-old.wit
index a9f798e8..e359426c 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/adapt-old.wit
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/adapt-old.wit
@@ -1,5 +1,5 @@
 world adapt-old {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/component.wat b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/component.wat
index 7bf87c7a..acdc4e2d 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/component.wat
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/component.wat
@@ -1,8 +1,9 @@
 (component
   (type (;0;)
     (instance
-      (type (;0;) (func (result "a" u32) (result "b" u32)))
-      (export (;0;) "get-two" (func (type 0)))
+      (type (;0;) (tuple u32 u32))
+      (type (;1;) (func (result 0)))
+      (export (;0;) "get-two" (func (type 1)))
     )
   )
   (import "new" (instance (;0;) (type 0)))
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/component.wit.print b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/component.wit.print
index b2c96248..806cb9e1 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/component.wit.print
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-realloc/component.wit.print
@@ -2,6 +2,6 @@ package root:component;
 
 world root {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/adapt-old.wit b/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/adapt-old.wit
index a9f798e8..e359426c 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/adapt-old.wit
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/adapt-old.wit
@@ -1,5 +1,5 @@
 world adapt-old {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/component.wat b/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/component.wat
index 2b81296d..81c0e720 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/component.wat
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/component.wat
@@ -1,8 +1,9 @@
 (component
   (type (;0;)
     (instance
-      (type (;0;) (func (result "a" u32) (result "b" u32)))
-      (export (;0;) "get-two" (func (type 0)))
+      (type (;0;) (tuple u32 u32))
+      (type (;1;) (func (result 0)))
+      (export (;0;) "get-two" (func (type 1)))
     )
   )
   (import "new" (instance (;0;) (type 0)))
diff --git a/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/component.wit.print b/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/component.wit.print
index b2c96248..806cb9e1 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/component.wit.print
+++ b/crates/wit-component/tests/components/adapt-inject-stack-with-reallocing-adapter/component.wit.print
@@ -2,6 +2,6 @@ package root:component;
 
 world root {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit b/crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit
index a9f798e8..e359426c 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit
+++ b/crates/wit-component/tests/components/adapt-inject-stack/adapt-old.wit
@@ -1,5 +1,5 @@
 world adapt-old {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/components/adapt-inject-stack/component.wat b/crates/wit-component/tests/components/adapt-inject-stack/component.wat
index 027fe09d..f6eb8fa0 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack/component.wat
+++ b/crates/wit-component/tests/components/adapt-inject-stack/component.wat
@@ -1,8 +1,9 @@
 (component
   (type (;0;)
     (instance
-      (type (;0;) (func (result "a" u32) (result "b" u32)))
-      (export (;0;) "get-two" (func (type 0)))
+      (type (;0;) (tuple u32 u32))
+      (type (;1;) (func (result 0)))
+      (export (;0;) "get-two" (func (type 1)))
     )
   )
   (import "new" (instance (;0;) (type 0)))
diff --git a/crates/wit-component/tests/components/adapt-inject-stack/component.wit.print b/crates/wit-component/tests/components/adapt-inject-stack/component.wit.print
index b2c96248..806cb9e1 100644
--- a/crates/wit-component/tests/components/adapt-inject-stack/component.wit.print
+++ b/crates/wit-component/tests/components/adapt-inject-stack/component.wit.print
@@ -2,6 +2,6 @@ package root:component;
 
 world root {
   import new: interface {
-    get-two: func() -> (a: u32, b: u32);
+    get-two: func() -> tuple<u32, u32>;
   }
 }
diff --git a/crates/wit-component/tests/interfaces/integers.wat b/crates/wit-component/tests/interfaces/integers.wat
index 4230f0e4..f7026f68 100644
--- a/crates/wit-component/tests/interfaces/integers.wat
+++ b/crates/wit-component/tests/interfaces/integers.wat
@@ -40,8 +40,7 @@
           (type (;17;) (tuple s64 u8))
           (type (;18;) (func (result 17)))
           (export (;17;) "pair-ret" (func (type 18)))
-          (type (;19;) (func (result "a" s64) (result "b" u8)))
-          (export (;18;) "multi-ret" (func (type 19)))
+          (export (;18;) "multi-ret" (func (type 18)))
         )
       )
       (export (;0;) "foo:foo/integers" (instance (type 0)))
@@ -91,8 +90,7 @@
               (type (;17;) (tuple s64 u8))
               (type (;18;) (func (result 17)))
               (export (;17;) "pair-ret" (func (type 18)))
-              (type (;19;) (func (result "a" s64) (result "b" u8)))
-              (export (;18;) "multi-ret" (func (type 19)))
+              (export (;18;) "multi-ret" (func (type 18)))
             )
           )
           (import "foo:foo/integers" (instance (;0;) (type 0)))
diff --git a/crates/wit-component/tests/interfaces/integers.wit b/crates/wit-component/tests/interfaces/integers.wit
index ae1cb7c2..5c43e9be 100644
--- a/crates/wit-component/tests/interfaces/integers.wit
+++ b/crates/wit-component/tests/interfaces/integers.wit
@@ -19,7 +19,7 @@ interface integers {
   r7: func() -> u64;
   r8: func() -> s64;
   pair-ret: func() -> tuple<s64, u8>;
-  multi-ret: func() -> (a: s64, b: u8);
+  multi-ret: func() -> tuple<s64, u8>;
 }
 
 world integers-world {
diff --git a/crates/wit-component/tests/interfaces/integers.wit.print b/crates/wit-component/tests/interfaces/integers.wit.print
index 0d97addd..821eee58 100644
--- a/crates/wit-component/tests/interfaces/integers.wit.print
+++ b/crates/wit-component/tests/interfaces/integers.wit.print
@@ -37,7 +37,7 @@ interface integers {
 
   pair-ret: func() -> tuple<s64, u8>;
 
-  multi-ret: func() -> (a: s64, b: u8);
+  multi-ret: func() -> tuple<s64, u8>;
 }
 
 world integers-world {
diff --git a/crates/wit-component/tests/merge/success/from/deps/foo/shared.wit b/crates/wit-component/tests/merge/success/from/deps/foo/shared.wit
index a3b65ebe..272c5928 100644
--- a/crates/wit-component/tests/merge/success/from/deps/foo/shared.wit
+++ b/crates/wit-component/tests/merge/success/from/deps/foo/shared.wit
@@ -23,3 +23,20 @@ world shared-world {
 
   import d: interface {}
 }
+
+@since(version = 1.0.0)
+interface shared-version-on-from {}
+
+interface shared-version-on-into {}
+
+world shared-world-with-versions {
+  @since(version = 1.0.0)
+  import shared-version-on-from;
+  import shared-version-on-into;
+}
+
+world shared-world-with-versions-and-include {
+  import shared-version-on-from;
+  import shared-version-on-into;
+  include shared-world-with-versions;
+}
diff --git a/crates/wit-component/tests/merge/success/into/deps/foo/shared.wit b/crates/wit-component/tests/merge/success/into/deps/foo/shared.wit
index bd9e90c0..0adea4f0 100644
--- a/crates/wit-component/tests/merge/success/into/deps/foo/shared.wit
+++ b/crates/wit-component/tests/merge/success/into/deps/foo/shared.wit
@@ -21,3 +21,20 @@ world shared-world {
 
   import d: interface {}
 }
+
+interface shared-version-on-from {}
+
+@since(version = 1.0.0)
+interface shared-version-on-into {}
+
+world shared-world-with-versions {
+  import shared-version-on-from;
+  @since(version = 1.0.0)
+  import shared-version-on-into;
+}
+
+world shared-world-with-versions-and-include {
+  import shared-version-on-from;
+  import shared-version-on-into;
+  include shared-world-with-versions;
+}
diff --git a/crates/wit-component/tests/merge/success/merge/foo.wit b/crates/wit-component/tests/merge/success/merge/foo.wit
index 1754a2cc..f209dac2 100644
--- a/crates/wit-component/tests/merge/success/merge/foo.wit
+++ b/crates/wit-component/tests/merge/success/merge/foo.wit
@@ -19,6 +19,14 @@ interface shared-items {
   type a = u32;
 }
 
+@since(version = 1.0.0)
+interface shared-version-on-from {
+}
+
+@since(version = 1.0.0)
+interface shared-version-on-into {
+}
+
 interface only-from {
   record r {
   }
@@ -45,3 +53,13 @@ world shared-world {
 
   export shared-items;
 }
+world shared-world-with-versions {
+  import shared-version-on-from;
+  @since(version = 1.0.0)
+  import shared-version-on-into;
+}
+world shared-world-with-versions-and-include {
+  import shared-version-on-from;
+  @since(version = 1.0.0)
+  import shared-version-on-into;
+}
diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr
index 3a72311f..7aed7b2c 100644
--- a/tests/cli/validate-unknown-features.wat.stderr
+++ b/tests/cli/validate-unknown-features.wat.stderr
@@ -1,4 +1,4 @@
 error: invalid value 'unknown' for '--features <FEATURES>': unknown feature `unknown`
-Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, legacy-exceptions
+Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, component-model-multiple-returns, legacy-exceptions
 
 For more information, try '--help'.
diff --git a/tests/local/component-model/func.wast b/tests/local/component-model/func.wast
index 0dceec97..f5ec7113 100644
--- a/tests/local/component-model/func.wast
+++ b/tests/local/component-model/func.wast
@@ -2,9 +2,14 @@
   (import "a" (func (param "foo" string)))
   (import "b" (func (param "foo" string) (param "bar" s32) (param "baz" u32)))
   (import "c" (func (result "foo" (tuple u8))))
-  (import "d" (func (result "foo" string) (result "bar" s32) (result "baz" u32)))
 )
 
+(assert_invalid
+  (component
+    (import "a" (func (result "foo" string) (result "bar" s32) (result "baz" u32)))
+  )
+  "multiple returns on a function is now a gated feature")
+
 (component
   (import "a" (func))
   (import "b" (func (param "p1" string)))
@@ -12,12 +17,6 @@
   (import "d" (func (param "p1" bool) (result string)))
 )
 
-(assert_invalid
-  (component
-    (import "a" (func (result "foo" string) (result s32) (result "bar" u32)))
-  )
-  "function result name cannot be empty"
-)
 
 (assert_invalid
   (component
@@ -26,12 +25,6 @@
   "function parameter name `FOO` conflicts with previous parameter name `foo`"
 )
 
-(assert_invalid
-  (component
-    (type (func (result "FOO" string) (result "foo" u32)))
-  )
-  "function result name `foo` conflicts with previous result name `FOO`"
-)
 
 (assert_invalid
   (component
diff --git a/tests/local/component-model/multiple-returns.wast b/tests/local/component-model/multiple-returns.wast
new file mode 100644
index 00000000..d8173fa4
--- /dev/null
+++ b/tests/local/component-model/multiple-returns.wast
@@ -0,0 +1,37 @@
+(component
+  (import "a" (func (result "foo" string) (result "bar" s32) (result "baz" u32)))
+)
+
+(component
+  (import "a" (func $f (param "a" string) (param "b" string) (result "c" s32) (result "d" s32)))
+  (import "b" (value $v string))
+  (import "c" (value $v2 string))
+  (start $f (value $v) (value $v2) (result (value $c)) (result (value $d)))
+  (export "d" (value $c))
+  (export "e" (value $d))
+)
+
+(assert_invalid
+  (component
+    (import "a" (func $f (param "a" string) (param "b" string) (result "c" s32) (result "d" s32)))
+    (import "b" (value $v string))
+    (import "c" (value $v2 string))
+    (start $f (value $v) (value $v2) (result (value $c)))
+    (export "a" (value $c))
+  )
+  "component start function has a result count of 1 but the function type has a result count of 2"
+)
+
+(assert_invalid
+  (component
+    (import "a" (func (result "foo" string) (result s32) (result "bar" u32)))
+  )
+  "function result name cannot be empty"
+)
+
+(assert_invalid
+  (component
+    (type (func (result "FOO" string) (result "foo" u32)))
+  )
+  "function result name `foo` conflicts with previous result name `FOO`"
+)
diff --git a/tests/local/component-model/start.wast b/tests/local/component-model/start.wast
index 31a86e1c..38b565fb 100644
--- a/tests/local/component-model/start.wast
+++ b/tests/local/component-model/start.wast
@@ -44,26 +44,6 @@
   (export "b" (value $a))
 )
 
-(component
-  (import "a" (func $f (param "a" string) (param "b" string) (result "c" s32) (result "d" s32)))
-  (import "b" (value $v string))
-  (import "c" (value $v2 string))
-  (start $f (value $v) (value $v2) (result (value $c)) (result (value $d)))
-  (export "d" (value $c))
-  (export "e" (value $d))
-)
-
-(assert_invalid
-  (component
-    (import "a" (func $f (param "a" string) (param "b" string) (result "c" s32) (result "d" s32)))
-    (import "b" (value $v string))
-    (import "c" (value $v2 string))
-    (start $f (value $v) (value $v2) (result (value $c)))
-    (export "a" (value $c))
-  )
-  "component start function has a result count of 1 but the function type has a result count of 2"
-)
-
 (assert_invalid
   (component
     (import "a" (func $f))
diff --git a/tests/local/legacy-exceptions.wat b/tests/local/legacy-exceptions/legacy-exceptions.wat
similarity index 100%
rename from tests/local/legacy-exceptions.wat
rename to tests/local/legacy-exceptions/legacy-exceptions.wat
diff --git a/tests/local/legacy-exceptions/rethrow.wast b/tests/local/legacy-exceptions/rethrow.wast
new file mode 100644
index 00000000..5a7b320c
--- /dev/null
+++ b/tests/local/legacy-exceptions/rethrow.wast
@@ -0,0 +1,124 @@
+;; Test rethrow instruction.
+
+(module
+  (tag $e0)
+  (tag $e1)
+
+  (func (export "catch-rethrow-0")
+    try
+      throw $e0
+    catch $e0
+      rethrow 0
+    end
+  )
+
+  (func (export "catch-rethrow-1") (param i32) (result i32)
+    try (result i32)
+      throw $e0
+    catch $e0
+      local.get 0
+      i32.eqz
+      if
+        rethrow 1
+      end
+      i32.const 23
+    end
+  )
+
+  (func (export "catchall-rethrow-0")
+    try
+      throw $e0
+    catch_all
+      rethrow 0
+    end
+  )
+
+  (func (export "catchall-rethrow-1") (param i32) (result i32)
+    try (result i32)
+      throw $e0
+    catch_all
+      local.get 0
+      i32.eqz
+      if
+        rethrow 1
+      end
+      i32.const 23
+    end
+  )
+
+  (func (export "rethrow-nested") (param i32) (result i32)
+    try (result i32)
+      throw $e1
+    catch $e1
+      try (result i32)
+        throw $e0
+      catch $e0
+        local.get 0
+        i32.const 0
+        i32.eq
+        if
+          rethrow 1
+        end
+        local.get 0
+        i32.const 1
+        i32.eq
+        if
+          rethrow 2
+        end
+        i32.const 23
+      end
+    end
+  )
+
+  (func (export "rethrow-recatch") (param i32) (result i32)
+    try (result i32)
+      throw $e0
+    catch $e0
+      try (result i32)
+        local.get 0
+        i32.eqz
+        if
+          rethrow 2
+        end
+        i32.const 42
+      catch $e0
+        i32.const 23
+      end
+    end
+  )
+
+  (func (export "rethrow-stack-polymorphism")
+    try
+      throw $e0
+    catch $e0
+      i32.const 1
+      rethrow 0
+    end
+  )
+)
+
+(assert_exception (invoke "catch-rethrow-0"))
+
+(assert_exception (invoke "catch-rethrow-1" (i32.const 0)))
+(assert_return (invoke "catch-rethrow-1" (i32.const 1)) (i32.const 23))
+
+(assert_exception (invoke "catchall-rethrow-0"))
+
+(assert_exception (invoke "catchall-rethrow-1" (i32.const 0)))
+(assert_return (invoke "catchall-rethrow-1" (i32.const 1)) (i32.const 23))
+(assert_exception (invoke "rethrow-nested" (i32.const 0)))
+(assert_exception (invoke "rethrow-nested" (i32.const 1)))
+(assert_return (invoke "rethrow-nested" (i32.const 2)) (i32.const 23))
+
+(assert_return (invoke "rethrow-recatch" (i32.const 0)) (i32.const 23))
+(assert_return (invoke "rethrow-recatch" (i32.const 1)) (i32.const 42))
+
+(assert_exception (invoke "rethrow-stack-polymorphism"))
+
+(assert_invalid (module (func (rethrow 0))) "invalid rethrow label")
+(assert_invalid (module (func (block (rethrow 0)))) "invalid rethrow label")
+(assert_invalid (module (func
+                           try
+			     rethrow 0
+			   delegate 0))
+                "invalid rethrow label")
diff --git a/tests/local/legacy-exceptions/throw.wast b/tests/local/legacy-exceptions/throw.wast
new file mode 100644
index 00000000..2ab9f2f3
--- /dev/null
+++ b/tests/local/legacy-exceptions/throw.wast
@@ -0,0 +1,56 @@
+;; Test throw instruction.
+
+(module
+  (tag $e0)
+  (tag $e-i32 (param i32))
+  (tag $e-f32 (param f32))
+  (tag $e-i64 (param i64))
+  (tag $e-f64 (param f64))
+  (tag $e-i32-i32 (param i32 i32))
+
+  (func $throw-if (export "throw-if") (param i32) (result i32)
+    (local.get 0)
+    (i32.const 0) (if (i32.ne) (then (throw $e0)))
+    (i32.const 0)
+  )
+
+  (func (export "throw-param-f32") (param f32) (local.get 0) (throw $e-f32))
+
+  (func (export "throw-param-i64") (param i64) (local.get 0) (throw $e-i64))
+
+  (func (export "throw-param-f64") (param f64) (local.get 0) (throw $e-f64))
+
+  (func $throw-1-2 (i32.const 1) (i32.const 2) (throw $e-i32-i32))
+  (func (export "test-throw-1-2")
+    try
+      call $throw-1-2
+    catch $e-i32-i32
+      i32.const 2
+      i32.ne
+      if
+        unreachable
+      end
+      i32.const 1
+      i32.ne
+      if
+        unreachable
+      end
+    end
+  )
+)
+
+(assert_return (invoke "throw-if" (i32.const 0)) (i32.const 0))
+(assert_exception (invoke "throw-if" (i32.const 10)))
+(assert_exception (invoke "throw-if" (i32.const -1)))
+
+(assert_exception (invoke "throw-param-f32" (f32.const 5.0)))
+(assert_exception (invoke "throw-param-i64" (i64.const 5)))
+(assert_exception (invoke "throw-param-f64" (f64.const 5.0)))
+
+(assert_return (invoke "test-throw-1-2"))
+
+(assert_invalid (module (func (throw 0))) "unknown tag 0")
+(assert_invalid (module (tag (param i32)) (func (throw 0)))
+                "type mismatch: instruction requires [i32] but stack has []")
+(assert_invalid (module (tag (param i32)) (func (i64.const 5) (throw 0)))
+                "type mismatch: instruction requires [i32] but stack has [i64]")
diff --git a/tests/local/legacy-exceptions/try_catch.wast b/tests/local/legacy-exceptions/try_catch.wast
new file mode 100644
index 00000000..8819e824
--- /dev/null
+++ b/tests/local/legacy-exceptions/try_catch.wast
@@ -0,0 +1,309 @@
+;; Test try-catch blocks.
+
+(module
+  (tag $e0 (export "e0"))
+  (func (export "throw") (throw $e0))
+)
+
+(register "test")
+
+(module
+  (tag $imported-e0 (import "test" "e0"))
+  (func $imported-throw (import "test" "throw"))
+  (tag $e0)
+  (tag $e1)
+  (tag $e2)
+  (tag $e-i32 (param i32))
+  (tag $e-f32 (param f32))
+  (tag $e-i64 (param i64))
+  (tag $e-f64 (param f64))
+
+  (func $throw-if (param i32) (result i32)
+    (local.get 0)
+    (i32.const 0) (if (i32.ne) (then (throw $e0)))
+    (i32.const 0)
+  )
+
+  (func (export "empty-catch")
+    try
+    catch $e0
+    end
+  )
+
+  (func (export "simple-throw-catch") (param i32) (result i32)
+    try (result i32)
+      local.get 0
+      i32.eqz
+      if
+        throw 1
+      end
+      i32.const 42
+    catch 1
+      i32.const 23
+    end
+  )
+
+  (func (export "unreachable-not-caught") try unreachable catch_all end)
+
+  (func $div (param i32 i32) (result i32)
+    (local.get 0) (local.get 1) (i32.div_u)
+  )
+  (func (export "trap-in-callee") (param i32 i32) (result i32)
+    try (result i32)
+      local.get 0
+      local.get 1
+      call 5
+    catch_all
+      i32.const 11
+    end
+  )
+
+  (func (export "catch-complex-1") (param i32) (result i32)
+    try (result i32)
+      try (result i32)
+        local.get 0
+        i32.eqz
+        if
+          throw 1
+        else
+          local.get 0
+          i32.const 1
+          i32.eq
+          if
+            throw 2
+          else
+            throw 3
+          end
+        end
+        i32.const 2
+      catch 1
+        i32.const 3
+      end
+    catch 2
+      i32.const 4
+    end
+  )
+
+  (func (export "catch-complex-2") (param i32) (result i32)
+    try (result i32)
+      local.get 0
+      i32.eqz
+      if
+        throw 1
+      else
+        local.get 0
+        i32.const 1
+        i32.eq
+        if
+          throw 2
+        else
+          throw 3
+        end
+      end
+      i32.const 2
+    catch 1
+      i32.const 3
+    catch 2
+      i32.const 4
+    end
+  )
+
+  (func (export "throw-catch-param-i32") (param i32) (result i32)
+    try (result i32)
+      local.get 0
+      throw 4
+      i32.const 2
+    catch 4
+      return
+    end
+  )
+
+  (func (export "throw-catch-param-f32") (param f32) (result f32)
+    try (result f32)
+      local.get 0
+      throw 5
+      f32.const 0
+    catch 5
+      return
+    end
+  )
+
+  (func (export "throw-catch-param-i64") (param i64) (result i64)
+    try (result i64)
+      local.get 0
+      throw 6
+      i64.const 2
+    catch 6
+      return
+    end
+  )
+
+  (func (export "throw-catch-param-f64") (param f64) (result f64)
+    try (result f64)
+      local.get 0
+      throw 7
+      f64.const 0
+    catch 7
+      return
+    end
+  )
+
+  (func $throw-param-i32 (param i32) (local.get 0) (throw $e-i32))
+  (func (export "catch-param-i32") (param i32) (result i32)
+    try (result i32)
+      i32.const 0
+      local.get 0
+      call 13
+    catch 4
+    end
+  )
+
+  (func (export "catch-imported") (result i32)
+    try (result i32)
+      i32.const 1
+      call 0
+    catch 0
+      i32.const 2
+    end
+  )
+
+  (func (export "catchless-try") (param i32) (result i32)
+    try (result i32)
+      try (result i32)
+        local.get 0
+        call 1
+      end
+    catch 1
+      i32.const 1
+    end
+  )
+
+  (func $throw-void (throw $e0))
+  (func (export "return-call-in-try-catch")
+    try
+      return_call 17
+    catch 1
+    end
+  )
+
+  (table funcref (elem $throw-void))
+  (func (export "return-call-indirect-in-try-catch")
+    try
+      i32.const 0
+      return_call_indirect (type 0)
+    catch 1
+    end
+  )
+
+  (func (export "break-try-catch")
+    try
+      br 0
+    catch 1
+    end
+  )
+
+  (func (export "break-try-catch_all")
+    try
+      br 0
+    catch_all
+    end
+  )
+)
+
+(assert_return (invoke "empty-catch"))
+
+(assert_return (invoke "simple-throw-catch" (i32.const 0)) (i32.const 23))
+(assert_return (invoke "simple-throw-catch" (i32.const 1)) (i32.const 42))
+
+(assert_trap (invoke "unreachable-not-caught") "unreachable")
+
+(assert_return (invoke "trap-in-callee" (i32.const 7) (i32.const 2)) (i32.const 3))
+(assert_trap (invoke "trap-in-callee" (i32.const 1) (i32.const 0)) "integer divide by zero")
+
+(assert_return (invoke "catch-complex-1" (i32.const 0)) (i32.const 3))
+(assert_return (invoke "catch-complex-1" (i32.const 1)) (i32.const 4))
+(assert_exception (invoke "catch-complex-1" (i32.const 2)))
+
+(assert_return (invoke "catch-complex-2" (i32.const 0)) (i32.const 3))
+(assert_return (invoke "catch-complex-2" (i32.const 1)) (i32.const 4))
+(assert_exception (invoke "catch-complex-2" (i32.const 2)))
+
+(assert_return (invoke "throw-catch-param-i32" (i32.const 0)) (i32.const 0))
+(assert_return (invoke "throw-catch-param-i32" (i32.const 1)) (i32.const 1))
+(assert_return (invoke "throw-catch-param-i32" (i32.const 10)) (i32.const 10))
+
+(assert_return (invoke "throw-catch-param-f32" (f32.const 5.0)) (f32.const 5.0))
+(assert_return (invoke "throw-catch-param-f32" (f32.const 10.5)) (f32.const 10.5))
+
+(assert_return (invoke "throw-catch-param-i64" (i64.const 5)) (i64.const 5))
+(assert_return (invoke "throw-catch-param-i64" (i64.const 0)) (i64.const 0))
+(assert_return (invoke "throw-catch-param-i64" (i64.const -1)) (i64.const -1))
+
+(assert_return (invoke "throw-catch-param-f64" (f64.const 5.0)) (f64.const 5.0))
+(assert_return (invoke "throw-catch-param-f64" (f64.const 10.5)) (f64.const 10.5))
+
+(assert_return (invoke "catch-param-i32" (i32.const 5)) (i32.const 5))
+
+(assert_return (invoke "catch-imported") (i32.const 2))
+
+(assert_return (invoke "catchless-try" (i32.const 0)) (i32.const 0))
+(assert_return (invoke "catchless-try" (i32.const 1)) (i32.const 1))
+
+(assert_exception (invoke "return-call-in-try-catch"))
+(assert_exception (invoke "return-call-indirect-in-try-catch"))
+
+(assert_return (invoke "break-try-catch"))
+(assert_return (invoke "break-try-catch_all"))
+
+(module
+  (func $imported-throw (import "test" "throw"))
+  (tag $e0)
+
+  (func (export "imported-mismatch") (result i32)
+    try (result i32)  ;; label = @1
+      try (result i32)  ;; label = @2
+        i32.const 1
+        call 0
+      catch 0
+        i32.const 2
+      end
+    catch_all
+      i32.const 3
+    end
+  )
+)
+
+(assert_return (invoke "imported-mismatch") (i32.const 3))
+
+;; Ignore per https://github.com/bytecodealliance/wasm-tools/issues/1671
+;;
+;; (assert_malformed
+;;  (module quote "(module (func (catch_all)))")
+;;  "unexpected token"
+;; )
+
+;; (assert_malformed
+;;  (module quote "(module (tag $e) (func (catch $e)))")
+;;  "unexpected token"
+;; )
+
+;; (assert_malformed
+;;  (module quote
+;;    "(module (func try catch_all catch_all end))"
+;;  )
+;;  "unexpected token"
+;; )
+
+(assert_invalid (module (func (result i32) try (result i32) end))
+                "type mismatch: instruction requires [i32] but stack has []")
+(assert_invalid (module (func (result i32) try (result i32) i64.const 42 end))
+                "type mismatch: instruction requires [i32] but stack has [i64]")
+(assert_invalid (module (tag) (func try catch 0 i32.const 42 end))
+                "type mismatch: block requires [] but stack has [i32]")
+(assert_invalid (module
+                  (tag (param i64))
+                  (func (result i32)
+                    try (result i32) i32.const 42 catch 0 end))
+                "type mismatch: instruction requires [i32] but stack has [i64]")
+(assert_invalid (module (func try catch_all i32.const 32 end))
+                "type mismatch: block requires [] but stack has [i32]")
diff --git a/tests/local/legacy-exceptions/try_delegate.wast b/tests/local/legacy-exceptions/try_delegate.wast
new file mode 100644
index 00000000..8f115268
--- /dev/null
+++ b/tests/local/legacy-exceptions/try_delegate.wast
@@ -0,0 +1,275 @@
+;; Test try-delegate blocks.
+
+(module
+  (tag $e0)
+  (tag $e1)
+
+  (func (export "delegate-no-throw") (result i32)
+    try (result i32)  ;; label = @1
+      try (result i32)  ;; label = @2
+        i32.const 1
+      delegate 0
+    catch 0
+      i32.const 2
+    end
+  )
+
+  (func $throw-if (param i32)
+    (local.get 0)
+    (if (then (throw $e0)) (else))
+  )
+
+  (func (export "delegate-throw") (param i32) (result i32)
+    try (result i32)  ;; label = @1
+      try (result i32)  ;; label = @2
+        local.get 0
+        call 1
+        i32.const 1
+      delegate 0
+    catch 0
+      i32.const 2
+    end
+  )
+
+  (func (export "delegate-skip") (result i32)
+    try (result i32)  ;; label = @1
+      try (result i32)  ;; label = @2
+        try (result i32)  ;; label = @3
+          throw 0
+          i32.const 1
+        delegate 1
+      catch 0
+        i32.const 2
+      end
+    catch 0
+      i32.const 3
+    end
+  )
+
+  (func (export "delegate-to-block") (result i32)
+    try (result i32)  ;; label = @1
+      block  ;; label = @2
+        try  ;; label = @3
+          throw 0
+        delegate 0
+      end
+      i32.const 0
+    catch_all
+      i32.const 1
+    end
+  )
+
+  (func (export "delegate-to-catch") (result i32)
+    try (result i32)  ;; label = @1
+      try  ;; label = @2
+        throw 0
+      catch 0
+        try  ;; label = @3
+          rethrow 1 (;@2;)
+        delegate 0
+      end
+      i32.const 0
+    catch_all
+      i32.const 1
+    end
+  )
+
+  (func (export "delegate-to-caller-trivial")
+    try  ;; label = @1
+      throw 0
+    delegate 0)
+
+  (func (export "delegate-to-caller-skipping")
+    try  ;; label = @1
+      try  ;; label = @2
+        throw 0
+      delegate 1
+    catch_all
+    end
+  )
+
+  (func $select-tag (param i32)
+    block  ;; label = @1
+      block  ;; label = @2
+        block  ;; label = @3
+          local.get 0
+          br_table 0 (;@3;) 1 (;@2;) 2 (;@1;)
+        end
+        return
+      end
+      throw 0
+    end
+    throw 1
+  )
+
+  (func (export "delegate-merge") (param i32 i32) (result i32)
+    try (result i32)  ;; label = @1
+      local.get 0
+      call 8
+      try (result i32)  ;; label = @2
+        local.get 1
+        call 8
+        i32.const 1
+      delegate 0
+    catch 0
+      i32.const 2
+    end
+  )
+
+  (func (export "delegate-throw-no-catch") (result i32)
+    try (result i32)  ;; label = @1
+      try (result i32)  ;; label = @2
+        throw 0
+        i32.const 1
+      delegate 0
+    catch 1
+      i32.const 2
+    end
+  )
+
+  (func (export "delegate-correct-targets") (result i32)
+    try (result i32)  ;; label = @1
+      try  ;; label = @2
+        try  ;; label = @3
+          try  ;; label = @4
+            try  ;; label = @5
+              try  ;; label = @6
+                throw 0
+              delegate 1
+            catch_all
+              unreachable
+            end
+          delegate 1
+        catch_all
+          unreachable
+        end
+      catch_all
+        try  ;; label = @3
+          throw 0
+        delegate 0
+      end
+      unreachable
+    catch_all
+      i32.const 1
+    end)
+
+  (func $throw-void (throw $e0))
+  (func (export "return-call-in-try-delegate")
+    try  ;; label = @1
+      try  ;; label = @2
+        return_call 12
+      delegate 0
+    catch 0
+    end
+  )
+
+  (table funcref (elem $throw-void))
+  (func (export "return-call-indirect-in-try-delegate")
+    try  ;; label = @1
+      try  ;; label = @2
+        i32.const 0
+        return_call_indirect (type 0)
+      delegate 0
+    catch 0
+    end
+  )
+
+  (func (export "break-try-delegate")
+    try  ;; label = @1
+      br 0 (;@1;)
+    delegate 0)
+
+  (func (export "break-and-call-throw") (result i32)
+    try (result i32)  ;; label = @1
+      try (result i32)  ;; label = @2
+        block  ;; label = @3
+          try  ;; label = @4
+            br 1 (;@3;)
+          delegate 2
+        end
+        call 12
+        i32.const 0
+      catch 0
+        i32.const 1
+      end
+    catch 0
+      i32.const 2
+    end
+  )
+
+  (func (export "break-and-throw") (result i32)
+    try (result i32)  ;; label = @1
+      try (result i32)  ;; label = @2
+        block  ;; label = @3
+          try  ;; label = @4
+            br 1 (;@3;)
+          delegate 2
+        end
+        throw 0
+        i32.const 0
+      catch 0
+        i32.const 1
+      end
+    catch 0
+      i32.const 2
+    end
+  )
+)
+
+(assert_return (invoke "delegate-no-throw") (i32.const 1))
+
+(assert_return (invoke "delegate-throw" (i32.const 0)) (i32.const 1))
+(assert_return (invoke "delegate-throw" (i32.const 1)) (i32.const 2))
+
+(assert_exception (invoke "delegate-throw-no-catch"))
+
+(assert_return (invoke "delegate-merge" (i32.const 1) (i32.const 0)) (i32.const 2))
+(assert_exception (invoke "delegate-merge" (i32.const 2) (i32.const 0)))
+(assert_return (invoke "delegate-merge" (i32.const 0) (i32.const 1)) (i32.const 2))
+(assert_exception (invoke "delegate-merge" (i32.const 0) (i32.const 2)))
+(assert_return (invoke "delegate-merge" (i32.const 0) (i32.const 0)) (i32.const 1))
+
+(assert_return (invoke "delegate-skip") (i32.const 3))
+
+(assert_return (invoke "delegate-to-block") (i32.const 1))
+(assert_return (invoke "delegate-to-catch") (i32.const 1))
+
+(assert_exception (invoke "delegate-to-caller-trivial"))
+(assert_exception (invoke "delegate-to-caller-skipping"))
+
+(assert_return (invoke "delegate-correct-targets") (i32.const 1))
+
+(assert_exception (invoke "return-call-in-try-delegate"))
+(assert_exception (invoke "return-call-indirect-in-try-delegate"))
+
+(assert_return (invoke "break-try-delegate"))
+
+(assert_return (invoke "break-and-call-throw") (i32.const 1))
+(assert_return (invoke "break-and-throw") (i32.const 1))
+
+;; Ignore per https://github.com/bytecodealliance/wasm-tools/issues/1671
+;;
+;; (assert_malformed
+;;   (module quote "(module (func (delegate 0)))")
+;;   "unexpected token"
+;; )
+
+;; (assert_malformed
+;;   (module quote "(module (tag $e) (func try catch $e delegate 0))")
+;;   "unexpected token"
+;; )
+
+;; (assert_malformed
+;;   (module quote "(module (func try catch_all delegate 0))")
+;;   "unexpected token"
+;; )
+
+;; (assert_malformed
+;;   (module quote "(module (func try delegate delegate 0))")
+;;   "unexpected token"
+;; )
+
+(assert_invalid
+  (module (func try delegate 1))
+  "unknown label"
+)
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index 4a8e4f2b..7f7d56bd 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -580,6 +580,7 @@ impl TestState {
             & !WasmFeatures::COMPONENT_MODEL
             & !WasmFeatures::COMPONENT_MODEL_NESTED_NAMES
             & !WasmFeatures::COMPONENT_MODEL_MORE_FLAGS
+            & !WasmFeatures::COMPONENT_MODEL_MULTIPLE_RETURNS
             & !WasmFeatures::LEGACY_EXCEPTIONS;
         for part in test.iter().filter_map(|t| t.to_str()) {
             match part {
@@ -605,7 +606,7 @@ impl TestState {
                 }
                 "simd" => features.insert(WasmFeatures::SIMD),
                 "exception-handling" => features.insert(WasmFeatures::EXCEPTIONS),
-                "legacy-exceptions.wat" => features.insert(WasmFeatures::LEGACY_EXCEPTIONS),
+                "legacy-exceptions" => features.insert(WasmFeatures::LEGACY_EXCEPTIONS),
                 "tail-call" => features.insert(WasmFeatures::TAIL_CALL),
                 "memory64" => features.insert(WasmFeatures::MEMORY64),
                 "component-model" => features.insert(WasmFeatures::COMPONENT_MODEL),
@@ -629,6 +630,9 @@ impl TestState {
                 "more-flags.wast" => {
                     features.insert(WasmFeatures::COMPONENT_MODEL_MORE_FLAGS);
                 }
+                "multiple-returns.wast" => {
+                    features.insert(WasmFeatures::COMPONENT_MODEL_MULTIPLE_RETURNS);
+                }
                 _ => {}
             }
         }
diff --git a/tests/snapshots/local/component-model/func.wast/0.print b/tests/snapshots/local/component-model/func.wast/0.print
index fc1ca89f..4613ae24 100644
--- a/tests/snapshots/local/component-model/func.wast/0.print
+++ b/tests/snapshots/local/component-model/func.wast/0.print
@@ -6,6 +6,4 @@
   (type (;2;) (tuple u8))
   (type (;3;) (func (result "foo" 2)))
   (import "c" (func (;2;) (type 3)))
-  (type (;4;) (func (result "foo" string) (result "bar" s32) (result "baz" u32)))
-  (import "d" (func (;3;) (type 4)))
 )
diff --git a/tests/snapshots/local/component-model/func.wast/1.print b/tests/snapshots/local/component-model/func.wast/2.print
similarity index 100%
rename from tests/snapshots/local/component-model/func.wast/1.print
rename to tests/snapshots/local/component-model/func.wast/2.print
diff --git a/tests/snapshots/local/component-model/func.wast/5.print b/tests/snapshots/local/component-model/func.wast/5.print
new file mode 100644
index 00000000..89724839
--- /dev/null
+++ b/tests/snapshots/local/component-model/func.wast/5.print
@@ -0,0 +1,11 @@
+(component
+  (type (;0;) (func (param "msg" string)))
+  (import "a" (func $log (;0;) (type 0)))
+  (core module $libc (;0;)
+    (memory (;0;) 1)
+    (export "memory" (memory 0))
+  )
+  (core instance $libc (;0;) (instantiate $libc))
+  (alias core export $libc "memory" (core memory (;0;)))
+  (core func (;0;) (canon lower (func $log) (memory 0)))
+)
diff --git a/tests/snapshots/local/component-model/func.wast/6.print b/tests/snapshots/local/component-model/func.wast/6.print
index 89724839..c417c7f1 100644
--- a/tests/snapshots/local/component-model/func.wast/6.print
+++ b/tests/snapshots/local/component-model/func.wast/6.print
@@ -1,11 +1,18 @@
 (component
-  (type (;0;) (func (param "msg" string)))
-  (import "a" (func $log (;0;) (type 0)))
-  (core module $libc (;0;)
+  (core module $m (;0;)
+    (type (;0;) (func (result i32)))
+    (func (;0;) (type 0) (result i32)
+      unreachable
+    )
     (memory (;0;) 1)
     (export "memory" (memory 0))
+    (export "ret-list" (func 0))
   )
-  (core instance $libc (;0;) (instantiate $libc))
-  (alias core export $libc "memory" (core memory (;0;)))
-  (core func (;0;) (canon lower (func $log) (memory 0)))
+  (core instance $i (;0;) (instantiate $m))
+  (type (;0;) (list u8))
+  (type (;1;) (func (result 0)))
+  (alias core export $i "ret-list" (core func (;0;)))
+  (alias core export $i "memory" (core memory (;0;)))
+  (func (;0;) (type 1) (canon lift (core func 0) (memory 0)))
+  (export (;1;) "ret-list" (func 0))
 )
diff --git a/tests/snapshots/local/component-model/func.wast/7.print b/tests/snapshots/local/component-model/func.wast/7.print
index c417c7f1..0fb8bfe3 100644
--- a/tests/snapshots/local/component-model/func.wast/7.print
+++ b/tests/snapshots/local/component-model/func.wast/7.print
@@ -1,18 +1,14 @@
 (component
-  (core module $m (;0;)
-    (type (;0;) (func (result i32)))
-    (func (;0;) (type 0) (result i32)
-      unreachable
+  (type $big (;0;) (func (param "p1" u32) (param "p2" u32) (param "p3" u32) (param "p4" u32) (param "p5" u32) (param "p6" u32) (param "p7" u32) (param "p8" u32) (param "p9" u32) (param "p10" u32) (param "p11" u32) (param "p12" u32) (param "p13" u32) (param "p14" u32) (param "p15" u32) (param "p16" u32) (param "p17" u32) (param "p18" u32) (param "p19" u32) (param "p20" u32)))
+  (component $c (;0;)
+    (alias outer 1 $big (type $big (;0;)))
+    (import "big" (func $big (;0;) (type $big)))
+    (core module $libc (;0;)
+      (memory (;0;) 1)
+      (export "memory" (memory 0))
     )
-    (memory (;0;) 1)
-    (export "memory" (memory 0))
-    (export "ret-list" (func 0))
+    (core instance $libc (;0;) (instantiate $libc))
+    (alias core export $libc "memory" (core memory (;0;)))
+    (core func $big (;0;) (canon lower (func $big) (memory 0)))
   )
-  (core instance $i (;0;) (instantiate $m))
-  (type (;0;) (list u8))
-  (type (;1;) (func (result 0)))
-  (alias core export $i "ret-list" (core func (;0;)))
-  (alias core export $i "memory" (core memory (;0;)))
-  (func (;0;) (type 1) (canon lift (core func 0) (memory 0)))
-  (export (;1;) "ret-list" (func 0))
 )
diff --git a/tests/snapshots/local/component-model/func.wast/8.print b/tests/snapshots/local/component-model/func.wast/8.print
deleted file mode 100644
index 0fb8bfe3..00000000
--- a/tests/snapshots/local/component-model/func.wast/8.print
+++ /dev/null
@@ -1,14 +0,0 @@
-(component
-  (type $big (;0;) (func (param "p1" u32) (param "p2" u32) (param "p3" u32) (param "p4" u32) (param "p5" u32) (param "p6" u32) (param "p7" u32) (param "p8" u32) (param "p9" u32) (param "p10" u32) (param "p11" u32) (param "p12" u32) (param "p13" u32) (param "p14" u32) (param "p15" u32) (param "p16" u32) (param "p17" u32) (param "p18" u32) (param "p19" u32) (param "p20" u32)))
-  (component $c (;0;)
-    (alias outer 1 $big (type $big (;0;)))
-    (import "big" (func $big (;0;) (type $big)))
-    (core module $libc (;0;)
-      (memory (;0;) 1)
-      (export "memory" (memory 0))
-    )
-    (core instance $libc (;0;) (instantiate $libc))
-    (alias core export $libc "memory" (core memory (;0;)))
-    (core func $big (;0;) (canon lower (func $big) (memory 0)))
-  )
-)
diff --git a/tests/snapshots/local/component-model/multiple-returns.wast/0.print b/tests/snapshots/local/component-model/multiple-returns.wast/0.print
new file mode 100644
index 00000000..5bb4d4dc
--- /dev/null
+++ b/tests/snapshots/local/component-model/multiple-returns.wast/0.print
@@ -0,0 +1,4 @@
+(component
+  (type (;0;) (func (result "foo" string) (result "bar" s32) (result "baz" u32)))
+  (import "a" (func (;0;) (type 0)))
+)
diff --git a/tests/snapshots/local/component-model/start.wast/6.print b/tests/snapshots/local/component-model/multiple-returns.wast/1.print
similarity index 100%
rename from tests/snapshots/local/component-model/start.wast/6.print
rename to tests/snapshots/local/component-model/multiple-returns.wast/1.print
diff --git a/tests/snapshots/local/legacy-exceptions.wat.print b/tests/snapshots/local/legacy-exceptions/legacy-exceptions.wat.print
similarity index 100%
rename from tests/snapshots/local/legacy-exceptions.wat.print
rename to tests/snapshots/local/legacy-exceptions/legacy-exceptions.wat.print
diff --git a/tests/snapshots/local/legacy-exceptions/rethrow.wast.json b/tests/snapshots/local/legacy-exceptions/rethrow.wast.json
new file mode 100644
index 00000000..40a67982
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/rethrow.wast.json
@@ -0,0 +1,214 @@
+{
+  "source_filename": "tests/local/legacy-exceptions/rethrow.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "rethrow.0.wasm"
+    },
+    {
+      "type": "assert_exception",
+      "line": 100,
+      "action": {
+        "type": "invoke",
+        "field": "catch-rethrow-0",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 102,
+      "action": {
+        "type": "invoke",
+        "field": "catch-rethrow-1",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 103,
+      "action": {
+        "type": "invoke",
+        "field": "catch-rethrow-1",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "23"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 105,
+      "action": {
+        "type": "invoke",
+        "field": "catchall-rethrow-0",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 107,
+      "action": {
+        "type": "invoke",
+        "field": "catchall-rethrow-1",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 108,
+      "action": {
+        "type": "invoke",
+        "field": "catchall-rethrow-1",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "23"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 109,
+      "action": {
+        "type": "invoke",
+        "field": "rethrow-nested",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 110,
+      "action": {
+        "type": "invoke",
+        "field": "rethrow-nested",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 111,
+      "action": {
+        "type": "invoke",
+        "field": "rethrow-nested",
+        "args": [
+          {
+            "type": "i32",
+            "value": "2"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "23"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 113,
+      "action": {
+        "type": "invoke",
+        "field": "rethrow-recatch",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "23"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 114,
+      "action": {
+        "type": "invoke",
+        "field": "rethrow-recatch",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "42"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 116,
+      "action": {
+        "type": "invoke",
+        "field": "rethrow-stack-polymorphism",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_invalid",
+      "line": 118,
+      "filename": "rethrow.1.wasm",
+      "text": "invalid rethrow label",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 119,
+      "filename": "rethrow.2.wasm",
+      "text": "invalid rethrow label",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 120,
+      "filename": "rethrow.3.wasm",
+      "text": "invalid rethrow label",
+      "module_type": "binary"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/legacy-exceptions/rethrow.wast/0.print b/tests/snapshots/local/legacy-exceptions/rethrow.wast/0.print
new file mode 100644
index 00000000..99834185
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/rethrow.wast/0.print
@@ -0,0 +1,98 @@
+(module
+  (type (;0;) (func))
+  (type (;1;) (func (param i32) (result i32)))
+  (func (;0;) (type 0)
+    try ;; label = @1
+      throw $e0
+    catch $e0
+      rethrow 0 (;@1;)
+    end
+  )
+  (func (;1;) (type 1) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      throw $e0
+    catch $e0
+      local.get 0
+      i32.eqz
+      if ;; label = @2
+        rethrow 1 (;@1;)
+      end
+      i32.const 23
+    end
+  )
+  (func (;2;) (type 0)
+    try ;; label = @1
+      throw $e0
+    catch_all
+      rethrow 0 (;@1;)
+    end
+  )
+  (func (;3;) (type 1) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      throw $e0
+    catch_all
+      local.get 0
+      i32.eqz
+      if ;; label = @2
+        rethrow 1 (;@1;)
+      end
+      i32.const 23
+    end
+  )
+  (func (;4;) (type 1) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      throw $e1
+    catch $e1
+      try (result i32) ;; label = @2
+        throw $e0
+      catch $e0
+        local.get 0
+        i32.const 0
+        i32.eq
+        if ;; label = @3
+          rethrow 1 (;@2;)
+        end
+        local.get 0
+        i32.const 1
+        i32.eq
+        if ;; label = @3
+          rethrow 2 (;@1;)
+        end
+        i32.const 23
+      end
+    end
+  )
+  (func (;5;) (type 1) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      throw $e0
+    catch $e0
+      try (result i32) ;; label = @2
+        local.get 0
+        i32.eqz
+        if ;; label = @3
+          rethrow 2 (;@1;)
+        end
+        i32.const 42
+      catch $e0
+        i32.const 23
+      end
+    end
+  )
+  (func (;6;) (type 0)
+    try ;; label = @1
+      throw $e0
+    catch $e0
+      i32.const 1
+      rethrow 0 (;@1;)
+    end
+  )
+  (tag $e0 (;0;) (type 0))
+  (tag $e1 (;1;) (type 0))
+  (export "catch-rethrow-0" (func 0))
+  (export "catch-rethrow-1" (func 1))
+  (export "catchall-rethrow-0" (func 2))
+  (export "catchall-rethrow-1" (func 3))
+  (export "rethrow-nested" (func 4))
+  (export "rethrow-recatch" (func 5))
+  (export "rethrow-stack-polymorphism" (func 6))
+)
diff --git a/tests/snapshots/local/legacy-exceptions/throw.wast.json b/tests/snapshots/local/legacy-exceptions/throw.wast.json
new file mode 100644
index 00000000..bbffce78
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/throw.wast.json
@@ -0,0 +1,131 @@
+{
+  "source_filename": "tests/local/legacy-exceptions/throw.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "throw.0.wasm"
+    },
+    {
+      "type": "assert_return",
+      "line": 42,
+      "action": {
+        "type": "invoke",
+        "field": "throw-if",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "0"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 43,
+      "action": {
+        "type": "invoke",
+        "field": "throw-if",
+        "args": [
+          {
+            "type": "i32",
+            "value": "10"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 44,
+      "action": {
+        "type": "invoke",
+        "field": "throw-if",
+        "args": [
+          {
+            "type": "i32",
+            "value": "-1"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 46,
+      "action": {
+        "type": "invoke",
+        "field": "throw-param-f32",
+        "args": [
+          {
+            "type": "f32",
+            "value": "1084227584"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 47,
+      "action": {
+        "type": "invoke",
+        "field": "throw-param-i64",
+        "args": [
+          {
+            "type": "i64",
+            "value": "5"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 48,
+      "action": {
+        "type": "invoke",
+        "field": "throw-param-f64",
+        "args": [
+          {
+            "type": "f64",
+            "value": "4617315517961601024"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 50,
+      "action": {
+        "type": "invoke",
+        "field": "test-throw-1-2",
+        "args": []
+      },
+      "expected": []
+    },
+    {
+      "type": "assert_invalid",
+      "line": 52,
+      "filename": "throw.1.wasm",
+      "text": "unknown tag 0",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 53,
+      "filename": "throw.2.wasm",
+      "text": "type mismatch: instruction requires [i32] but stack has []",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 55,
+      "filename": "throw.3.wasm",
+      "text": "type mismatch: instruction requires [i32] but stack has [i64]",
+      "module_type": "binary"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/legacy-exceptions/throw.wast/0.print b/tests/snapshots/local/legacy-exceptions/throw.wast/0.print
new file mode 100644
index 00000000..afaf56a8
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/throw.wast/0.print
@@ -0,0 +1,62 @@
+(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)))
+  (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 $throw-1-2 (;4;) (type 0)
+    i32.const 1
+    i32.const 2
+    throw $e-i32-i32
+  )
+  (func (;5;) (type 0)
+    try ;; label = @1
+      call $throw-1-2
+    catch $e-i32-i32
+      i32.const 2
+      i32.ne
+      if ;; label = @2
+        unreachable
+      end
+      i32.const 1
+      i32.ne
+      if ;; label = @2
+        unreachable
+      end
+    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 "test-throw-1-2" (func 5))
+)
diff --git a/tests/snapshots/local/legacy-exceptions/try_catch.wast.json b/tests/snapshots/local/legacy-exceptions/try_catch.wast.json
new file mode 100644
index 00000000..8b256d18
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/try_catch.wast.json
@@ -0,0 +1,599 @@
+{
+  "source_filename": "tests/local/legacy-exceptions/try_catch.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "try_catch.0.wasm"
+    },
+    {
+      "type": "register",
+      "line": 8,
+      "as": "test"
+    },
+    {
+      "type": "module",
+      "line": 10,
+      "filename": "try_catch.1.wasm"
+    },
+    {
+      "type": "assert_return",
+      "line": 213,
+      "action": {
+        "type": "invoke",
+        "field": "empty-catch",
+        "args": []
+      },
+      "expected": []
+    },
+    {
+      "type": "assert_return",
+      "line": 215,
+      "action": {
+        "type": "invoke",
+        "field": "simple-throw-catch",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "23"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 216,
+      "action": {
+        "type": "invoke",
+        "field": "simple-throw-catch",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "42"
+        }
+      ]
+    },
+    {
+      "type": "assert_trap",
+      "line": 218,
+      "action": {
+        "type": "invoke",
+        "field": "unreachable-not-caught",
+        "args": []
+      },
+      "text": "unreachable"
+    },
+    {
+      "type": "assert_return",
+      "line": 220,
+      "action": {
+        "type": "invoke",
+        "field": "trap-in-callee",
+        "args": [
+          {
+            "type": "i32",
+            "value": "7"
+          },
+          {
+            "type": "i32",
+            "value": "2"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "3"
+        }
+      ]
+    },
+    {
+      "type": "assert_trap",
+      "line": 221,
+      "action": {
+        "type": "invoke",
+        "field": "trap-in-callee",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          },
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "text": "integer divide by zero"
+    },
+    {
+      "type": "assert_return",
+      "line": 223,
+      "action": {
+        "type": "invoke",
+        "field": "catch-complex-1",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "3"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 224,
+      "action": {
+        "type": "invoke",
+        "field": "catch-complex-1",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "4"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 225,
+      "action": {
+        "type": "invoke",
+        "field": "catch-complex-1",
+        "args": [
+          {
+            "type": "i32",
+            "value": "2"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 227,
+      "action": {
+        "type": "invoke",
+        "field": "catch-complex-2",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "3"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 228,
+      "action": {
+        "type": "invoke",
+        "field": "catch-complex-2",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "4"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 229,
+      "action": {
+        "type": "invoke",
+        "field": "catch-complex-2",
+        "args": [
+          {
+            "type": "i32",
+            "value": "2"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 231,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-i32",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "0"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 232,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-i32",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 233,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-i32",
+        "args": [
+          {
+            "type": "i32",
+            "value": "10"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "10"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 235,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-f32",
+        "args": [
+          {
+            "type": "f32",
+            "value": "1084227584"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "f32",
+          "value": "1084227584"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 236,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-f32",
+        "args": [
+          {
+            "type": "f32",
+            "value": "1093140480"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "f32",
+          "value": "1093140480"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 238,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-i64",
+        "args": [
+          {
+            "type": "i64",
+            "value": "5"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i64",
+          "value": "5"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 239,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-i64",
+        "args": [
+          {
+            "type": "i64",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i64",
+          "value": "0"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 240,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-i64",
+        "args": [
+          {
+            "type": "i64",
+            "value": "-1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i64",
+          "value": "-1"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 242,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-f64",
+        "args": [
+          {
+            "type": "f64",
+            "value": "4617315517961601024"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "f64",
+          "value": "4617315517961601024"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 243,
+      "action": {
+        "type": "invoke",
+        "field": "throw-catch-param-f64",
+        "args": [
+          {
+            "type": "f64",
+            "value": "4622100592565682176"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "f64",
+          "value": "4622100592565682176"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 245,
+      "action": {
+        "type": "invoke",
+        "field": "catch-param-i32",
+        "args": [
+          {
+            "type": "i32",
+            "value": "5"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "5"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 247,
+      "action": {
+        "type": "invoke",
+        "field": "catch-imported",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "2"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 249,
+      "action": {
+        "type": "invoke",
+        "field": "catchless-try",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "0"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 250,
+      "action": {
+        "type": "invoke",
+        "field": "catchless-try",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 252,
+      "action": {
+        "type": "invoke",
+        "field": "return-call-in-try-catch",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 253,
+      "action": {
+        "type": "invoke",
+        "field": "return-call-indirect-in-try-catch",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 255,
+      "action": {
+        "type": "invoke",
+        "field": "break-try-catch",
+        "args": []
+      },
+      "expected": []
+    },
+    {
+      "type": "assert_return",
+      "line": 256,
+      "action": {
+        "type": "invoke",
+        "field": "break-try-catch_all",
+        "args": []
+      },
+      "expected": []
+    },
+    {
+      "type": "module",
+      "line": 258,
+      "filename": "try_catch.2.wasm"
+    },
+    {
+      "type": "assert_return",
+      "line": 276,
+      "action": {
+        "type": "invoke",
+        "field": "imported-mismatch",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "3"
+        }
+      ]
+    },
+    {
+      "type": "assert_invalid",
+      "line": 297,
+      "filename": "try_catch.3.wasm",
+      "text": "type mismatch: instruction requires [i32] but stack has []",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 299,
+      "filename": "try_catch.4.wasm",
+      "text": "type mismatch: instruction requires [i32] but stack has [i64]",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 301,
+      "filename": "try_catch.5.wasm",
+      "text": "type mismatch: block requires [] but stack has [i32]",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 303,
+      "filename": "try_catch.6.wasm",
+      "text": "type mismatch: instruction requires [i32] but stack has [i64]",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 308,
+      "filename": "try_catch.7.wasm",
+      "text": "type mismatch: block requires [] but stack has [i32]",
+      "module_type": "binary"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/legacy-exceptions/try_catch.wast/0.print b/tests/snapshots/local/legacy-exceptions/try_catch.wast/0.print
new file mode 100644
index 00000000..121840d8
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/try_catch.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))
+)
diff --git a/tests/snapshots/local/legacy-exceptions/try_catch.wast/2.print b/tests/snapshots/local/legacy-exceptions/try_catch.wast/2.print
new file mode 100644
index 00000000..dc973cca
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/try_catch.wast/2.print
@@ -0,0 +1,229 @@
+(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)))
+  (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 0)
+    try ;; label = @1
+    catch $e0
+    end
+  )
+  (func (;3;) (type 5) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      local.get 0
+      i32.eqz
+      if ;; label = @2
+        throw $e0
+      end
+      i32.const 42
+    catch $e0
+      i32.const 23
+    end
+  )
+  (func (;4;) (type 0)
+    try ;; label = @1
+      unreachable
+    catch_all
+    end
+  )
+  (func $div (;5;) (type 6) (param i32 i32) (result i32)
+    local.get 0
+    local.get 1
+    i32.div_u
+  )
+  (func (;6;) (type 6) (param i32 i32) (result i32)
+    try (result i32) ;; label = @1
+      local.get 0
+      local.get 1
+      call $div
+    catch_all
+      i32.const 11
+    end
+  )
+  (func (;7;) (type 5) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        local.get 0
+        i32.eqz
+        if ;; label = @3
+          throw $e0
+        else
+          local.get 0
+          i32.const 1
+          i32.eq
+          if ;; label = @4
+            throw $e1
+          else
+            throw $e2
+          end
+        end
+        i32.const 2
+      catch $e0
+        i32.const 3
+      end
+    catch $e1
+      i32.const 4
+    end
+  )
+  (func (;8;) (type 5) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      local.get 0
+      i32.eqz
+      if ;; label = @2
+        throw $e0
+      else
+        local.get 0
+        i32.const 1
+        i32.eq
+        if ;; label = @3
+          throw $e1
+        else
+          throw $e2
+        end
+      end
+      i32.const 2
+    catch $e0
+      i32.const 3
+    catch $e1
+      i32.const 4
+    end
+  )
+  (func (;9;) (type 5) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      local.get 0
+      throw $e-i32
+      i32.const 2
+    catch $e-i32
+      return
+    end
+  )
+  (func (;10;) (type 7) (param f32) (result f32)
+    try (result f32) ;; label = @1
+      local.get 0
+      throw $e-f32
+      f32.const 0x0p+0 (;=0;)
+    catch $e-f32
+      return
+    end
+  )
+  (func (;11;) (type 8) (param i64) (result i64)
+    try (result i64) ;; label = @1
+      local.get 0
+      throw $e-i64
+      i64.const 2
+    catch $e-i64
+      return
+    end
+  )
+  (func (;12;) (type 9) (param f64) (result f64)
+    try (result f64) ;; label = @1
+      local.get 0
+      throw $e-f64
+      f64.const 0x0p+0 (;=0;)
+    catch $e-f64
+      return
+    end
+  )
+  (func $throw-param-i32 (;13;) (type 1) (param i32)
+    local.get 0
+    throw $e-i32
+  )
+  (func (;14;) (type 5) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      i32.const 0
+      local.get 0
+      call $throw-param-i32
+    catch $e-i32
+    end
+  )
+  (func (;15;) (type 10) (result i32)
+    try (result i32) ;; label = @1
+      i32.const 1
+      call $imported-throw
+    catch $imported-e0
+      i32.const 2
+    end
+  )
+  (func (;16;) (type 5) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        local.get 0
+        call $throw-if
+      end
+    catch $e0
+      i32.const 1
+    end
+  )
+  (func $throw-void (;17;) (type 0)
+    throw $e0
+  )
+  (func (;18;) (type 0)
+    try ;; label = @1
+      return_call $throw-void
+    catch $e0
+    end
+  )
+  (func (;19;) (type 0)
+    try ;; label = @1
+      i32.const 0
+      return_call_indirect (type 0)
+    catch $e0
+    end
+  )
+  (func (;20;) (type 0)
+    try ;; label = @1
+      br 0 (;@1;)
+    catch $e0
+    end
+  )
+  (func (;21;) (type 0)
+    try ;; label = @1
+      br 0 (;@1;)
+    catch_all
+    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 "empty-catch" (func 2))
+  (export "simple-throw-catch" (func 3))
+  (export "unreachable-not-caught" (func 4))
+  (export "trap-in-callee" (func 6))
+  (export "catch-complex-1" (func 7))
+  (export "catch-complex-2" (func 8))
+  (export "throw-catch-param-i32" (func 9))
+  (export "throw-catch-param-f32" (func 10))
+  (export "throw-catch-param-i64" (func 11))
+  (export "throw-catch-param-f64" (func 12))
+  (export "catch-param-i32" (func 14))
+  (export "catch-imported" (func 15))
+  (export "catchless-try" (func 16))
+  (export "return-call-in-try-catch" (func 18))
+  (export "return-call-indirect-in-try-catch" (func 19))
+  (export "break-try-catch" (func 20))
+  (export "break-try-catch_all" (func 21))
+  (elem (;0;) (i32.const 0) func $throw-void)
+)
diff --git a/tests/snapshots/local/legacy-exceptions/try_catch.wast/33.print b/tests/snapshots/local/legacy-exceptions/try_catch.wast/33.print
new file mode 100644
index 00000000..8210510b
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/try_catch.wast/33.print
@@ -0,0 +1,19 @@
+(module
+  (type (;0;) (func))
+  (type (;1;) (func (result i32)))
+  (import "test" "throw" (func $imported-throw (;0;) (type 0)))
+  (func (;1;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        i32.const 1
+        call $imported-throw
+      catch $e0
+        i32.const 2
+      end
+    catch_all
+      i32.const 3
+    end
+  )
+  (tag $e0 (;0;) (type 0))
+  (export "imported-mismatch" (func 1))
+)
diff --git a/tests/snapshots/local/legacy-exceptions/try_delegate.wast.json b/tests/snapshots/local/legacy-exceptions/try_delegate.wast.json
new file mode 100644
index 00000000..2ba6f887
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/try_delegate.wast.json
@@ -0,0 +1,325 @@
+{
+  "source_filename": "tests/local/legacy-exceptions/try_delegate.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 3,
+      "filename": "try_delegate.0.wasm"
+    },
+    {
+      "type": "assert_return",
+      "line": 219,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-no-throw",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 221,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-throw",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 222,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-throw",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "2"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 224,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-throw-no-catch",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 226,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-merge",
+        "args": [
+          {
+            "type": "i32",
+            "value": "1"
+          },
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "2"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 227,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-merge",
+        "args": [
+          {
+            "type": "i32",
+            "value": "2"
+          },
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 228,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-merge",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          },
+          {
+            "type": "i32",
+            "value": "1"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "2"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 229,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-merge",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          },
+          {
+            "type": "i32",
+            "value": "2"
+          }
+        ]
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 230,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-merge",
+        "args": [
+          {
+            "type": "i32",
+            "value": "0"
+          },
+          {
+            "type": "i32",
+            "value": "0"
+          }
+        ]
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 232,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-skip",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "3"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 234,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-to-block",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 235,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-to-catch",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 237,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-to-caller-trivial",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 238,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-to-caller-skipping",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 240,
+      "action": {
+        "type": "invoke",
+        "field": "delegate-correct-targets",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_exception",
+      "line": 242,
+      "action": {
+        "type": "invoke",
+        "field": "return-call-in-try-delegate",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_exception",
+      "line": 243,
+      "action": {
+        "type": "invoke",
+        "field": "return-call-indirect-in-try-delegate",
+        "args": []
+      }
+    },
+    {
+      "type": "assert_return",
+      "line": 245,
+      "action": {
+        "type": "invoke",
+        "field": "break-try-delegate",
+        "args": []
+      },
+      "expected": []
+    },
+    {
+      "type": "assert_return",
+      "line": 247,
+      "action": {
+        "type": "invoke",
+        "field": "break-and-call-throw",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_return",
+      "line": 248,
+      "action": {
+        "type": "invoke",
+        "field": "break-and-throw",
+        "args": []
+      },
+      "expected": [
+        {
+          "type": "i32",
+          "value": "1"
+        }
+      ]
+    },
+    {
+      "type": "assert_invalid",
+      "line": 273,
+      "filename": "try_delegate.1.wasm",
+      "text": "unknown label",
+      "module_type": "binary"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/legacy-exceptions/try_delegate.wast/0.print b/tests/snapshots/local/legacy-exceptions/try_delegate.wast/0.print
new file mode 100644
index 00000000..7144255e
--- /dev/null
+++ b/tests/snapshots/local/legacy-exceptions/try_delegate.wast/0.print
@@ -0,0 +1,227 @@
+(module
+  (type (;0;) (func))
+  (type (;1;) (func (result i32)))
+  (type (;2;) (func (param i32)))
+  (type (;3;) (func (param i32) (result i32)))
+  (type (;4;) (func (param i32 i32) (result i32)))
+  (func (;0;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        i32.const 1
+      delegate 0 (;@1;)
+    catch $e0
+      i32.const 2
+    end
+  )
+  (func $throw-if (;1;) (type 2) (param i32)
+    local.get 0
+    if ;; label = @1
+      throw $e0
+    else
+    end
+  )
+  (func (;2;) (type 3) (param i32) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        local.get 0
+        call $throw-if
+        i32.const 1
+      delegate 0 (;@1;)
+    catch $e0
+      i32.const 2
+    end
+  )
+  (func (;3;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        try (result i32) ;; label = @3
+          throw $e0
+          i32.const 1
+        delegate 1 (;@1;)
+      catch $e0
+        i32.const 2
+      end
+    catch $e0
+      i32.const 3
+    end
+  )
+  (func (;4;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      block ;; label = @2
+        try ;; label = @3
+          throw $e0
+        delegate 0 (;@2;)
+      end
+      i32.const 0
+    catch_all
+      i32.const 1
+    end
+  )
+  (func (;5;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      try ;; label = @2
+        throw $e0
+      catch $e0
+        try ;; label = @3
+          rethrow 1 (;@2;)
+        delegate 0 (;@2;)
+      end
+      i32.const 0
+    catch_all
+      i32.const 1
+    end
+  )
+  (func (;6;) (type 0)
+    try ;; label = @1
+      throw $e0
+    delegate 0
+  )
+  (func (;7;) (type 0)
+    try ;; label = @1
+      try ;; label = @2
+        throw $e0
+      delegate 1
+    catch_all
+    end
+  )
+  (func $select-tag (;8;) (type 2) (param i32)
+    block ;; label = @1
+      block ;; label = @2
+        block ;; label = @3
+          local.get 0
+          br_table 0 (;@3;) 1 (;@2;) 2 (;@1;)
+        end
+        return
+      end
+      throw $e0
+    end
+    throw $e1
+  )
+  (func (;9;) (type 4) (param i32 i32) (result i32)
+    try (result i32) ;; label = @1
+      local.get 0
+      call $select-tag
+      try (result i32) ;; label = @2
+        local.get 1
+        call $select-tag
+        i32.const 1
+      delegate 0 (;@1;)
+    catch $e0
+      i32.const 2
+    end
+  )
+  (func (;10;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        throw $e0
+        i32.const 1
+      delegate 0 (;@1;)
+    catch $e1
+      i32.const 2
+    end
+  )
+  (func (;11;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      try ;; label = @2
+        try ;; label = @3
+          try ;; label = @4
+            try ;; label = @5
+              try ;; label = @6
+                throw $e0
+              delegate 1 (;@4;)
+            catch_all
+              unreachable
+            end
+          delegate 1 (;@2;)
+        catch_all
+          unreachable
+        end
+      catch_all
+        try ;; label = @3
+          throw $e0
+        delegate 0 (;@2;)
+      end
+      unreachable
+    catch_all
+      i32.const 1
+    end
+  )
+  (func $throw-void (;12;) (type 0)
+    throw $e0
+  )
+  (func (;13;) (type 0)
+    try ;; label = @1
+      try ;; label = @2
+        return_call $throw-void
+      delegate 0 (;@1;)
+    catch $e0
+    end
+  )
+  (func (;14;) (type 0)
+    try ;; label = @1
+      try ;; label = @2
+        i32.const 0
+        return_call_indirect (type 0)
+      delegate 0 (;@1;)
+    catch $e0
+    end
+  )
+  (func (;15;) (type 0)
+    try ;; label = @1
+      br 0 (;@1;)
+    delegate 0
+  )
+  (func (;16;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        block ;; label = @3
+          try ;; label = @4
+            br 1 (;@3;)
+          delegate 2 (;@1;)
+        end
+        call $throw-void
+        i32.const 0
+      catch $e0
+        i32.const 1
+      end
+    catch $e0
+      i32.const 2
+    end
+  )
+  (func (;17;) (type 1) (result i32)
+    try (result i32) ;; label = @1
+      try (result i32) ;; label = @2
+        block ;; label = @3
+          try ;; label = @4
+            br 1 (;@3;)
+          delegate 2 (;@1;)
+        end
+        throw $e0
+        i32.const 0
+      catch $e0
+        i32.const 1
+      end
+    catch $e0
+      i32.const 2
+    end
+  )
+  (table (;0;) 1 1 funcref)
+  (tag $e0 (;0;) (type 0))
+  (tag $e1 (;1;) (type 0))
+  (export "delegate-no-throw" (func 0))
+  (export "delegate-throw" (func 2))
+  (export "delegate-skip" (func 3))
+  (export "delegate-to-block" (func 4))
+  (export "delegate-to-catch" (func 5))
+  (export "delegate-to-caller-trivial" (func 6))
+  (export "delegate-to-caller-skipping" (func 7))
+  (export "delegate-merge" (func 9))
+  (export "delegate-throw-no-catch" (func 10))
+  (export "delegate-correct-targets" (func 11))
+  (export "return-call-in-try-delegate" (func 13))
+  (export "return-call-indirect-in-try-delegate" (func 14))
+  (export "break-try-delegate" (func 15))
+  (export "break-and-call-throw" (func 16))
+  (export "break-and-throw" (func 17))
+  (elem (;0;) (i32.const 0) func $throw-void)
+)