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) + ) + ) +)