summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ir/eh-utils.cpp162
-rw-r--r--src/ir/eh-utils.h10
-rw-r--r--src/pass.h10
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/pass.cpp17
-rw-r--r--src/passes/passes.h5
-rw-r--r--src/passes/test_passes.cpp46
-rw-r--r--src/support/command-line.cpp12
-rw-r--r--src/support/command-line.h4
-rw-r--r--src/tools/optimization-options.h45
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));
}
}