diff options
Diffstat (limited to 'src')
-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 |
10 files changed, 246 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)); } } |