summaryrefslogtreecommitdiff
path: root/src/passes/Flatten.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/passes/Flatten.cpp')
-rw-r--r--src/passes/Flatten.cpp337
1 files changed, 337 insertions, 0 deletions
diff --git a/src/passes/Flatten.cpp b/src/passes/Flatten.cpp
new file mode 100644
index 000000000..b16d9df3f
--- /dev/null
+++ b/src/passes/Flatten.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2017 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.
+ */
+
+//
+// Flattens code, removing nesting.e.g. an if return value would be
+// converted to a local
+//
+// (i32.add
+// (if (..condition..)
+// (..if true..)
+// (..if false..)
+// )
+// (i32.const 1)
+// )
+// =>
+// (if (..condition..)
+// (set_local $temp
+// (..if true..)
+// )
+// (set_local $temp
+// (..if false..)
+// )
+// )
+// (i32.add
+// (get_local $temp)
+// (i32.const 1)
+// )
+//
+// Formally, this pass flattens in the precise sense of
+// making the AST have these properties:
+//
+// 1. The operands of an instruction must be a get_local or a const.
+// anything else is written to a local earlier.
+// 2. Disallow block, loop, and if return values, i.e., do not use
+// control flow to pass around values.
+//
+
+#include <wasm.h>
+#include <pass.h>
+#include <wasm-builder.h>
+#include <ast_utils.h>
+#include <ast/effects.h>
+
+namespace wasm {
+
+// We use the following algorithm: we maintain a list of "preludes", code
+// that runs right before an expression. When we visit an expression we
+// must handle it and its preludes. If the expression has side effects,
+// we reduce it to a get_local and add a prelude for that. We then handle
+// the preludes, by moving them to the parent or handling them directly.
+// we can move them to the parent if the parent is not a control flow
+// structure. Otherwise, if the parent is a control flow structure, it
+// will incorporate the preludes of its children accordingly.
+// As a result, when we reach a node, we know its children have no
+// side effects (they have been moved to a prelude), or we are a
+// control flow structure (which allows children with side effects,
+// e.g. a return as a block element).
+// Once exception is that we allow an (unreachable) node, which is used
+// when we move something unreachable to another place, and need a
+// placeholder. We will never reach that (unreachable) anyhow
+struct Flatten : public WalkerPass<ExpressionStackWalker<Flatten, UnifiedExpressionVisitor<Flatten>>> {
+ bool isFunctionParallel() override { return true; }
+
+ Pass* create() override { return new Flatten; }
+
+ // For each expression, a bunch of expressions that should execute right before it
+ std::unordered_map<Expression*, std::vector<Expression*>> preludes;
+
+ // Break values are sent through a temp local
+ std::unordered_map<Name, Index> breakTemps;
+
+ void visitExpression(Expression* curr) {
+ std::vector<Expression*> ourPreludes;
+ Builder builder(*getModule());
+
+ if (isControlFlowStructure(curr)) {
+ // handle control flow explicitly. our children do not have control flow,
+ // but they do have preludes which we need to set up in the right place
+ assert(preludes.find(curr) == preludes.end()); // no one should have given us preludes, they are on the children
+ if (auto* block = curr->dynCast<Block>()) {
+ // make a new list, where each item's preludes are added before it
+ ExpressionList newList(getModule()->allocator);
+ for (auto* item : block->list) {
+ auto iter = preludes.find(item);
+ if (iter != preludes.end()) {
+ auto& itemPreludes = iter->second;
+ for (auto* prelude : itemPreludes) {
+ newList.push_back(prelude);
+ }
+ itemPreludes.clear();
+ }
+ newList.push_back(item);
+ }
+ block->list.swap(newList);
+ // remove a block return value
+ auto type = block->type;
+ if (isConcreteWasmType(type)) {
+ // if there is a temp index for breaking to the block, use that
+ Index temp;
+ auto iter = breakTemps.find(block->name);
+ if (iter != breakTemps.end()) {
+ temp = iter->second;
+ } else {
+ temp = builder.addVar(getFunction(), type);
+ }
+ auto*& last = block->list.back();
+ if (isConcreteWasmType(last->type)) {
+ last = builder.makeSetLocal(temp, last);
+ }
+ block->finalize(none);
+ // and we leave just a get of the value
+ auto* rep = builder.makeGetLocal(temp, type);
+ replaceCurrent(rep);
+ // the whole block is now a prelude
+ ourPreludes.push_back(block);
+ }
+ // the block now has no return value, and may have become unreachable
+ block->finalize(none);
+ } else if (auto* iff = curr->dynCast<If>()) {
+ // condition preludes go before the entire if
+ auto* rep = getPreludesWithExpression(iff->condition, iff);
+ // arm preludes go in the arms. we must also remove an if value
+ auto* originalIfTrue = iff->ifTrue;
+ auto* originalIfFalse = iff->ifFalse;
+ auto type = iff->type;
+ Expression* prelude = nullptr;
+ if (isConcreteWasmType(type)) {
+ Index temp = builder.addVar(getFunction(), type);
+ if (isConcreteWasmType(iff->ifTrue->type)) {
+ iff->ifTrue = builder.makeSetLocal(temp, iff->ifTrue);
+ }
+ if (iff->ifFalse && isConcreteWasmType(iff->ifFalse->type)) {
+ iff->ifFalse = builder.makeSetLocal(temp, iff->ifFalse);
+ }
+ // the whole if (+any preludes from the condition) is now a prelude
+ prelude = rep;
+ // and we leave just a get of the value
+ rep = builder.makeGetLocal(temp, type);
+ }
+ iff->ifTrue = getPreludesWithExpression(originalIfTrue, iff->ifTrue);
+ if (iff->ifFalse) iff->ifFalse = getPreludesWithExpression(originalIfFalse, iff->ifFalse);
+ iff->finalize();
+ if (prelude) {
+ ReFinalizeNode().visit(prelude);
+ ourPreludes.push_back(prelude);
+ }
+ replaceCurrent(rep);
+ } else if (auto* loop = curr->dynCast<Loop>()) {
+ // remove a loop value
+ Expression* rep = loop;
+ auto* originalBody = loop->body;
+ auto type = loop->type;
+ if (isConcreteWasmType(type)) {
+ Index temp = builder.addVar(getFunction(), type);
+ loop->body = builder.makeSetLocal(temp, loop->body);
+ // and we leave just a get of the value
+ rep = builder.makeGetLocal(temp, type);
+ // the whole if is now a prelude
+ ourPreludes.push_back(loop);
+ loop->type = none;
+ }
+ loop->body = getPreludesWithExpression(originalBody, loop->body);
+ loop->finalize();
+ replaceCurrent(rep);
+ } else {
+ WASM_UNREACHABLE();
+ }
+ } else {
+ // for anything else, there may be existing preludes
+ auto iter = preludes.find(curr);
+ if (iter != preludes.end()) {
+ ourPreludes.swap(iter->second);
+ }
+ // special handling
+ if (auto* br = curr->dynCast<Break>()) {
+ if (br->value) {
+ auto type = br->value->type;
+ if (isConcreteWasmType(type)) {
+ // we are sending a value. use a local instead
+ Index temp = getTempForBreakTarget(br->name, type);
+ ourPreludes.push_back(builder.makeSetLocal(temp, br->value));
+ if (br->condition) {
+ // the value must also flow out
+ ourPreludes.push_back(br);
+ if (isConcreteWasmType(br->type)) {
+ replaceCurrent(builder.makeGetLocal(temp, type));
+ } else {
+ assert(br->type == unreachable);
+ replaceCurrent(builder.makeUnreachable());
+ }
+ }
+ br->value = nullptr;
+ br->finalize();
+ } else {
+ assert(type == unreachable);
+ // we don't need the br at all
+ replaceCurrent(br->value);
+ }
+ }
+ } else if (auto* sw = curr->dynCast<Switch>()) {
+ if (sw->value) {
+ auto type = sw->value->type;
+ if (isConcreteWasmType(type)) {
+ // we are sending a value. use a local instead
+ Index temp = builder.addVar(getFunction(), type);
+ ourPreludes.push_back(builder.makeSetLocal(temp, sw->value));
+ // we don't know which break target will be hit - assign to them all
+ std::set<Name> names;
+ for (auto target : sw->targets) {
+ names.insert(target);
+ }
+ names.insert(sw->default_);
+ for (auto name : names) {
+ ourPreludes.push_back(builder.makeSetLocal(
+ getTempForBreakTarget(name, type),
+ builder.makeGetLocal(temp, type)
+ ));
+ }
+ sw->value = nullptr;
+ sw->finalize();
+ } else {
+ assert(type == unreachable);
+ // we don't need the br at all
+ replaceCurrent(sw->value);
+ }
+ }
+ }
+ }
+ // continue for general handling of everything, control flow or otherwise
+ curr = getCurrent(); // we may have replaced it
+ // we have changed children
+ ReFinalizeNode().visit(curr);
+ // handle side effects and control flow, things we need to be
+ // in the prelude. note that we must handle anything here, not just
+ // side effects, as a sibling after us may have side effect for us,
+ // and thus we need to move in anticipation of that (e.g., we are
+ // a get, and a later sibling is a tee - if just the tee moves,
+ // that is bade) TODO optimize
+ if (isControlFlowStructure(curr) || EffectAnalyzer(getPassOptions(), curr).hasAnything()) {
+ // we need to move the side effects to the prelude
+ if (curr->type == unreachable) {
+ ourPreludes.push_back(curr);
+ replaceCurrent(builder.makeUnreachable());
+ } else if (curr->type == none) {
+ if (!curr->is<Nop>()) {
+ ourPreludes.push_back(curr);
+ replaceCurrent(builder.makeNop());
+ }
+ } else {
+ // use a local
+ auto type = curr->type;
+ Index temp = builder.addVar(getFunction(), type);
+ ourPreludes.push_back(builder.makeSetLocal(temp, curr));
+ replaceCurrent(builder.makeGetLocal(temp, type));
+ }
+ }
+ // next, finish up: migrate our preludes if we can
+ if (!ourPreludes.empty()) {
+ auto* parent = getParent();
+ if (parent && !isControlFlowStructure(parent)) {
+ auto& parentPreludes = preludes[parent];
+ for (auto* prelude : ourPreludes) {
+ parentPreludes.push_back(prelude);
+ }
+ } else {
+ // keep our preludes, parent will handle them
+ preludes[getCurrent()].swap(ourPreludes);
+ }
+ }
+ }
+
+ void visitFunction(Function* curr) {
+ auto* originalBody = curr->body;
+ // if the body is a block with a result, turn that into a return
+ if (isConcreteWasmType(curr->body->type)) {
+ curr->body = Builder(*getModule()).makeReturn(curr->body);
+ }
+ // the body may have preludes
+ curr->body = getPreludesWithExpression(originalBody, curr->body);
+ }
+
+private:
+ bool isControlFlowStructure(Expression* curr) {
+ return curr->is<Block>() || curr->is<If>() || curr->is<Loop>();
+ }
+
+ // gets an expression, either by itself, or in a block with its
+ // preludes (which we use up) before it
+ Expression* getPreludesWithExpression(Expression* curr) {
+ return getPreludesWithExpression(curr, curr);
+ }
+
+ // gets an expression, either by itself, or in a block with some
+ // preludes (which we use up) for another expression before it
+ Expression* getPreludesWithExpression(Expression* preluder, Expression* after) {
+ auto iter = preludes.find(preluder);
+ if (iter == preludes.end()) return after;
+ // we have preludes
+ auto& thePreludes = iter->second;
+ auto* ret = Builder(*getModule()).makeBlock(thePreludes);
+ thePreludes.clear();
+ ret->list.push_back(after);
+ ret->finalize();
+ return ret;
+ }
+
+ // get the temp local to be used for breaks to that target. allocates
+ // one if there isn't one yet
+ Index getTempForBreakTarget(Name name, WasmType type) {
+ auto iter = breakTemps.find(name);
+ if (iter != breakTemps.end()) {
+ return iter->second;
+ } else {
+ return breakTemps[name] = Builder(*getModule()).addVar(getFunction(), type);
+ }
+ }
+};
+
+Pass *createFlattenPass() {
+ return new Flatten();
+}
+
+} // namespace wasm
+