blob: 39d2b85c9974d08c164222d3789560fe8d57dc4a [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.
*/
//
// 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>
namespace wasm {
struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks, Visitor<MergeBlocks>>> {
bool isFunctionParallel() override { return true; }
Pass* create() override { return new MergeBlocks; }
void visitBlock(Block *curr) {
bool more = true;
while (more) {
more = false;
for (size_t i = 0; i < curr->list.size(); i++) {
Block* child = curr->list[i]->dynCast<Block>();
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(getModule()->allocator);
for (size_t j = 0; j < i; j++) {
merged.push_back(curr->list[j]);
}
for (auto item : child->list) {
merged.push_back(item);
}
for (size_t j = i + 1; j < curr->list.size(); j++) {
merged.push_back(curr->list[j]);
}
curr->list = merged;
more = true;
break;
}
}
}
Block* optimize(Expression* curr, Expression*& child, Block* outer = nullptr, Expression** dependency1 = nullptr, Expression** dependency2 = nullptr) {
if (!child) return outer;
if (dependency1 && *dependency1 && EffectAnalyzer(*dependency1).hasSideEffects()) return outer;
if (dependency2 && *dependency2 && EffectAnalyzer(*dependency2).hasSideEffects()) 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]);
}
outer->list.push_back(curr);
}
}
}
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);
}
};
static RegisterPass<MergeBlocks> registerPass("merge-blocks", "merges blocks to their parents");
} // namespace wasm