summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xscripts/fuzz_opt.py1
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/TranslateEH.cpp821
-rw-r--r--src/passes/pass.cpp3
-rw-r--r--src/passes/passes.h1
-rw-r--r--test/lit/help/wasm-opt.test3
-rw-r--r--test/lit/help/wasm2js.test3
-rw-r--r--test/lit/passes/translate-eh-old-to-new.wast1479
8 files changed, 2312 insertions, 0 deletions
diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py
index 953108a44..27b278703 100755
--- a/scripts/fuzz_opt.py
+++ b/scripts/fuzz_opt.py
@@ -318,6 +318,7 @@ INITIAL_CONTENTS_IGNORE = [
'typed_continuations_resume.wast',
# New EH implementation is in progress
'exception-handling.wast',
+ 'translate-eh-old-to-new.wast',
]
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index 9ab9f0247..0e74d4b93 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -119,6 +119,7 @@ set(passes_SOURCES
StripEH.cpp
SSAify.cpp
TupleOptimization.cpp
+ TranslateEH.cpp
TypeFinalizing.cpp
Unsubtyping.cpp
Untee.cpp
diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp
new file mode 100644
index 000000000..d8a004468
--- /dev/null
+++ b/src/passes/TranslateEH.cpp
@@ -0,0 +1,821 @@
+/*
+ * Copyright 2024 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// TranslateEHOldToNew translates the old Phase 3 EH instructions, which include
+// try, catch, catch_all, delegate, and rethrow, into the new EH instructions,
+// which include try_table (with catch / catch_ref / catch_all / catch_all_ref)
+// and throw_ref, passed at the Oct 2023 CG meeting. This translator can be used
+// as a standalone tool by users of the previous EH toolchain to generate
+// binaries for the new spec without recompiling, and also can be used at the
+// end of the Binaryen pipeline to produce binaries for the new spec while the
+// end-to-end toolchain implementation for the new spec is in progress.
+//
+// TODO
+// TranslateEHNewToOld translates the new EH instructions to the old ones. This
+// can be used as a stopgap tool while Binaryen implementation for the whole
+// optimization pipeline is not complete but we need to test our LLVM
+// implementation for the new spec. This has not been implemented yet.
+//
+
+#include <ir/drop.h>
+#include <ir/find_all.h>
+#include <ir/label-utils.h>
+#include <ir/utils.h>
+#include <pass.h>
+#include <wasm.h>
+
+namespace wasm {
+
+namespace {
+
+// Translates the old EH instructions (try / catch / catch_all / delegate /
+// rethrow) into the new ones (try_table (+ catch / catch_ref / catch_all /
+// catch_all_ref) / throw_ref).
+struct TranslateEHOldToNew
+ : public WalkerPass<PostWalker<TranslateEHOldToNew>> {
+ bool isFunctionParallel() override { return true; }
+
+ // Scans and records which try labels are targeted by delegates and rethrows.
+ struct TargetTryLabelScanner : public PostWalker<TargetTryLabelScanner> {
+ TargetTryLabelScanner(Function* func) { walkFunction(func); }
+
+ std::set<Name> delegateTargets;
+ std::set<Name> rethrowTargets;
+ bool isTargetedByDelegates(Try* curr) const {
+ return delegateTargets.find(curr->name) != delegateTargets.end();
+ }
+ bool isTargetedByRethrows(Try* curr) const {
+ return rethrowTargets.find(curr->name) != rethrowTargets.end();
+ }
+
+ void visitTry(Try* curr) {
+ if (curr->isDelegate()) {
+ delegateTargets.insert(curr->delegateTarget);
+ }
+ }
+
+ void visitRethrow(Rethrow* curr) { rethrowTargets.insert(curr->target); }
+ };
+
+ // For each try label targeted by rethrows, assign a local that will contain
+ // its exnref in the new spec (via try + catch(_all)_ref instruction), so that
+ // every 'rethrow $somelabel' can later be translated into
+ // 'throw_ref $somelocal'.
+ //
+ // In the examples below, try's (do) bodies are omitted for the sake of
+ // simplicity.
+ //
+ // For example,
+ // (try $l0
+ // (catch
+ // (try $l1
+ // (catch
+ // (rethrow $l0) ;; will become (throw_ref $local0)
+ // )
+ // (catch
+ // (rethrow $l1) ;; will become (throw_ref $local1)
+ // )
+ // )
+ // )
+ // )
+ // In this case, we need two locals for exnrefs, each for $l0 and $l1.
+ //
+ // Note that the number of locals required is the maximum depth of try-catch
+ // nests, when only counting 'try's that are targeted by rethrows, which means
+ // the pattern below only needs one extra local:
+ // (try $l0
+ // (catch
+ // (rethrow $l0)
+ // )
+ // )
+ // (try $l1
+ // (catch
+ // (rethrow $l1)
+ // )
+ // )
+ // Because the two trys are not nested within each other, one local can be
+ // reused in both trys.
+ //
+ // Also even if there is a try-catch nest with depth N, if only some of them
+ // are targeted by rethrows, we only need that many extra locals for the whole
+ // nest:
+ // (try $l0
+ // (catch
+ // (try $l1
+ // (catch
+ // ...
+ // ...
+ // (try $lN
+ // (catch
+ // ;; If this is the only rethrow in this nest, we only need one
+ // ;; extra exnref local for all N try-catches
+ // (rethrow $l1)
+ // )
+ // )
+ // ...
+ // ...
+ // )
+ // )
+ // )
+ // )
+ struct ExnrefLocalAssigner : public PostWalker<ExnrefLocalAssigner> {
+ TargetTryLabelScanner* labelScanner = nullptr;
+
+ std::vector<Index> exnrefLocals;
+ std::unordered_map<Name, Index> rethrowTargetToExnrefLocal;
+
+ // Depth of the current try nest, when only counting trys targeted with
+ // rethrows.
+ size_t rethrowTryDepth = 0;
+
+ ExnrefLocalAssigner(Function* func, TargetTryLabelScanner* labelScanner)
+ : labelScanner(labelScanner) {
+ walkFunction(func);
+ }
+
+ std::optional<Index> getExnrefLocal(Name rethrowTarget) const {
+ auto it = rethrowTargetToExnrefLocal.find(rethrowTarget);
+ if (it == rethrowTargetToExnrefLocal.end()) {
+ return {};
+ } else {
+ return it->second;
+ }
+ }
+
+ static void incrementRethrowTryDepth(ExnrefLocalAssigner* self,
+ Expression** currp) {
+ self->rethrowTryDepth++;
+ }
+ static void decrementRethrowTryDepth(ExnrefLocalAssigner* self,
+ Expression** currp) {
+ self->rethrowTryDepth--;
+ }
+
+ static void scan(ExnrefLocalAssigner* self, Expression** currp) {
+ auto* curr = *currp;
+ if (auto* tryy = curr->dynCast<Try>()) {
+ if (self->labelScanner->isTargetedByRethrows(tryy)) {
+ self->pushTask(decrementRethrowTryDepth, currp);
+ }
+ }
+ PostWalker<ExnrefLocalAssigner>::scan(self, currp);
+ if (auto* tryy = curr->dynCast<Try>()) {
+ if (self->labelScanner->isTargetedByRethrows(tryy)) {
+ self->pushTask(incrementRethrowTryDepth, currp);
+ }
+ }
+ }
+
+ void visitTry(Try* curr) {
+ if (labelScanner->isTargetedByRethrows(curr)) {
+ while (exnrefLocals.size() < rethrowTryDepth) {
+ exnrefLocals.push_back(
+ Builder::addVar(getFunction(), Type(HeapType::exn, Nullable)));
+ }
+ rethrowTargetToExnrefLocal[curr->name] =
+ exnrefLocals[rethrowTryDepth - 1];
+ }
+ }
+ };
+
+ std::optional<LabelUtils::LabelManager> labels;
+ std::optional<TargetTryLabelScanner> labelScanner;
+ std::optional<ExnrefLocalAssigner> localAssigner;
+
+ std::unordered_map<Name, Name> delegateTargetToBrTarget;
+ // Scratch locals used to contain extracted values and (extracted values,
+ // exnref) tuples for a short time.
+ std::unordered_map<Type, Index> typeToScratchLocal;
+
+ bool refinalize = false;
+
+ std::unique_ptr<Pass> create() override {
+ return std::make_unique<TranslateEHOldToNew>();
+ }
+
+ // Get a scratch local for a given type. These locals are used to contain
+ // extracted value(s) and (extracted value(s), exnref) tuples for a short
+ // time. Because these locals are written and read right after that, we can
+ // reuse these for all extracted values and (extracted values, exnref) tuples
+ // as long as the type matches.
+ Index getScratchLocal(Type type) {
+ auto [it, inserted] = typeToScratchLocal.insert({type, 0});
+ if (inserted) {
+ it->second = Builder::addVar(getFunction(), type);
+ }
+ return it->second;
+ }
+
+ // Process try labels targeted by rethrows. This does NOT transform the
+ // current 'try' into 'try_table' yet; it only adds block, br, and throw_ref
+ // instructions to complete the conversions of inner try~delegates that target
+ // the current try.
+ void processDelegateTarget(Try* curr, Block* outerBlock) {
+ Builder builder(*getModule());
+
+ // Convert
+ //
+ // (try $delegate_target (result sometype)
+ // (do
+ // ...
+ // ;; This had originally been an inner try~delegate and has been
+ // ;; already translated to try_table at this point. See
+ // ;; processDelegate() for how it is done.
+ // (try_table (catch_all_ref $delegate_br_target)
+ // ...
+ // )
+ // ...
+ // (catch
+ // ...
+ // )
+ // )
+ //
+ // to =>
+ //
+ // If sometype (try's type) is none:
+ // (block $outer (result sometype)
+ // (try (result sometype)
+ // (do
+ // (throw_ref
+ // (block $delegate_br_target (result exnref)
+ // ...
+ // (try_table (catch_all_ref $delegate_br_target)
+ // ...
+ // )
+ // ...
+ // (br $outer)
+ // )
+ // )
+ // )
+ // (catch
+ // ...
+ // )
+ // )
+ // )
+ //
+ // If sometype (try's type) is concrete:
+ // (block $outer (result sometype)
+ // (try (result sometype)
+ // (do
+ // (throw_ref
+ // (block $delegate_br_target (result exnref)
+ // (br $outer ;; Now has the try_table as a child.
+ // ...
+ // (try_table (catch_all_ref $delegate_br_target)
+ // ...
+ // )
+ // ...
+ // )
+ // )
+ // )
+ // )
+ // (catch
+ // ...
+ // )
+ // )
+ // )
+ //
+ // Note that the current try-catch (or try-delegate) stays as is for now; it
+ // will be converted to a try_table later in processDelegate() and
+ // processCatches().
+ //
+ // Also note that even in case there are multiple inner try~delegates
+ // targeting this try, we need to do this only once per try target. Those
+ // multiple try~delegates that used to target the same delegate target now
+ // jump to the same $delegate_br_target using catch_all_ref.
+ Name delegateBrTarget = delegateTargetToBrTarget[curr->name];
+ Expression* innerBody = nullptr;
+ if (curr->type.isConcrete()) {
+ auto* brToOuter = builder.makeBreak(outerBlock->name, curr->body);
+ innerBody = builder.blockifyWithName(
+ brToOuter, delegateBrTarget, nullptr, Type(HeapType::exn, Nullable));
+ } else {
+ auto* brToOuter = curr->body->type == Type::unreachable
+ ? nullptr
+ : builder.makeBreak(outerBlock->name);
+ innerBody = builder.blockifyWithName(
+ curr->body, delegateBrTarget, brToOuter, Type(HeapType::exn, Nullable));
+ }
+ curr->body = builder.makeThrowRef(innerBody);
+ }
+
+ void processDelegate(Try* curr, Block* outerBlock) {
+ Builder builder(*getModule());
+ // Convert
+ // (try
+ // (do
+ // ...
+ // )
+ // (delegate $delegate_target)
+ // )
+ //
+ // to =>
+ //
+ // (try_table (catch_ref $delegate_br_target)
+ // ...
+ // )
+ //
+ // $delegate_br_target is a block label that will be created in
+ // processDelegateTarget(), when we process the 'try' that is the target of
+ // this try~delegate. See processDelegateTarget() for how the rest of the
+ // conversion is completed.
+ auto* tryTable =
+ builder.makeTryTable(curr->body,
+ {Name()},
+ {delegateTargetToBrTarget[curr->delegateTarget]},
+ {true});
+ // If we need an outer block for other reasons (if this is a target of a
+ // delegate), we insert the new try_table into it. If not we just replace
+ // the current try with the new try_table.
+ if (outerBlock) {
+ outerBlock->list.push_back(tryTable);
+ replaceCurrent(outerBlock);
+ } else {
+ replaceCurrent(tryTable);
+ }
+ }
+
+ void processCatches(Try* curr, Block* outerBlock) {
+ Module* wasm = getModule();
+ Builder builder(*wasm);
+
+ // Determine whether a given catch body should be translated to
+ // catch/catch_all vs. catch_ref/catch_all_ref.
+ auto shouldBeRef = [&](Expression* catchBody) -> bool {
+ // If this try is targeted by rethrows and those rethrows exist in the
+ // current catch body, we need to use catch_ref/catch_all_ref. By this
+ // point, all rethrows in the catch bodies have been already converted to
+ // throw_refs, so we check the local numbers to see if those (original)
+ // rethrows used to target the current try label.
+ std::optional<Index> local = localAssigner->getExnrefLocal(curr->name);
+ if (local) {
+ for (auto* throwRef : FindAll<ThrowRef>(catchBody).list) {
+ // All throw_refs generated in this pass has a local.get as its child.
+ // See visitRethrow().
+ auto* localGet = throwRef->exnref->cast<LocalGet>();
+ if (localGet->index == *local) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ // Create try_table instruction with catch clauses.
+ std::vector<Name> catchTags;
+ std::vector<Name> catchDests;
+ std::vector<bool> catchRefs;
+ for (Index i = 0; i < curr->catchTags.size(); i++) {
+ catchTags.push_back(curr->catchTags[i]);
+ catchDests.push_back(labels->getUnique("catch"));
+ catchRefs.push_back(shouldBeRef(curr->catchBodies[i]));
+ }
+ if (curr->hasCatchAll()) {
+ catchTags.push_back(Name());
+ catchDests.push_back(labels->getUnique("catch_all"));
+ catchRefs.push_back(shouldBeRef(curr->catchBodies.back()));
+ }
+ auto* tryTable = builder.makeTryTable(
+ curr->body, catchTags, catchDests, catchRefs, curr->type);
+
+ // If we don't have any catches, we don't need to do more.
+ if (curr->catchBodies.empty()) { // catch-less try
+ replaceCurrent(tryTable);
+ return;
+ }
+
+ // Now we convert catch bodies.
+ //
+ // For simplicity, let's assume all tags have the none type and there are no
+ // rethrows (which have converted to throw_refs at this point) and the try's
+ // type is none as well. Then
+ //
+ // (try
+ // (catch $e
+ // (catch_body)
+ // )
+ // (catch_all
+ // (catch_all_body)
+ // )
+ // )
+ //
+ // becomes
+ //
+ // (block $outer
+ // (block $catch_all
+ // (block $catch
+ // (try_table (catch $e $catch) (catch_all $catch_all)
+ // ...
+ // )
+ // (br $outer)
+ // )
+ // (catch_body)
+ // (br $outer)
+ // )
+ // (catch_all_body) ;; We don't need (br $outer) for the last catch
+ // )
+ //
+ // Here (block $outer) has been already created and given as an argument to
+ // this function. This is the outer block that will contain the whole
+ // structure.
+ //
+ // If there are more catch clauses, there will be more layers of blocks,
+ // catch bodies, and (br $outer)s.
+ //
+ // If try's type is concrete, (br $outer)s will contain try_table and catch
+ // bodies as values:
+ //
+ // (block $outer
+ // (block $catch_all
+ // (block $catch
+ // (br $outer
+ // (try_table (result sometype)
+ // (catch $e $catch) (catch_all $catch_all)
+ // ...
+ // )
+ // )
+ // )
+ // (br $outer
+ // (catch_body)
+ // )
+ // )
+ // (catch_all_body) ;; We don't need (br $outer) for the last catch
+ // )
+ //
+ // ---
+ //
+ // When a tag has a concrete type, we assign it to a scratch local of that
+ // type, and (pop tagtype) would have been already converted to
+ // (local.get $scratch) in visitPop(). These scratch locals have extremely
+ // short lifetimes; basically they are read right after they are written, so
+ // we can reuse them throughout the function.
+ //
+ // So, when there is no rethrows (throw_refs), and if we assume that try's
+ // type is none for simplicity,
+ // (try
+ // ...
+ // (catch $e-i32 ;; concrete type
+ // (use_inst
+ // (pop i32)
+ // )
+ // )
+ // )
+ //
+ // becomes
+ // (block $outer
+ // (local.set $scratch-i32
+ // (block $catch (result tagtype)
+ // (try_table (catch $e-i32 $catch)
+ // ...
+ // )
+ // (br $outer)
+ // )
+ // )
+ // (use_inst
+ // (local.get $scratch-i32)
+ // )
+ // )
+ //
+ // When the tag type is none or it is a catch_all, but there are rethrows
+ // (throws_refs at this point) within the catch body that targets this try's
+ // label, we use catch_ref (or catch_all_ref in case of catch_all), and
+ // assign the block return value to a exnref local. rethrows would have been
+ // converted to (local.get $exn) in visitRethrow() already. Unlike scratch
+ // locals used for pops, exnref locals can have longer lifetimes and are
+ // assigned in ExnrefLocalAssigner. So for example,
+ // (try $l0
+ // ...
+ // (catch_all
+ // (rethrow $l0)
+ // )
+ // )
+ //
+ // becomes
+ //
+ // (block $outer
+ // (local.set $exn
+ // (block $catch_all (result exnref)
+ // (try_table (catch_all_ref $catch_all)
+ // ...
+ // )
+ // (br $outer)
+ // )
+ // )
+ // (throw_ref
+ // (local.get $exn)
+ // )
+ // )
+ //
+ // When the tag type is concrete and also there are rethrows within the
+ // catch body that target this try's label, (extracted values, exnref)
+ // is saved in a tuple local matching its tuple type, and tuple.extract
+ // instructions are added to extract each of them and set them to a scratch
+ // local for a later pop and exnref local for later throw_ref.
+ //
+ // (try $l0
+ // ...
+ // (catch $e-i32 ;; concrete type
+ // (use_inst
+ // (pop i32)
+ // )
+ // (rethrow $l0)
+ // )
+ // )
+ //
+ // becomes
+ //
+ // (block $outer
+ // (local.set $tuple
+ // (block $catch (result i32 exnref)
+ // (try_table (catch_ref $e-i32 $catch)
+ // ...
+ // )
+ // (br $outer)
+ // )
+ // )
+ // (local.set $scratch-i32
+ // (tuple.extract 2 0
+ // (local.get $tuple)
+ // )
+ // )
+ // (local.set $exn
+ // (tuple.extract 2 1
+ // (local.get $tuple)
+ // )
+ // )
+ // (block
+ // (use_inst
+ // (local.get $scratch-i32)
+ // )
+ // (throw_ref
+ // (local.get $exn)
+ // )
+ // )
+ // )
+ //
+ // The transformation is similar when the tag type itself is a tuple and
+ // there are rethrows. We store the whole (tag types, exnref) in a tuple
+ // scratch local, and tuple.extract 1~(n-1)th elements and set them in
+ // another scratch local and nth element in an exnref local.
+
+ // Make the body for the innermost block
+ std::vector<Expression*> items;
+ if (tryTable->type.isConcrete()) {
+ items.push_back(builder.makeBreak(outerBlock->name, tryTable));
+ } else {
+ items.push_back(tryTable);
+ if (tryTable->type != Type::unreachable) {
+ items.push_back(builder.makeBreak(outerBlock->name));
+ }
+ }
+
+ // Convert each catch body to a wrapping block + catch body + br
+ for (Index i = 0; i < tryTable->catchTags.size(); i++) {
+ Type sentType = tryTable->sentTypes[i];
+ auto* catchBody = curr->catchBodies[i];
+ Type tagType = Type::none;
+ if (tryTable->catchTags[i]) {
+ tagType = wasm->getTag(tryTable->catchTags[i])->sig.params;
+ }
+
+ // This is to be the body of the next(outer) level block
+ std::vector<Expression*> nextItems;
+
+ auto* block = builder.makeBlock(tryTable->catchDests[i], items, sentType);
+
+ if (tryTable->catchRefs[i]) {
+ // When we use the exnref (i.e., there are throw_refs in the catch body)
+ Index exnrefLocal = *localAssigner->getExnrefLocal(curr->name);
+ if (tagType.isConcrete()) {
+ // If the tag type is single and we use the exnref, the block
+ // returns (tagtype, exnref). Get a scratch local to contain this
+ // tuple and reassign its elements to a pop scratch local and this
+ // try's corresponding exnref local respectively.
+ //
+ // If the tag type is a tuple and we use the exnref, the block returns
+ // (tagtype0, ..., tagtypeN, exnref). Assign (tagtype0, ..., tagtypeN)
+ // to a scratch (tuple) local and the exnref to this try's
+ // corresponding exnref local respectively.
+ Index allLocal = getScratchLocal(sentType);
+ Index popLocal = getScratchLocal(tagType);
+ auto* allLocalSet = builder.makeLocalSet(allLocal, block);
+ nextItems.push_back(allLocalSet);
+ Expression* popLocalSet = nullptr;
+ if (tagType.isTuple()) {
+ std::vector<Expression*> popVals;
+ for (Index j = 0; j < tagType.size(); j++) {
+ popVals.push_back(builder.makeTupleExtract(
+ builder.makeLocalGet(allLocal, sentType), j));
+ }
+ popLocalSet =
+ builder.makeLocalSet(popLocal, builder.makeTupleMake(popVals));
+ } else {
+ popLocalSet = builder.makeLocalSet(
+ popLocal,
+ builder.makeTupleExtract(builder.makeLocalGet(allLocal, sentType),
+ 0));
+ }
+ nextItems.push_back(popLocalSet);
+ auto* exnrefLocalSet = builder.makeLocalSet(
+ exnrefLocal,
+ builder.makeTupleExtract(builder.makeLocalGet(allLocal, sentType),
+ sentType.size() - 1));
+ nextItems.push_back(exnrefLocalSet);
+ } else {
+ // If the tag type is none and we use the exnref, the block only
+ // returns exnref. Assign in to this try's corresponding exnref local.
+ auto* exnrefLocalSet = builder.makeLocalSet(exnrefLocal, block);
+ nextItems.push_back(exnrefLocalSet);
+ }
+ } else {
+ // When we don't use exnref and the tag type is concrete, we get a
+ // scratch local of the tag type and assign the block return value to
+ // that local. This process is the same for single and tuple tag types.
+ // If the tag type is none, we don't need to use any locals.
+ if (tagType.isConcrete()) {
+ Index popLocal = getScratchLocal(tagType);
+ auto* popLocalSet = builder.makeLocalSet(popLocal, block);
+ nextItems.push_back(popLocalSet);
+ } else {
+ nextItems.push_back(block);
+ }
+ }
+
+ if (catchBody->type.isConcrete()) {
+ // If this is the last catch body, we can omit the br and fall through
+ if (i < tryTable->catchTags.size() - 1) {
+ nextItems.push_back(builder.makeBreak(outerBlock->name, catchBody));
+ } else {
+ nextItems.push_back(catchBody);
+ }
+ } else {
+ nextItems.push_back(catchBody);
+ // If this is the last catch body, we can omit the br and fall through
+ if (i < tryTable->catchTags.size() - 1 &&
+ catchBody->type != Type::unreachable) {
+ nextItems.push_back(builder.makeBreak(outerBlock->name));
+ }
+ }
+ items.swap(nextItems);
+ }
+
+ for (auto* item : items) {
+ outerBlock->list.push_back(item);
+ }
+ replaceCurrent(outerBlock);
+ }
+
+ void visitTry(Try* curr) {
+ refinalize = true;
+ Builder builder(*getModule());
+ Block* outerBlock = nullptr;
+ auto it = delegateTargetToBrTarget.find(curr->name);
+ if (it != delegateTargetToBrTarget.end() || curr->isCatch()) {
+ outerBlock =
+ builder.makeBlock(labels->getUnique("outer"), {}, curr->type);
+ }
+
+ if (it != delegateTargetToBrTarget.end()) {
+ processDelegateTarget(curr, outerBlock);
+ }
+ if (curr->isDelegate()) {
+ processDelegate(curr, outerBlock);
+ } else { // try-catch or catch-less try
+ processCatches(curr, outerBlock);
+ }
+ }
+
+ void visitPop(Pop* curr) {
+ // We can assume a 'pop' value is always in a scratch local that matches
+ // its type, because these locals are read right after being written and can
+ // be reused for all pops. This applies to tuple type pops as well.
+ Builder builder(*getModule());
+ Index popLocal = getScratchLocal(curr->type);
+ replaceCurrent(builder.makeLocalGet(popLocal, curr->type));
+ }
+
+ void visitRethrow(Rethrow* curr) {
+ // After we assigned an exnref local for each try label targeted by
+ // rethrows, we can assume the exnref we want to rethrow is located in that
+ // exnref local at this point. We ensure this to happen when converting the
+ // corresponding 'try' to 'try_table' by using catch_ref/catch_all_ref and
+ // assining the exnref to that local.
+ Builder builder(*getModule());
+ Index exnrefLocal = *localAssigner->getExnrefLocal(curr->target);
+ replaceCurrent(builder.makeThrowRef(
+ builder.makeLocalGet(exnrefLocal, Type(HeapType::exn, Nullable))));
+ }
+
+ // Similar to processDelegateTarget(), but does it for the caller delegate
+ // target, which means we should rethrow to the caller.
+ void processCallerDelegateTarget() {
+ Name callerDelegateBrTarget =
+ delegateTargetToBrTarget[DELEGATE_CALLER_TARGET];
+ Builder builder(*getModule());
+ Function* func = getFunction();
+
+ // The transformation is similar to that of normal delegate targets, but
+ // instead of branching to an outer label in case no exception is thrown, we
+ // just return.
+ //
+ // Convert
+ //
+ // (func $test (result sometype)
+ // ...
+ // (try_table (catch_all_ref $caller_delegate_br_target)
+ // ...
+ // )
+ // ...
+ // )
+ //
+ // to =>
+ //
+ // If sometype (func's type) is none:
+ // (func $test (result sometype)
+ // (throw_ref
+ // (block $caller_delegate_br_target (result exnref)
+ // ...
+ // (try_table (catch_all_ref $caller_delegate_br_target)
+ // ...
+ // )
+ // ...
+ // (return)
+ // )
+ // )
+ // )
+ //
+ // If sometype (func's type) is concrete:
+ // (throw_ref
+ // (block $caller_delegate_br_target (result exnref)
+ // (return
+ // ...
+ // (try_table (catch_all_ref $caller_delegate_br_target)
+ // ...
+ // )
+ // ...
+ // )
+ // )
+ // )
+ // )
+ Expression* innerBody = nullptr;
+ if (func->body->type.isConcrete()) {
+ auto* ret = builder.makeReturn(func->body);
+ innerBody = builder.blockifyWithName(
+ ret, callerDelegateBrTarget, nullptr, Type(HeapType::exn, Nullable));
+ } else {
+ auto* ret = builder.makeReturn();
+ innerBody = builder.blockifyWithName(
+ func->body, callerDelegateBrTarget, ret, Type(HeapType::exn, Nullable));
+ }
+ func->body = builder.makeThrowRef(innerBody);
+ }
+
+ void doWalkFunction(Function* func) {
+ labels = std::make_optional<LabelUtils::LabelManager>(func);
+ labelScanner = std::make_optional<TargetTryLabelScanner>(func);
+ localAssigner =
+ std::make_optional<ExnrefLocalAssigner>(func, &labelScanner.value());
+
+ // Create a unique br target label for each existing delegate target label,
+ // because we are going to achieve 'delegate's effects with 'br's. See
+ // processDelegateTarget() for details.
+ for (auto& target : labelScanner->delegateTargets) {
+ delegateTargetToBrTarget[target] = labels->getUnique(target.toString());
+ }
+
+ super::doWalkFunction(func);
+
+ // Similar to processDelegateTarget(), but for the caller target.
+ if (delegateTargetToBrTarget.find(DELEGATE_CALLER_TARGET) !=
+ delegateTargetToBrTarget.end()) {
+ processCallerDelegateTarget();
+ }
+ }
+};
+
+struct TranslateEHNewToOld
+ : public WalkerPass<PostWalker<TranslateEHNewToOld>> {
+ // TODO
+};
+
+} // namespace
+
+Pass* createTranslateEHOldToNewPass() { return new TranslateEHOldToNew(); }
+
+Pass* createTranslateEHNewToOldPass() { return new TranslateEHNewToOld(); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 62b3683fa..5f5db2f7f 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -491,6 +491,9 @@ void PassRegistry::registerPasses() {
registerPass("strip-target-features",
"strip the wasm target features section",
createStripTargetFeaturesPass);
+ registerPass("translate-eh-old-to-new",
+ "translate old EH instructions to new ones",
+ createTranslateEHOldToNewPass);
registerPass("trap-mode-clamp",
"replace trapping operations with clamping semantics",
createTrapModeClamp);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index 93a8245c8..66535bdc0 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -164,6 +164,7 @@ Pass* createStripEHPass();
Pass* createStubUnsupportedJSOpsPass();
Pass* createSSAifyPass();
Pass* createSSAifyNoMergePass();
+Pass* createTranslateEHOldToNewPass();
Pass* createTrapModeClamp();
Pass* createTrapModeJS();
Pass* createTupleOptimizationPass();
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 132296d5b..07d218007 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -486,6 +486,9 @@
;; CHECK-NEXT:
;; CHECK-NEXT: --symbolmap (alias for print-function-map)
;; CHECK-NEXT:
+;; CHECK-NEXT: --translate-eh-old-to-new translate old EH instructions to
+;; CHECK-NEXT: new ones
+;; CHECK-NEXT:
;; CHECK-NEXT: --trap-mode-clamp replace trapping operations with
;; CHECK-NEXT: clamping semantics
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 1d378f0ad..e81d07718 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -445,6 +445,9 @@
;; CHECK-NEXT:
;; CHECK-NEXT: --symbolmap (alias for print-function-map)
;; CHECK-NEXT:
+;; CHECK-NEXT: --translate-eh-old-to-new translate old EH instructions to
+;; CHECK-NEXT: new ones
+;; CHECK-NEXT:
;; CHECK-NEXT: --trap-mode-clamp replace trapping operations with
;; CHECK-NEXT: clamping semantics
;; CHECK-NEXT:
diff --git a/test/lit/passes/translate-eh-old-to-new.wast b/test/lit/passes/translate-eh-old-to-new.wast
new file mode 100644
index 000000000..b012a8536
--- /dev/null
+++ b/test/lit/passes/translate-eh-old-to-new.wast
@@ -0,0 +1,1479 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: wasm-opt %s -all --translate-eh-old-to-new -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $0 (func (result i32 i64)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $2 (func (result i32)))
+
+ ;; CHECK: (type $3 (func (result i32 exnref)))
+
+ ;; CHECK: (type $4 (func (result i32 i64 exnref)))
+
+ ;; CHECK: (type $5 (func (param i32)))
+
+ ;; CHECK: (type $6 (func (param i32 i64)))
+
+ ;; CHECK: (tag $e-empty)
+ (tag $e-empty)
+ ;; CHECK: (tag $e-i32 (param i32))
+ (tag $e-i32 (param i32))
+ ;; CHECK: (tag $e-i32-i64 (param i32 i64))
+ (tag $e-i32-i64 (param i32 i64))
+
+ ;; CHECK: (func $foo (type $1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo)
+ ;; CHECK: (func $bar (type $1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $bar)
+ ;; CHECK: (func $baz (type $1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $baz)
+
+ ;; ---------------------------------------------------------------------------
+ ;; Basic tests for all combinations of try body's type (none, single, and
+ ;; tuple) and the catch's tag type (none, single, and tuple) and with /
+ ;; without rethrows
+
+ ;; CHECK: (func $try-none-tag-none (type $1)
+ ;; CHECK-NEXT: (block $outer0
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (block $catch1
+ ;; CHECK-NEXT: (try_table (catch $e-empty $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-none-tag-none
+ ;; try's type is none and catch's tag type is none
+ (try $l0
+ (do
+ (call $foo)
+ )
+ (catch $e-empty ;; converted to catch
+ (call $foo)
+ )
+ (catch_all ;; converted to catch_all
+ (call $bar)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-none-tag-none-with-rethrow (type $1)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (block $outer0
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-none-tag-none-with-rethrow
+ ;; try's type is none and catch's tag type is none, and there are rethrows
+ (try $l0
+ (do
+ (call $foo)
+ )
+ (catch $e-empty ;; converted to catch_ref, because of rethrow
+ (rethrow $l0)
+ )
+ (catch_all ;; converted to catch_all_ref, because of rethrow
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-none-tag-single (type $1)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (block $outer0
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (result i32)
+ ;; CHECK-NEXT: (try_table (catch $e-i32 $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-none-tag-single
+ ;; try's type is none and catch's tag type is single
+ (try $l0
+ (do
+ (call $foo)
+ )
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ )
+ (catch_all
+ (call $bar)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-none-tag-single-with-rethrow (type $1)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 (i32 exnref))
+ ;; CHECK-NEXT: (block $outer0
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 exnref)
+ ;; CHECK-NEXT: (try_table (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.extract 2 0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (tuple.extract 2 1
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-none-tag-single-with-rethrow
+ ;; try's type is none and catch's tag type is single, and there are rethrows
+ (try $l0
+ (do
+ (call $foo)
+ )
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-none-tag-tuple (type $1)
+ ;; CHECK-NEXT: (local $0 (i32 i64))
+ ;; CHECK-NEXT: (block $outer0
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (try_table (catch $e-i32-i64 $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.drop 2
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-none-tag-tuple
+ ;; try's type is none and catch's tag type is tuple
+ (try $l0
+ (do
+ (call $foo)
+ )
+ (catch $e-i32-i64
+ (tuple.drop 2
+ (pop i32 i64)
+ )
+ )
+ (catch_all
+ (call $bar)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-none-tag-tuple-with-rethrow (type $1)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (local $1 (i32 i64))
+ ;; CHECK-NEXT: (local $2 (i32 i64 exnref))
+ ;; CHECK-NEXT: (block $outer0
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 exnref)
+ ;; CHECK-NEXT: (try_table (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (tuple.extract 3 0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 3 1
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (tuple.extract 3 2
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (tuple.drop 2
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-none-tag-tuple-with-rethrow
+ ;; try's type is none and catch's tag type is tuple, and there are rethrows
+ (try $l0
+ (do
+ (call $foo)
+ )
+ (catch $e-i32-i64
+ (tuple.drop 2
+ (pop i32 i64)
+ )
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-single-tag-none (type $2) (result i32)
+ ;; CHECK-NEXT: (block $outer0 (result i32)
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (block $catch1
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (result i32) (catch $e-empty $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-single-tag-none (result i32)
+ ;; try's type is single and catch's tag type is none
+ (try $l0 (result i32)
+ (do
+ (call $foo)
+ (i32.const 0)
+ )
+ (catch $e-empty
+ (i32.const 1)
+ )
+ (catch_all
+ (i32.const 2)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-single-tag-none-with-rethrow (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (block $outer0 (result i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (result exnref)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (result i32) (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-single-tag-none-with-rethrow (result i32)
+ ;; try's type is single and catch's tag type is none, and there are rethrows
+ (try $l0 (result i32)
+ (do
+ (call $foo)
+ (i32.const 0)
+ )
+ (catch $e-empty
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-single-tag-single (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (block $outer0 (result i32)
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (result i32)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (result i32) (catch $e-i32 $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-single-tag-single (result i32)
+ ;; try's type is single and catch's tag type is single
+ (try $l0 (result i32)
+ (do
+ (call $foo)
+ (i32.const 0)
+ )
+ (catch $e-i32
+ (pop i32)
+ )
+ (catch_all
+ (i32.const 2)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-single-tag-single-with-rethrow (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 (i32 exnref))
+ ;; CHECK-NEXT: (block $outer0 (result i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 exnref)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (result i32) (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.extract 2 0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (tuple.extract 2 1
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-single-tag-single-with-rethrow (result i32)
+ ;; try's type is single and catch's tag type is single, and there are
+ ;; rethrows
+ (try $l0 (result i32)
+ (do
+ (call $foo)
+ (i32.const 0)
+ )
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-single-tag-tuple (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 (i32 i64))
+ ;; CHECK-NEXT: (block $outer0 (result i32)
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (result i32) (catch $e-i32-i64 $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (tuple.drop 2
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-single-tag-tuple (result i32)
+ ;; try's type is single and catch's tag type is tuple
+ (try $l0 (result i32)
+ (do
+ (call $foo)
+ (i32.const 0)
+ )
+ (catch $e-i32-i64
+ (tuple.drop 2
+ (pop i32 i64)
+ )
+ (i32.const 1)
+ )
+ (catch_all
+ (i32.const 2)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-single-tag-tuple-with-rethrow (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (local $1 (i32 i64))
+ ;; CHECK-NEXT: (local $2 (i32 i64 exnref))
+ ;; CHECK-NEXT: (block $outer0 (result i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 exnref)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (result i32) (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (tuple.extract 3 0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 3 1
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (tuple.extract 3 2
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (tuple.drop 2
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-single-tag-tuple-with-rethrow (result i32)
+ ;; try's type is single and catch's tag type is tuple, and there are
+ ;; rethrows
+ (try $l0 (result i32)
+ (do
+ (call $foo)
+ (i32.const 0)
+ )
+ (catch $e-i32-i64
+ (tuple.drop 2
+ (pop i32 i64)
+ )
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-tuple-tag-none (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (block $catch1
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch $e-empty $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-tuple-tag-none (result i32 i64)
+ ;; try's type is tuple and catch's tag type is none
+ (try $l0 (result i32 i64)
+ (do
+ (call $foo)
+ (tuple.make 2
+ (i32.const 0)
+ (i64.const 0)
+ )
+ )
+ (catch $e-empty
+ (tuple.make 2
+ (i32.const 1)
+ (i64.const 1)
+ )
+ )
+ (catch_all
+ (tuple.make 2
+ (i32.const 2)
+ (i64.const 0)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-tuple-tag-none-with-rethrow (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (result exnref)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch_ref $e-empty $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-tuple-tag-none-with-rethrow (result i32 i64)
+ ;; try's type is tuple and catch's tag type is none, and there are rethrows
+ (try $l0 (result i32 i64)
+ (do
+ (call $foo)
+ (tuple.make 2
+ (i32.const 0)
+ (i64.const 0)
+ )
+ )
+ (catch $e-empty
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-tuple-tag-single (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (result i32)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch $e-i32 $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: (i64.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-tuple-tag-single (result i32 i64)
+ ;; try's type is tuple and catch's tag type is single
+ (try $l0 (result i32 i64)
+ (do
+ (call $foo)
+ (tuple.make 2
+ (i32.const 0)
+ (i64.const 0)
+ )
+ )
+ (catch $e-i32
+ (tuple.make 2
+ (pop i32)
+ (i64.const 0)
+ )
+ )
+ (catch_all
+ (tuple.make 2
+ (i32.const 2)
+ (i64.const 2)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-tuple-tag-single-with-rethrow (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 (i32 exnref))
+ ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (block $catch1 (type $3) (result i32 exnref)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch_ref $e-i32 $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.extract 2 0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (tuple.extract 2 1
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (block (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-tuple-tag-single-with-rethrow (result i32 i64)
+ ;; try's type is tuple and catch's tag type is single, and there are
+ ;; rethrows
+ (try $l0 (result i32 i64)
+ (do
+ (call $foo)
+ (tuple.make 2
+ (i32.const 0)
+ (i64.const 0)
+ )
+ )
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-tuple-tag-tuple (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (local $0 (i32 i64))
+ ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch $e-i32-i64 $catch1) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: (i64.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-tuple-tag-tuple (result i32 i64)
+ ;; try's type is tuple and catch's tag type is tuple
+ (try $l0 (result i32 i64)
+ (do
+ (call $foo)
+ (tuple.make 2
+ (i32.const 0)
+ (i64.const 0)
+ )
+ )
+ (catch $e-i32-i64
+ (pop i32 i64)
+ )
+ (catch_all
+ (tuple.make 2
+ (i32.const 2)
+ (i64.const 2)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $try-tuple-tag-tuple-with-rethrow (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (local $1 (i32 i64))
+ ;; CHECK-NEXT: (local $2 (i32 i64 exnref))
+ ;; CHECK-NEXT: (block $outer0 (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all2 (result exnref)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (block $catch1 (type $4) (result i32 i64 exnref)
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (try_table (type $0) (result i32 i64) (catch_ref $e-i32-i64 $catch1) (catch_all_ref $catch_all2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (tuple.extract 3 0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 3 1
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (tuple.extract 3 2
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0
+ ;; CHECK-NEXT: (block (type $0) (result i32 i64)
+ ;; CHECK-NEXT: (tuple.drop 2
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-tuple-tag-tuple-with-rethrow (result i32 i64)
+ ;; try's type is tuple and catch's tag type is tuple, and there are
+ ;; rethrows
+ (try $l0 (result i32 i64)
+ (do
+ (call $foo)
+ (tuple.make 2
+ (i32.const 0)
+ (i64.const 0)
+ )
+ )
+ (catch $e-i32-i64
+ (tuple.drop 2
+ (pop i32 i64)
+ )
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; ---------------------------------------------------------------------------
+ ;; More try-catch tests
+
+ ;; CHECK: (func $catchless-delegateless-try (type $1)
+ ;; CHECK-NEXT: (try_table
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $catchless-delegateless-try
+ (try
+ (do
+ (call $foo)
+ )
+ )
+ )
+
+ ;; CHECK: (func $multiple-catches-and-catch_all (type $1)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 (i32 i64))
+ ;; CHECK-NEXT: (local $3 (i32 exnref))
+ ;; CHECK-NEXT: (local $4 (i32 i64 exnref))
+ ;; CHECK-NEXT: (block $outer0
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all4 (result exnref)
+ ;; CHECK-NEXT: (local.set $4
+ ;; CHECK-NEXT: (block $catch3 (type $4) (result i32 i64 exnref)
+ ;; CHECK-NEXT: (local.set $3
+ ;; CHECK-NEXT: (block $catch2 (type $3) (result i32 exnref)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch1 (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_ref $e-empty $catch1) (catch_ref $e-i32 $catch2) (catch_ref $e-i32-i64 $catch3) (catch_all_ref $catch_all4)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.extract 2 0
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (tuple.extract 2 1
+ ;; CHECK-NEXT: (local.get $3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (tuple.make 2
+ ;; CHECK-NEXT: (tuple.extract 3 0
+ ;; CHECK-NEXT: (local.get $4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 3 1
+ ;; CHECK-NEXT: (local.get $4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (tuple.extract 3 2
+ ;; CHECK-NEXT: (local.get $4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (tuple.drop 2
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $multiple-catches-and-catch_all
+ (try $l0
+ (do
+ (call $foo)
+ )
+ (catch $e-empty
+ (rethrow $l0)
+ )
+ (catch $e-i32
+ (drop
+ (pop i32)
+ )
+ (rethrow $l0)
+ )
+ (catch $e-i32-i64
+ (tuple.drop 2
+ (pop i32 i64)
+ )
+ (rethrow $l0)
+ )
+ (catch_all
+ (rethrow $l0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $nested-catch-rethrows (type $1)
+ ;; CHECK-NEXT: (local $0 exnref)
+ ;; CHECK-NEXT: (local $1 exnref)
+ ;; CHECK-NEXT: (block $outer3
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block $catch_all4 (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $catch_all4)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $outer0
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (block $catch2 (result exnref)
+ ;; CHECK-NEXT: (block $catch1
+ ;; CHECK-NEXT: (try_table (catch $e-empty $catch1) (catch_ref $e-empty $catch2)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nested-catch-rethrows
+ (try $l0
+ (do
+ (call $foo)
+ )
+ (catch_all
+ (try $l1
+ (do
+ (call $foo)
+ )
+ ;; This catch will be converted to a 'catch' clause in a try_table,
+ ;; because the rethrow in this catch body does not refer to the
+ ;; current try
+ (catch $e-empty
+ (rethrow $l0)
+ )
+ ;; This catch will be converted to a 'catch_ref' clause in a
+ ;; try_table, because the rethrow in this catch body refers to the
+ ;; current try
+ (catch $e-empty
+ (rethrow $l1)
+ )
+ )
+ )
+ )
+ )
+
+ ;; ---------------------------------------------------------------------------
+ ;; try-delegate tests
+
+ ;; CHECK: (func $delegate-target-outer-try-none (type $1)
+ ;; CHECK-NEXT: (block $outer1
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (try_table (catch_all $catch_all2)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l00 (result exnref)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $l00)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $baz)
+ ;; CHECK-NEXT: (br $outer1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $delegate-target-outer-try-none
+ ;; An inner try-delegate targets an outer try whose type is none
+ (try $l0
+ (do
+ (call $foo)
+ (try
+ (do
+ (call $bar)
+ )
+ (delegate $l0)
+ )
+ (call $baz)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; CHECK: (func $multiple-delegates-target-outer-try-none (type $1)
+ ;; CHECK-NEXT: (block $outer1
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (try_table (catch_all $catch_all2)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l00 (result exnref)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $l00)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (try_table (catch_all_ref $l00)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $baz)
+ ;; CHECK-NEXT: (br $outer1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $multiple-delegates-target-outer-try-none
+ ;; Multiple inner try-delegates target an outer try whose type is none
+ (try $l0
+ (do
+ (call $foo)
+ (try
+ (do
+ (call $bar)
+ )
+ (delegate $l0)
+ )
+ (try
+ (do
+ (call $bar)
+ )
+ (delegate $l0)
+ )
+ (call $baz)
+ )
+ (catch_all)
+ )
+ )
+
+ ;; CHECK: (func $delegate-target-outer-try-concrete (type $2) (result i32)
+ ;; CHECK-NEXT: (block $outer1 (result i32)
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (br $outer1
+ ;; CHECK-NEXT: (try_table (result i32) (catch_all $catch_all2)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l00 (result exnref)
+ ;; CHECK-NEXT: (br $outer1
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (try_table (result i32) (catch_all_ref $l00)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $delegate-target-outer-try-concrete (result i32)
+ ;; An inner try-delegate targets an outer try whose type is concrete
+ (try $l0 (result i32)
+ (do
+ (call $foo)
+ (try (result i32)
+ (do
+ (call $bar)
+ (i32.const 0)
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all
+ (i32.const 1)
+ )
+ )
+ )
+
+ ;; CHECK: (func $deletate-target-outer-try-unreachable (type $1)
+ ;; CHECK-NEXT: (try_table
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l00 (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $l00)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $deletate-target-outer-try-unreachable
+ ;; An inner try-delegate targets an outer try whose body type is unreachable
+ ;; (due to a return). In this case we don't need an additional 'br' to an
+ ;; outer block.
+ (try $l0
+ (do
+ (try
+ (do
+ (call $foo)
+ )
+ (delegate $l0)
+ )
+ (return)
+ )
+ )
+ )
+
+ ;; CHECK: (func $delegate-target-caller-none (type $1)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $__binaryen_delegate_caller_target0)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $baz)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $delegate-target-caller-none
+ ;; A try-delegate targets the caller whose type is none
+ (call $foo)
+ (try
+ (do
+ (call $bar)
+ )
+ (delegate 0)
+ )
+ (call $baz)
+ )
+
+ ;; CHECK: (func $multiple-delegates-target-caller-none (type $1)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $__binaryen_delegate_caller_target0)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (try_table (catch_all_ref $__binaryen_delegate_caller_target0)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $baz)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $multiple-delegates-target-caller-none
+ ;; Multiple try-delegates target the caller whose type is none
+ (call $foo)
+ (try
+ (do
+ (call $bar)
+ )
+ (delegate 0)
+ )
+ (try
+ (do
+ (call $bar)
+ )
+ (delegate 0)
+ )
+ (call $baz)
+ )
+
+ ;; CHECK: (func $delegate-target-caller-concrete (type $2) (result i32)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (try_table (result i32) (catch_all_ref $__binaryen_delegate_caller_target0)
+ ;; CHECK-NEXT: (call $bar)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $delegate-target-caller-concrete (result i32)
+ ;; A try-delegate targets the caller whose type is concrete
+ (call $foo)
+ (try (result i32)
+ (do
+ (call $bar)
+ (i32.const 0)
+ )
+ (delegate 0)
+ )
+ )
+
+ ;; CHECK: (func $delegate-nested-more (type $1)
+ ;; CHECK-NEXT: (block $outer3
+ ;; CHECK-NEXT: (block $catch_all4
+ ;; CHECK-NEXT: (try_table (catch_all $catch_all4)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l00 (result exnref)
+ ;; CHECK-NEXT: (block $outer1
+ ;; CHECK-NEXT: (block $catch_all2
+ ;; CHECK-NEXT: (try_table (catch_all $catch_all2)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $l00)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $delegate-nested-more
+ (try $l0
+ (do
+ (try
+ (do
+ (try
+ (do
+ (call $foo)
+ )
+ (delegate $l0)
+ )
+ )
+ (catch_all)
+ )
+ )
+ (catch_all)
+ )
+ )
+
+ ;; CHECK: (func $delegate-target-outer-try-delegate (type $1)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref)
+ ;; CHECK-NEXT: (block $outer2
+ ;; CHECK-NEXT: (try_table (catch_all_ref $__binaryen_delegate_caller_target0)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l01 (result exnref)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $l01)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $outer2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $delegate-target-outer-try-delegate
+ ;; An inner try-delegate targets an outer try-delegate that targets the
+ ;; caller
+ (try $l0
+ (do
+ (try
+ (do
+ (call $foo)
+ )
+ (delegate $l0)
+ )
+ )
+ (delegate 0)
+ )
+ )
+
+ ;; CHECK: (func $delegate-target-outer-try-delegate-concrete (type $2) (result i32)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (block $outer2 (result i32)
+ ;; CHECK-NEXT: (try_table (catch_all_ref $__binaryen_delegate_caller_target0)
+ ;; CHECK-NEXT: (throw_ref
+ ;; CHECK-NEXT: (block $l01 (result exnref)
+ ;; CHECK-NEXT: (br $outer2
+ ;; CHECK-NEXT: (try_table (result i32) (catch_all_ref $l01)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $delegate-target-outer-try-delegate-concrete (result i32)
+ ;; An inner try-delegate targets an outer try-delegate that targets the
+ ;; caller, where the type of the try-delegates and the caller is concrete
+ (try $l0 (result i32)
+ (do
+ (try (result i32)
+ (do
+ (call $foo)
+ (i32.const 0)
+ )
+ (delegate $l0)
+ )
+ )
+ (delegate 0)
+ )
+ )
+)