summaryrefslogtreecommitdiff
path: root/src/ir/branch-utils.h
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2020-09-03 16:11:05 -0700
committerGitHub <noreply@github.com>2020-09-03 16:11:05 -0700
commit44df23efd69fd2dd4c260755c82ddede226c40ff (patch)
treed828947439ac4dd47fc023b176b86d4949890b81 /src/ir/branch-utils.h
parent132c72bb5e93591de34a9bfc267e4a2007908626 (diff)
downloadbinaryen-44df23efd69fd2dd4c260755c82ddede226c40ff.tar.gz
binaryen-44df23efd69fd2dd4c260755c82ddede226c40ff.tar.bz2
binaryen-44df23efd69fd2dd4c260755c82ddede226c40ff.zip
Optimize MergeBlocks by caching branch results (#3102)
BranchSeekerCache caches the set of branches in a node + its children, and helps compute new results by looking in the cache and using data for the children. This avoids quadratic time in the common case of a post-walk on a tower of nested blocks which is common in a switch. Fixes #3090 . On the testcase there this pass goes from over a minute to less than a second.
Diffstat (limited to 'src/ir/branch-utils.h')
-rw-r--r--src/ir/branch-utils.h102
1 files changed, 94 insertions, 8 deletions
diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h
index 363a9c9e2..9b439be89 100644
--- a/src/ir/branch-utils.h
+++ b/src/ir/branch-utils.h
@@ -17,6 +17,7 @@
#ifndef wasm_ir_branch_h
#define wasm_ir_branch_h
+#include "ir/iteration.h"
#include "wasm-traversal.h"
#include "wasm.h"
@@ -52,10 +53,12 @@ inline bool isBranchReachable(Expression* expr) {
WASM_UNREACHABLE("unexpected expression type");
}
-inline std::set<Name> getUniqueTargets(Break* br) { return {br->name}; }
+using NameSet = std::set<Name>;
-inline std::set<Name> getUniqueTargets(Switch* sw) {
- std::set<Name> ret;
+inline NameSet getUniqueTargets(Break* br) { return {br->name}; }
+
+inline NameSet getUniqueTargets(Switch* sw) {
+ NameSet ret;
for (auto target : sw->targets) {
ret.insert(target);
}
@@ -63,8 +66,20 @@ inline std::set<Name> getUniqueTargets(Switch* sw) {
return ret;
}
-inline std::set<Name> getUniqueTargets(BrOnExn* br) { return {br->name}; }
+inline NameSet getUniqueTargets(BrOnExn* br) { return {br->name}; }
+inline NameSet getUniqueTargets(Expression* expr) {
+ if (auto* br = expr->dynCast<Break>()) {
+ return getUniqueTargets(br);
+ }
+ if (auto* br = expr->dynCast<Switch>()) {
+ return getUniqueTargets(br);
+ }
+ if (auto* br = expr->dynCast<BrOnExn>()) {
+ return getUniqueTargets(br);
+ }
+ return {};
+}
// If we branch to 'from', change that to 'to' instead.
inline bool replacePossibleTarget(Expression* branch, Name from, Name to) {
bool worked = false;
@@ -97,9 +112,9 @@ inline bool replacePossibleTarget(Expression* branch, Name from, Name to) {
// returns the set of targets to which we branch that are
// outside of a node
-inline std::set<Name> getExitingBranches(Expression* ast) {
+inline NameSet getExitingBranches(Expression* ast) {
struct Scanner : public PostWalker<Scanner> {
- std::set<Name> targets;
+ NameSet targets;
void visitBreak(Break* curr) { targets.insert(curr->name); }
void visitSwitch(Switch* curr) {
@@ -128,9 +143,9 @@ inline std::set<Name> getExitingBranches(Expression* ast) {
// returns the list of all branch targets in a node
-inline std::set<Name> getBranchTargets(Expression* ast) {
+inline NameSet getBranchTargets(Expression* ast) {
struct Scanner : public PostWalker<Scanner> {
- std::set<Name> targets;
+ NameSet targets;
void visitBlock(Block* curr) {
if (curr->name.is()) {
@@ -218,6 +233,77 @@ struct BranchSeeker : public PostWalker<BranchSeeker> {
}
};
+// Accumulates all the branches in an entire tree.
+struct BranchAccumulator
+ : public PostWalker<BranchAccumulator,
+ UnifiedExpressionVisitor<BranchAccumulator>> {
+ NameSet branches;
+
+ void visitExpression(Expression* curr) {
+ auto selfBranches = getUniqueTargets(curr);
+ branches.insert(selfBranches.begin(), selfBranches.end());
+ }
+};
+
+// A helper structure for the common case of post-walking some IR while querying
+// whether a branch is present. We can cache results for children in order to
+// avoid quadratic time searches.
+// We assume that a node will be scanned *once* here. That means that if we
+// scan a node, we can discard all information for its children. This avoids
+// linearly increasing memory usage over time.
+class BranchSeekerCache {
+ // Maps all the branches present in an expression and all its nested children.
+ std::unordered_map<Expression*, NameSet> branches;
+
+public:
+ const NameSet& getBranches(Expression* curr) {
+ auto iter = branches.find(curr);
+ if (iter != branches.end()) {
+ return iter->second;
+ }
+ NameSet currBranches;
+ auto add = [&](NameSet& moreBranches) {
+ // Make sure to do a fast swap for the first set of branches to arrive.
+ // This helps the case of the first child being a block with a very large
+ // set of names.
+ if (currBranches.empty()) {
+ currBranches.swap(moreBranches);
+ } else {
+ currBranches.insert(moreBranches.begin(), moreBranches.end());
+ }
+ };
+ // Add from the children, which are hopefully cached.
+ for (auto child : ChildIterator(curr)) {
+ auto iter = branches.find(child);
+ if (iter != branches.end()) {
+ add(iter->second);
+ // We are scanning the parent, which means we assume the child will
+ // never be visited again.
+ branches.erase(iter);
+ } else {
+ // The child was not cached. Scan it manually.
+ BranchAccumulator childBranches;
+ childBranches.walk(child);
+ add(childBranches.branches);
+ // Don't bother caching anything - we are scanning the parent, so the
+ // child will presumably not be scanned again.
+ }
+ }
+ // Finish with the parent's own branches.
+ auto selfBranches = getUniqueTargets(curr);
+ add(selfBranches);
+ return branches[curr] = std::move(currBranches);
+ }
+
+ bool hasBranch(Expression* curr, Name target) {
+ bool result = getBranches(curr).count(target);
+#ifdef BRANCH_UTILS_DEBUG
+ assert(bresult == BranchSeeker::has(curr, target));
+#endif
+ return result;
+ }
+};
+
} // namespace BranchUtils
} // namespace wasm