summaryrefslogtreecommitdiff
path: root/src/passes/ReReloop.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/passes/ReReloop.cpp')
-rw-r--r--src/passes/ReReloop.cpp325
1 files changed, 325 insertions, 0 deletions
diff --git a/src/passes/ReReloop.cpp b/src/passes/ReReloop.cpp
new file mode 100644
index 000000000..6bdc0eb06
--- /dev/null
+++ b/src/passes/ReReloop.cpp
@@ -0,0 +1,325 @@
+/*
+ * 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.
+ */
+
+//
+// Convert the AST to a CFG, and optimize+convert it back to the AST
+// using the relooper.
+//
+// This pass depends on flatten-control-flow being run before it.
+//
+
+#include <memory>
+
+#include "wasm.h"
+#include "wasm-builder.h"
+#include "wasm-traversal.h"
+#include "pass.h"
+#include "cfg/Relooper.h"
+
+namespace wasm {
+
+struct ReReloop : public Pass {
+ bool isFunctionParallel() override { return true; }
+
+ Pass* create() override { return new ReReloop; }
+
+ CFG::Relooper relooper;
+ std::unique_ptr<Builder> builder;
+
+ // block handling
+
+ CFG::Block* currCFGBlock = nullptr;
+
+ CFG::Block* makeCFGBlock() {
+ auto* ret = new CFG::Block(builder->makeBlock());
+ relooper.AddBlock(ret);
+ return ret;
+ }
+
+ CFG::Block* setCurrCFGBlock(CFG::Block* curr) {
+ if (currCFGBlock) {
+ finishBlock();
+ }
+ return currCFGBlock = curr;
+ }
+
+ CFG::Block* startCFGBlock() {
+ return setCurrCFGBlock(makeCFGBlock());
+ }
+
+ CFG::Block* getCurrCFGBlock() {
+ return currCFGBlock;
+ }
+
+ Block* getCurrBlock() {
+ return currCFGBlock->Code->cast<Block>();
+ }
+
+ void finishBlock() {
+ getCurrBlock()->finalize();
+ }
+
+ // break handling
+
+ std::map<Name, CFG::Block*> breakTargets;
+
+ void addBreakTarget(Name name, CFG::Block* target) {
+ breakTargets[name] = target;
+ }
+
+ CFG::Block* getBreakTarget(Name name) {
+ return breakTargets[name];
+ }
+
+ // branch handling
+
+ void addBranch(CFG::Block* from, CFG::Block* to, Expression* condition = nullptr) {
+ from->AddBranchTo(to, condition);
+ }
+
+ void addSwitchBranch(CFG::Block* from, CFG::Block* to, const std::set<Index>& values) {
+ std::vector<Index> list;
+ for (auto i : values) list.push_back(i);
+ from->AddSwitchBranchTo(to, std::move(list));
+ }
+
+ // we work using a stack of control flow tasks
+
+ struct Task {
+ ReReloop& parent;
+ Task(ReReloop& parent) : parent(parent) {}
+ virtual void run() {
+ WASM_UNREACHABLE();
+ }
+ };
+
+ typedef std::shared_ptr<Task> TaskPtr;
+ std::vector<TaskPtr> stack;
+
+ struct TriageTask : public Task {
+ Expression* curr;
+
+ TriageTask(ReReloop& parent, Expression* curr) : Task(parent), curr(curr) {}
+
+ void run() override {
+ parent.triage(curr);
+ }
+ };
+
+ struct BlockTask : public Task {
+ Block* curr;
+ CFG::Block* later;
+
+ BlockTask(ReReloop& parent, Block* curr) : Task(parent), curr(curr) {}
+
+ static void handle(ReReloop& parent, Block* curr) {
+ if (curr->name.is()) {
+ // we may be branched to. create a target, and
+ // ensure we are called at the join point
+ auto task = std::make_shared<BlockTask>(parent, curr);
+ task->curr = curr;
+ task->later = parent.makeCFGBlock();
+ parent.addBreakTarget(curr->name, task->later);
+ parent.stack.push_back(task);
+ }
+ auto& list = curr->list;
+ for (int i = int(list.size()) - 1; i >= 0; i--) {
+ parent.stack.push_back(std::make_shared<TriageTask>(parent, list[i]));
+ }
+ }
+
+ void run() override {
+ // add fallthrough
+ parent.addBranch(parent.getCurrCFGBlock(), later);
+ parent.setCurrCFGBlock(later);
+ }
+ };
+
+ struct LoopTask : public Task {
+ static void handle(ReReloop& parent, Loop* curr) {
+ parent.stack.push_back(std::make_shared<TriageTask>(parent, curr->body));
+ if (curr->name.is()) {
+ // we may be branched to. create a target
+ auto* before = parent.getCurrCFGBlock();
+ auto* top = parent.startCFGBlock();
+ parent.addBreakTarget(curr->name, top);
+ parent.addBranch(before, top);
+ }
+ }
+ };
+
+ struct IfTask : public Task {
+ If* curr;
+ CFG::Block* condition;
+ CFG::Block* ifTrueEnd;
+ int phase = 0;
+
+ IfTask(ReReloop& parent, If* curr) : Task(parent), curr(curr) {}
+
+ static void handle(ReReloop& parent, If* curr) {
+ auto task = std::make_shared<IfTask>(parent, curr);
+ task->curr = curr;
+ task->condition = parent.getCurrCFGBlock();
+ auto* ifTrueBegin = parent.startCFGBlock();
+ parent.addBranch(task->condition, ifTrueBegin, curr->condition);
+ if (curr->ifFalse) {
+ parent.stack.push_back(task);
+ parent.stack.push_back(std::make_shared<TriageTask>(parent, curr->ifFalse));
+ }
+ parent.stack.push_back(task);
+ parent.stack.push_back(std::make_shared<TriageTask>(parent, curr->ifTrue));
+ }
+
+ void run() override {
+ if (phase == 0) {
+ // end of ifTrue
+ ifTrueEnd = parent.getCurrCFGBlock();
+ auto* after = parent.startCFGBlock();
+ parent.addBranch(condition, after); // if condition was false, go after the ifTrue, to ifFalse or outside
+ if (!curr->ifFalse) {
+ parent.addBranch(ifTrueEnd, after);
+ }
+ phase++;
+ } else if (phase == 1) {
+ // end if ifFalse
+ auto* ifFalseEnd = parent.getCurrCFGBlock();
+ auto* after = parent.startCFGBlock();
+ parent.addBranch(ifTrueEnd, after);
+ parent.addBranch(ifFalseEnd, after);
+ } else {
+ WASM_UNREACHABLE();
+ }
+ }
+ };
+
+ struct BreakTask : public Task {
+ static void handle(ReReloop& parent, Break* curr) {
+ // add the branch. note how if the condition is false, it is the right value there as well
+ auto* before = parent.getCurrCFGBlock();
+ parent.addBranch(before, parent.getBreakTarget(curr->name), curr->condition);
+ if (curr->condition) {
+ auto* after = parent.startCFGBlock();
+ parent.addBranch(before, after);
+ } else {
+ parent.stopControlFlow();
+ }
+ }
+ };
+
+ struct SwitchTask : public Task {
+ static void handle(ReReloop& parent, Switch* curr) {
+ // set the switch condition for the block ending now
+ auto* before = parent.getCurrCFGBlock();
+ assert(!before->SwitchCondition);
+ before->SwitchCondition = curr->condition;
+ std::map<Name, std::set<Index>> targetValues;
+ auto& targets = curr->targets;
+ auto num = targets.size();
+ for (Index i = 0; i < num; i++) {
+ targetValues[targets[i]].insert(i);
+ }
+ for (auto& iter : targetValues) {
+ parent.addSwitchBranch(before, parent.getBreakTarget(iter.first), iter.second);
+ }
+ // the default may be among the targets, in which case, we can't add it simply as
+ // it would be a duplicate, so create a temp block
+ if (targetValues.count(curr->default_) == 0) {
+ parent.addSwitchBranch(before, parent.getBreakTarget(curr->default_), std::set<Index>());
+ } else {
+ auto* temp = parent.startCFGBlock();
+ parent.addSwitchBranch(before, temp, std::set<Index>());
+ parent.addBranch(temp, parent.getBreakTarget(curr->default_));
+ }
+ parent.stopControlFlow();
+ }
+ };
+
+ struct ReturnTask : public Task {
+ static void handle(ReReloop& parent, Return* curr) {
+ // reuse the return
+ parent.getCurrBlock()->list.push_back(curr);
+ parent.stopControlFlow();
+ }
+ };
+
+ struct UnreachableTask : public Task {
+ static void handle(ReReloop& parent, Unreachable* curr) {
+ // reuse the unreachable
+ parent.getCurrBlock()->list.push_back(curr);
+ parent.stopControlFlow();
+ }
+ };
+
+ // handle an element we encounter
+
+ void triage(Expression* curr) {
+ if (auto* block = curr->dynCast<Block>()) {
+ BlockTask::handle(*this, block);
+ } else if (auto* loop = curr->dynCast<Loop>()) {
+ LoopTask::handle(*this, loop);
+ } else if (auto* iff = curr->dynCast<If>()) {
+ IfTask::handle(*this, iff);
+ } else if (auto* br = curr->dynCast<Break>()) {
+ BreakTask::handle(*this, br);
+ } else if (auto* sw = curr->dynCast<Switch>()) {
+ SwitchTask::handle(*this, sw);
+ } else if (auto* ret = curr->dynCast<Return>()) {
+ ReturnTask::handle(*this, ret);
+ } else if (auto* un = curr->dynCast<Unreachable>()) {
+ UnreachableTask::handle(*this, un);
+ } else {
+ // not control flow, so just a simple element
+ getCurrBlock()->list.push_back(curr);
+ }
+ }
+
+ void stopControlFlow() {
+ startCFGBlock();
+ // TODO: optimize with this?
+ }
+
+ void runFunction(PassRunner* runner, Module* module, Function* function) override {
+ // since control flow is flattened, this is pretty simple
+ // first, traverse the function body. note how we don't need to traverse
+ // into expressions, as we know they contain no control flow
+ builder = make_unique<Builder>(*module);
+ auto* entry = startCFGBlock();
+ stack.push_back(TaskPtr(new TriageTask(*this, function->body)));
+ // main loop
+ while (stack.size() > 0) {
+ TaskPtr curr = stack.back();
+ stack.pop_back();
+ curr->run();
+ }
+ // finish the current block
+ finishBlock();
+ // run the relooper to recreate control flow
+ relooper.Calculate(entry);
+ // render
+ {
+ auto temp = builder->addVar(function, i32);
+ CFG::RelooperBuilder builder(*module, temp);
+ function->body = relooper.Render(builder);
+ }
+ }
+};
+
+Pass *createReReloopPass() {
+ return new ReReloop();
+}
+
+} // namespace wasm
+