diff options
Diffstat (limited to 'src/passes/RemoveUnusedBrs.cpp')
-rw-r--r-- | src/passes/RemoveUnusedBrs.cpp | 581 |
1 files changed, 321 insertions, 260 deletions
diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 614503581..4b5c9613e 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -18,26 +18,31 @@ // Removes branches for which we go to where they go anyhow // -#include <wasm.h> -#include <pass.h> -#include <parsing.h> #include <ir/branch-utils.h> #include <ir/cost.h> #include <ir/effects.h> #include <ir/utils.h> +#include <parsing.h> +#include <pass.h> #include <wasm-builder.h> +#include <wasm.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, PassOptions& options) { +static bool canTurnIfIntoBrIf(Expression* ifCondition, + Expression* brValue, + PassOptions& options) { // if the if isn't even reached, this is all dead code anyhow - if (ifCondition->type == unreachable) return false; - if (!brValue) return true; + if (ifCondition->type == unreachable) + return false; + if (!brValue) + return true; EffectAnalyzer value(options, brValue); - if (value.hasSideEffects()) return false; + if (value.hasSideEffects()) + return false; return !EffectAnalyzer(options, ifCondition).invalidates(value); } @@ -50,8 +55,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { typedef std::vector<Expression**> Flows; - // list of breaks that are currently flowing. if they reach their target without - // interference, they can be removed (or their value forwarded TODO) + // list of breaks that are currently flowing. if they reach their target + // without interference, they can be removed (or their value forwarded TODO) Flows flows; // a stack for if-else contents, we merge their outputs @@ -160,23 +165,22 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } } - void stopFlow() { - flows.clear(); - } + void stopFlow() { flows.clear(); } void removeValueFlow(Flows& currFlows) { - currFlows.erase(std::remove_if(currFlows.begin(), currFlows.end(), [&](Expression** currp) { - auto* curr = *currp; - if (auto* ret = curr->dynCast<Return>()) { - return ret->value; - } - return curr->cast<Break>()->value; - }), currFlows.end()); + currFlows.erase(std::remove_if(currFlows.begin(), + currFlows.end(), + [&](Expression** currp) { + auto* curr = *currp; + if (auto* ret = curr->dynCast<Return>()) { + return ret->value; + } + return curr->cast<Break>()->value; + }), + currFlows.end()); } - void stopValueFlow() { - removeValueFlow(flows); - } + void stopValueFlow() { removeValueFlow(flows); } static void clear(RemoveUnusedBrs* self, Expression** currp) { self->flows.clear(); @@ -186,20 +190,20 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { self->ifStack.push_back(std::move(self->flows)); } - void visitLoop(Loop* curr) { - loops.push_back(curr); - } + void visitLoop(Loop* curr) { loops.push_back(curr); } void optimizeSwitch(Switch* curr) { // if the final element is the default, we don't need it while (!curr->targets.empty() && curr->targets.back() == curr->default_) { curr->targets.pop_back(); } - // if the first element is the default, we can remove it by shifting everything (which - // does add a subtraction of a constant, but often that is worth it as the constant can - // be folded away and/or we remove multiple elements here) + // if the first element is the default, we can remove it by shifting + // everything (which does add a subtraction of a constant, but often that is + // worth it as the constant can be folded away and/or we remove multiple + // elements here) Index removable = 0; - while (removable < curr->targets.size() && curr->targets[removable] == curr->default_) { + while (removable < curr->targets.size() && + curr->targets[removable] == curr->default_) { removable++; } if (removable > 0) { @@ -208,50 +212,47 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } curr->targets.resize(curr->targets.size() - removable); Builder builder(*getModule()); - curr->condition = builder.makeBinary(SubInt32, - curr->condition, - builder.makeConst(Literal(int32_t(removable))) - ); + curr->condition = + builder.makeBinary(SubInt32, + curr->condition, + builder.makeConst(Literal(int32_t(removable)))); } - // when there isn't a value, we can do some trivial optimizations without worrying about - // the value being executed before the condition - if (curr->value) return; + // when there isn't a value, we can do some trivial optimizations without + // worrying about the value being executed before the condition + if (curr->value) + return; if (curr->targets.size() == 0) { // a switch with just a default always goes there Builder builder(*getModule()); - replaceCurrent(builder.makeSequence( - builder.makeDrop(curr->condition), - builder.makeBreak(curr->default_) - )); + replaceCurrent(builder.makeSequence(builder.makeDrop(curr->condition), + builder.makeBreak(curr->default_))); } else if (curr->targets.size() == 1) { // a switch with two options is basically an if Builder builder(*getModule()); - replaceCurrent(builder.makeIf( - curr->condition, - builder.makeBreak(curr->default_), - builder.makeBreak(curr->targets.front()) - )); + replaceCurrent(builder.makeIf(curr->condition, + builder.makeBreak(curr->default_), + builder.makeBreak(curr->targets.front()))); } else { - // there are also some other cases where we want to convert a switch into ifs, - // especially if the switch is large and we are focusing on size. - // an especially egregious case is a switch like this: - // [a b b [..] b b c] with default b - // (which may be arrived at after we trim away excess default values on both - // sides). in this case, we really have 3 values in a simple form, so it is the - // next logical case after handling 1 and 2 values right above here. - // to optimize this, we must add a local + a bunch of nodes (if*2, tee, eq, - // get, const, break*3), so the table must be big enough for it to make sense + // there are also some other cases where we want to convert a switch into + // ifs, especially if the switch is large and we are focusing on size. an + // especially egregious case is a switch like this: [a b b [..] b b c] + // with default b (which may be arrived at after we trim away excess + // default values on both sides). in this case, we really have 3 values in + // a simple form, so it is the next logical case after handling 1 and 2 + // values right above here. to optimize this, we must add a local + a + // bunch of nodes (if*2, tee, eq, get, const, break*3), so the table must + // be big enough for it to make sense - // How many targets we need when shrinking. This is literally the size at which - // the transformation begins to be smaller. + // How many targets we need when shrinking. This is literally the size at + // which the transformation begins to be smaller. const uint32_t MIN_SHRINK = 13; - // How many targets we need when not shrinking, in which case, 2 ifs may be slower, - // so we do this when the table is ridiculously large for one with just 3 values - // in it. + // How many targets we need when not shrinking, in which case, 2 ifs may + // be slower, so we do this when the table is ridiculously large for one + // with just 3 values in it. const uint32_t MIN_GENERAL = 128; auto shrink = getPassRunner()->options.shrinkLevel > 0; - if ((curr->targets.size() >= MIN_SHRINK && shrink) || + if ((curr->targets.size() >= MIN_SHRINK && shrink) || (curr->targets.size() >= MIN_GENERAL && !shrink)) { for (Index i = 1; i < curr->targets.size() - 1; i++) { if (curr->targets[i] != curr->default_) { @@ -262,25 +263,24 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { Builder builder(*getModule()); auto temp = builder.addVar(getFunction(), i32); Expression* z; - replaceCurrent(z = builder.makeIf( - builder.makeTeeLocal(temp, curr->condition), - builder.makeIf( - builder.makeBinary(EqInt32, - builder.makeGetLocal(temp, i32), - builder.makeConst(Literal(int32_t(curr->targets.size() - 1))) - ), - builder.makeBreak(curr->targets.back()), - builder.makeBreak(curr->default_) - ), - builder.makeBreak(curr->targets.front()) - )); + replaceCurrent( + z = builder.makeIf( + builder.makeTeeLocal(temp, curr->condition), + builder.makeIf(builder.makeBinary(EqInt32, + builder.makeGetLocal(temp, i32), + builder.makeConst(Literal(int32_t( + curr->targets.size() - 1)))), + builder.makeBreak(curr->targets.back()), + builder.makeBreak(curr->default_)), + builder.makeBreak(curr->targets.front()))); } } } void visitIf(If* curr) { if (!curr->ifFalse) { - // if without an else. try to reduce if (condition) br => br_if (condition) + // 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 (canTurnIfIntoBrIf(curr->condition, br->value, getPassOptions())) { @@ -291,9 +291,9 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } } } - // TODO: if-else can be turned into a br_if as well, if one of the sides is a dead end - // we handle the case of a returned value to a local.set later down, see - // visitSetLocal. + // TODO: if-else can be turned into a br_if as well, if one of the sides is + // a dead end we handle the case of a returned value to a local.set + // later down, see visitSetLocal. } // override scan to add a pre and a post check task to all nodes @@ -309,9 +309,11 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } self->pushTask(doVisitIf, currp); if (iff->ifFalse) { - // we need to join up if-else control flow, and clear after the condition + // we need to join up if-else control flow, and clear after the + // condition self->pushTask(scan, &iff->ifFalse); - self->pushTask(saveIfTrue, currp); // safe the ifTrue flow, we'll join it later + // safe the ifTrue flow, we'll join it later + self->pushTask(saveIfTrue, currp); } self->pushTask(scan, &iff->ifTrue); self->pushTask(clear, currp); // clear all flow after the condition @@ -342,24 +344,32 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // 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; + if (!loop->name.is()) + return false; auto* block = loop->body->dynCast<Block>(); - if (!block) return false; + 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; + 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. + 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 + // 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 + // we need the ifTrue to break, so it cannot reach the code we want to + // move if (iff->ifTrue->type == unreachable) { iff->ifFalse = builder.stealSlice(block, i + 1, list.size()); iff->finalize(); @@ -367,20 +377,25 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { 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(!isConcreteType(iff->type)); // can't be, since in the middle of a block + // 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 - // ensures the first node is a block, if it isn't already, and merges in the second, - // either as a single element or, if a block, by appending to the first block. this - // keeps the order of operations in place, that is, the appended element will be - // executed after the first node's elements - auto blockifyMerge = [&](Expression* any, Expression* append) -> Block* { + // can't be, since in the middle of a block + assert(!isConcreteType(iff->type)); + + // ensures the first node is a block, if it isn't already, and merges + // in the second, either as a single element or, if a block, by + // appending to the first block. this keeps the order of operations in + // place, that is, the appended element will be executed after the + // first node's elements + auto blockifyMerge = [&](Expression* any, + Expression* append) -> Block* { Block* block = nullptr; - if (any) block = any->dynCast<Block>(); - // if the first isn't a block, or it's a block with a name (so we might - // branch to the end, and so can't append to it, we might skip that code!) - // then make a new block + if (any) + block = any->dynCast<Block>(); + // if the first isn't a block, or it's a block with a name (so we + // might branch to the end, and so can't append to it, we might skip + // that code!) then make a new block if (!block || block->name.is()) { block = builder.makeBlock(any); } else { @@ -399,12 +414,14 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { }; if (iff->ifTrue->type == unreachable) { - iff->ifFalse = blockifyMerge(iff->ifFalse, builder.stealSlice(block, i + 1, list.size())); + iff->ifFalse = blockifyMerge( + iff->ifFalse, builder.stealSlice(block, i + 1, list.size())); iff->finalize(); block->finalize(); return true; } else if (iff->ifFalse->type == unreachable) { - iff->ifTrue = blockifyMerge(iff->ifTrue, builder.stealSlice(block, i + 1, list.size())); + iff->ifTrue = blockifyMerge( + iff->ifTrue, builder.stealSlice(block, i + 1, list.size())); iff->finalize(); block->finalize(); return true; @@ -415,7 +432,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // 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 + // 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; @@ -428,11 +446,18 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // 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 && BranchUtils::BranchSeeker::countNamed(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())); + // (b) this br_if is the only branch to that block (so the block + // will vanish) + if (brIf->name == block->name && + BranchUtils::BranchSeeker::countNamed(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; } } @@ -443,7 +468,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { if (EffectAnalyzer(getPassOptions(), curr).branches) { return false; } - if (i == 0) return false; + if (i == 0) + return false; i--; } } @@ -453,11 +479,11 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { bool worked = false; void visitBlock(Block* curr) { - // If the block has a single child which is a loop, and the block is named, - // then it is the exit for the loop. It's better to move it into the loop, - // where it can be better optimized by other passes. - // Similar logic for ifs: if the block is an exit for the if, we can - // move the block in, consider for example: + // If the block has a single child which is a loop, and the block is + // named, then it is the exit for the loop. It's better to move it into + // the loop, where it can be better optimized by other passes. Similar + // logic for ifs: if the block is an exit for the if, we can move the + // block in, consider for example: // (block $label // (if (..condition1..) // (block @@ -484,27 +510,31 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { worked = true; } else if (auto* iff = curr->list[0]->dynCast<If>()) { // The label can't be used in the condition. - if (BranchUtils::BranchSeeker::countNamed(iff->condition, curr->name) == 0) { - // We can move the block into either arm, if there are no uses in the other. + if (BranchUtils::BranchSeeker::countNamed(iff->condition, + curr->name) == 0) { + // We can move the block into either arm, if there are no uses in + // the other. Expression** target = nullptr; - if (!iff->ifFalse || - BranchUtils::BranchSeeker::countNamed(iff->ifFalse, curr->name) == 0) { + if (!iff->ifFalse || BranchUtils::BranchSeeker::countNamed( + iff->ifFalse, curr->name) == 0) { target = &iff->ifTrue; - } else if (BranchUtils::BranchSeeker::countNamed(iff->ifTrue, curr->name) == 0) { + } else if (BranchUtils::BranchSeeker::countNamed( + iff->ifTrue, curr->name) == 0) { target = &iff->ifFalse; } if (target) { curr->list[0] = *target; *target = curr; - // The block used to contain the if, and may have changed type from unreachable - // to none, for example, if the if has an unreachable condition but the arm - // is not unreachable. + // The block used to contain the if, and may have changed type + // from unreachable to none, for example, if the if has an + // unreachable condition but the arm is not unreachable. curr->finalize(); iff->finalize(); replaceCurrent(iff); worked = true; - // Note that the type might change, e.g. if the if condition is unreachable - // but the block that was on the outside had a break. + // Note that the type might change, e.g. if the if condition is + // unreachable but the block that was on the outside had a + // break. } } } @@ -526,10 +556,12 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { anotherCycle = false; super::doWalkFunction(func); assert(ifStack.empty()); - // flows may contain returns, which are flowing out and so can be optimized + // flows may contain returns, which are flowing out and so can be + // optimized for (Index i = 0; i < flows.size(); i++) { auto* flow = (*flows[i])->dynCast<Return>(); - if (!flow) continue; + if (!flow) + continue; if (!flow->value) { // return => nop ExpressionManipulator::nop(flow); @@ -541,7 +573,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } } flows.clear(); - // optimize loops (we don't do it while tracking flows, as they can interfere) + // optimize loops (we don't do it while tracking flows, as they can + // interfere) for (auto* loop : loops) { anotherCycle |= optimizeLoop(loop); } @@ -557,7 +590,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // thread trivial jumps struct JumpThreader : public ControlFlowWalker<JumpThreader> { - // map of all value-less breaks and switches going to a block (and not a loop) + // map of all value-less breaks and switches going to a block (and not a + // loop) std::map<Block*, std::vector<Expression*>> branchesToBlock; bool worked = false; @@ -582,19 +616,25 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { void visitBlock(Block* curr) { 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 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>()) { - // the two blocks must have the same type for us to update the branch, as otherwise - // one block may be unreachable and the other concrete, so one might lack a value - if (child->name.is() && child->name != curr->name && child->type == curr->type) { + // the two blocks must have the same type for us to update the + // branch, as otherwise one block may be unreachable and the other + // concrete, so one might lack a value + if (child->name.is() && child->name != curr->name && + child->type == curr->type) { redirectBranches(child, curr->name); } } } 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 + // 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)) { + if (child && child->name.is() && jump && + ExpressionAnalyzer::isSimple(jump)) { redirectBranches(child, jump->name); } } @@ -607,7 +647,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { worked = true; } } - // if the jump is to another block then we can update the list, and maybe push it even more later + // if the jump is to another block then we can update the list, and + // maybe push it even more later if (auto* newTarget = findBreakTarget(to)->dynCast<Block>()) { for (auto* branch : branches) { branchesToBlock[newTarget].push_back(branch); @@ -637,18 +678,27 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { FinalOptimizer(PassOptions& passOptions) : passOptions(passOptions) {} 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. + // 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) continue; // if it lacked an if-false, it would already be a br_if, as that's the easy case + if (!iff || !iff->ifFalse) + // if it lacked an if-false, it would already be a br_if, as that's + // the easy case + continue; auto* ifTrueBreak = iff->ifTrue->dynCast<Break>(); - if (ifTrueBreak && !ifTrueBreak->condition && canTurnIfIntoBrIf(iff->condition, ifTrueBreak->value, passOptions)) { - // we are an if-else where the ifTrue is a break without a condition, so we can do this + if (ifTrueBreak && !ifTrueBreak->condition && + canTurnIfIntoBrIf( + iff->condition, ifTrueBreak->value, passOptions)) { + // we are an if-else where the ifTrue is a break without a + // condition, so we can do this ifTrueBreak->condition = iff->condition; ifTrueBreak->finalize(); list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifTrueBreak); @@ -657,8 +707,11 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } // otherwise, perhaps we can flip the if auto* ifFalseBreak = iff->ifFalse->dynCast<Break>(); - if (ifFalseBreak && !ifFalseBreak->condition && canTurnIfIntoBrIf(iff->condition, ifFalseBreak->value, passOptions)) { - ifFalseBreak->condition = Builder(*getModule()).makeUnary(EqZInt32, iff->condition); + if (ifFalseBreak && !ifFalseBreak->condition && + canTurnIfIntoBrIf( + iff->condition, ifFalseBreak->value, passOptions)) { + ifFalseBreak->condition = + Builder(*getModule()).makeUnary(EqZInt32, iff->condition); ifFalseBreak->finalize(); list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifFalseBreak); ExpressionManipulator::spliceIntoBlock(curr, i + 1, iff->ifTrue); @@ -669,22 +722,26 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // combine/optimize adjacent br_ifs + a br (maybe _if) right after it for (Index i = 0; i < list.size() - 1; i++) { auto* br1 = list[i]->dynCast<Break>(); - // avoid unreachable brs, as they are dead code anyhow, and after merging - // them the outer scope could need type changes - if (!br1 || !br1->condition || br1->type == unreachable) continue; + // avoid unreachable brs, as they are dead code anyhow, and after + // merging them the outer scope could need type changes + if (!br1 || !br1->condition || br1->type == unreachable) + continue; assert(!br1->value); auto* br2 = list[i + 1]->dynCast<Break>(); - if (!br2 || br1->name != br2->name) continue; + if (!br2 || br1->name != br2->name) + continue; assert(!br2->value); // same target as previous, which has no value // a br_if and then a br[_if] with the same target right after it if (br2->condition) { if (shrink && br2->type != unreachable) { - // Join adjacent br_ifs to the same target, making one br_if with - // a "selectified" condition that executes both. - if (!EffectAnalyzer(passOptions, br2->condition).hasSideEffects()) { + // Join adjacent br_ifs to the same target, making one br_if + // with a "selectified" condition that executes both. + if (!EffectAnalyzer(passOptions, br2->condition) + .hasSideEffects()) { // it's ok to execute them both, do it Builder builder(*getModule()); - br1->condition = builder.makeBinary(OrInt32, br1->condition, br2->condition); + br1->condition = + builder.makeBinary(OrInt32, br1->condition, br2->condition); ExpressionManipulator::nop(br2); } } @@ -706,12 +763,9 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { if (BranchUtils::getUniqueTargets(curr).size() == 1) { // This switch has just one target no matter what; replace with a br. Builder builder(*getModule()); - replaceCurrent( - builder.makeSequence( - builder.makeDrop(curr->condition), // might have side effects - builder.makeBreak(curr->default_, curr->value) - ) - ); + replaceCurrent(builder.makeSequence( + builder.makeDrop(curr->condition), // might have side effects + builder.makeBreak(curr->default_, curr->value))); } } @@ -754,36 +808,32 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } else { br = list[0]->dynCast<Break>(); } - // Check if the br is conditional and goes to the block. It may or may not have - // a value, depending on if it was dropped or not. - // If the type is unreachable that means it is not actually reached, - // which we can ignore. - if (br && br->condition && br->name == curr->name && br->type != unreachable) { + // Check if the br is conditional and goes to the block. It may or may + // not have a value, depending on if it was dropped or not. If the + // type is unreachable that means it is not actually reached, which we + // can ignore. + if (br && br->condition && br->name == curr->name && + br->type != unreachable) { if (BranchUtils::BranchSeeker::countNamed(curr, curr->name) == 1) { // no other breaks to that name, so we can do this if (!drop) { assert(!br->value); Builder builder(*getModule()); replaceCurrent(builder.makeIf( - builder.makeUnary(EqZInt32, br->condition), - curr - )); + builder.makeUnary(EqZInt32, br->condition), curr)); ExpressionManipulator::nop(br); curr->finalize(curr->type); } else { - // If the items we move around have side effects, we can't do this. + // If the items we move around have side effects, we can't do + // this. // TODO: we could use a select, in some cases..? if (!EffectAnalyzer(passOptions, br->value).hasSideEffects() && - !EffectAnalyzer(passOptions, br->condition).hasSideEffects()) { + !EffectAnalyzer(passOptions, br->condition) + .hasSideEffects()) { ExpressionManipulator::nop(list[0]); Builder builder(*getModule()); replaceCurrent( - builder.makeIf( - br->condition, - br->value, - curr - ) - ); + builder.makeIf(br->condition, br->value, curr)); } } } @@ -800,20 +850,20 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // Convert an if into a select, if possible and beneficial to do so. Select* selectify(If* iff) { - if (!iff->ifFalse || - !isConcreteType(iff->ifTrue->type) || + if (!iff->ifFalse || !isConcreteType(iff->ifTrue->type) || !isConcreteType(iff->ifFalse->type)) { return nullptr; } - // This is always helpful for code size, but can be a tradeoff with performance - // as we run both code paths. So when shrinking we always try to do this, but - // otherwise must consider more carefully. + // This is always helpful for code size, but can be a tradeoff with + // performance as we run both code paths. So when shrinking we always + // try to do this, but otherwise must consider more carefully. if (!passOptions.shrinkLevel) { // Consider the cost of executing all the code unconditionally const auto MAX_COST = 7; - auto total = CostAnalyzer(iff->ifTrue).cost + - CostAnalyzer(iff->ifFalse).cost; - if (total >= MAX_COST) return nullptr; + auto total = + CostAnalyzer(iff->ifTrue).cost + CostAnalyzer(iff->ifFalse).cost; + if (total >= MAX_COST) + return nullptr; } // Check if side effects allow this. EffectAnalyzer condition(passOptions, iff->condition); @@ -822,11 +872,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { if (!ifTrue.hasSideEffects()) { EffectAnalyzer ifFalse(passOptions, iff->ifFalse); if (!ifFalse.hasSideEffects()) { - return Builder(*getModule()).makeSelect( - iff->condition, - iff->ifTrue, - iff->ifFalse - ); + return Builder(*getModule()) + .makeSelect(iff->condition, iff->ifTrue, iff->ifFalse); } } } @@ -842,8 +889,10 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } void optimizeSetIf(Expression** currp) { - if (optimizeSetIfWithBrArm(currp)) return; - if (optimizeSetIfWithCopyArm(currp)) return; + if (optimizeSetIfWithBrArm(currp)) + return; + if (optimizeSetIfWithCopyArm(currp)) + return; } // If one arm is a br, we prefer a br_if and the set later: @@ -865,33 +914,33 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { bool optimizeSetIfWithBrArm(Expression** currp) { auto* set = (*currp)->cast<SetLocal>(); auto* iff = set->value->dynCast<If>(); - if (!iff || - !isConcreteType(iff->type) || + if (!iff || !isConcreteType(iff->type) || !isConcreteType(iff->condition->type)) { return false; } - auto tryToOptimize = [&](Expression* one, Expression* two, bool flipCondition) { - if (one->type == unreachable && two->type != unreachable) { - if (auto* br = one->dynCast<Break>()) { - if (ExpressionAnalyzer::isSimple(br)) { - // Wonderful, do it! - Builder builder(*getModule()); - if (flipCondition) { - builder.flip(iff); + auto tryToOptimize = + [&](Expression* one, Expression* two, bool flipCondition) { + if (one->type == unreachable && two->type != unreachable) { + if (auto* br = one->dynCast<Break>()) { + if (ExpressionAnalyzer::isSimple(br)) { + // Wonderful, do it! + Builder builder(*getModule()); + if (flipCondition) { + builder.flip(iff); + } + br->condition = iff->condition; + br->finalize(); + set->value = two; + auto* block = builder.makeSequence(br, set); + *currp = block; + // Recurse on the set, which now has a new value. + optimizeSetIf(&block->list[1]); + return true; } - br->condition = iff->condition; - br->finalize(); - set->value = two; - auto* block = builder.makeSequence(br, set); - *currp = block; - // Recurse on the set, which now has a new value. - optimizeSetIf(&block->list[1]); - return true; } } - } - return false; - }; + return false; + }; return tryToOptimize(iff->ifTrue, iff->ifFalse, false) || tryToOptimize(iff->ifFalse, iff->ifTrue, true); } @@ -940,8 +989,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { bool optimizeSetIfWithCopyArm(Expression** currp) { auto* set = (*currp)->cast<SetLocal>(); auto* iff = set->value->dynCast<If>(); - if (!iff || - !isConcreteType(iff->type) || + if (!iff || !isConcreteType(iff->type) || !isConcreteType(iff->condition->type)) { return false; } @@ -955,7 +1003,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { get = nullptr; } } - if (!get) return false; + if (!get) + return false; // We can do it! bool tee = set->isTee(); assert(set->index == get->index); @@ -969,9 +1018,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { if (tee) { set->setTee(false); // We need a block too. - replacement = builder.makeSequence( - iff, - get // reuse the get + replacement = builder.makeSequence(iff, + get // reuse the get ); } *currp = replacement; @@ -996,10 +1044,12 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // ) // TODO: consider also looking at <= etc. and not just eq void tablify(Block* block) { - auto &list = block->list; - if (list.size() <= 1) return; + auto& list = block->list; + if (list.size() <= 1) + return; - // Heuristics. These are slightly inspired by the constants from the asm.js backend. + // Heuristics. These are slightly inspired by the constants from the + // asm.js backend. // How many br_ifs we need to see to consider doing this const uint32_t MIN_NUM = 3; @@ -1009,35 +1059,51 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // this is high, we allow larger ranges. const uint32_t NUM_TO_RANGE_FACTOR = 3; - // check if the input is a proper br_if on an i32.eq of a condition value to a const, - // and the const is in the proper range, [0-int32_max), to avoid overflow concerns. - // returns the br_if if so, or nullptr otherwise - auto getProperBrIf = [](Expression* curr) -> Break*{ + // check if the input is a proper br_if on an i32.eq of a condition + // value to a const, and the const is in the proper range, + // [0-int32_max), to avoid overflow concerns. returns the br_if if so, + // or nullptr otherwise + auto getProperBrIf = [](Expression* curr) -> Break* { auto* br = curr->dynCast<Break>(); - if (!br) return nullptr; - if (!br->condition || br->value) return nullptr; - if (br->type != none) return nullptr; // no value, so can be unreachable or none. ignore unreachable ones, dce will clean it up + if (!br) + return nullptr; + if (!br->condition || br->value) + return nullptr; + if (br->type != none) + // no value, so can be unreachable or none. ignore unreachable ones, + // dce will clean it up + return nullptr; auto* binary = br->condition->dynCast<Binary>(); - if (!binary) return nullptr; - if (binary->op != EqInt32) return nullptr; + if (!binary) + return nullptr; + if (binary->op != EqInt32) + return nullptr; auto* c = binary->right->dynCast<Const>(); - if (!c) return nullptr; + if (!c) + return nullptr; uint32_t value = c->value.geti32(); - if (value >= uint32_t(std::numeric_limits<int32_t>::max())) return nullptr; + if (value >= uint32_t(std::numeric_limits<int32_t>::max())) + return nullptr; return br; }; // check if the input is a proper br_if // and returns the condition if so, or nullptr otherwise - auto getProperBrIfConditionValue = [&getProperBrIf](Expression* curr) -> Expression* { + auto getProperBrIfConditionValue = + [&getProperBrIf](Expression* curr) -> Expression* { auto* br = getProperBrIf(curr); - if (!br) return nullptr; + if (!br) + return nullptr; return br->condition->cast<Binary>()->left; }; // returns the constant value, as a uint32_t - auto getProperBrIfConstant = [&getProperBrIf](Expression* curr) -> uint32_t { - return getProperBrIf(curr)->condition->cast<Binary>()->right->cast<Const>()->value.geti32(); + auto getProperBrIfConstant = + [&getProperBrIf](Expression* curr) -> uint32_t { + return getProperBrIf(curr) + ->condition->cast<Binary>() + ->right->cast<Const>() + ->value.geti32(); }; Index start = 0; while (start < list.size() - 1) { @@ -1046,23 +1112,25 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { start++; continue; } - // if the condition has side effects, we can't replace many appearances of it - // with a single one + // if the condition has side effects, we can't replace many + // appearances of it with a single one if (EffectAnalyzer(passOptions, conditionValue).hasSideEffects()) { start++; continue; } - // look for a "run" of br_ifs with all the same conditionValue, and having - // unique constants (an overlapping constant could be handled, just the first - // branch is taken, but we can't remove the other br_if (it may be the only - // branch keeping a block reachable), which may make this bad for code size. + // look for a "run" of br_ifs with all the same conditionValue, and + // having unique constants (an overlapping constant could be handled, + // just the first branch is taken, but we can't remove the other br_if + // (it may be the only branch keeping a block reachable), which may + // make this bad for code size. Index end = start + 1; std::unordered_set<uint32_t> usedConstants; usedConstants.insert(getProperBrIfConstant(list[start])); while (end < list.size() && - ExpressionAnalyzer::equal(getProperBrIfConditionValue(list[end]), - conditionValue)) { - if (!usedConstants.insert(getProperBrIfConstant(list[end])).second) { + ExpressionAnalyzer::equal( + getProperBrIfConditionValue(list[end]), conditionValue)) { + if (!usedConstants.insert(getProperBrIfConstant(list[end])) + .second) { // this constant already appeared break; } @@ -1081,8 +1149,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } uint32_t range = max - min; // decision time - if (range <= MAX_RANGE && - range <= num * NUM_TO_RANGE_FACTOR) { + if (range <= MAX_RANGE && range <= num * NUM_TO_RANGE_FACTOR) { // great! let's do this std::unordered_set<Name> usedNames; for (Index i = start; i < end; i++) { @@ -1093,7 +1160,8 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { Index i = 0; while (1) { defaultName = "tablify|" + std::to_string(i++); - if (usedNames.count(defaultName) == 0) break; + if (usedNames.count(defaultName) == 0) + break; } std::vector<Name> table; for (Index i = start; i < end; i++) { @@ -1103,26 +1171,21 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { while (table.size() <= index) { table.push_back(defaultName); } - assert(table[index] == defaultName); // we should have made sure there are no overlaps + // we should have made sure there are no overlaps + assert(table[index] == defaultName); table[index] = name; } Builder builder(*getModule()); // the table and condition are offset by the min if (min != 0) { - conditionValue = builder.makeBinary( - SubInt32, - conditionValue, - builder.makeConst(Literal(int32_t(min))) - ); + conditionValue = + builder.makeBinary(SubInt32, + conditionValue, + builder.makeConst(Literal(int32_t(min)))); } list[end - 1] = builder.makeBlock( defaultName, - builder.makeSwitch( - table, - defaultName, - conditionValue - ) - ); + builder.makeSwitch(table, defaultName, conditionValue)); for (Index i = start; i < end - 1; i++) { ExpressionManipulator::nop(list[i]); } @@ -1145,8 +1208,6 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { } }; -Pass *createRemoveUnusedBrsPass() { - return new RemoveUnusedBrs(); -} +Pass* createRemoveUnusedBrsPass() { return new RemoveUnusedBrs(); } } // namespace wasm |