summaryrefslogtreecommitdiff
path: root/src/passes/MergeLocals.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/passes/MergeLocals.cpp')
-rw-r--r--src/passes/MergeLocals.cpp220
1 files changed, 220 insertions, 0 deletions
diff --git a/src/passes/MergeLocals.cpp b/src/passes/MergeLocals.cpp
new file mode 100644
index 000000000..31be495c1
--- /dev/null
+++ b/src/passes/MergeLocals.cpp
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+
+//
+// Merges locals when it is beneficial to do so.
+//
+// An obvious case is when locals are copied. In that case, two locals have the
+// same value in a range, and we can pick which of the two to use. For
+// example, in
+//
+// (if (result i32)
+// (tee_local $x
+// (get_local $y)
+// )
+// (i32.const 100)
+// (get_local $x)
+// )
+//
+// If that assignment of $y is never used again, everything is fine. But if
+// if is, then the live range of $y does not end in that get, and will
+// necessarily overlap with that of $x - making them appear to interfere
+// with each other in coalesce-locals, even though the value is identical.
+//
+// To fix that, we replace uses of $y with uses of $x. This extends $x's
+// live range and shrinks $y's live range. This tradeoff is not always good,
+// but $x and $y definitely overlap already, so trying to shrink the overlap
+// makes sense - if we remove the overlap entirely, we may be able to let
+// $x and $y be coalesced later.
+//
+// If we can remove only some of $y's uses, then we are definitely not
+// removing the overlap, and they do conflict. In that case, it's not clear
+// if this is beneficial or not, and we don't do it for now
+// TODO: investigate more
+//
+
+#include <wasm.h>
+#include <pass.h>
+#include <wasm-builder.h>
+#include <ir/local-graph.h>
+
+namespace wasm {
+
+struct MergeLocals : public WalkerPass<PostWalker<MergeLocals, UnifiedExpressionVisitor<MergeLocals>>> {
+ bool isFunctionParallel() override { return true; }
+
+ Pass* create() override { return new MergeLocals(); }
+
+ void doWalkFunction(Function* func) {
+ // first, instrument the graph by modifying each copy
+ // (set_local $x
+ // (get_local $y)
+ // )
+ // to
+ // (set_local $x
+ // (tee_local $y
+ // (get_local $y)
+ // )
+ // )
+ // That is, we add a trivial assign of $y. This ensures we
+ // have a new assignment of $y at the location of the copy,
+ // which makes it easy for us to see if the value if $y
+ // is still used after that point
+ super::doWalkFunction(func);
+
+ // optimize the copies, merging when we can, and removing
+ // the trivial assigns we added temporarily
+ optimizeCopies();
+ }
+
+ std::vector<SetLocal*> copies;
+
+ void visitSetLocal(SetLocal* curr) {
+ if (auto* get = curr->value->dynCast<GetLocal>()) {
+ if (get->index != curr->index) {
+ Builder builder(*getModule());
+ auto* trivial = builder.makeTeeLocal(get->index, get);
+ curr->value = trivial;
+ copies.push_back(curr);
+ }
+ }
+ }
+
+ void optimizeCopies() {
+ if (copies.empty()) return;
+ // compute all dependencies
+ LocalGraph preGraph(getFunction(), getModule());
+ preGraph.computeInfluences();
+ // optimize each copy
+ std::unordered_map<SetLocal*, SetLocal*> optimizedToCopy, optimizedToTrivial;
+ for (auto* copy : copies) {
+ auto* trivial = copy->value->cast<SetLocal>();
+ bool canOptimizeToCopy = false;
+ auto& trivialInfluences = preGraph.setInfluences[trivial];
+ if (!trivialInfluences.empty()) {
+ canOptimizeToCopy = true;
+ for (auto* influencedGet : trivialInfluences) {
+ // this get uses the trivial write, so it uses the value in the copy.
+ // however, it may depend on other writes too, if there is a merge/phi,
+ // and in that case we can't do anything
+ assert(influencedGet->index == trivial->index);
+ if (preGraph.getSetses[influencedGet].size() == 1) {
+ // this is ok
+ assert(*preGraph.getSetses[influencedGet].begin() == trivial);
+ } else {
+ canOptimizeToCopy = false;
+ break;
+ }
+ }
+ }
+ if (canOptimizeToCopy) {
+ // worth it for this copy, do it
+ for (auto* influencedGet : trivialInfluences) {
+ influencedGet->index = copy->index;
+ }
+ optimizedToCopy[copy] = trivial;
+ } else {
+ // alternatively, we can try to remove the conflict in the opposite way: given
+ // (set_local $x
+ // (get_local $y)
+ // )
+ // we can look for uses of $x that could instead be uses of $y. this extends
+ // $y's live range, but if it removes the conflict between $x and $y, it may be
+ // worth it
+ if (!trivialInfluences.empty()) { // if the trivial set we added has influences, it means $y lives on
+ auto& copyInfluences = preGraph.setInfluences[copy];
+ if (!copyInfluences.empty()) {
+ bool canOptimizeToTrivial = true;
+ for (auto* influencedGet : copyInfluences) {
+ // as above, avoid merges/phis
+ assert(influencedGet->index == copy->index);
+ if (preGraph.getSetses[influencedGet].size() == 1) {
+ // this is ok
+ assert(*preGraph.getSetses[influencedGet].begin() == copy);
+ } else {
+ canOptimizeToTrivial = false;
+ break;
+ }
+ }
+ if (canOptimizeToTrivial) {
+ // worth it for this copy, do it
+ for (auto* influencedGet : copyInfluences) {
+ influencedGet->index = trivial->index;
+ }
+ optimizedToTrivial[copy] = trivial;
+ // note that we don't
+ }
+ }
+ }
+ }
+ }
+ if (!optimizedToCopy.empty() || !optimizedToTrivial.empty()) {
+ // finally, we need to verify that the changes work properly, that is,
+ // they use the value from the right place (and are not affected by
+ // another set of the index we changed to).
+ // if one does not work, we need to undo all its siblings (don't extend
+ // the live range unless we are definitely removing a conflict, same
+ // logic as before).
+ LocalGraph postGraph(getFunction(), getModule());
+ postGraph.computeInfluences();
+ for (auto& pair : optimizedToCopy) {
+ auto* copy = pair.first;
+ auto* trivial = pair.second;
+ auto& trivialInfluences = preGraph.setInfluences[trivial];
+ for (auto* influencedGet : trivialInfluences) {
+ // verify the set
+ auto& sets = postGraph.getSetses[influencedGet];
+ if (sets.size() != 1 || *sets.begin() != copy) {
+ // not good, undo all the changes for this copy
+ for (auto* undo : trivialInfluences) {
+ undo->index = trivial->index;
+ }
+ break;
+ }
+ }
+ }
+ for (auto& pair : optimizedToTrivial) {
+ auto* copy = pair.first;
+ auto* trivial = pair.second;
+ auto& copyInfluences = preGraph.setInfluences[copy];
+ for (auto* influencedGet : copyInfluences) {
+ // verify the set
+ auto& sets = postGraph.getSetses[influencedGet];
+ if (sets.size() != 1 || *sets.begin() != trivial) {
+ // not good, undo all the changes for this copy
+ for (auto* undo : copyInfluences) {
+ undo->index = copy->index;
+ }
+ break;
+ }
+ }
+ // if this change was ok, we can probably remove the copy itself,
+ // but we leave that for other passes
+ }
+ }
+ // remove the trivial sets
+ for (auto* copy : copies) {
+ copy->value = copy->value->cast<SetLocal>()->value;
+ }
+ }
+};
+
+Pass *createMergeLocalsPass() {
+ return new MergeLocals();
+}
+
+} // namespace wasm
+