summaryrefslogtreecommitdiff
path: root/src/passes/RemoveUnusedBrs.cpp
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2016-09-14 21:28:43 -0700
committerGitHub <noreply@github.com>2016-09-14 21:28:43 -0700
commite567fa8675831e79f855cea2181fa58beb107e42 (patch)
tree14f1e37d27244b349e8ee34939119002f742748d /src/passes/RemoveUnusedBrs.cpp
parent63b499e3ec9bbdf4e79ab6d9dc198299516e8aec (diff)
parentaf3bea2786fe62070522b7fd7add4290a4cb4e6d (diff)
downloadbinaryen-e567fa8675831e79f855cea2181fa58beb107e42.tar.gz
binaryen-e567fa8675831e79f855cea2181fa58beb107e42.tar.bz2
binaryen-e567fa8675831e79f855cea2181fa58beb107e42.zip
Merge pull request #695 from WebAssembly/opts
Get optimizer on par with emscripten asm.js optimizer
Diffstat (limited to 'src/passes/RemoveUnusedBrs.cpp')
-rw-r--r--src/passes/RemoveUnusedBrs.cpp283
1 files changed, 252 insertions, 31 deletions
diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp
index 59d3af6fc..86a46374f 100644
--- a/src/passes/RemoveUnusedBrs.cpp
+++ b/src/passes/RemoveUnusedBrs.cpp
@@ -21,9 +21,20 @@
#include <wasm.h>
#include <pass.h>
#include <ast_utils.h>
+#include <wasm-builder.h>
namespace wasm {
+// to turn an if into a br-if, we must be able to reorder the
+// condition and possible value, and the possible value must
+// not have side effects (as they would run unconditionally)
+static bool canTurnIfIntoBrIf(Expression* ifCondition, Expression* brValue) {
+ if (!brValue) return true;
+ EffectAnalyzer value(brValue);
+ if (value.hasSideEffects()) return false;
+ return !EffectAnalyzer(ifCondition).invalidates(value);
+}
+
struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<RemoveUnusedBrs>>> {
bool isFunctionParallel() override { return true; }
@@ -44,6 +55,9 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R
// a stack for if-else contents, we merge their outputs
std::vector<Flows> ifStack;
+ // list of all loops, so we can optimize them
+ std::vector<Loop*> loops;
+
static void visitAny(RemoveUnusedBrs* self, Expression** currp) {
auto* curr = *currp;
auto& flows = self->flows;
@@ -109,7 +123,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R
// ignore (could be result of a previous cycle)
self->valueCanFlow = false;
} else {
- // anything else stops the flow TODO: optimize loops?
+ // anything else stops the flow
flows.clear();
self->valueCanFlow = false;
}
@@ -123,23 +137,25 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R
self->ifStack.push_back(std::move(self->flows));
}
+ void visitLoop(Loop* curr) {
+ loops.push_back(curr);
+ }
+
void visitIf(If* curr) {
if (!curr->ifFalse) {
// if without an else. try to reduce if (condition) br => br_if (condition)
Break* br = curr->ifTrue->dynCast<Break>();
if (br && !br->condition) { // TODO: if there is a condition, join them
// if the br has a value, then if => br_if means we always execute the value, and also the order is value,condition vs condition,value
- if (br->value) {
- EffectAnalyzer value(br->value);
- if (value.hasSideEffects()) return;
- EffectAnalyzer condition(curr->condition);
- if (condition.invalidates(value)) return;
+ if (canTurnIfIntoBrIf(curr->condition, br->value)) {
+ br->condition = curr->condition;
+ br->finalize();
+ replaceCurrent(br);
+ anotherCycle = true;
}
- br->condition = curr->condition;
- replaceCurrent(br);
- anotherCycle = true;
}
}
+ // TODO: if-else can be turned into a br_if as well, if one of the sides is a dead end
}
// override scan to add a pre and a post check task to all nodes
@@ -163,6 +179,99 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R
}
}
+ // optimizes a loop. returns true if we made changes
+ bool optimizeLoop(Loop* loop) {
+ // if a loop ends in
+ // (loop $in
+ // (block $out
+ // if (..) br $in; else br $out;
+ // )
+ // )
+ // then our normal opts can remove the break out since it flows directly out
+ // (and later passes make the if one-armed). however, the simple analysis
+ // fails on patterns like
+ // if (..) br $out;
+ // br $in;
+ // which is a common way to do a while (1) loop (end it with a jump to the
+ // top), so we handle that here. Specifically we want to conditionalize
+ // breaks to the loop top, i.e., put them behind a condition, so that other
+ // code can flow directly out and thus brs out can be removed. (even if
+ // the change is to let a break somewhere else flow out, that can still be
+ // helpful, as it shortens the logical loop. it is also good to generate
+ // an if-else instead of an if, as it might allow an eqz to be removed
+ // by flipping arms)
+ if (!loop->name.is()) return false;
+ auto* block = loop->body->dynCast<Block>();
+ if (!block) return false;
+ // does the last element break to the top of the loop?
+ auto& list = block->list;
+ if (list.size() <= 1) return false;
+ auto* last = list.back()->dynCast<Break>();
+ if (!last || !ExpressionAnalyzer::isSimple(last) || last->name != loop->name) return false;
+ // last is a simple break to the top of the loop. if we can conditionalize it,
+ // it won't block things from flowing out and not needing breaks to do so.
+ Index i = list.size() - 2;
+ Builder builder(*getModule());
+ while (1) {
+ auto* curr = list[i];
+ if (auto* iff = curr->dynCast<If>()) {
+ // let's try to move the code going to the top of the loop into the if-else
+ if (!iff->ifFalse) {
+ // we need the ifTrue to break, so it cannot reach the code we want to move
+ if (ExpressionAnalyzer::getEndingSimpleBreak(iff->ifTrue)) {
+ iff->ifFalse = builder.stealSlice(block, i + 1, list.size());
+ return true;
+ }
+ } else {
+ // this is already an if-else. if one side is a dead end, we can append to the other, if
+ // there is no returned value to concern us
+ assert(!isConcreteWasmType(iff->type)); // can't be, since in the middle of a block
+ if (ExpressionAnalyzer::getEndingSimpleBreak(iff->ifTrue)) {
+ iff->ifFalse = builder.blockifyMerge(iff->ifFalse, builder.stealSlice(block, i + 1, list.size()));
+ return true;
+ } else if (ExpressionAnalyzer::getEndingSimpleBreak(iff->ifFalse)) {
+ iff->ifTrue = builder.blockifyMerge(iff->ifTrue, builder.stealSlice(block, i + 1, list.size()));
+ return true;
+ }
+ }
+ return false;
+ } else if (auto* brIf = curr->dynCast<Break>()) {
+ // br_if is similar to if.
+ if (brIf->condition && !brIf->value && brIf->name != loop->name) {
+ if (i == list.size() - 2) {
+ // there is the br_if, and then the br to the top, so just flip them and the condition
+ brIf->condition = builder.makeUnary(EqZInt32, brIf->condition);
+ last->name = brIf->name;
+ brIf->name = loop->name;
+ return true;
+ } else {
+ // there are elements in the middle,
+ // br_if $somewhere (condition)
+ // (..more..)
+ // br $in
+ // we can convert the br_if to an if. this has a cost, though,
+ // so only do it if it looks useful, which it definitely is if
+ // (a) $somewhere is straight out (so the br out vanishes), and
+ // (b) this br_if is the only branch to that block (so the block will vanish)
+ if (brIf->name == block->name && BreakSeeker::count(block, block->name) == 1) {
+ // note that we could drop the last element here, it is a br we know for sure is removable,
+ // but telling stealSlice to steal all to the end is more efficient, it can just truncate.
+ list[i] = builder.makeIf(brIf->condition, builder.makeBreak(brIf->name), builder.stealSlice(block, i + 1, list.size()));
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ // if there is control flow, we must stop looking
+ if (EffectAnalyzer(curr).branches) {
+ return false;
+ }
+ if (i == 0) return false;
+ i--;
+ }
+ }
+
void doWalkFunction(Function* func) {
// multiple cycles may be needed
bool worked = false;
@@ -172,7 +281,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R
assert(ifStack.empty());
// flows may contain returns, which are flowing out and so can be optimized
for (size_t i = 0; i < flows.size(); i++) {
- auto* flow = (*flows[i])->cast<Return>(); // cannot be a break
+ auto* flow = (*flows[i])->dynCast<Return>();
+ if (!flow) continue;
if (!flow->value) {
// return => nop
ExpressionManipulator::nop(flow);
@@ -184,11 +294,138 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R
}
}
flows.clear();
+ // optimize loops (we don't do it while tracking flows, as they can interfere)
+ for (auto* loop : loops) {
+ anotherCycle |= optimizeLoop(loop);
+ }
+ loops.clear();
if (anotherCycle) worked = true;
} while (anotherCycle);
- // finally, we may have simplified ifs enough to turn them into selects
- struct Selectifier : public WalkerPass<PostWalker<Selectifier, Visitor<Selectifier>>> {
+
+ if (worked) {
+ // Our work may alter block and if types, they may now return
+ struct TypeUpdater : public WalkerPass<PostWalker<TypeUpdater, Visitor<TypeUpdater>>> {
+ void visitBlock(Block* curr) {
+ curr->finalize();
+ }
+ void visitLoop(Loop* curr) {
+ curr->finalize();
+ }
+ void visitIf(If* curr) {
+ curr->finalize();
+ }
+ };
+ TypeUpdater typeUpdater;
+ typeUpdater.walkFunction(func);
+ }
+
+ // thread trivial jumps
+ struct JumpThreader : public ControlFlowWalker<JumpThreader, Visitor<JumpThreader>> {
+ // map of all value-less breaks going to a block (and not a loop)
+ std::map<Block*, std::vector<Break*>> breaksToBlock;
+
+ // number of definitions of each name - when a name is defined more than once, it is not trivially safe to do this
+ std::map<Name, Index> numDefs;
+
+ // the names to update, when we can (just one def)
+ std::map<Break*, Name> newNames;
+
+ void visitBreak(Break* curr) {
+ if (!curr->value) {
+ if (auto* target = findBreakTarget(curr->name)->dynCast<Block>()) {
+ breaksToBlock[target].push_back(curr);
+ }
+ }
+ }
+ // TODO: Switch?
+ void visitBlock(Block* curr) {
+ if (curr->name.is()) numDefs[curr->name]++;
+
+ auto& list = curr->list;
+ if (list.size() == 1 && curr->name.is()) {
+ // if this block has just one child, a sub-block, then jumps to the former are jumps to us, really
+ if (auto* child = list[0]->dynCast<Block>()) {
+ if (child->name.is() && child->name != curr->name) {
+ auto& breaks = breaksToBlock[child];
+ for (auto* br : breaks) {
+ newNames[br] = curr->name;
+ breaksToBlock[curr].push_back(br); // update the list - we may push it even more later
+ }
+ breaksToBlock.erase(child);
+ }
+ }
+ } else if (list.size() == 2) {
+ // if this block has two children, a child-block and a simple jump, then jumps to child-block can be replaced with jumps to the new target
+ auto* child = list[0]->dynCast<Block>();
+ auto* jump = list[1]->dynCast<Break>();
+ if (child && child->name.is() && jump && ExpressionAnalyzer::isSimple(jump)) {
+ auto& breaks = breaksToBlock[child];
+ for (auto* br : breaks) {
+ newNames[br] = jump->name;
+ }
+ // if the jump is to another block then we can update the list, and maybe push it even more later
+ if (auto* newTarget = findBreakTarget(jump->name)->dynCast<Block>()) {
+ for (auto* br : breaks) {
+ breaksToBlock[newTarget].push_back(br);
+ }
+ }
+ breaksToBlock.erase(child);
+ }
+ }
+ }
+ void visitLoop(Loop* curr) {
+ if (curr->name.is()) numDefs[curr->name]++;
+ }
+
+ void finish() {
+ for (auto& iter : newNames) {
+ auto* br = iter.first;
+ auto name = iter.second;
+ if (numDefs[name] == 1) {
+ br->name = name;
+ }
+ }
+ }
+ };
+ JumpThreader jumpThreader;
+ jumpThreader.setModule(getModule());
+ jumpThreader.walkFunction(func);
+ jumpThreader.finish();
+
+ // perform some final optimizations
+ struct FinalOptimizer : public PostWalker<FinalOptimizer, Visitor<FinalOptimizer>> {
+ void visitBlock(Block* curr) {
+ // if a block has an if br else br, we can un-conditionalize the latter, allowing
+ // the if to become a br_if.
+ // * note that if not in a block already, then we need to create a block for this, so not useful otherwise
+ // * note that this only happens at the end of a block, as code after the if is dead
+ // * note that we do this at the end, because un-conditionalizing can interfere with optimizeLoop()ing.
+ auto& list = curr->list;
+ for (Index i = 0; i < list.size(); i++) {
+ auto* iff = list[i]->dynCast<If>();
+ if (!iff || !iff->ifFalse || isConcreteWasmType(iff->type)) continue; // if it lacked an if-false, it would already be a br_if, as that's the easy case
+ auto* ifTrueBreak = iff->ifTrue->dynCast<Break>();
+ if (ifTrueBreak && !ifTrueBreak->condition && canTurnIfIntoBrIf(iff->condition, ifTrueBreak->value)) {
+ // we are an if-else where the ifTrue is a break without a condition, so we can do this
+ list[i] = ifTrueBreak;
+ ifTrueBreak->condition = iff->condition;
+ ifTrueBreak->finalize();
+ ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifFalse);
+ continue;
+ }
+ // otherwise, perhaps we can flip the if
+ auto* ifFalseBreak = iff->ifFalse->dynCast<Break>();
+ if (ifFalseBreak && !ifFalseBreak->condition && canTurnIfIntoBrIf(iff->condition, ifFalseBreak->value)) {
+ list[i] = ifFalseBreak;
+ ifFalseBreak->condition = Builder(*getModule()).makeUnary(EqZInt32, iff->condition);
+ ifFalseBreak->finalize();
+ ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifTrue);
+ continue;
+ }
+ }
+ }
void visitIf(If* curr) {
+ // we may have simplified ifs enough to turn them into selects
if (curr->ifFalse && isConcreteWasmType(curr->ifTrue->type) && isConcreteWasmType(curr->ifFalse->type)) {
// if with else, consider turning it into a select if there is no control flow
// TODO: estimate cost
@@ -210,25 +447,9 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R
}
}
};
- Selectifier selectifier;
- selectifier.setModule(getModule());
- selectifier.walkFunction(func);
- if (worked) {
- // Our work may alter block and if types, they may now return
- struct TypeUpdater : public WalkerPass<PostWalker<TypeUpdater, Visitor<TypeUpdater>>> {
- void visitBlock(Block* curr) {
- curr->finalize();
- }
- void visitLoop(Loop* curr) {
- curr->finalize();
- }
- void visitIf(If* curr) {
- curr->finalize();
- }
- };
- TypeUpdater typeUpdater;
- typeUpdater.walkFunction(func);
- }
+ FinalOptimizer finalOptimizer;
+ finalOptimizer.setModule(getModule());
+ finalOptimizer.walkFunction(func);
}
};