| /* |
| * Copyright (C) 2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "B3OptimizeAssociativeExpressionTrees.h" |
| |
| #if ENABLE(B3_JIT) |
| |
| #include "B3BasicBlock.h" |
| #include "B3Const32Value.h" |
| #include "B3Const64Value.h" |
| #include "B3InsertionSetInlines.h" |
| #include "B3Opcode.h" |
| #include "B3PhaseScope.h" |
| #include "B3Procedure.h" |
| #include "B3Value.h" |
| #include "B3ValueInlines.h" |
| |
| namespace JSC { namespace B3 { |
| |
| class OptimizeAssociativeExpressionTrees { |
| public: |
| OptimizeAssociativeExpressionTrees(Procedure& proc) |
| : m_proc(proc) |
| { |
| } |
| |
| bool run(); |
| |
| private: |
| int64_t neutralElement(Opcode); |
| bool isAbsorbingElement(Opcode, int64_t); |
| void combineConstants(Opcode, int64_t&, int64_t); |
| void emitValue(Opcode, Value*, unsigned numSeen, InsertionSet&, size_t indexInBlock, Vector<Value*, 4>& results); |
| bool optimizeRootedTree(Value* root, InsertionSet&, size_t indexInBlock, const Vector<unsigned>& useCounts); |
| |
| Procedure& m_proc; |
| bool verbose { false }; |
| }; |
| |
| int64_t OptimizeAssociativeExpressionTrees::neutralElement(Opcode op) |
| { |
| switch (op) { |
| case Add: |
| case BitOr: |
| case BitXor: |
| return 0; |
| case Mul: |
| return 1; |
| case BitAnd: |
| return -1; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| bool OptimizeAssociativeExpressionTrees::isAbsorbingElement(Opcode op, int64_t constant) |
| { |
| switch (op) { |
| case Add: |
| case BitXor: |
| return false; |
| case Mul: |
| case BitAnd: |
| return !constant; |
| case BitOr: |
| return constant == -1; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| void OptimizeAssociativeExpressionTrees::combineConstants(Opcode op, int64_t& const1, int64_t const2) |
| { |
| switch (op) { |
| case Add: |
| const1 += const2; |
| break; |
| case Mul: |
| const1 *= const2; |
| break; |
| case BitAnd: |
| const1 &= const2; |
| break; |
| case BitOr: |
| const1 |= const2; |
| break; |
| case BitXor: |
| const1 ^= const2; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| void OptimizeAssociativeExpressionTrees::emitValue(Opcode op, Value* value, unsigned numSeen, InsertionSet& insertionSet, size_t indexInBlock, Vector<Value*, 4>& results) |
| { |
| switch (op) { |
| case Add: |
| if (numSeen > 1) { |
| Value* constNumSeen; |
| if (value->type() == Int32) |
| constNumSeen = insertionSet.insert<Const32Value>(indexInBlock, value->origin(), numSeen); |
| else |
| constNumSeen = insertionSet.insert<Const64Value>(indexInBlock, value->origin(), static_cast<int64_t>(numSeen)); |
| results.append(insertionSet.insert<Value>(indexInBlock, Mul, value->origin(), value, constNumSeen)); |
| } else |
| results.append(value); |
| break; |
| case Mul: |
| for (unsigned i = 0; i < numSeen; ++i) |
| results.append(value); |
| break; |
| case BitAnd: |
| case BitOr: |
| results.append(value); |
| break; |
| case BitXor: |
| if (numSeen % 2) |
| results.append(value); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| bool OptimizeAssociativeExpressionTrees::optimizeRootedTree(Value* root, InsertionSet& insertionSet, size_t indexInBlock, const Vector<unsigned>& useCounts) |
| { |
| Opcode op = root->opcode(); |
| if ((root->child(0)->opcode() != op || useCounts[root->child(0)->index()] > 1) |
| && (root->child(1)->opcode() != op || useCounts[root->child(1)->index()] > 1)) { |
| // This is a trivial expression tree of size two, we have nothing to do here that B3ReduceStrength cannot do better than us. |
| return false; |
| } |
| |
| // We proceed in three steps: |
| // - gather all the leaves of the expression tree |
| // - sort them, and combine as many as possible |
| // - make a balanced binary tree of them |
| |
| Vector<Value*, 4> leaves; |
| Vector<Value*, 3> worklist = { root->child(0), root->child(1) }; |
| int64_t constant = neutralElement(op); |
| unsigned numVisited = 0; |
| while (!worklist.isEmpty()) { |
| Value* val = worklist.takeLast(); |
| if (val->opcode() == op && useCounts[val->index()] < 2) { |
| worklist.append(val->child(0)); |
| worklist.append(val->child(1)); |
| } else if (val->hasInt()) { |
| combineConstants(op, constant, val->asInt()); |
| numVisited++; |
| } else { |
| numVisited++; |
| leaves.append(val); |
| } |
| } |
| if (isAbsorbingElement(op, constant)) { |
| Value* newRoot; |
| if (root->type() == Int32) |
| newRoot = insertionSet.insert<Const32Value>(indexInBlock, root->origin(), static_cast<int32_t>(constant)); |
| else |
| newRoot = insertionSet.insert<Const64Value>(indexInBlock, root->origin(), constant); |
| root->replaceWithIdentity(newRoot); |
| return true; |
| } |
| if (numVisited < 4) { |
| // This is a nearly-trivial expression of size 3. B3ReduceStrength is still able to deal with such expressions competently, and there is no possible win from balancing them. |
| return false; |
| } |
| |
| std::sort(leaves.begin(), leaves.end(), [](Value* x, Value* y) { |
| return x->index() < y->index(); |
| }); |
| Vector<Value*, 4> optLeaves; |
| Value* lastValue = nullptr; |
| unsigned numSeen = 0; |
| for (Value* value : leaves) { |
| if (lastValue == value) |
| numSeen++; |
| else { |
| if (lastValue) |
| emitValue(op, lastValue, numSeen, insertionSet, indexInBlock, optLeaves); |
| lastValue = value; |
| numSeen = 1; |
| } |
| } |
| if (lastValue) |
| emitValue(op, lastValue, numSeen, insertionSet, indexInBlock, optLeaves); |
| |
| // optLeaves can be empty for trees of BitXor where all leaves happen an even number of times. |
| // In that case, we make the whole tree equivalent to the neutral element (which is 0 for BitXor). |
| if (constant != neutralElement(op) || optLeaves.isEmpty()) { |
| if (root->type() == Int32) |
| optLeaves.append(insertionSet.insert<Const32Value>(indexInBlock, root->origin(), static_cast<int32_t>(constant))); |
| else |
| optLeaves.append(insertionSet.insert<Const64Value>(indexInBlock, root->origin(), constant)); |
| } |
| |
| if (verbose) { |
| dataLog(" Expression tree rooted at ", *root, " (", root->opcode(), ") with leaves (numVisited = ", numVisited, ") "); |
| for (Value* leaf : leaves) |
| dataLog(" ", *leaf); |
| dataLog(" =>"); |
| for (Value* leaf : optLeaves) |
| dataLog(" ", *leaf); |
| dataLog("\n"); |
| } |
| |
| // Finally we can build the balanced binary tree |
| unsigned leafIndex = 0; |
| while (leafIndex + 1 < optLeaves.size()) { |
| optLeaves.append(insertionSet.insert<Value>(indexInBlock, op, root->origin(), optLeaves[leafIndex], optLeaves[leafIndex + 1])); |
| leafIndex += 2; |
| } |
| ASSERT(leafIndex == optLeaves.size() - 1); |
| root->replaceWithIdentity(optLeaves[leafIndex]); |
| return true; |
| } |
| |
| bool OptimizeAssociativeExpressionTrees::run() |
| { |
| bool changed = false; |
| |
| // We proceed in two phases. |
| // In the first one we compute the use counts of each value (of an interesting opcode), and find potential roots of interesting expression trees. |
| // In the second one we optimize each such expression tree in turn. |
| // We need the use counts to avoid duplicating code. |
| |
| Vector<unsigned> useCounts(m_proc.values().size(), 0); // Mapping from Value::m_index to use counts. |
| HashSet<Value*> expressionTreeRoots; |
| HashSet<BasicBlock*> rootOwners; |
| |
| for (BasicBlock* block : m_proc) { |
| for (Value* value : *block) { |
| for (Value* child : value->children()) { |
| if (!child->isInteger()) |
| continue; |
| switch (child->opcode()) { |
| case Mul: |
| case Add: |
| case BitAnd: |
| case BitOr: |
| case BitXor: |
| useCounts[child->index()]++; |
| if (child->opcode() != value->opcode() || useCounts[child->index()] > 1) { |
| expressionTreeRoots.add(child); |
| rootOwners.add(child->owner); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| InsertionSet insertionSet = InsertionSet(m_proc); |
| for (BasicBlock* block : rootOwners) { |
| for (unsigned index = 0; index < block->size(); ++index) { |
| Value* value = block->at(index); |
| if (expressionTreeRoots.contains(value)) |
| changed |= optimizeRootedTree(value, insertionSet, index, useCounts); |
| } |
| insertionSet.execute(block); |
| } |
| |
| return changed; |
| } |
| |
| bool optimizeAssociativeExpressionTrees(Procedure& proc) |
| { |
| PhaseScope phaseScope(proc, "optimizeAssociativeExpressionTrees"); |
| OptimizeAssociativeExpressionTrees optimizeAssociativeExpressionTrees(proc); |
| return optimizeAssociativeExpressionTrees.run(); |
| } |
| |
| } } // namespace JSC::B3 |
| |
| #endif // ENABLE(B3_JIT) |