blob: b36fe6396129000fe9fddf490a9547a7af60b3a7 [file] [log] [blame] [edit]
/*
* Copyright 2016 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.
*/
#ifndef wasm_ir_localizer_h
#define wasm_ir_localizer_h
#include "ir/iteration.h"
#include "wasm-builder.h"
namespace wasm {
// Make an expression available in a local. If already in one, just
// use that local, otherwise use a new local.
//
// Note that if the local is reused, this assumes it is not modified in between
// the set and the get, which the caller must ensure.
struct Localizer {
Index index;
Expression* expr;
Localizer(Expression* input, Function* func, Module* wasm) {
expr = input;
if (auto* get = expr->dynCast<LocalGet>()) {
index = get->index;
} else if (auto* set = expr->dynCast<LocalSet>()) {
index = set->index;
} else {
index = Builder::addVar(func, expr->type);
expr = Builder(*wasm).makeLocalTee(index, expr, expr->type);
}
}
};
// Replaces all children with gets of locals, if they have any effects that
// interact with any of the others, or if they have side effects which cannot be
// removed. Also replace unreachable things with an unreachable, leaving in
// place only things without interacting effects. For example:
//
// (parent
// (call $foo)
// (br $out)
// (i32.const)
// )
//
// =>
//
// (local.set $temp.foo
// (call $foo) ;; moved out
// )
// (br $out) ;; moved out
// (parent
// (local.get $temp.foo) ;; value saved to a local
// (unreachable) ;; complex effect replaced by unreachable
// (i32.const)
// )
//
// After this it is safe to reorder and remove things from the parent: all
// interesting interactions happen before the parent.
//
// Typical usage is to call getReplacement() will produces the entire output
// just shown (i.e., possible initial local.sets and other stuff that was pulled
// out, followed by the parent, as relevant). Note that getReplacement() may
// omit the parent, if it had an unreachable child. That is useful behavior in
// that it removes unneeded code (& otherwise some users of this code would need
// to write their own removal logic). However, that does imply that it is valid
// to remove the parent in such cases, which is not so for e.g. br when it is
// the last thing keeping a block reachable. Calling this with something like a
// struct.new or a call (the current intended users) is valid; if we want to
// generalize this fully then we need to make changes here.
//
// TODO: use in more places
struct ChildLocalizer {
ChildLocalizer(Expression* parent,
Function* func,
Module& wasm,
const PassOptions& options)
: parent(parent), wasm(wasm) {
Builder builder(wasm);
ChildIterator iterator(parent);
auto& children = iterator.children;
auto num = children.size();
// Compute the effects of all children.
std::vector<EffectAnalyzer> effects;
for (Index i = 0; i < num; i++) {
// The children are in reverse order in ChildIterator, but we want to
// process them in the normal order.
auto* child = *children[num - 1 - i];
effects.emplace_back(options, wasm, child);
}
// Go through the children and move to locals those that we need to.
for (Index i = 0; i < num; i++) {
auto** childp = children[num - 1 - i];
auto* child = *childp;
if (child->type == Type::unreachable) {
// Move the child out, and put an unreachable in its place (note that we
// don't need an actual set here, as there is no value to set to a
// local).
sets.push_back(child);
*childp = builder.makeUnreachable();
hasUnreachableChild = true;
continue;
}
if (hasUnreachableChild) {
// Once we pass one unreachable, we only need to copy the children over.
// (The only reason we still need them is that they may be needed for
// validation, e.g. if one contains a break to a block that is the only
// reason the block has type none.)
sets.push_back(builder.makeDrop(child));
*childp = builder.makeUnreachable();
continue;
}
// Use a local if we need to. That is the case either if this has side
// effects we can't remove, or if it interacts with other children.
bool needLocal = effects[i].hasUnremovableSideEffects();
if (!needLocal) {
// TODO: Avoid quadratic time here by accumulating effects and checking
// vs the accumulation.
for (Index j = 0; j < num; j++) {
if (j != i && effects[i].invalidates(effects[j])) {
needLocal = true;
break;
}
}
}
if (needLocal) {
auto local = builder.addVar(func, child->type);
sets.push_back(builder.makeLocalSet(local, child));
*childp = builder.makeLocalGet(local, child->type);
}
}
}
// Helper that gets a replacement for the parent: a block containing the
// sets + the parent. This will not contain the parent if we don't need it
// (if it was never reached).
Expression* getReplacement() {
if (sets.empty()) {
// Nothing to add.
return parent;
}
auto* block = getChildrenReplacement();
if (!hasUnreachableChild) {
block->list.push_back(parent);
block->finalize();
}
return block;
}
// Like `getReplacement`, but the result never contains the parent.
Block* getChildrenReplacement() {
auto* block = Builder(wasm).makeBlock();
block->list.set(sets);
if (hasUnreachableChild) {
block->type = Type::unreachable;
}
return block;
}
private:
Expression* parent;
Module& wasm;
std::vector<Expression*> sets;
bool hasUnreachableChild = false;
};
} // namespace wasm
#endif // wasm_ir_localizer_h