summaryrefslogtreecommitdiff
path: root/src/cfg/liveness-traversal.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/cfg/liveness-traversal.h')
-rw-r--r--src/cfg/liveness-traversal.h234
1 files changed, 234 insertions, 0 deletions
diff --git a/src/cfg/liveness-traversal.h b/src/cfg/liveness-traversal.h
new file mode 100644
index 000000000..24578dcd7
--- /dev/null
+++ b/src/cfg/liveness-traversal.h
@@ -0,0 +1,234 @@
+#include <wasm-printing.h>
+/*
+ * 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, while traversing it.
+//
+// Note that this is not the same as the relooper CFG. The relooper is
+// designed for compilation to an AST, this is for processing. There is
+// no built-in support for transforming this CFG into the AST back
+// again, it is just metadata on the side for computation purposes.
+//
+// Usage: As the traversal proceeds, you can note information and add it to
+// the current basic block using currBasicBlock, on the contents
+// property, whose type is user-defined.
+//
+
+#ifndef liveness_traversal_h
+#define liveness_traversal_h
+
+#include "support/sorted_vector.h"
+#include "wasm.h"
+#include "wasm-builder.h"
+#include "wasm-traversal.h"
+#include "cfg-traversal.h"
+
+namespace wasm {
+
+// A set of locals. This is optimized for comparisons,
+// mergings, and iteration on elements, assuming that there
+// may be a great many potential elements but actual sets
+// may be fairly small. Specifically, we use a sorted
+// vector.
+typedef SortedVector LocalSet;
+
+// A liveness-relevant action. Supports a get, a set, or an
+// "other" which can be used for other purposes, to mark
+// their position in a block
+struct Action {
+ enum What {
+ Get = 0,
+ Set = 1,
+ Other = 2
+ };
+ What what;
+ Index index; // the local index read or written
+ Expression** origin; // the origin
+ bool effective; // whether a store is actually effective, i.e., may be read
+
+ Action(What what, Index index, Expression** origin) : what(what), index(index), origin(origin), effective(false) {
+ assert(what != Other);
+ if (what == Get) assert((*origin)->is<GetLocal>());
+ if (what == Set) assert((*origin)->is<SetLocal>());
+ }
+ Action(Expression** origin) : what(Other), origin(origin) {}
+
+ bool isGet() { return what == Get; }
+ bool isSet() { return what == Set; }
+ bool isOther() { return what == Other; }
+};
+
+// information about liveness in a basic block
+struct Liveness {
+ LocalSet start, end; // live locals at the start and end
+ std::vector<Action> actions; // actions occurring in this block
+
+ void dump(Function* func) {
+ if (actions.empty()) return;
+ std::cout << " actions:\n";
+ for (auto& action : actions) {
+ std::cout << " " << (action.isGet() ? "get" : "set") << " " << func->getLocalName(action.index) << "\n";
+ }
+ }
+};
+
+template<typename SubType, typename VisitorType>
+struct LivenessWalker : public CFGWalker<SubType, VisitorType, Liveness> {
+ typedef typename CFGWalker<SubType, VisitorType, Liveness>::BasicBlock BasicBlock;
+
+ Index numLocals;
+ std::unordered_set<BasicBlock*> liveBlocks;
+ std::vector<uint8_t> copies; // canonicalized - accesses should check (low, high) TODO: use a map for high N, as this tends to be sparse? or don't look at copies at all for big N?
+ std::vector<Index> totalCopies; // total # of copies for each local, with all others
+
+ // cfg traversal work
+
+ static void doVisitGetLocal(SubType* self, Expression** currp) {
+ auto* curr = (*currp)->cast<GetLocal>();
+ // if in unreachable code, ignore
+ if (!self->currBasicBlock) {
+ *currp = Builder(*self->getModule()).replaceWithIdenticalType(curr);
+ return;
+ }
+ self->currBasicBlock->contents.actions.emplace_back(Action::Get, curr->index, currp);
+ }
+
+ static void doVisitSetLocal(SubType* self, Expression** currp) {
+ auto* curr = (*currp)->cast<SetLocal>();
+ // if in unreachable code, we don't need the tee (but might need the value, if it has side effects)
+ if (!self->currBasicBlock) {
+ if (curr->isTee()) {
+ *currp = curr->value;
+ } else {
+ *currp = Builder(*self->getModule()).makeDrop(curr->value);
+ }
+ return;
+ }
+ self->currBasicBlock->contents.actions.emplace_back(Action::Set, curr->index, currp);
+ // if this is a copy, note it
+ if (auto* get = self->getCopy(curr)) {
+ // add 2 units, so that backedge prioritization can decide ties, but not much more
+ self->addCopy(curr->index, get->index);
+ self->addCopy(curr->index, get->index);
+ }
+ }
+
+ // A simple copy is a set of a get. A more interesting copy
+ // is a set of an if with a value, where one side a get.
+ // That can happen when we create an if value in simplify-locals. TODO: recurse into
+ // nested ifs, and block return values? Those cases are trickier, need to
+ // count to see if worth it.
+ // TODO: an if can have two copies
+ GetLocal* getCopy(SetLocal* set) {
+ if (auto* get = set->value->dynCast<GetLocal>()) return get;
+ if (auto* iff = set->value->dynCast<If>()) {
+ if (auto* get = iff->ifTrue->dynCast<GetLocal>()) return get;
+ if (iff->ifFalse) {
+ if (auto* get = iff->ifFalse->dynCast<GetLocal>()) return get;
+ }
+ }
+ return nullptr;
+ }
+
+ // main entry point
+
+ void doWalkFunction(Function* func) {
+ numLocals = func->getNumLocals();
+ copies.resize(numLocals * numLocals);
+ std::fill(copies.begin(), copies.end(), 0);
+ totalCopies.resize(numLocals);
+ std::fill(totalCopies.begin(), totalCopies.end(), 0);
+ // create the CFG by walking the IR
+ CFGWalker<SubType, VisitorType, Liveness>::doWalkFunction(func);
+ // ignore links to dead blocks, so they don't confuse us and we can see their stores are all ineffective
+ liveBlocks = CFGWalker<SubType, VisitorType, Liveness>::findLiveBlocks();
+ CFGWalker<SubType, VisitorType, Liveness>::unlinkDeadBlocks(liveBlocks);
+ // flow liveness across blocks
+ flowLiveness();
+ }
+
+ void flowLiveness() {
+ // keep working while stuff is flowing
+ std::unordered_set<BasicBlock*> queue;
+ for (auto& curr : CFGWalker<SubType, VisitorType, Liveness>::basicBlocks) {
+ if (liveBlocks.count(curr.get()) == 0) continue; // ignore dead blocks
+ queue.insert(curr.get());
+ // do the first scan through the block, starting with nothing live at the end, and updating the liveness at the start
+ scanLivenessThroughActions(curr->contents.actions, curr->contents.start);
+ }
+ // at every point in time, we assume we already noted interferences between things already known alive at the end, and scanned back through the block using that
+ while (queue.size() > 0) {
+ auto iter = queue.begin();
+ auto* curr = *iter;
+ queue.erase(iter);
+ LocalSet live;
+ if (!mergeStartsAndCheckChange(curr->out, curr->contents.end, live)) continue;
+ assert(curr->contents.end.size() < live.size());
+ curr->contents.end = live;
+ scanLivenessThroughActions(curr->contents.actions, live);
+ // liveness is now calculated at the start. if something
+ // changed, all predecessor blocks need recomputation
+ if (curr->contents.start == live) continue;
+ assert(curr->contents.start.size() < live.size());
+ curr->contents.start = live;
+ for (auto* in : curr->in) {
+ queue.insert(in);
+ }
+ }
+ }
+
+ // merge starts of a list of blocks. return
+ // whether anything changed vs an old state (which indicates further processing is necessary).
+ bool mergeStartsAndCheckChange(std::vector<BasicBlock*>& blocks, LocalSet& old, LocalSet& ret) {
+ if (blocks.size() == 0) return false;
+ ret = blocks[0]->contents.start;
+ if (blocks.size() > 1) {
+ // more than one, so we must merge
+ for (Index i = 1; i < blocks.size(); i++) {
+ ret = ret.merge(blocks[i]->contents.start);
+ }
+ }
+ return old != ret;
+ }
+
+ void scanLivenessThroughActions(std::vector<Action>& actions, LocalSet& live) {
+ // move towards the front
+ for (int i = int(actions.size()) - 1; i >= 0; i--) {
+ auto& action = actions[i];
+ if (action.isGet()) {
+ live.insert(action.index);
+ } else {
+ live.erase(action.index);
+ }
+ }
+ }
+
+ void addCopy(Index i, Index j) {
+ auto k = std::min(i, j) * numLocals + std::max(i, j);
+ copies[k] = std::min(copies[k], uint8_t(254)) + 1;
+ totalCopies[i]++;
+ totalCopies[j]++;
+ }
+
+ uint8_t getCopies(Index i, Index j) {
+ return copies[std::min(i, j) * numLocals + std::max(i, j)];
+ }
+};
+
+} // namespace wasm
+
+#endif // liveness_traversal_h