| /* |
| * 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) |
| // (local.tee $x |
| // (local.get $y) |
| // ) |
| // (i32.const 100) |
| // (local.get $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 <ir/local-graph.h> |
| #include <pass.h> |
| #include <wasm-builder.h> |
| #include <wasm.h> |
| |
| namespace wasm { |
| |
| struct MergeLocals |
| : public WalkerPass< |
| PostWalker<MergeLocals, UnifiedExpressionVisitor<MergeLocals>>> { |
| bool isFunctionParallel() override { return true; } |
| |
| // This pass merges locals, mapping the originals to new ones. |
| // FIXME DWARF updating does not handle local changes yet. |
| bool invalidatesDWARF() override { return true; } |
| |
| std::unique_ptr<Pass> create() override { |
| return std::make_unique<MergeLocals>(); |
| } |
| |
| void doWalkFunction(Function* func) { |
| // first, instrument the graph by modifying each copy |
| // (local.set $x |
| // (local.get $y) |
| // ) |
| // to |
| // (local.set $x |
| // (local.tee $y |
| // (local.get $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<LocalSet*> copies; |
| |
| void visitLocalSet(LocalSet* curr) { |
| if (auto* get = curr->value->dynCast<LocalGet>()) { |
| if (get->index != curr->index) { |
| Builder builder(*getModule()); |
| auto* trivial = builder.makeLocalTee(get->index, get, get->type); |
| curr->value = trivial; |
| copies.push_back(curr); |
| } |
| } |
| } |
| |
| void optimizeCopies() { |
| if (copies.empty()) { |
| return; |
| } |
| auto* func = getFunction(); |
| |
| // Compute the local graph. Note that we *cannot* do this lazily, as we want |
| // to read from the original state of the function while we are doing |
| // changes on it. That is, using an eager graph makes a snapshot of the |
| // initial state, which is what we want. If we can avoid that, this pass can |
| // be sped up by around 25%. |
| LocalGraph preGraph(func, getModule()); |
| preGraph.computeSetInfluences(); |
| |
| // optimize each copy |
| std::unordered_map<LocalSet*, LocalSet*> optimizedToCopy, |
| optimizedToTrivial; |
| for (auto* copy : copies) { |
| auto* trivial = copy->value->cast<LocalSet>(); |
| bool canOptimizeToCopy = false; |
| auto& trivialInfluences = preGraph.getSetInfluences(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); |
| auto& sets = preGraph.getSets(influencedGet); |
| if (sets.size() == 1) { |
| // this is ok |
| assert(*sets.begin() == trivial); |
| // If local types are different (when one is a subtype of the |
| // other), don't optimize |
| if (func->getLocalType(copy->index) != influencedGet->type) { |
| canOptimizeToCopy = false; |
| } |
| } 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 |
| // (local.set $x |
| // (local.get $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 the trivial set we added has influences, it means $y lives on |
| if (!trivialInfluences.empty()) { |
| auto& copyInfluences = preGraph.getSetInfluences(copy); |
| if (!copyInfluences.empty()) { |
| bool canOptimizeToTrivial = true; |
| for (auto* influencedGet : copyInfluences) { |
| // as above, avoid merges/phis |
| assert(influencedGet->index == copy->index); |
| auto& sets = preGraph.getSets(influencedGet); |
| if (sets.size() == 1) { |
| // this is ok |
| assert(*sets.begin() == copy); |
| // If local types are different (when one is a subtype of the |
| // other), don't optimize |
| if (func->getLocalType(trivial->index) != influencedGet->type) { |
| canOptimizeToTrivial = false; |
| } |
| } 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(func, getModule()); |
| postGraph.computeSetInfluences(); |
| for (auto& [copy, trivial] : optimizedToCopy) { |
| auto& trivialInfluences = preGraph.getSetInfluences(trivial); |
| for (auto* influencedGet : trivialInfluences) { |
| // verify the set |
| auto& sets = postGraph.getSets(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& [copy, trivial] : optimizedToTrivial) { |
| auto& copyInfluences = preGraph.getSetInfluences(copy); |
| for (auto* influencedGet : copyInfluences) { |
| // verify the set |
| auto& sets = postGraph.getSets(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<LocalSet>()->value; |
| } |
| } |
| }; |
| |
| Pass* createMergeLocalsPass() { return new MergeLocals(); } |
| |
| } // namespace wasm |