From 046ebef62ec0e2b9ee41398d5498a943b0018bed Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 18 Dec 2023 17:23:36 -0800 Subject: [PATCH 01/16] [EH] Add translator from old to new EH instructions 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. --- src/passes/CMakeLists.txt | 1 + src/passes/TranslateEH.cpp | 823 ++++++++++ src/passes/pass.cpp | 3 + src/passes/passes.h | 2 + src/wasm-builder.h | 2 +- test/lit/help/wasm-opt.test | 3 + test/lit/help/wasm2js.test | 3 + test/lit/passes/translate-eh-old-to-new.wast | 1421 ++++++++++++++++++ 8 files changed, 2257 insertions(+), 1 deletion(-) create mode 100644 src/passes/TranslateEH.cpp create mode 100644 test/lit/passes/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..1b15f6db533 --- /dev/null +++ b/src/passes/TranslateEH.cpp @@ -0,0 +1,823 @@ +/* + * Copyright 2023 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 targetted by delegates and rethrows. + struct TargetTryLabelScanner : public PostWalker { + TargetTryLabelScanner(Function* func) { walkFunction(func); } + + std::unordered_set delegateTargets; + std::unordered_set rethrowTargets; + bool isTargettedByDelegates(Try* curr) const { + return delegateTargets.find(curr->name) != delegateTargets.end(); + } + bool isTargettedByRethrows(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 targetted 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 targetted 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 + // is targetted by rethrows, we only need that many extra local 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 targetted with + // rethrows. + size_t rethrowTryDepth = 0; + + ExnrefLocalAssigner(Function* func, TargetTryLabelScanner* labelScanner) + : labelScanner(labelScanner) { + walkFunction(func); + } + + bool hasExnrefLocal(Name rethrowTarget) const { + return rethrowTargetToExnrefLocal.find(rethrowTarget) != + rethrowTargetToExnrefLocal.end(); + } + Index getExnrefLocal(Name rethrowTarget) const { + auto it = rethrowTargetToExnrefLocal.find(rethrowTarget); + assert(it != rethrowTargetToExnrefLocal.end()); + 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->isTargettedByRethrows(tryy)) { + self->pushTask(decrementRethrowTryDepth, currp); + } + } + PostWalker::scan(self, currp); + if (auto* tryy = curr->dynCast()) { + if (self->labelScanner->isTargettedByRethrows(tryy)) { + self->pushTask(incrementRethrowTryDepth, currp); + } + } + } + + void visitTry(Try* curr) { + if (labelScanner->isTargettedByRethrows(curr)) { + while (exnrefLocals.size() < rethrowTryDepth) { + exnrefLocals.push_back( + Builder::addVar(getFunction(), Type(HeapType::exn, Nullable))); + } + rethrowTargetToExnrefLocal[curr->name] = + exnrefLocals[rethrowTryDepth - 1]; + } + } + }; + + std::unique_ptr labels; + std::unique_ptr labelScanner; + std::unique_ptr 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 = typeToScratchLocal.find(type); + if (it != typeToScratchLocal.end()) { + return it->second; + } + typeToScratchLocal[type] = Builder::addVar(getFunction(), type); + return typeToScratchLocal[type]; + } + + // Process try labels targetted 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 + // ... + // (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 + // targetting 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); + assert(outerBlock->list.empty()); + outerBlock->list.push_back(curr->body); + } + + void processDelegate(Try* curr) { + 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. + replaceCurrent( + builder.makeTryTable(curr->body, + {Name()}, + {delegateTargetToBrTarget[curr->delegateTarget]}, + {true})); + } + + 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 targetted 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. + if (localAssigner->hasExnrefLocal(curr->name)) { + Index local = localAssigner->getExnrefLocal(curr->name); + for (auto* throwRef : FindAll(catchBody).list) { + if (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 + // 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 targets 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); + } + + // In case this was already popuated in processDelegateTarget(), we empty + // the block to repopulate with the newly created structures here + outerBlock->list.clear(); + for (auto* item : items) { + outerBlock->list.push_back(item); + } + } + + 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); + } else { // try-catch or catch-less try + processCatches(curr, outerBlock); + } + + if (outerBlock) { + replaceCurrent(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 targetted 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 none: + // INNER_BODY = + // 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_unique(func); + labelScanner = std::make_unique(func); + localAssigner = + std::make_unique(func, labelScanner.get()); + + // 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 d7391a390c3..ad0c02406b9 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..c14ecf4a77a 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -164,6 +164,8 @@ Pass* createStripEHPass(); Pass* createStubUnsupportedJSOpsPass(); Pass* createSSAifyPass(); Pass* createSSAifyNoMergePass(); +Pass* createTranslateEHOldToNewPass(); +Pass* createTranslateEHNewToOldPass(); Pass* createTrapModeClamp(); Pass* createTrapModeJS(); Pass* createTupleOptimizationPass(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index a873a73094b..0d708605be1 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1328,8 +1328,8 @@ class Builder { block->name = name; if (append) { block->list.push_back(append); - block->finalize(type); } + block->finalize(type); return block; } 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..0b81a1272b8 --- /dev/null +++ b/test/lit/passes/translate-eh-old-to-new.wast @@ -0,0 +1,1421 @@ +;; 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: (nop) + ;; 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) + (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: (nop) + ;; 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) + (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: (nop) + ;; 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) + (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: (nop) + ;; 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) + (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: (nop) + ;; 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) + (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: (nop) + ;; 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) + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (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 + (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: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $catchless-delegateless-try + (try + (do) + ) + ) + + ;; 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: (nop) + ;; 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) + (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: (nop) + ;; 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: (nop) + ;; 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) + (catch_all + (try $l1 + (do) + ;; 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: (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 + (i32.const 0) + ) + (delegate $l0) + ) + ) + (catch_all + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $deletate-target-outer-try-unreachable (type $1) + ;; CHECK-NEXT: (block $outer1 + ;; CHECK-NEXT: (throw_ref + ;; CHECK-NEXT: (block $l00 (result exnref) + ;; CHECK-NEXT: (try_table (catch_all_ref $l00) + ;; CHECK-NEXT: (nop) + ;; 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) + (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: (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 + (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: (throw_ref + ;; CHECK-NEXT: (block $l01 (result exnref) + ;; CHECK-NEXT: (try_table (catch_all_ref $l01) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $outer2) + ;; 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) + (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: (throw_ref + ;; CHECK-NEXT: (block $l01 (result exnref) + ;; CHECK-NEXT: (br $outer2 + ;; CHECK-NEXT: (try_table (result i32) (catch_all_ref $l01) + ;; CHECK-NEXT: (i32.const 0) + ;; 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 + (i32.const 0) + ) + (delegate $l0) + ) + ) + (delegate 0) + ) + ) +) From 9eb535bafef7c5636b7ebead3b85fc128d27a5fb Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 8 Jan 2024 14:41:02 -0800 Subject: [PATCH 02/16] Fix try-delegate targetted by try-delegate case --- src/passes/TranslateEH.cpp | 27 ++++++++++---------- test/lit/passes/translate-eh-old-to-new.wast | 26 +++++++++++-------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index 1b15f6db533..fff528660d7 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -313,11 +313,9 @@ struct TranslateEHOldToNew curr->body, delegateBrTarget, brToOuter, Type(HeapType::exn, Nullable)); } curr->body = builder.makeThrowRef(innerBody); - assert(outerBlock->list.empty()); - outerBlock->list.push_back(curr->body); } - void processDelegate(Try* curr) { + void processDelegate(Try* curr, Block* outerBlock) { Builder builder(*getModule()); // Convert // (try @@ -337,11 +335,20 @@ struct TranslateEHOldToNew // 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. - replaceCurrent( + auto* tryTable = builder.makeTryTable(curr->body, {Name()}, {delegateTargetToBrTarget[curr->delegateTarget]}, - {true})); + {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) { @@ -667,12 +674,10 @@ struct TranslateEHOldToNew items.swap(nextItems); } - // In case this was already popuated in processDelegateTarget(), we empty - // the block to repopulate with the newly created structures here - outerBlock->list.clear(); for (auto* item : items) { outerBlock->list.push_back(item); } + replaceCurrent(outerBlock); } void visitTry(Try* curr) { @@ -689,14 +694,10 @@ struct TranslateEHOldToNew processDelegateTarget(curr, outerBlock); } if (curr->isDelegate()) { - processDelegate(curr); + processDelegate(curr, outerBlock); } else { // try-catch or catch-less try processCatches(curr, outerBlock); } - - if (outerBlock) { - replaceCurrent(outerBlock); - } } void visitPop(Pop* curr) { diff --git a/test/lit/passes/translate-eh-old-to-new.wast b/test/lit/passes/translate-eh-old-to-new.wast index 0b81a1272b8..20271714663 100644 --- a/test/lit/passes/translate-eh-old-to-new.wast +++ b/test/lit/passes/translate-eh-old-to-new.wast @@ -1201,7 +1201,7 @@ ) ;; CHECK: (func $deletate-target-outer-try-unreachable (type $1) - ;; CHECK-NEXT: (block $outer1 + ;; CHECK-NEXT: (try_table ;; CHECK-NEXT: (throw_ref ;; CHECK-NEXT: (block $l00 (result exnref) ;; CHECK-NEXT: (try_table (catch_all_ref $l00) @@ -1357,12 +1357,14 @@ ;; CHECK-NEXT: (throw_ref ;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref) ;; CHECK-NEXT: (block $outer2 - ;; CHECK-NEXT: (throw_ref - ;; CHECK-NEXT: (block $l01 (result exnref) - ;; CHECK-NEXT: (try_table (catch_all_ref $l01) - ;; CHECK-NEXT: (nop) + ;; 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: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $outer2) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $outer2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1389,11 +1391,13 @@ ;; CHECK-NEXT: (block $__binaryen_delegate_caller_target0 (result exnref) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (block $outer2 (result i32) - ;; 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: (i32.const 0) + ;; 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: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From b13aeaee3b4a2f840ef4d9d5a1a45472b10547b3 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 8 Jan 2024 17:00:22 -0800 Subject: [PATCH 03/16] Fix `blockifyWithName` --- src/passes/passes.h | 1 - src/wasm-builder.h | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/passes/passes.h b/src/passes/passes.h index c14ecf4a77a..66535bdc0e2 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -165,7 +165,6 @@ Pass* createStubUnsupportedJSOpsPass(); Pass* createSSAifyPass(); Pass* createSSAifyNoMergePass(); Pass* createTranslateEHOldToNewPass(); -Pass* createTranslateEHNewToOldPass(); Pass* createTrapModeClamp(); Pass* createTrapModeJS(); Pass* createTupleOptimizationPass(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 0d708605be1..7530ea76be6 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1323,13 +1323,16 @@ class Builder { block = any->dynCast(); } if (!block || block->name.is()) { - block = makeBlock(any); + block = makeBlock(name, any); + } else { + block->name = name; } - block->name = name; if (append) { block->list.push_back(append); } - block->finalize(type); + if (append || type) { + block->finalize(type); + } return block; } From 6c4d17f1fd423e4e52278b81f60ae84317edec5c Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 8 Jan 2024 17:03:53 -0800 Subject: [PATCH 04/16] Apply suggestions from code review Co-authored-by: Thomas Lively --- src/passes/TranslateEH.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index fff528660d7..c218db9ced1 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -111,7 +111,7 @@ struct TranslateEHOldToNew // reused in both trys. // // Also even if there is a try-catch nest with depth N, if only some of them - // is targetted by rethrows, we only need that many extra local for the whole + // are targeted by rethrows, we only need that many extra locals for the whole // nest: // (try $l0 // (catch @@ -214,12 +214,11 @@ struct TranslateEHOldToNew // reuse these for all extracted values and (extracted values, exnref) tuples // as long as the type matches. Index getScratchLocal(Type type) { - auto it = typeToScratchLocal.find(type); - if (it != typeToScratchLocal.end()) { - return it->second; - } - typeToScratchLocal[type] = Builder::addVar(getFunction(), type); - return typeToScratchLocal[type]; + auto [it, inserted] = typeToScratchLocal.insert({type, 0}); + if (inserted) { + it->second = Builder::addVar(getFuntion(), type); + ) + return it->second; } // Process try labels targetted by rethrows. This does NOT transform the @@ -275,7 +274,7 @@ struct TranslateEHOldToNew // (do // (throw_ref // (block $delegate_br_target (result exnref) - // (br $outer + // (br $outer ;; Now has the try_table as a child. // ... // (try_table (catch_all_ref $delegate_br_target) // ... @@ -496,7 +495,7 @@ struct TranslateEHOldToNew // 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 + // locals used for pops, exnref locals can have longer lifetimes and are // assigned in ExnrefLocalAssigner. So for example, // (try $l0 // ... @@ -522,7 +521,7 @@ struct TranslateEHOldToNew // ) // // When the tag type is concrete and also there are rethrows within the - // catch body that targets this try's label, (extracted values, exnref) + // 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. From 76978f4daab38325264d8dff77c3c4c0ebd11039 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 8 Jan 2024 17:07:52 -0800 Subject: [PATCH 05/16] Fix typo --- src/passes/TranslateEH.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index c218db9ced1..be31c09765d 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -216,8 +216,8 @@ struct TranslateEHOldToNew Index getScratchLocal(Type type) { auto [it, inserted] = typeToScratchLocal.insert({type, 0}); if (inserted) { - it->second = Builder::addVar(getFuntion(), type); - ) + it->second = Builder::addVar(getFunction(), type); + } return it->second; } @@ -807,7 +807,7 @@ struct TranslateEHOldToNew processCallerDelegateTarget(); } } -}; + }; struct TranslateEHNewToOld : public WalkerPass> { From c44a66d79d7776d719b278b065b8d0b18ee52515 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 8 Jan 2024 17:09:56 -0800 Subject: [PATCH 06/16] targetting -> targeting / targetted -> targeted --- src/passes/TranslateEH.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index be31c09765d..d8f99807394 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -49,16 +49,16 @@ struct TranslateEHOldToNew : public WalkerPass> { bool isFunctionParallel() override { return true; } - // Scans and records which try labels are targetted by delegates and rethrows. + // Scans and records which try labels are targeted by delegates and rethrows. struct TargetTryLabelScanner : public PostWalker { TargetTryLabelScanner(Function* func) { walkFunction(func); } std::unordered_set delegateTargets; std::unordered_set rethrowTargets; - bool isTargettedByDelegates(Try* curr) const { + bool isTargetedByDelegates(Try* curr) const { return delegateTargets.find(curr->name) != delegateTargets.end(); } - bool isTargettedByRethrows(Try* curr) const { + bool isTargetedByRethrows(Try* curr) const { return rethrowTargets.find(curr->name) != rethrowTargets.end(); } @@ -71,7 +71,7 @@ struct TranslateEHOldToNew void visitRethrow(Rethrow* curr) { rethrowTargets.insert(curr->target); } }; - // For each try label targetted by rethrows, assign a local that will contain + // 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'. @@ -95,8 +95,8 @@ struct TranslateEHOldToNew // 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 targetted by rethrows, which - // means the pattern below only needs one extra local: + // 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) @@ -138,7 +138,7 @@ struct TranslateEHOldToNew std::vector exnrefLocals; std::unordered_map rethrowTargetToExnrefLocal; - // Depth of the current try nest, when only counting trys targetted with + // Depth of the current try nest, when only counting trys targeted with // rethrows. size_t rethrowTryDepth = 0; @@ -169,20 +169,20 @@ struct TranslateEHOldToNew static void scan(ExnrefLocalAssigner* self, Expression** currp) { auto* curr = *currp; if (auto* tryy = curr->dynCast()) { - if (self->labelScanner->isTargettedByRethrows(tryy)) { + if (self->labelScanner->isTargetedByRethrows(tryy)) { self->pushTask(decrementRethrowTryDepth, currp); } } PostWalker::scan(self, currp); if (auto* tryy = curr->dynCast()) { - if (self->labelScanner->isTargettedByRethrows(tryy)) { + if (self->labelScanner->isTargetedByRethrows(tryy)) { self->pushTask(incrementRethrowTryDepth, currp); } } } void visitTry(Try* curr) { - if (labelScanner->isTargettedByRethrows(curr)) { + if (labelScanner->isTargetedByRethrows(curr)) { while (exnrefLocals.size() < rethrowTryDepth) { exnrefLocals.push_back( Builder::addVar(getFunction(), Type(HeapType::exn, Nullable))); @@ -221,7 +221,7 @@ struct TranslateEHOldToNew return it->second; } - // Process try labels targetted by rethrows. This does NOT transform the + // 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. @@ -295,7 +295,7 @@ struct TranslateEHOldToNew // processCatches(). // // Also note that even in case there are multiple inner try~delegates - // targetting this try, we need to do this only once per try target. Those + // 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]; @@ -357,7 +357,7 @@ struct TranslateEHOldToNew // 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 targetted by rethrows and those rethrows exist in the + // 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) @@ -709,11 +709,11 @@ struct TranslateEHOldToNew } void visitRethrow(Rethrow* curr) { - // After we assigned an exnref local for each try label targetted by + // 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. + // 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( From 69e789535ac95ce5c0feb3d3ea06b246cec7734b Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 8 Jan 2024 17:13:59 -0800 Subject: [PATCH 07/16] clang-format --- src/passes/TranslateEH.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index d8f99807394..a91df3e7d50 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -807,7 +807,7 @@ struct TranslateEHOldToNew processCallerDelegateTarget(); } } - }; +}; struct TranslateEHNewToOld : public WalkerPass> { From 87f65d34d07282351868d39c7fff9390b7beac00 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 12 Jan 2024 16:17:11 -0800 Subject: [PATCH 08/16] unique_ptr -> optional --- src/passes/TranslateEH.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index a91df3e7d50..b64fc7736b5 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -193,9 +193,9 @@ struct TranslateEHOldToNew } }; - std::unique_ptr labels; - std::unique_ptr labelScanner; - std::unique_ptr localAssigner; + std::optional labels; + std::optional labelScanner; + std::optional localAssigner; std::unordered_map delegateTargetToBrTarget; // Scratch locals used to contain extracted values and (extracted values, @@ -787,10 +787,10 @@ struct TranslateEHOldToNew } void doWalkFunction(Function* func) { - labels = std::make_unique(func); - labelScanner = std::make_unique(func); + labels = std::make_optional(func); + labelScanner = std::make_optional(func); localAssigner = - std::make_unique(func, labelScanner.get()); + 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 From 04606560a563ecbcd9d9603a65e7ed9cb2b34df5 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 12 Jan 2024 16:30:31 -0800 Subject: [PATCH 09/16] Combine hasExnrefLocal and getExnrefLocal --- src/passes/TranslateEH.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index b64fc7736b5..e30c14ef409 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -147,14 +147,13 @@ struct TranslateEHOldToNew walkFunction(func); } - bool hasExnrefLocal(Name rethrowTarget) const { - return rethrowTargetToExnrefLocal.find(rethrowTarget) != - rethrowTargetToExnrefLocal.end(); - } - Index getExnrefLocal(Name rethrowTarget) const { + std::optional getExnrefLocal(Name rethrowTarget) const { auto it = rethrowTargetToExnrefLocal.find(rethrowTarget); - assert(it != rethrowTargetToExnrefLocal.end()); - return it->second; + if (it == rethrowTargetToExnrefLocal.end()) { + return {}; + } else { + return it->second; + } } static void incrementRethrowTryDepth(ExnrefLocalAssigner* self, @@ -362,11 +361,11 @@ struct TranslateEHOldToNew // 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. - if (localAssigner->hasExnrefLocal(curr->name)) { - Index local = localAssigner->getExnrefLocal(curr->name); + std::optional local = localAssigner->getExnrefLocal(curr->name); + if (local) { for (auto* throwRef : FindAll(catchBody).list) { if (auto* localGet = throwRef->exnref->cast()) { - if (localGet->index == local) { + if (localGet->index == *local) { return true; } } @@ -599,7 +598,7 @@ struct TranslateEHOldToNew 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); + 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 @@ -715,7 +714,7 @@ struct TranslateEHOldToNew // 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); + Index exnrefLocal = *localAssigner->getExnrefLocal(curr->target); replaceCurrent(builder.makeThrowRef( builder.makeLocalGet(exnrefLocal, Type(HeapType::exn, Nullable)))); } From 3412caa8a9337a8f9a5335279925b3883df1d071 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 12 Jan 2024 17:01:11 -0800 Subject: [PATCH 10/16] cast comment --- src/passes/TranslateEH.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index e30c14ef409..87254a6abce 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -364,6 +364,8 @@ struct TranslateEHOldToNew 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(). if (auto* localGet = throwRef->exnref->cast()) { if (localGet->index == *local) { return true; From 8659480dcdcf4389d83e3f892cd455d2337c2159 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 12 Jan 2024 17:21:39 -0800 Subject: [PATCH 11/16] Remove stale comments --- src/passes/TranslateEH.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index 87254a6abce..6a2dbe330d1 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -759,8 +759,6 @@ struct TranslateEHOldToNew // ) // ) // - // If sometype (func's type) is none: - // INNER_BODY = // If sometype (func's type) is concrete: // (throw_ref // (block $caller_delegate_br_target (result exnref) From 774956008be10d9439379a3a71ee4efacfcd4886 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 16 Jan 2024 15:22:11 -0800 Subject: [PATCH 12/16] Update src/passes/TranslateEH.cpp Co-authored-by: Alon Zakai --- src/passes/TranslateEH.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index 6a2dbe330d1..dbeb2805b2c 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2023 WebAssembly Community Group participants + * 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. From a707ba72825e1b249685534cf78537eb4fc90bdc Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 16 Jan 2024 15:31:43 -0800 Subject: [PATCH 13/16] Remove unnecessary `cast` --- src/passes/TranslateEH.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index dbeb2805b2c..5e78636a627 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -366,10 +366,9 @@ struct TranslateEHOldToNew for (auto* throwRef : FindAll(catchBody).list) { // All throw_refs generated in this pass has a local.get as its child. // See visitRethrow(). - if (auto* localGet = throwRef->exnref->cast()) { - if (localGet->index == *local) { - return true; - } + auto* localGet = throwRef->exnref->cast(); + if (localGet->index == *local) { + return true; } } } From 4fbdf137e739aad90d17280f0778409bb0c57265 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 17 Jan 2024 15:03:38 -0800 Subject: [PATCH 14/16] Exclude new test file from fuzzing --- scripts/fuzz_opt.py | 1 + 1 file changed, 1 insertion(+) 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', ] From ad35e5ab0aaccd2afd2501133c52901d596deee1 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 18 Jan 2024 21:58:33 -0800 Subject: [PATCH 15/16] unordered_set -> set to fix label ordering for tests --- src/passes/TranslateEH.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/TranslateEH.cpp b/src/passes/TranslateEH.cpp index 5e78636a627..d8a00446811 100644 --- a/src/passes/TranslateEH.cpp +++ b/src/passes/TranslateEH.cpp @@ -53,8 +53,8 @@ struct TranslateEHOldToNew struct TargetTryLabelScanner : public PostWalker { TargetTryLabelScanner(Function* func) { walkFunction(func); } - std::unordered_set delegateTargets; - std::unordered_set rethrowTargets; + std::set delegateTargets; + std::set rethrowTargets; bool isTargetedByDelegates(Try* curr) const { return delegateTargets.find(curr->name) != delegateTargets.end(); } From 47f99528eafe7f89df75ac399fbaf708972428c8 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sun, 21 Jan 2024 17:48:42 -0800 Subject: [PATCH 16/16] Add function calls in try body in tests --- test/lit/passes/translate-eh-old-to-new.wast | 102 ++++++++++++++----- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/test/lit/passes/translate-eh-old-to-new.wast b/test/lit/passes/translate-eh-old-to-new.wast index 20271714663..b012a85369b 100644 --- a/test/lit/passes/translate-eh-old-to-new.wast +++ b/test/lit/passes/translate-eh-old-to-new.wast @@ -46,7 +46,7 @@ ;; CHECK-NEXT: (block $catch_all2 ;; CHECK-NEXT: (block $catch1 ;; CHECK-NEXT: (try_table (catch $e-empty $catch1) (catch_all $catch_all2) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer0) ;; CHECK-NEXT: ) @@ -59,7 +59,9 @@ (func $try-none-tag-none ;; try's type is none and catch's tag type is none (try $l0 - (do) + (do + (call $foo) + ) (catch $e-empty ;; converted to catch (call $foo) ) @@ -77,7 +79,7 @@ ;; 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: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer0) ;; CHECK-NEXT: ) @@ -95,7 +97,9 @@ (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) + (do + (call $foo) + ) (catch $e-empty ;; converted to catch_ref, because of rethrow (rethrow $l0) ) @@ -112,7 +116,7 @@ ;; 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: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer0) ;; CHECK-NEXT: ) @@ -128,7 +132,9 @@ (func $try-none-tag-single ;; try's type is none and catch's tag type is single (try $l0 - (do) + (do + (call $foo) + ) (catch $e-i32 (drop (pop i32) @@ -150,7 +156,7 @@ ;; 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: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer0) ;; CHECK-NEXT: ) @@ -183,7 +189,9 @@ (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) + (do + (call $foo) + ) (catch $e-i32 (drop (pop i32) @@ -203,7 +211,7 @@ ;; 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: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer0) ;; CHECK-NEXT: ) @@ -219,7 +227,9 @@ (func $try-none-tag-tuple ;; try's type is none and catch's tag type is tuple (try $l0 - (do) + (do + (call $foo) + ) (catch $e-i32-i64 (tuple.drop 2 (pop i32 i64) @@ -241,7 +251,7 @@ ;; 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: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer0) ;; CHECK-NEXT: ) @@ -279,7 +289,9 @@ (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) + (do + (call $foo) + ) (catch $e-i32-i64 (tuple.drop 2 (pop i32 i64) @@ -298,6 +310,7 @@ ;; 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: ) @@ -313,6 +326,7 @@ ;; 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 @@ -333,6 +347,7 @@ ;; 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: ) @@ -352,6 +367,7 @@ ;; 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 @@ -371,6 +387,7 @@ ;; 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: ) @@ -387,6 +404,7 @@ ;; 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 @@ -409,6 +427,7 @@ ;; 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: ) @@ -446,6 +465,7 @@ ;; rethrows (try $l0 (result i32) (do + (call $foo) (i32.const 0) ) (catch $e-i32 @@ -468,6 +488,7 @@ ;; 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: ) @@ -489,6 +510,7 @@ ;; 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 @@ -514,6 +536,7 @@ ;; 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: ) @@ -556,6 +579,7 @@ ;; rethrows (try $l0 (result i32) (do + (call $foo) (i32.const 0) ) (catch $e-i32-i64 @@ -576,6 +600,7 @@ ;; 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) @@ -600,6 +625,7 @@ ;; 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) @@ -629,6 +655,7 @@ ;; 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) @@ -651,6 +678,7 @@ ;; 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) @@ -673,6 +701,7 @@ ;; 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) @@ -698,6 +727,7 @@ ;; 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) @@ -729,6 +759,7 @@ ;; 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) @@ -769,6 +800,7 @@ ;; rethrows (try $l0 (result i32 i64) (do + (call $foo) (tuple.make 2 (i32.const 0) (i64.const 0) @@ -794,6 +826,7 @@ ;; 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) @@ -816,6 +849,7 @@ ;; 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) @@ -844,6 +878,7 @@ ;; 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) @@ -889,6 +924,7 @@ ;; rethrows (try $l0 (result i32 i64) (do + (call $foo) (tuple.make 2 (i32.const 0) (i64.const 0) @@ -911,12 +947,14 @@ ;; CHECK: (func $catchless-delegateless-try (type $1) ;; CHECK-NEXT: (try_table - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $catchless-delegateless-try (try - (do) + (do + (call $foo) + ) ) ) @@ -936,7 +974,7 @@ ;; 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: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer0) ;; CHECK-NEXT: ) @@ -998,7 +1036,9 @@ ;; CHECK-NEXT: ) (func $multiple-catches-and-catch_all (try $l0 - (do) + (do + (call $foo) + ) (catch $e-empty (rethrow $l0) ) @@ -1027,7 +1067,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (block $catch_all4 (result exnref) ;; CHECK-NEXT: (try_table (catch_all_ref $catch_all4) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer3) ;; CHECK-NEXT: ) @@ -1037,7 +1077,7 @@ ;; 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: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer0) ;; CHECK-NEXT: ) @@ -1054,10 +1094,14 @@ ;; CHECK-NEXT: ) (func $nested-catch-rethrows (try $l0 - (do) + (do + (call $foo) + ) (catch_all (try $l1 - (do) + (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 @@ -1170,6 +1214,7 @@ ;; 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: ) @@ -1189,6 +1234,7 @@ (call $foo) (try (result i32) (do + (call $bar) (i32.const 0) ) (delegate $l0) @@ -1205,7 +1251,7 @@ ;; CHECK-NEXT: (throw_ref ;; CHECK-NEXT: (block $l00 (result exnref) ;; CHECK-NEXT: (try_table (catch_all_ref $l00) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) @@ -1219,7 +1265,9 @@ (try $l0 (do (try - (do) + (do + (call $foo) + ) (delegate $l0) ) (return) @@ -1291,6 +1339,7 @@ ;; 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: ) @@ -1303,6 +1352,7 @@ (call $foo) (try (result i32) (do + (call $bar) (i32.const 0) ) (delegate 0) @@ -1361,7 +1411,7 @@ ;; CHECK-NEXT: (throw_ref ;; CHECK-NEXT: (block $l01 (result exnref) ;; CHECK-NEXT: (try_table (catch_all_ref $l01) - ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $outer2) ;; CHECK-NEXT: ) @@ -1378,7 +1428,9 @@ (try $l0 (do (try - (do) + (do + (call $foo) + ) (delegate $l0) ) ) @@ -1396,6 +1448,7 @@ ;; 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: ) @@ -1414,6 +1467,7 @@ (do (try (result i32) (do + (call $foo) (i32.const 0) ) (delegate $l0)