diff options
-rw-r--r-- | src/ir/eh-utils.cpp | 162 | ||||
-rw-r--r-- | src/ir/eh-utils.h | 10 | ||||
-rw-r--r-- | src/pass.h | 10 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/pass.cpp | 17 | ||||
-rw-r--r-- | src/passes/passes.h | 5 | ||||
-rw-r--r-- | src/passes/test_passes.cpp | 46 | ||||
-rw-r--r-- | src/support/command-line.cpp | 12 | ||||
-rw-r--r-- | src/support/command-line.h | 4 | ||||
-rw-r--r-- | src/tools/optimization-options.h | 45 | ||||
-rw-r--r-- | test/lit/passes/catch-pop-fixup.wast | 396 |
11 files changed, 642 insertions, 66 deletions
diff --git a/src/ir/eh-utils.cpp b/src/ir/eh-utils.cpp index de85478a2..ed7ec1e0a 100644 --- a/src/ir/eh-utils.cpp +++ b/src/ir/eh-utils.cpp @@ -16,54 +16,144 @@ #include "ir/eh-utils.h" #include "ir/branch-utils.h" +#include "ir/find_all.h" +#include "ir/type-updating.h" namespace wasm { namespace EHUtils { -bool isPopValid(Expression* catchBody) { - Expression* firstChild = nullptr; - auto* block = catchBody->dynCast<Block>(); - if (!block) { - firstChild = catchBody; - } else { - // When there are multiple expressions within a catch body, an implicit - // block is created within it for convenience purposes, and if there are no - // branches that targets the block, it will be omitted when written back. - // But if there is a branch targetting this block, this block cannot be - // removed, and 'pop''s location will be like - // (catch $e - // (block $l0 - // (pop i32) ;; within a block! - // (br $l0) - // ... - // ) - // ) - // which is invalid. - if (BranchUtils::BranchSeeker::has(block, block->name)) { - return false; - } - // There should be a pop somewhere - if (block->list.empty()) { - return false; - } - firstChild = *block->list.begin(); - } +// This returns three values, some of them as output parameters: +// - Return value: 'pop' expression (Expression*), when there is one in +// first-descendant line. If there's no such pop, it returns null. +// - isPopNested: Whether the discovered 'pop' is nested within a block +// - popPtr: 'pop' expression's pointer (Expression**), when there is one found +// +// When 'catchBody' itself is a 'pop', 'pop''s pointer is null, because there is +// no way to get the given expression's address. But that's fine because pop's +// pointer is only necessary (in handleBlockNestedPops) to fix it up when it is +// nested, and if 'catchBody' itself is a pop, we don't need to fix it up. +static Expression* +getFirstPop(Expression* catchBody, bool& isPopNested, Expression**& popPtr) { + Expression* firstChild = catchBody; + isPopNested = false; + popPtr = nullptr; + // When there are multiple expressions within a catch body, an implicit + // block is created within it for convenience purposes. + auto* implicitBlock = catchBody->dynCast<Block>(); // Go down the line for the first child until we reach a leaf. A pop should be - // in that first-decendent line. + // in that first-decendant line. + Expression** firstChildPtr = nullptr; while (true) { if (firstChild->is<Pop>()) { - return true; + popPtr = firstChildPtr; + return firstChild; } - // We use ValueChildIterator in order not to go into block/loop/try/if - // bodies, because a pop cannot be in those control flow expressions. - ValueChildIterator it(firstChild); - if (it.begin() == it.end()) { - return false; + + if (Properties::isControlFlowStructure(firstChild)) { + if (auto* iff = firstChild->dynCast<If>()) { + // If's condition is a value child who comes before an 'if' instruction + // in binary, it is fine if a 'pop' is in there. We don't allow a 'pop' + // to be in an 'if''s then or else body because they are not first + // descendants. + firstChild = iff->condition; + firstChildPtr = &iff->condition; + continue; + } else if (firstChild->is<Loop>()) { + // We don't allow the pop to be included in a loop, because it cannot be + // run more than once + return nullptr; + } + if (firstChild->is<Block>()) { + // If there are no branches that targets the implicit block, it will be + // removed when written back. But if there are branches that target the + // implicit block, + // (catch $e + // (block $l0 + // (pop i32) ;; within a block! + // (br $l0) + // ... + // ) + // This cannot be removed, so this is considered a nested pop (which we + // should fix). + if (firstChild == implicitBlock) { + if (BranchUtils::BranchSeeker::has(implicitBlock, + implicitBlock->name)) { + isPopNested = true; + } + } else { + isPopNested = true; + } + } else if (firstChild->is<Try>()) { + isPopNested = true; + } else { + WASM_UNREACHABLE("Unexpected control flow expression"); + } + } + ChildIterator it(firstChild); + if (it.getNumChildren() == 0) { + return nullptr; + } + firstChildPtr = &*it.begin(); + firstChild = *firstChildPtr; + } +} + +bool isPopValid(Expression* catchBody) { + bool isPopNested = false; + Expression** popPtr = nullptr; + auto* pop = getFirstPop(catchBody, isPopNested, popPtr); + return pop != nullptr && !isPopNested; +} + +void handleBlockNestedPops(Function* func, Module& wasm) { + Builder builder(wasm); + FindAll<Try> trys(func->body); + for (auto* try_ : trys.list) { + for (Index i = 0; i < try_->catchTags.size(); i++) { + Name tagName = try_->catchTags[i]; + auto* tag = wasm.getTag(tagName); + if (tag->sig.params == Type::none) { + continue; + } + + auto* catchBody = try_->catchBodies[i]; + bool isPopNested = false; + Expression** popPtr = nullptr; + Expression* pop = getFirstPop(catchBody, isPopNested, popPtr); + assert(pop && "Pop has not been found in this catch"); + + // Change code like + // (catch $e + // ... + // (block + // (pop i32) + // ) + // ) + // into + // (catch $e + // (local.set $new + // (pop i32) + // ) + // ... + // (block + // (local.get $new) + // ) + // ) + if (isPopNested) { + assert(popPtr); + Index newLocal = builder.addVar(func, pop->type); + try_->catchBodies[i] = + builder.makeSequence(builder.makeLocalSet(newLocal, pop), catchBody); + *popPtr = builder.makeLocalGet(newLocal, pop->type); + } } - firstChild = *it.begin(); } + + // Pops we handled can be of non-defaultable types, so we may have created + // non-nullable type locals. Fix them. + TypeUpdating::handleNonDefaultableLocals(func, wasm); } } // namespace EHUtils diff --git a/src/ir/eh-utils.h b/src/ir/eh-utils.h index 844fda448..733eedc67 100644 --- a/src/ir/eh-utils.h +++ b/src/ir/eh-utils.h @@ -17,9 +17,9 @@ #ifndef wasm_ir_eh_h #define wasm_ir_eh_h -namespace wasm { +#include "wasm.h" -class Expression; +namespace wasm { namespace EHUtils { @@ -31,6 +31,12 @@ namespace EHUtils { // catch body, which is invalid. That will be taken care of in validation. bool isPopValid(Expression* catchBody); +// Fixes up 'pop's nested in blocks, which are currently not supported without +// block param types, by creating a new local, putting a (local.set $new (pop +// type)) right after 'catch', and putting a '(local.get $new)' where the 'pop' +// used to be. +void handleBlockNestedPops(Function* func, Module& wasm); + } // namespace EHUtils } // namespace wasm diff --git a/src/pass.h b/src/pass.h index cc63971fe..bc9b3edde 100644 --- a/src/pass.h +++ b/src/pass.h @@ -39,9 +39,14 @@ struct PassRegistry { typedef std::function<Pass*()> Creator; void registerPass(const char* name, const char* description, Creator create); + // Register a pass that's used for internal testing. These passes do not show + // up in --help. + void + registerTestPass(const char* name, const char* description, Creator create); std::unique_ptr<Pass> createPass(std::string name); std::vector<std::string> getRegisteredNames(); std::string getPassDescription(std::string name); + bool isPassHidden(std::string name); private: void registerPasses(); @@ -49,9 +54,10 @@ private: struct PassInfo { std::string description; Creator create; + bool hidden; PassInfo() = default; - PassInfo(std::string description, Creator create) - : description(description), create(create) {} + PassInfo(std::string description, Creator create, bool hidden = false) + : description(description), create(create), hidden(hidden) {} }; std::map<std::string, PassInfo> passInfos; }; diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index c3f42d41f..bc09736a4 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -9,6 +9,7 @@ add_custom_command( FILE(GLOB passes_HEADERS *.h) set(passes_SOURCES pass.cpp + test_passes.cpp AlignmentLowering.cpp Asyncify.cpp AvoidReinterprets.cpp diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 92206deff..198cb9ab5 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -47,6 +47,13 @@ void PassRegistry::registerPass(const char* name, passInfos[name] = PassInfo(description, create); } +void PassRegistry::registerTestPass(const char* name, + const char* description, + Creator create) { + assert(passInfos.find(name) == passInfos.end()); + passInfos[name] = PassInfo(description, create, true); +} + std::unique_ptr<Pass> PassRegistry::createPass(std::string name) { if (passInfos.find(name) == passInfos.end()) { Fatal() << "Could not find pass: " << name << "\n"; @@ -70,6 +77,11 @@ std::string PassRegistry::getPassDescription(std::string name) { return passInfos[name].description; } +bool PassRegistry::isPassHidden(std::string name) { + assert(passInfos.find(name) != passInfos.end()); + return passInfos[name].hidden; +} + // PassRunner void PassRegistry::registerPasses() { @@ -410,6 +422,11 @@ void PassRegistry::registerPasses() { registerPass("vacuum", "removes obviously unneeded code", createVacuumPass); // registerPass( // "lower-i64", "lowers i64 into pairs of i32s", createLowerInt64Pass); + + // Register passes used for internal testing. These don't show up in --help. + registerTestPass("catch-pop-fixup", + "fixup nested pops within catches", + createCatchPopFixupPass); } void PassRunner::addIfNoDWARFIssues(std::string passName) { diff --git a/src/passes/passes.h b/src/passes/passes.h index 8a2c4cacc..0e72bb518 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -21,7 +21,7 @@ namespace wasm { class Pass; -// All passes: +// Normal passes: Pass* createAlignmentLoweringPass(); Pass* createAsyncifyPass(); Pass* createAvoidReinterpretsPass(); @@ -136,6 +136,9 @@ Pass* createTypeRefiningPass(); Pass* createUnteePass(); Pass* createVacuumPass(); +// Test passes: +Pass* createCatchPopFixupPass(); + } // namespace wasm #endif diff --git a/src/passes/test_passes.cpp b/src/passes/test_passes.cpp new file mode 100644 index 000000000..4ea5b8696 --- /dev/null +++ b/src/passes/test_passes.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2021 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. + */ + +// +// This file contains passes used for internal testing. This file can be used to +// test utility functions separately. +// + +#include "ir/eh-utils.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" + +namespace wasm { + +namespace { + +// Pass to test EHUtil::handleBlockNestedPops function +struct CatchPopFixup : public WalkerPass<PostWalker<CatchPopFixup>> { + bool isFunctionParallel() override { return true; } + Pass* create() override { return new CatchPopFixup; } + + void doWalkFunction(Function* func) { + EHUtils::handleBlockNestedPops(func, *getModule()); + } + void run(PassRunner* runner, Module* module) override {} +}; + +} // anonymous namespace + +Pass* createCatchPopFixupPass() { return new CatchPopFixup(); } + +} // namespace wasm diff --git a/src/support/command-line.cpp b/src/support/command-line.cpp index d9b7170f5..c84474259 100644 --- a/src/support/command-line.cpp +++ b/src/support/command-line.cpp @@ -75,10 +75,16 @@ Options::Options(const std::string& command, const std::string& description) std::cout << "\n\nOptions:\n"; size_t optionWidth = 0; for (const auto& o : options) { + if (o.hidden) { + continue; + } optionWidth = std::max(optionWidth, o.longName.size() + o.shortName.size()); } for (const auto& o : options) { + if (o.hidden) { + continue; + } std::cout << '\n'; bool long_n_short = o.longName.size() != 0 && o.shortName.size() != 0; size_t pad = 1 + optionWidth - o.longName.size() - o.shortName.size(); @@ -106,8 +112,10 @@ Options& Options::add(const std::string& longName, const std::string& shortName, const std::string& description, Arguments arguments, - const Action& action) { - options.push_back({longName, shortName, description, arguments, action, 0}); + const Action& action, + bool hidden) { + options.push_back( + {longName, shortName, description, arguments, action, hidden, 0}); return *this; } diff --git a/src/support/command-line.h b/src/support/command-line.h index 99d98eec9..19b2546d9 100644 --- a/src/support/command-line.h +++ b/src/support/command-line.h @@ -57,7 +57,8 @@ public: const std::string& shortName, const std::string& description, Arguments arguments, - const Action& action); + const Action& action, + bool hidden = false); Options& add_positional(const std::string& name, Arguments arguments, const Action& action); @@ -70,6 +71,7 @@ private: std::string description; Arguments arguments; Action action; + bool hidden; size_t seen; }; std::vector<Option> options; diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h index 8d526f92f..f1872bf3d 100644 --- a/src/tools/optimization-options.h +++ b/src/tools/optimization-options.h @@ -220,28 +220,29 @@ struct OptimizationOptions : public ToolOptions { }); // add passes in registry for (const auto& p : PassRegistry::get()->getRegisteredNames()) { - (*this).add(std::string("--") + p, - "", - PassRegistry::get()->getPassDescription(p), - // Allow an optional parameter to a pass. If provided, it is - // the same as if using --pass-arg, that is, - // - // --foo=ARG - // - // is the same as - // - // --foo --pass-arg=foo@ARG - Options::Arguments::Optional, - [this, p](Options*, const std::string& arg) { - if (!arg.empty()) { - if (passOptions.arguments.count(p)) { - Fatal() - << "Cannot pass multiple pass arguments to " << p; - } - passOptions.arguments[p] = arg; - } - passes.push_back(p); - }); + (*this).add( + std::string("--") + p, + "", + PassRegistry::get()->getPassDescription(p), + // Allow an optional parameter to a pass. If provided, it is + // the same as if using --pass-arg, that is, + // + // --foo=ARG + // + // is the same as + // + // --foo --pass-arg=foo@ARG + Options::Arguments::Optional, + [this, p](Options*, const std::string& arg) { + if (!arg.empty()) { + if (passOptions.arguments.count(p)) { + Fatal() << "Cannot pass multiple pass arguments to " << p; + } + passOptions.arguments[p] = arg; + } + passes.push_back(p); + }, + PassRegistry::get()->isPassHidden(p)); } } diff --git a/test/lit/passes/catch-pop-fixup.wast b/test/lit/passes/catch-pop-fixup.wast new file mode 100644 index 000000000..6c7d71860 --- /dev/null +++ b/test/lit/passes/catch-pop-fixup.wast @@ -0,0 +1,396 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; We run wasm-opt with --no-validation because functions in this file contain +;; 'pop's in invalid positions and the objective of this test is to fix them. +;; But wasm-opt runs validation after reading functions, so we need to disable +;; it to proceed. +;; RUN: wasm-opt %s --catch-pop-fixup --no-validation -all -S -o - | filecheck %s + +(module + ;; CHECK: (type $struct.A (struct (field i32))) + + ;; CHECK: (tag $e-i32 (param i32)) + (tag $e-i32 (param i32)) + ;; CHECK: (tag $e-i32-f32 (param i32 f32)) + (tag $e-i32-f32 (param i32 f32)) + + (type $struct.A (struct i32)) + ;; CHECK: (tag $e-struct.A (param (ref $struct.A))) + (tag $e-struct.A (param (ref $struct.A))) + + ;; CHECK: (func $pop-within-block1 + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (throw $e-i32 + ;; CHECK-NEXT: (block $block (result i32) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-block1 + (try + (do) + (catch $e-i32 + (throw $e-i32 + ;; The pop is within a block, so it will be handled + (block (result i32) + (pop i32) + ) + ) + ) + ) + ) + + ;; CHECK: (func $pop-within-block2 + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (throw $e-i32 + ;; CHECK-NEXT: (block $block (result i32) + ;; CHECK-NEXT: (block $block0 (result i32) + ;; CHECK-NEXT: (block $block1 (result i32) + ;; CHECK-NEXT: (block $block2 (result i32) + ;; CHECK-NEXT: (block $block3 (result i32) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-block2 + (try + (do) + (catch $e-i32 + (throw $e-i32 + ;; More nesting of blocks can be handled too + (block (result i32) + (block (result i32) + (block (result i32) + (block (result i32) + (block (result i32) + (pop i32) + ) + ) + ) + ) + ) + ) + ) + ) + ) + + ;; CHECK: (func $pop-within-block3 (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try $try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $l0 (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $l0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-block3 (result i32) + (try (result i32) + (do + (i32.const 0) + ) + (catch $e-i32 + ;; This block cannot be deleted when written back because there is a + ;; branch targeting this block. So the pop inside will be handled. + (block $l0 (result i32) + (drop + (pop i32) + ) + (br $l0 + (i32.const 0) + ) + ) + ) + ) + ) + + ;; CHECK: (func $helper + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $helper) + ;; CHECK: (func $pop-within-implicit-block1 + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-implicit-block1 + (try + (do) + (catch $e-i32 + ;; Because this catch contains multiple instructions, an implicit + ;; block will be created within the catch when parsed. But that block + ;; will be deleted when written back, so this pop is not considered + ;; nested in a block. + (drop + (pop i32) + ) + (call $helper) + ) + ) + ) + + ;; CHECK: (func $pop-within-implicit-block2 + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-implicit-block2 + (try + (do) + (catch $e-i32 + ;; In this case we explicitly wrap the pop with a 'block', but this + ;; block doesn't have any targeting branches, it will be also deleted + ;; when written back to binary. So this pop is fine and not considered + ;; nested in a block. + (block + (drop + (pop i32) + ) + (call $helper) + ) + ) + ) + ) + + ;; CHECK: (func $pop-within-try (result i32) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try $try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $try4 (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-try (result i32) + (try (result i32) + (do + (i32.const 0) + ) + (catch $e-i32 + ;; The pop is wihtin a try, so it will be handled + (try (result i32) + (do + (pop i32) + ) + (catch_all + (i32.const 0) + ) + ) + ) + ) + ) + + ;; CHECK: (func $pop-within-if-condition (result i32) + ;; CHECK-NEXT: (try $try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-if-condition (result i32) + (try (result i32) + (do + (i32.const 0) + ) + (catch $e-i32 + ;; The pop is wihtin an if condition, which is considered not nested. + ;; This will be not handled. + (if (result i32) + (pop i32) + (then (i32.const 1)) + (else (i32.const 0)) + ) + ) + ) + ) + + ;; CHECK: (func $pop-within-block-within-if-condition + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $l0 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $l0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-within-block-within-if-condition + (try + (do) + (catch $e-i32 + ;; This block cannot be removed because there is a branch targeting + ;; this. This pop should be handled because the whole 'if' is nested + ;; within the block. + (block $l0 + (drop + (if (result i32) + (pop i32) + (then (i32.const 1)) + (else (i32.const 0)) + ) + ) + (br $l0) + ) + ) + ) + ) + + ;; CHECK: (func $pop-tuple-within-block + ;; CHECK-NEXT: (local $x (i32 f32)) + ;; CHECK-NEXT: (local $1 (i32 f32)) + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32-f32 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (pop i32 f32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (throw $e-i32 + ;; CHECK-NEXT: (block $block (result i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-tuple-within-block (local $x (i32 f32)) + (try + (do) + (catch $e-i32-f32 + (throw $e-i32 + ;; This tests a pop taking a tuple type. + (block (result i32) + (local.set $x (pop i32 f32)) + (i32.const 0) + ) + ) + ) + ) + ) + + ;; CHECK: (func $pop-non-defaultable-type-within-block + ;; CHECK-NEXT: (local $0 (ref null $struct.A)) + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-struct.A + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop (ref $struct.A)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (throw $e-struct.A + ;; CHECK-NEXT: (block $block (result (ref $struct.A)) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-non-defaultable-type-within-block + (try + (do) + (catch $e-struct.A + (throw $e-struct.A + ;; The pop is within a block, so it will be handled. But because this + ;; pop is of non-defaultable type, we have to fix it up using + ;; TypeUpdating::handleNonDefaultableLocals: the new local created is + ;; converted to (ref null $struct.A) type and we read the local using + ;; 'ref.as_non_null'. + (block (result (ref $struct.A)) + (pop (ref $struct.A)) + ) + ) + ) + ) + ) +) |