blob: 3f909c32e3d0b8eecd02be53bba36f4dbdc867ff [file] [log] [blame] [edit]
/*
* Copyright 2015 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.
*/
//
// Removes branches for which we go to where they go anyhow
//
#include <wasm.h>
#include <pass.h>
#include <ast_utils.h>
namespace wasm {
struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<RemoveUnusedBrs>>> {
bool isFunctionParallel() override { return true; }
Pass* create() override { return new RemoveUnusedBrs; }
bool anotherCycle;
// Whether a value can flow in the current path. If so, then a br with value
// can be turned into a value, which will flow through blocks/ifs to the right place
bool valueCanFlow;
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)
Flows flows;
// a stack for if-else contents, we merge their outputs
std::vector<Flows> ifStack;
static void visitAny(RemoveUnusedBrs* self, Expression** currp) {
auto* curr = *currp;
auto& flows = self->flows;
if (curr->is<Break>()) {
flows.clear();
auto* br = curr->cast<Break>();
if (!br->condition) { // TODO: optimize?
// a break, let's see where it flows to
flows.push_back(currp);
self->valueCanFlow = true; // start optimistic
} else {
self->valueCanFlow = false;
}
} else if (curr->is<Return>()) {
flows.clear();
flows.push_back(currp);
self->valueCanFlow = true; // start optimistic
} else if (curr->is<If>()) {
auto* iff = curr->cast<If>();
if (iff->ifFalse) {
assert(self->ifStack.size() > 0);
for (auto* flow : self->ifStack.back()) {
flows.push_back(flow);
}
self->ifStack.pop_back();
}
} else if (curr->is<Block>()) {
// any breaks flowing to here are unnecessary, as we get here anyhow
auto* block = curr->cast<Block>();
auto name = block->name;
if (name.is()) {
size_t size = flows.size();
size_t skip = 0;
for (size_t i = 0; i < size; i++) {
auto* flow = (*flows[i])->dynCast<Break>();
if (flow && flow->name == name) {
if (!flow->value || self->valueCanFlow) {
if (!flow->value) {
// br => nop
ExpressionManipulator::nop<Break>(flow);
} else {
// br with value => value
*flows[i] = flow->value;
}
skip++;
self->anotherCycle = true;
}
} else if (skip > 0) {
flows[i - skip] = flows[i];
}
}
if (skip > 0) {
flows.resize(size - skip);
}
// drop a nop at the end of a block, which prevents a value flowing
while (block->list.size() > 0 && block->list.back()->is<Nop>()) {
block->list.resize(block->list.size() - 1);
self->anotherCycle = true;
}
}
} else if (curr->is<Nop>()) {
// ignore (could be result of a previous cycle)
self->valueCanFlow = false;
} else {
// anything else stops the flow TODO: optimize loops?
flows.clear();
self->valueCanFlow = false;
}
}
static void clear(RemoveUnusedBrs* self, Expression** currp) {
self->flows.clear();
}
static void saveIfTrue(RemoveUnusedBrs* self, Expression** currp) {
self->ifStack.push_back(std::move(self->flows));
}
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;
}
br->condition = curr->condition;
replaceCurrent(br);
anotherCycle = true;
}
}
}
// override scan to add a pre and a post check task to all nodes
static void scan(RemoveUnusedBrs* self, Expression** currp) {
self->pushTask(visitAny, currp);
auto* iff = (*currp)->dynCast<If>();
if (iff) {
self->pushTask(doVisitIf, currp);
if (iff->ifFalse) {
// 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
}
self->pushTask(scan, &iff->ifTrue);
self->pushTask(clear, currp); // clear all flow after the condition
self->pushTask(scan, &iff->condition);
} else {
WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<RemoveUnusedBrs>>>::scan(self, currp);
}
}
void doWalkFunction(Function* func) {
// multiple cycles may be needed
bool worked = false;
do {
anotherCycle = false;
WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<RemoveUnusedBrs>>>::doWalkFunction(func);
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
if (!flow->value) {
// return => nop
ExpressionManipulator::nop(flow);
anotherCycle = true;
} else if (valueCanFlow) {
// return with value => value
*flows[i] = flow->value;
anotherCycle = true;
}
}
flows.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>>> {
void visitIf(If* curr) {
if (curr->ifFalse) {
// if with else, consider turning it into a select if there is no control flow
// TODO: estimate cost
EffectAnalyzer condition(curr->condition);
if (!condition.hasSideEffects()) {
EffectAnalyzer ifTrue(curr->ifTrue);
if (!ifTrue.hasSideEffects()) {
EffectAnalyzer ifFalse(curr->ifFalse);
if (!ifFalse.hasSideEffects()) {
auto* select = getModule()->allocator.alloc<Select>();
select->condition = curr->condition;
select->ifTrue = curr->ifTrue;
select->ifFalse = curr->ifFalse;
select->finalize();
replaceCurrent(select);
}
}
}
}
}
};
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);
}
}
};
static RegisterPass<RemoveUnusedBrs> registerPass("remove-unused-brs", "removes breaks from locations that are not needed");
} // namespace wasm