From 1ce851d7a2044cd1c121bec7de676a61aa147c79 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 23 Jan 2024 18:51:57 -0800 Subject: [PATCH] [EH] Add translator from old to new EH instructions (#6210) This 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. While the goal of this pass is not optimization, this tries to a little better than the most naive implementation, namely by omitting a few instructions where possible and trying to minimize the number of additional locals, because this can be used as a standalone translator or the last stage of the pipeline while we can't post-optimize the results because the whole pipeline (-On) is not ready for the new EH. --- scripts/fuzz_opt.py | 1 + src/passes/CMakeLists.txt | 1 + src/passes/TranslateEH.cpp | 821 ++++++++++ src/passes/pass.cpp | 3 + src/passes/passes.h | 1 + test/lit/help/wasm-opt.test | 3 + test/lit/help/wasm2js.test | 3 + test/lit/passes/translate-eh-old-to-new.wast | 1479 ++++++++++++++++++ 8 files changed, 2312 insertions(+) create mode 100644 src/passes/TranslateEH.cpp create mode 100644 test/lit/passes/translate-eh-old-to-new.wast diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 953108a4421..27b2787032f 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -318,6 +318,7 @@ def is_git_repo(): '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 9ab9f0247e1..0e74d4b93b8 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 00000000000..d8a00446811 --- /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 +#include +#include +#include +#include +#include + +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> { + bool isFunctionParallel() override { return true; } + + // Scans and records which try labels are targeted by delegates and rethrows. + struct TargetTryLabelScanner : public PostWalker { + TargetTryLabelScanner(Function* func) { walkFunction(func); } + + std::set delegateTargets; + std::set 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 { + TargetTryLabelScanner* labelScanner = nullptr; + + std::vector exnrefLocals; + std::unordered_map 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 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()) { + if (self->labelScanner->isTargetedByRethrows(tryy)) { + self->pushTask(decrementRethrowTryDepth, currp); + } + } + PostWalker::scan(self, currp); + if (auto* tryy = curr->dynCast()) { + 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 labels; + std::optional labelScanner; + std::optional localAssigner; + + std::unordered_map delegateTargetToBrTarget; + // Scratch locals used to contain extracted values and (extracted values, + // exnref) tuples for a short time. + std::unordered_map typeToScratchLocal; + + bool refinalize = false; + + std::unique_ptr create() override { + return std::make_unique(); + } + + // 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 local = localAssigner->getExnrefLocal(curr->name); + if (local) { + for (auto* throwRef : FindAll(catchBody).list) { + // All throw_refs generated in this pass has a local.get as its child. + // See visitRethrow(). + auto* localGet = throwRef->exnref->cast(); + if (localGet->index == *local) { + return true; + } + } + } + return false; + }; + + // Create try_table instruction with catch clauses. + std::vector catchTags; + std::vector catchDests; + std::vector 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 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 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 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(func); + labelScanner = std::make_optional(func); + localAssigner = + std::make_optional(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> { + // 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 62b3683fad9..5f5db2f7f49 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 93a8245c80e..66535bdc0e2 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 132296d5b6b..07d218007d7 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 1d378f0ad3e..e81d07718ea 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 00000000000..b012a85369b --- /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) + ) + ) +)