blob: 072f86e1698eec6577dc6cf1db5d03f58df75f95 [file] [log] [blame]
/*
* 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.
*/
//
// Merges blocks to their parents.
//
// We also restructure blocks in order to enable such merging. For
// example,
//
// (i32.store
// (block
// (call $foo)
// (i32.load (i32.const 100))
// )
// (i32.const 0)
// )
//
// can be transformed into
//
// (block
// (call $foo)
// (i32.store
// (block
// (i32.load (i32.const 100))
// )
// (i32.const 0)
// )
// )
//
// after which the internal block can go away, and
// the new external block might be mergeable. This is always
// worth it if the internal block ends up with 1 item.
// For the second operand,
//
// (i32.store
// (i32.const 100)
// (block
// (call $foo)
// (i32.load (i32.const 200))
// )
// )
//
// The order of operations requires that the first execute
// before. We can do the same operation, but only if the
// first has no side effects, or the code we are moving out
// has no side effects.
// If we can do this to both operands, we can generate a
// single outside block.
//
#include <wasm.h>
#include <pass.h>
#include <ast_utils.h>
#include <wasm-builder.h>
namespace wasm {
// Looks for reasons we can't remove the values from breaks to an origin
// For example, if there is a switch targeting us, we can't do it - we can't remove the value from other targets
struct ProblemFinder : public ControlFlowWalker<ProblemFinder, Visitor<ProblemFinder>> {
Name origin;
bool foundSwitch = false;
// count br_ifs, and dropped br_ifs. if they don't match, then a br_if flow value is used, and we can't drop it
Index brIfs = 0;
Index droppedBrIfs = 0;
void visitBreak(Break* curr) {
if (curr->name == origin && curr->condition) {
brIfs++;
}
}
void visitDrop(Drop* curr) {
if (auto* br = curr->value->dynCast<Break>()) {
if (br->name == origin && br->condition) {
droppedBrIfs++;
}
}
}
void visitSwitch(Switch* curr) {
if (curr->default_ == origin) {
foundSwitch = true;
return;
}
for (auto& target : curr->targets) {
if (target == origin) {
foundSwitch = true;
return;
}
}
}
bool found() {
assert(brIfs >= droppedBrIfs);
return foundSwitch || brIfs > droppedBrIfs;
}
};
// Drops values from breaks to an origin.
// While doing so it can create new blocks, so optimize blocks as well.
struct BreakValueDropper : public ControlFlowWalker<BreakValueDropper, Visitor<BreakValueDropper>> {
Name origin;
void visitBlock(Block* curr);
void visitBreak(Break* curr) {
if (curr->value && curr->name == origin) {
Builder builder(*getModule());
auto* value = curr->value;
curr->value = nullptr;
curr->finalize();
replaceCurrent(builder.makeSequence(builder.makeDrop(value), curr));
}
}
void visitDrop(Drop* curr) {
// if we dropped a br_if whose value we removed, then we are now dropping a (block (drop value) (br_if)) with type none, which does not need a drop
if (curr->value->type == none) {
replaceCurrent(curr->value);
}
}
};
// core block optimizer routine
static void optimizeBlock(Block* curr, Module* module) {
bool more = true;
bool changed = false;
while (more) {
more = false;
for (size_t i = 0; i < curr->list.size(); i++) {
Block* child = curr->list[i]->dynCast<Block>();
if (!child) {
// if we have a child that is (drop (block ..)) then we can move the drop into the block, and remove br values. this allows more merging,
auto* drop = curr->list[i]->dynCast<Drop>();
if (drop) {
child = drop->value->dynCast<Block>();
if (child) {
if (child->name.is()) {
Expression* expression = child;
// check if it's ok to remove the value from all breaks to us
ProblemFinder finder;
finder.origin = child->name;
finder.walk(expression);
if (finder.found()) {
child = nullptr;
} else {
// fix up breaks
BreakValueDropper fixer;
fixer.origin = child->name;
fixer.setModule(module);
fixer.walk(expression);
}
}
if (child) {
// we can do it!
// reuse the drop
drop->value = child->list.back();
child->list.back() = drop;
child->finalize();
curr->list[i] = child;
more = true;
changed = true;
}
}
}
}
if (!child) continue;
if (child->name.is()) continue; // named blocks can have breaks to them (and certainly do, if we ran RemoveUnusedNames and RemoveUnusedBrs)
ExpressionList merged;
for (size_t j = 0; j < i; j++) {
merged.push_back(curr->list[j], module->allocator);
}
for (auto item : child->list) {
merged.push_back(item, module->allocator);
}
for (size_t j = i + 1; j < curr->list.size(); j++) {
merged.push_back(curr->list[j], module->allocator);
}
curr->list.set(merged, module->allocator);
more = true;
changed = true;
break;
}
}
if (changed) curr->finalize();
}
void BreakValueDropper::visitBlock(Block* curr) {
optimizeBlock(curr, getModule());
}
struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks, Visitor<MergeBlocks>>> {
bool isFunctionParallel() override { return true; }
Pass* create() override { return new MergeBlocks; }
void visitBlock(Block *curr) {
optimizeBlock(curr, getModule());
}
Block* optimize(Expression* curr, Expression*& child, Block* outer = nullptr, Expression** dependency1 = nullptr, Expression** dependency2 = nullptr) {
if (!child) return outer;
if ((dependency1 && *dependency1) || (dependency2 && *dependency2)) {
// there are dependencies, things we must be reordered through. make sure no problems there
EffectAnalyzer childEffects(child);
if (dependency1 && *dependency1 && EffectAnalyzer(*dependency1).invalidates(childEffects)) return outer;
if (dependency2 && *dependency2 && EffectAnalyzer(*dependency2).invalidates(childEffects)) return outer;
}
if (auto* block = child->dynCast<Block>()) {
if (!block->name.is() && block->list.size() >= 2) {
child = block->list.back();
if (outer == nullptr) {
// reuse the block, move it out
block->list.back() = curr;
block->finalize(); // last block element was our input, and is now our output, which may differ TODO optimize
replaceCurrent(block);
return block;
} else {
// append to an existing outer block
assert(outer->list.back() == curr);
outer->list.pop_back();
for (Index i = 0; i < block->list.size() - 1; i++) {
outer->list.push_back(block->list[i], getAllocator());
}
outer->list.push_back(curr, getAllocator());
}
}
}
return outer;
}
void visitUnary(Unary* curr) {
optimize(curr, curr->value);
}
void visitSetLocal(SetLocal* curr) {
optimize(curr, curr->value);
}
void visitLoad(Load* curr) {
optimize(curr, curr->ptr);
}
void visitReturn(Return* curr) {
optimize(curr, curr->value);
}
void visitBinary(Binary* curr) {
optimize(curr, curr->right, optimize(curr, curr->left), &curr->left);
}
void visitStore(Store* curr) {
optimize(curr, curr->value, optimize(curr, curr->ptr), &curr->ptr);
}
void visitSelect(Select* curr) {
optimize(curr, curr->condition, optimize(curr, curr->ifFalse, optimize(curr, curr->ifTrue), &curr->ifTrue), &curr->ifTrue, &curr->ifFalse);
}
void visitBreak(Break* curr) {
optimize(curr, curr->condition, optimize(curr, curr->value), &curr->value);
}
void visitSwitch(Switch* curr) {
optimize(curr, curr->condition, optimize(curr, curr->value), &curr->value);
}
template<typename T>
void handleCall(T* curr, Block* outer = nullptr) {
for (Index i = 0; i < curr->operands.size(); i++) {
outer = optimize(curr, curr->operands[i], outer);
if (EffectAnalyzer(curr->operands[i]).hasSideEffects()) return;
}
}
void visitCall(Call* curr) {
handleCall(curr);
}
void visitCallImport(CallImport* curr) {
handleCall(curr);
}
void visitCallIndirect(CallIndirect* curr) {
auto* outer = optimize(curr, curr->target);
if (EffectAnalyzer(curr->target).hasSideEffects()) return;
handleCall(curr, outer);
}
};
Pass *createMergeBlocksPass() {
return new MergeBlocks();
}
} // namespace wasm