summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/b3/B3ReduceStrength.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/JavaScriptCore/b3/B3ReduceStrength.cpp')
-rw-r--r--Source/JavaScriptCore/b3/B3ReduceStrength.cpp2420
1 files changed, 2420 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/b3/B3ReduceStrength.cpp b/Source/JavaScriptCore/b3/B3ReduceStrength.cpp
new file mode 100644
index 000000000..4363b9e2c
--- /dev/null
+++ b/Source/JavaScriptCore/b3/B3ReduceStrength.cpp
@@ -0,0 +1,2420 @@
+/*
+ * Copyright (C) 2015-2016 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 "B3ReduceStrength.h"
+
+#if ENABLE(B3_JIT)
+
+#include "B3BasicBlockInlines.h"
+#include "B3BlockInsertionSet.h"
+#include "B3ComputeDivisionMagic.h"
+#include "B3ControlValue.h"
+#include "B3Dominators.h"
+#include "B3IndexSet.h"
+#include "B3InsertionSetInlines.h"
+#include "B3MemoryValue.h"
+#include "B3PhaseScope.h"
+#include "B3PhiChildren.h"
+#include "B3ProcedureInlines.h"
+#include "B3PureCSE.h"
+#include "B3SlotBaseValue.h"
+#include "B3StackSlot.h"
+#include "B3UpsilonValue.h"
+#include "B3ValueKeyInlines.h"
+#include "B3ValueInlines.h"
+#include "B3Variable.h"
+#include "B3VariableValue.h"
+#include <wtf/GraphNodeWorklist.h>
+#include <wtf/HashMap.h>
+
+namespace JSC { namespace B3 {
+
+namespace {
+
+// The goal of this phase is to:
+//
+// - Replace operations with less expensive variants. This includes constant folding and classic
+// strength reductions like turning Mul(x, 1 << k) into Shl(x, k).
+//
+// - Reassociate constant operations. For example, Load(Add(x, c)) is turned into Load(x, offset = c)
+// and Add(Add(x, c), d) is turned into Add(x, c + d).
+//
+// - Canonicalize operations. There are some cases where it's not at all obvious which kind of
+// operation is less expensive, but it's useful for subsequent phases - particularly LowerToAir -
+// to have only one way of representing things.
+//
+// This phase runs to fixpoint. Therefore, the canonicalizations must be designed to be monotonic.
+// For example, if we had a canonicalization that said that Add(x, -c) should be Sub(x, c) and
+// another canonicalization that said that Sub(x, d) should be Add(x, -d), then this phase would end
+// up running forever. We don't want that.
+//
+// Therefore, we need to prioritize certain canonical forms over others. Naively, we want strength
+// reduction to reduce the number of values, and so a form involving fewer total values is more
+// canonical. But we might break this, for example when reducing strength of Mul(x, 9). This could be
+// better written as Add(Shl(x, 3), x), which also happens to be representable using a single
+// instruction on x86.
+//
+// Here are some of the rules we have:
+//
+// Canonical form of logical not: BitXor(value, 1). We may have to avoid using this form if we don't
+// know for sure that 'value' is 0-or-1 (i.e. returnsBool). In that case we fall back on
+// Equal(value, 0).
+//
+// Canonical form of commutative operations: if the operation involves a constant, the constant must
+// come second. Add(x, constant) is canonical, while Add(constant, x) is not. If there are no
+// constants then the canonical form involves the lower-indexed value first. Given Add(x, y), it's
+// canonical if x->index() <= y->index().
+
+bool verbose = false;
+
+// FIXME: This IntRange stuff should be refactored into a general constant propagator. It's weird
+// that it's just sitting here in this file.
+class IntRange {
+public:
+ IntRange()
+ {
+ }
+
+ IntRange(int64_t min, int64_t max)
+ : m_min(min)
+ , m_max(max)
+ {
+ }
+
+ template<typename T>
+ static IntRange top()
+ {
+ return IntRange(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
+ }
+
+ static IntRange top(Type type)
+ {
+ switch (type) {
+ case Int32:
+ return top<int32_t>();
+ case Int64:
+ return top<int64_t>();
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+ template<typename T>
+ static IntRange rangeForMask(T mask)
+ {
+ if (!(mask + 1))
+ return top<T>();
+ return IntRange(0, mask);
+ }
+
+ static IntRange rangeForMask(int64_t mask, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return rangeForMask<int32_t>(static_cast<int32_t>(mask));
+ case Int64:
+ return rangeForMask<int64_t>(mask);
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+ template<typename T>
+ static IntRange rangeForZShr(int32_t shiftAmount)
+ {
+ typename std::make_unsigned<T>::type mask = 0;
+ mask--;
+ mask >>= shiftAmount;
+ return rangeForMask<T>(static_cast<T>(mask));
+ }
+
+ static IntRange rangeForZShr(int32_t shiftAmount, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return rangeForZShr<int32_t>(shiftAmount);
+ case Int64:
+ return rangeForZShr<int64_t>(shiftAmount);
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+ int64_t min() const { return m_min; }
+ int64_t max() const { return m_max; }
+
+ void dump(PrintStream& out) const
+ {
+ out.print("[", m_min, ",", m_max, "]");
+ }
+
+ template<typename T>
+ bool couldOverflowAdd(const IntRange& other)
+ {
+ return sumOverflows<T>(m_min, other.m_min)
+ || sumOverflows<T>(m_min, other.m_max)
+ || sumOverflows<T>(m_max, other.m_min)
+ || sumOverflows<T>(m_max, other.m_max);
+ }
+
+ bool couldOverflowAdd(const IntRange& other, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return couldOverflowAdd<int32_t>(other);
+ case Int64:
+ return couldOverflowAdd<int64_t>(other);
+ default:
+ return true;
+ }
+ }
+
+ template<typename T>
+ bool couldOverflowSub(const IntRange& other)
+ {
+ return differenceOverflows<T>(m_min, other.m_min)
+ || differenceOverflows<T>(m_min, other.m_max)
+ || differenceOverflows<T>(m_max, other.m_min)
+ || differenceOverflows<T>(m_max, other.m_max);
+ }
+
+ bool couldOverflowSub(const IntRange& other, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return couldOverflowSub<int32_t>(other);
+ case Int64:
+ return couldOverflowSub<int64_t>(other);
+ default:
+ return true;
+ }
+ }
+
+ template<typename T>
+ bool couldOverflowMul(const IntRange& other)
+ {
+ return productOverflows<T>(m_min, other.m_min)
+ || productOverflows<T>(m_min, other.m_max)
+ || productOverflows<T>(m_max, other.m_min)
+ || productOverflows<T>(m_max, other.m_max);
+ }
+
+ bool couldOverflowMul(const IntRange& other, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return couldOverflowMul<int32_t>(other);
+ case Int64:
+ return couldOverflowMul<int64_t>(other);
+ default:
+ return true;
+ }
+ }
+
+ template<typename T>
+ IntRange shl(int32_t shiftAmount)
+ {
+ T newMin = static_cast<T>(m_min) << static_cast<T>(shiftAmount);
+ T newMax = static_cast<T>(m_max) << static_cast<T>(shiftAmount);
+
+ if ((newMin >> shiftAmount) != static_cast<T>(m_min))
+ newMin = std::numeric_limits<T>::min();
+ if ((newMax >> shiftAmount) != static_cast<T>(m_max))
+ newMax = std::numeric_limits<T>::max();
+
+ return IntRange(newMin, newMax);
+ }
+
+ IntRange shl(int32_t shiftAmount, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return shl<int32_t>(shiftAmount);
+ case Int64:
+ return shl<int64_t>(shiftAmount);
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+ template<typename T>
+ IntRange sShr(int32_t shiftAmount)
+ {
+ T newMin = static_cast<T>(m_min) >> static_cast<T>(shiftAmount);
+ T newMax = static_cast<T>(m_max) >> static_cast<T>(shiftAmount);
+
+ return IntRange(newMin, newMax);
+ }
+
+ IntRange sShr(int32_t shiftAmount, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return sShr<int32_t>(shiftAmount);
+ case Int64:
+ return sShr<int64_t>(shiftAmount);
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+ template<typename T>
+ IntRange zShr(int32_t shiftAmount)
+ {
+ // This is an awkward corner case for all of the other logic.
+ if (!shiftAmount)
+ return *this;
+
+ // If the input range may be negative, then all we can say about the output range is that it
+ // will be masked. That's because -1 right shifted just produces that mask.
+ if (m_min < 0)
+ return rangeForZShr<T>(shiftAmount);
+
+ // If the input range is non-negative, then this just brings the range closer to zero.
+ typedef typename std::make_unsigned<T>::type UnsignedT;
+ UnsignedT newMin = static_cast<UnsignedT>(m_min) >> static_cast<UnsignedT>(shiftAmount);
+ UnsignedT newMax = static_cast<UnsignedT>(m_max) >> static_cast<UnsignedT>(shiftAmount);
+
+ return IntRange(newMin, newMax);
+ }
+
+ IntRange zShr(int32_t shiftAmount, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return zShr<int32_t>(shiftAmount);
+ case Int64:
+ return zShr<int64_t>(shiftAmount);
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+ template<typename T>
+ IntRange add(const IntRange& other)
+ {
+ if (couldOverflowAdd<T>(other))
+ return top<T>();
+ return IntRange(m_min + other.m_min, m_max + other.m_max);
+ }
+
+ IntRange add(const IntRange& other, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return add<int32_t>(other);
+ case Int64:
+ return add<int64_t>(other);
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+ template<typename T>
+ IntRange sub(const IntRange& other)
+ {
+ if (couldOverflowSub<T>(other))
+ return top<T>();
+ return IntRange(m_min - other.m_max, m_max - other.m_min);
+ }
+
+ IntRange sub(const IntRange& other, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return sub<int32_t>(other);
+ case Int64:
+ return sub<int64_t>(other);
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+ template<typename T>
+ IntRange mul(const IntRange& other)
+ {
+ if (couldOverflowMul<T>(other))
+ return top<T>();
+ return IntRange(
+ std::min(
+ std::min(m_min * other.m_min, m_min * other.m_max),
+ std::min(m_max * other.m_min, m_max * other.m_max)),
+ std::max(
+ std::max(m_min * other.m_min, m_min * other.m_max),
+ std::max(m_max * other.m_min, m_max * other.m_max)));
+ }
+
+ IntRange mul(const IntRange& other, Type type)
+ {
+ switch (type) {
+ case Int32:
+ return mul<int32_t>(other);
+ case Int64:
+ return mul<int64_t>(other);
+ default:
+ RELEASE_ASSERT_NOT_REACHED();
+ return IntRange();
+ }
+ }
+
+private:
+ int64_t m_min { 0 };
+ int64_t m_max { 0 };
+};
+
+class ReduceStrength {
+public:
+ ReduceStrength(Procedure& proc)
+ : m_proc(proc)
+ , m_insertionSet(proc)
+ , m_blockInsertionSet(proc)
+ {
+ }
+
+ bool run()
+ {
+ bool result = false;
+ bool first = true;
+ unsigned index = 0;
+ do {
+ m_changed = false;
+ m_changedCFG = false;
+ ++index;
+
+ if (first)
+ first = false;
+ else if (verbose) {
+ dataLog("B3 after iteration #", index - 1, " of reduceStrength:\n");
+ dataLog(m_proc);
+ }
+
+ m_proc.resetValueOwners();
+ m_dominators = &m_proc.dominators(); // Recompute if necessary.
+ m_pureCSE.clear();
+
+ for (BasicBlock* block : m_proc.blocksInPreOrder()) {
+ m_block = block;
+
+ for (m_index = 0; m_index < block->size(); ++m_index) {
+ if (verbose) {
+ dataLog(
+ "Looking at ", *block, " #", m_index, ": ",
+ deepDump(m_proc, block->at(m_index)), "\n");
+ }
+ m_value = m_block->at(m_index);
+ m_value->performSubstitution();
+
+ reduceValueStrength();
+ replaceIfRedundant();
+ }
+ m_insertionSet.execute(m_block);
+ }
+
+ if (m_blockInsertionSet.execute()) {
+ m_proc.resetReachability();
+ m_proc.invalidateCFG();
+ m_dominators = &m_proc.dominators(); // Recompute if necessary.
+ m_changedCFG = true;
+ }
+
+ simplifyCFG();
+
+ if (m_changedCFG) {
+ m_proc.resetReachability();
+ m_proc.invalidateCFG();
+ m_changed = true;
+ }
+
+ killDeadCode();
+ simplifySSA();
+
+ result |= m_changed;
+ } while (m_changed);
+ return result;
+ }
+
+private:
+ void reduceValueStrength()
+ {
+ switch (m_value->opcode()) {
+ case Add:
+ handleCommutativity();
+
+ // Turn this: Add(Add(value, constant1), constant2)
+ // Into this: Add(value, constant1 + constant2)
+ if (m_value->child(0)->opcode() == Add && isInt(m_value->type())) {
+ Value* newSum = m_value->child(1)->addConstant(m_proc, m_value->child(0)->child(1));
+ if (newSum) {
+ m_insertionSet.insertValue(m_index, newSum);
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_value->child(1) = newSum;
+ m_changed = true;
+ }
+ }
+
+ // Turn this: Add(constant1, constant2)
+ // Into this: constant1 + constant2
+ if (Value* constantAdd = m_value->child(0)->addConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(constantAdd);
+ break;
+ }
+
+ // Turn this: Integer Add(value, value)
+ // Into this: Shl(value, 1)
+ // This is a useful canonicalization. It's not meant to be a strength reduction.
+ if (m_value->isInteger() && m_value->child(0) == m_value->child(1)) {
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ Shl, m_value->origin(), m_value->child(0),
+ m_insertionSet.insert<Const32Value>(m_index, m_value->origin(), 1)));
+ break;
+ }
+
+ // Turn this: Add(value, zero)
+ // Into an Identity.
+ //
+ // Addition is subtle with doubles. Zero is not the neutral value, negative zero is:
+ // 0 + 0 = 0
+ // 0 + -0 = 0
+ // -0 + 0 = 0
+ // -0 + -0 = -0
+ if (m_value->child(1)->isInt(0) || m_value->child(1)->isNegativeZero()) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: Integer Add(Sub(0, value), -1)
+ // Into this: BitXor(value, -1)
+ if (m_value->isInteger()
+ && m_value->child(0)->opcode() == Sub
+ && m_value->child(1)->isInt(-1)
+ && m_value->child(0)->child(0)->isInt(0)) {
+ replaceWithNewValue(m_proc.add<Value>(BitXor, m_value->origin(), m_value->child(0)->child(1), m_value->child(1)));
+ break;
+ }
+
+ break;
+
+ case Sub:
+ // Turn this: Sub(constant1, constant2)
+ // Into this: constant1 - constant2
+ if (Value* constantSub = m_value->child(0)->subConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(constantSub);
+ break;
+ }
+
+ if (isInt(m_value->type())) {
+ // Turn this: Sub(value, constant)
+ // Into this: Add(value, -constant)
+ if (Value* negatedConstant = m_value->child(1)->negConstant(m_proc)) {
+ m_insertionSet.insertValue(m_index, negatedConstant);
+ replaceWithNew<Value>(
+ Add, m_value->origin(), m_value->child(0), negatedConstant);
+ break;
+ }
+
+ // Turn this: Sub(0, value)
+ // Into this: Neg(value)
+ if (m_value->child(0)->isInt(0)) {
+ replaceWithNew<Value>(Neg, m_value->origin(), m_value->child(1));
+ break;
+ }
+ }
+
+ break;
+
+ case Neg:
+ // Turn this: Neg(constant)
+ // Into this: -constant
+ if (Value* constant = m_value->child(0)->negConstant(m_proc)) {
+ replaceWithNewValue(constant);
+ break;
+ }
+
+ // Turn this: Neg(Neg(value))
+ // Into this: value
+ if (m_value->child(0)->opcode() == Neg) {
+ replaceWithIdentity(m_value->child(0)->child(0));
+ break;
+ }
+
+ break;
+
+ case Mul:
+ handleCommutativity();
+
+ // Turn this: Mul(constant1, constant2)
+ // Into this: constant1 * constant2
+ if (Value* value = m_value->child(0)->mulConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(value);
+ break;
+ }
+
+ if (m_value->child(1)->hasInt()) {
+ int64_t factor = m_value->child(1)->asInt();
+
+ // Turn this: Mul(value, 0)
+ // Into this: 0
+ // Note that we don't do this for doubles because that's wrong. For example, -1 * 0
+ // and 1 * 0 yield different results.
+ if (!factor) {
+ replaceWithIdentity(m_value->child(1));
+ break;
+ }
+
+ // Turn this: Mul(value, 1)
+ // Into this: value
+ if (factor == 1) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: Mul(value, -1)
+ // Into this: Sub(0, value)
+ if (factor == -1) {
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ Sub, m_value->origin(),
+ m_insertionSet.insertIntConstant(m_index, m_value, 0),
+ m_value->child(0)));
+ break;
+ }
+
+ // Turn this: Mul(value, constant)
+ // Into this: Shl(value, log2(constant))
+ if (hasOneBitSet(factor)) {
+ unsigned shiftAmount = WTF::fastLog2(static_cast<uint64_t>(factor));
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ Shl, m_value->origin(), m_value->child(0),
+ m_insertionSet.insert<Const32Value>(
+ m_index, m_value->origin(), shiftAmount)));
+ break;
+ }
+ } else if (m_value->child(1)->hasDouble()) {
+ double factor = m_value->child(1)->asDouble();
+
+ // Turn this: Mul(value, 1)
+ // Into this: value
+ if (factor == 1) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+ }
+
+ break;
+
+ case Div:
+ case ChillDiv:
+ // Turn this: Div(constant1, constant2)
+ // Into this: constant1 / constant2
+ // Note that this uses ChillDiv semantics. That's fine, because the rules for Div
+ // are strictly weaker: it has corner cases where it's allowed to do anything it
+ // likes.
+ if (replaceWithNewValue(m_value->child(0)->divConstant(m_proc, m_value->child(1))))
+ break;
+
+ if (m_value->child(1)->hasInt()) {
+ switch (m_value->child(1)->asInt()) {
+ case -1:
+ // Turn this: Div(value, -1)
+ // Into this: Neg(value)
+ replaceWithNewValue(
+ m_proc.add<Value>(Neg, m_value->origin(), m_value->child(0)));
+ break;
+
+ case 0:
+ // Turn this: Div(value, 0)
+ // Into this: 0
+ // We can do this because it's precisely correct for ChillDiv and for Div we
+ // are allowed to do whatever we want.
+ replaceWithIdentity(m_value->child(1));
+ break;
+
+ case 1:
+ // Turn this: Div(value, 1)
+ // Into this: value
+ replaceWithIdentity(m_value->child(0));
+ break;
+
+ default:
+ // Perform super comprehensive strength reduction of division. Currently we
+ // only do this for 32-bit divisions, since we need a high multiply
+ // operation. We emulate it using 64-bit multiply. We can't emulate 64-bit
+ // high multiply with a 128-bit multiply because we don't have a 128-bit
+ // multiply. We could do it with a patchpoint if we cared badly enough.
+
+ if (m_value->type() != Int32)
+ break;
+
+ int32_t divisor = m_value->child(1)->asInt32();
+ DivisionMagic<int32_t> magic = computeDivisionMagic(divisor);
+
+ // Perform the "high" multiplication. We do it just to get the high bits.
+ // This is sort of like multiplying by the reciprocal, just more gnarly. It's
+ // from Hacker's Delight and I don't claim to understand it.
+ Value* magicQuotient = m_insertionSet.insert<Value>(
+ m_index, Trunc, m_value->origin(),
+ m_insertionSet.insert<Value>(
+ m_index, ZShr, m_value->origin(),
+ m_insertionSet.insert<Value>(
+ m_index, Mul, m_value->origin(),
+ m_insertionSet.insert<Value>(
+ m_index, SExt32, m_value->origin(), m_value->child(0)),
+ m_insertionSet.insert<Const64Value>(
+ m_index, m_value->origin(), magic.magicMultiplier)),
+ m_insertionSet.insert<Const32Value>(
+ m_index, m_value->origin(), 32)));
+
+ if (divisor > 0 && magic.magicMultiplier < 0) {
+ magicQuotient = m_insertionSet.insert<Value>(
+ m_index, Add, m_value->origin(), magicQuotient, m_value->child(0));
+ }
+ if (divisor < 0 && magic.magicMultiplier > 0) {
+ magicQuotient = m_insertionSet.insert<Value>(
+ m_index, Sub, m_value->origin(), magicQuotient, m_value->child(0));
+ }
+ if (magic.shift > 0) {
+ magicQuotient = m_insertionSet.insert<Value>(
+ m_index, SShr, m_value->origin(), magicQuotient,
+ m_insertionSet.insert<Const32Value>(
+ m_index, m_value->origin(), magic.shift));
+ }
+ replaceWithIdentity(
+ m_insertionSet.insert<Value>(
+ m_index, Add, m_value->origin(), magicQuotient,
+ m_insertionSet.insert<Value>(
+ m_index, ZShr, m_value->origin(), magicQuotient,
+ m_insertionSet.insert<Const32Value>(
+ m_index, m_value->origin(), 31))));
+ break;
+ }
+ break;
+ }
+ break;
+
+ case Mod:
+ case ChillMod:
+ // Turn this: Mod(constant1, constant2)
+ // Into this: constant1 / constant2
+ // Note that this uses ChillMod semantics.
+ if (replaceWithNewValue(m_value->child(0)->modConstant(m_proc, m_value->child(1))))
+ break;
+
+ // Modulo by constant is more efficient if we turn it into Div, and then let Div get
+ // optimized.
+ if (m_value->child(1)->hasInt()) {
+ switch (m_value->child(1)->asInt()) {
+ case 0:
+ // Turn this: Mod(value, 0)
+ // Into this: 0
+ // This is correct according to ChillMod semantics.
+ replaceWithIdentity(m_value->child(1));
+ break;
+
+ default:
+ // Turn this: Mod(N, D)
+ // Into this: Sub(N, Mul(Div(N, D), D))
+ //
+ // This is a speed-up because we use our existing Div optimizations.
+ //
+ // Here's an easier way to look at it:
+ // N % D = N - N / D * D
+ //
+ // Note that this does not work for D = 0 and ChillMod. The expected result is 0.
+ // That's why we have a special-case above.
+ // X % 0 = X - X / 0 * 0 = X (should be 0)
+ //
+ // This does work for the D = -1 special case.
+ // -2^31 % -1 = -2^31 - -2^31 / -1 * -1
+ // = -2^31 - -2^31 * -1
+ // = -2^31 - -2^31
+ // = 0
+
+ Opcode divOpcode;
+ switch (m_value->opcode()) {
+ case Mod:
+ divOpcode = Div;
+ break;
+ case ChillMod:
+ divOpcode = ChillDiv;
+ break;
+ default:
+ divOpcode = Oops;
+ RELEASE_ASSERT_NOT_REACHED();
+ break;
+ }
+
+ replaceWithIdentity(
+ m_insertionSet.insert<Value>(
+ m_index, Sub, m_value->origin(),
+ m_value->child(0),
+ m_insertionSet.insert<Value>(
+ m_index, Mul, m_value->origin(),
+ m_insertionSet.insert<Value>(
+ m_index, divOpcode, m_value->origin(),
+ m_value->child(0), m_value->child(1)),
+ m_value->child(1))));
+ break;
+ }
+ break;
+ }
+
+ break;
+
+ case BitAnd:
+ handleCommutativity();
+
+ // Turn this: BitAnd(constant1, constant2)
+ // Into this: constant1 & constant2
+ if (Value* constantBitAnd = m_value->child(0)->bitAndConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(constantBitAnd);
+ break;
+ }
+
+ // Turn this: BitAnd(BitAnd(value, constant1), constant2)
+ // Into this: BitAnd(value, constant1 & constant2).
+ if (m_value->child(0)->opcode() == BitAnd) {
+ Value* newConstant = m_value->child(1)->bitAndConstant(m_proc, m_value->child(0)->child(1));
+ if (newConstant) {
+ m_insertionSet.insertValue(m_index, newConstant);
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_value->child(1) = newConstant;
+ m_changed = true;
+ }
+ }
+
+ // Turn this: BitAnd(valueX, valueX)
+ // Into this: valueX.
+ if (m_value->child(0) == m_value->child(1)) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: BitAnd(value, zero-constant)
+ // Into this: zero-constant.
+ if (m_value->child(1)->isInt(0)) {
+ replaceWithIdentity(m_value->child(1));
+ break;
+ }
+
+ // Turn this: BitAnd(value, all-ones)
+ // Into this: value.
+ if ((m_value->type() == Int64 && m_value->child(1)->isInt(0xffffffffffffffff))
+ || (m_value->type() == Int32 && m_value->child(1)->isInt(0xffffffff))) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: BitAnd(64-bit value, 32 ones)
+ // Into this: ZExt32(Trunc(64-bit value))
+ if (m_value->child(1)->isInt64(0xffffffffllu)) {
+ Value* newValue = m_insertionSet.insert<Value>(
+ m_index, ZExt32, m_value->origin(),
+ m_insertionSet.insert<Value>(m_index, Trunc, m_value->origin(), m_value->child(0)));
+ replaceWithIdentity(newValue);
+ break;
+ }
+
+ // Turn this: BitAnd(SExt8(value), mask) where (mask & 0xffffff00) == 0
+ // Into this: BitAnd(value, mask)
+ if (m_value->child(0)->opcode() == SExt8 && m_value->child(1)->hasInt32()
+ && !(m_value->child(1)->asInt32() & 0xffffff00)) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_changed = true;
+ }
+
+ // Turn this: BitAnd(SExt16(value), mask) where (mask & 0xffff0000) == 0
+ // Into this: BitAnd(value, mask)
+ if (m_value->child(0)->opcode() == SExt16 && m_value->child(1)->hasInt32()
+ && !(m_value->child(1)->asInt32() & 0xffff0000)) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_changed = true;
+ }
+
+ // Turn this: BitAnd(SExt32(value), mask) where (mask & 0xffffffff00000000) == 0
+ // Into this: BitAnd(ZExt32(value), mask)
+ if (m_value->child(0)->opcode() == SExt32 && m_value->child(1)->hasInt32()
+ && !(m_value->child(1)->asInt32() & 0xffffffff00000000llu)) {
+ m_value->child(0) = m_insertionSet.insert<Value>(
+ m_index, ZExt32, m_value->origin(),
+ m_value->child(0)->child(0), m_value->child(0)->child(1));
+ m_changed = true;
+ }
+
+ // Turn this: BitAnd(Op(value, constant1), constant2)
+ // where !(constant1 & constant2)
+ // and Op is BitOr or BitXor
+ // into this: BitAnd(value, constant2)
+ if (m_value->child(1)->hasInt()) {
+ int64_t constant2 = m_value->child(1)->asInt();
+ switch (m_value->child(0)->opcode()) {
+ case BitOr:
+ case BitXor:
+ if (m_value->child(0)->child(1)->hasInt()
+ && !(m_value->child(0)->child(1)->asInt() & constant2)) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_changed = true;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case BitOr:
+ handleCommutativity();
+
+ // Turn this: BitOr(constant1, constant2)
+ // Into this: constant1 | constant2
+ if (Value* constantBitOr = m_value->child(0)->bitOrConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(constantBitOr);
+ break;
+ }
+
+ // Turn this: BitOr(BitOr(value, constant1), constant2)
+ // Into this: BitOr(value, constant1 & constant2).
+ if (m_value->child(0)->opcode() == BitOr) {
+ Value* newConstant = m_value->child(1)->bitOrConstant(m_proc, m_value->child(0)->child(1));
+ if (newConstant) {
+ m_insertionSet.insertValue(m_index, newConstant);
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_value->child(1) = newConstant;
+ m_changed = true;
+ }
+ }
+
+ // Turn this: BitOr(valueX, valueX)
+ // Into this: valueX.
+ if (m_value->child(0) == m_value->child(1)) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: BitOr(value, zero-constant)
+ // Into this: value.
+ if (m_value->child(1)->isInt(0)) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: BitOr(value, all-ones)
+ // Into this: all-ones.
+ if ((m_value->type() == Int64 && m_value->child(1)->isInt(0xffffffffffffffff))
+ || (m_value->type() == Int32 && m_value->child(1)->isInt(0xffffffff))) {
+ replaceWithIdentity(m_value->child(1));
+ break;
+ }
+
+ break;
+
+ case BitXor:
+ handleCommutativity();
+
+ // Turn this: BitXor(constant1, constant2)
+ // Into this: constant1 ^ constant2
+ if (Value* constantBitXor = m_value->child(0)->bitXorConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(constantBitXor);
+ break;
+ }
+
+ // Turn this: BitXor(BitXor(value, constant1), constant2)
+ // Into this: BitXor(value, constant1 ^ constant2).
+ if (m_value->child(0)->opcode() == BitXor) {
+ Value* newConstant = m_value->child(1)->bitXorConstant(m_proc, m_value->child(0)->child(1));
+ if (newConstant) {
+ m_insertionSet.insertValue(m_index, newConstant);
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_value->child(1) = newConstant;
+ m_changed = true;
+ }
+ }
+
+ // Turn this: BitXor(compare, 1)
+ // Into this: invertedCompare
+ if (m_value->child(1)->isInt32(1)) {
+ if (Value* invertedCompare = m_value->child(0)->invertedCompare(m_proc)) {
+ replaceWithNewValue(invertedCompare);
+ break;
+ }
+ }
+
+ // Turn this: BitXor(valueX, valueX)
+ // Into this: zero-constant.
+ if (m_value->child(0) == m_value->child(1)) {
+ replaceWithNewValue(m_proc.addIntConstant(m_value, 0));
+ break;
+ }
+
+ // Turn this: BitXor(value, zero-constant)
+ // Into this: value.
+ if (m_value->child(1)->isInt(0)) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ break;
+
+ case Shl:
+ // Turn this: Shl(constant1, constant2)
+ // Into this: constant1 << constant2
+ if (Value* constant = m_value->child(0)->shlConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(constant);
+ break;
+ }
+
+ if (handleShiftAmount())
+ break;
+
+ break;
+
+ case SShr:
+ // Turn this: SShr(constant1, constant2)
+ // Into this: constant1 >> constant2
+ if (Value* constant = m_value->child(0)->sShrConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(constant);
+ break;
+ }
+
+ if (m_value->child(1)->hasInt32()
+ && m_value->child(0)->opcode() == Shl
+ && m_value->child(0)->child(1)->hasInt32()
+ && m_value->child(1)->asInt32() == m_value->child(0)->child(1)->asInt32()) {
+ switch (m_value->child(1)->asInt32()) {
+ case 16:
+ if (m_value->type() == Int32) {
+ // Turn this: SShr(Shl(value, 16), 16)
+ // Into this: SExt16(value)
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ SExt16, m_value->origin(), m_value->child(0)->child(0)));
+ }
+ break;
+
+ case 24:
+ if (m_value->type() == Int32) {
+ // Turn this: SShr(Shl(value, 24), 24)
+ // Into this: SExt8(value)
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ SExt8, m_value->origin(), m_value->child(0)->child(0)));
+ }
+ break;
+
+ case 32:
+ if (m_value->type() == Int64) {
+ // Turn this: SShr(Shl(value, 32), 32)
+ // Into this: SExt32(Trunc(value))
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ SExt32, m_value->origin(),
+ m_insertionSet.insert<Value>(
+ m_index, Trunc, m_value->origin(),
+ m_value->child(0)->child(0))));
+ }
+ break;
+
+ // FIXME: Add cases for 48 and 56, but that would translate to SExt32(SExt8) or
+ // SExt32(SExt16), which we don't currently lower efficiently.
+
+ default:
+ break;
+ }
+
+ if (m_value->opcode() != SShr)
+ break;
+ }
+
+ if (handleShiftAmount())
+ break;
+
+ break;
+
+ case ZShr:
+ // Turn this: ZShr(constant1, constant2)
+ // Into this: (unsigned)constant1 >> constant2
+ if (Value* constant = m_value->child(0)->zShrConstant(m_proc, m_value->child(1))) {
+ replaceWithNewValue(constant);
+ break;
+ }
+
+ if (handleShiftAmount())
+ break;
+
+ break;
+
+ case Abs:
+ // Turn this: Abs(constant)
+ // Into this: fabs<value->type()>(constant)
+ if (Value* constant = m_value->child(0)->absConstant(m_proc)) {
+ replaceWithNewValue(constant);
+ break;
+ }
+
+ // Turn this: Abs(Abs(value))
+ // Into this: Abs(value)
+ if (m_value->child(0)->opcode() == Abs) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: Abs(BitwiseCast(value))
+ // Into this: BitwiseCast(And(value, mask-top-bit))
+ if (m_value->child(0)->opcode() == BitwiseCast) {
+ Value* mask;
+ if (m_value->type() == Double)
+ mask = m_insertionSet.insert<Const64Value>(m_index, m_value->origin(), ~(1ll << 63));
+ else
+ mask = m_insertionSet.insert<Const32Value>(m_index, m_value->origin(), ~(1l << 31));
+
+ Value* bitAnd = m_insertionSet.insert<Value>(m_index, BitAnd, m_value->origin(),
+ m_value->child(0)->child(0),
+ mask);
+ Value* cast = m_insertionSet.insert<Value>(m_index, BitwiseCast, m_value->origin(), bitAnd);
+ replaceWithIdentity(cast);
+ break;
+ }
+ break;
+
+ case Ceil:
+ // Turn this: Ceil(constant)
+ // Into this: ceil<value->type()>(constant)
+ if (Value* constant = m_value->child(0)->ceilConstant(m_proc)) {
+ replaceWithNewValue(constant);
+ break;
+ }
+
+ // Turn this: Ceil(roundedValue)
+ // Into this: roundedValue
+ if (m_value->child(0)->isRounded()) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+ break;
+
+ case Floor:
+ // Turn this: Floor(constant)
+ // Into this: floor<value->type()>(constant)
+ if (Value* constant = m_value->child(0)->floorConstant(m_proc)) {
+ replaceWithNewValue(constant);
+ break;
+ }
+
+ // Turn this: Floor(roundedValue)
+ // Into this: roundedValue
+ if (m_value->child(0)->isRounded()) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+ break;
+
+ case Sqrt:
+ // Turn this: Sqrt(constant)
+ // Into this: sqrt<value->type()>(constant)
+ if (Value* constant = m_value->child(0)->sqrtConstant(m_proc)) {
+ replaceWithNewValue(constant);
+ break;
+ }
+ break;
+
+ case BitwiseCast:
+ // Turn this: BitwiseCast(constant)
+ // Into this: bitwise_cast<value->type()>(constant)
+ if (Value* constant = m_value->child(0)->bitwiseCastConstant(m_proc)) {
+ replaceWithNewValue(constant);
+ break;
+ }
+
+ // Turn this: BitwiseCast(BitwiseCast(value))
+ // Into this: value
+ if (m_value->child(0)->opcode() == BitwiseCast) {
+ replaceWithIdentity(m_value->child(0)->child(0));
+ break;
+ }
+ break;
+
+ case SExt8:
+ // Turn this: SExt8(constant)
+ // Into this: static_cast<int8_t>(constant)
+ if (m_value->child(0)->hasInt32()) {
+ int32_t result = static_cast<int8_t>(m_value->child(0)->asInt32());
+ replaceWithNewValue(m_proc.addIntConstant(m_value, result));
+ break;
+ }
+
+ // Turn this: SExt8(SExt8(value))
+ // or this: SExt8(SExt16(value))
+ // Into this: SExt8(value)
+ if (m_value->child(0)->opcode() == SExt8 || m_value->child(0)->opcode() == SExt16) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_changed = true;
+ }
+
+ if (m_value->child(0)->opcode() == BitAnd && m_value->child(0)->child(1)->hasInt32()) {
+ Value* input = m_value->child(0)->child(0);
+ int32_t mask = m_value->child(0)->child(1)->asInt32();
+
+ // Turn this: SExt8(BitAnd(input, mask)) where (mask & 0xff) == 0xff
+ // Into this: SExt8(input)
+ if ((mask & 0xff) == 0xff) {
+ m_value->child(0) = input;
+ m_changed = true;
+ break;
+ }
+
+ // Turn this: SExt8(BitAnd(input, mask)) where (mask & 0x80) == 0
+ // Into this: BitAnd(input, const & 0x7f)
+ if (!(mask & 0x80)) {
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ BitAnd, m_value->origin(), input,
+ m_insertionSet.insert<Const32Value>(
+ m_index, m_value->origin(), mask & 0x7f)));
+ break;
+ }
+ }
+ break;
+
+ case SExt16:
+ // Turn this: SExt16(constant)
+ // Into this: static_cast<int16_t>(constant)
+ if (m_value->child(0)->hasInt32()) {
+ int32_t result = static_cast<int16_t>(m_value->child(0)->asInt32());
+ replaceWithNewValue(m_proc.addIntConstant(m_value, result));
+ break;
+ }
+
+ // Turn this: SExt16(SExt16(value))
+ // Into this: SExt16(value)
+ if (m_value->child(0)->opcode() == SExt16) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_changed = true;
+ }
+
+ // Turn this: SExt16(SExt8(value))
+ // Into this: SExt8(value)
+ if (m_value->child(0)->opcode() == SExt8) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ if (m_value->child(0)->opcode() == BitAnd && m_value->child(0)->child(1)->hasInt32()) {
+ Value* input = m_value->child(0)->child(0);
+ int32_t mask = m_value->child(0)->child(1)->asInt32();
+
+ // Turn this: SExt16(BitAnd(input, mask)) where (mask & 0xffff) == 0xffff
+ // Into this: SExt16(input)
+ if ((mask & 0xffff) == 0xffff) {
+ m_value->child(0) = input;
+ m_changed = true;
+ break;
+ }
+
+ // Turn this: SExt16(BitAnd(input, mask)) where (mask & 0x8000) == 0
+ // Into this: BitAnd(input, const & 0x7fff)
+ if (!(mask & 0x8000)) {
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ BitAnd, m_value->origin(), input,
+ m_insertionSet.insert<Const32Value>(
+ m_index, m_value->origin(), mask & 0x7fff)));
+ break;
+ }
+ }
+ break;
+
+ case SExt32:
+ // Turn this: SExt32(constant)
+ // Into this: static_cast<int64_t>(constant)
+ if (m_value->child(0)->hasInt32()) {
+ replaceWithNewValue(m_proc.addIntConstant(m_value, m_value->child(0)->asInt32()));
+ break;
+ }
+
+ // Turn this: SExt32(BitAnd(input, mask)) where (mask & 0x80000000) == 0
+ // Into this: ZExt32(BitAnd(input, mask))
+ if (m_value->child(0)->opcode() == BitAnd && m_value->child(0)->child(1)->hasInt32()
+ && !(m_value->child(0)->child(1)->asInt32() & 0x80000000)) {
+ replaceWithNewValue(
+ m_proc.add<Value>(
+ ZExt32, m_value->origin(), m_value->child(0)));
+ break;
+ }
+ break;
+
+ case ZExt32:
+ // Turn this: ZExt32(constant)
+ // Into this: static_cast<uint64_t>(static_cast<uint32_t>(constant))
+ if (m_value->child(0)->hasInt32()) {
+ replaceWithNewValue(
+ m_proc.addIntConstant(
+ m_value,
+ static_cast<uint64_t>(static_cast<uint32_t>(m_value->child(0)->asInt32()))));
+ break;
+ }
+ break;
+
+ case Trunc:
+ // Turn this: Trunc(constant)
+ // Into this: static_cast<int32_t>(constant)
+ if (m_value->child(0)->hasInt64()) {
+ replaceWithNewValue(
+ m_proc.addIntConstant(m_value, static_cast<int32_t>(m_value->child(0)->asInt64())));
+ break;
+ }
+
+ // Turn this: Trunc(SExt32(value)) or Trunc(ZExt32(value))
+ // Into this: value
+ if (m_value->child(0)->opcode() == SExt32 || m_value->child(0)->opcode() == ZExt32) {
+ replaceWithIdentity(m_value->child(0)->child(0));
+ break;
+ }
+
+ // Turn this: Trunc(Op(value, constant))
+ // where !(constant & 0xffffffff)
+ // and Op is Add, Sub, BitOr, or BitXor
+ // into this: Trunc(value)
+ switch (m_value->child(0)->opcode()) {
+ case Add:
+ case Sub:
+ case BitOr:
+ case BitXor:
+ if (m_value->child(0)->child(1)->hasInt64()
+ && !(m_value->child(0)->child(1)->asInt64() & 0xffffffffll)) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_changed = true;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case FloatToDouble:
+ // Turn this: FloatToDouble(constant)
+ // Into this: ConstDouble(constant)
+ if (Value* constant = m_value->child(0)->floatToDoubleConstant(m_proc)) {
+ replaceWithNewValue(constant);
+ break;
+ }
+ break;
+
+ case DoubleToFloat:
+ // Turn this: DoubleToFloat(FloatToDouble(value))
+ // Into this: value
+ if (m_value->child(0)->opcode() == FloatToDouble) {
+ replaceWithIdentity(m_value->child(0)->child(0));
+ break;
+ }
+
+ // Turn this: DoubleToFloat(constant)
+ // Into this: ConstFloat(constant)
+ if (Value* constant = m_value->child(0)->doubleToFloatConstant(m_proc)) {
+ replaceWithNewValue(constant);
+ break;
+ }
+ break;
+
+ case Select:
+ // Turn this: Select(constant, a, b)
+ // Into this: constant ? a : b
+ if (m_value->child(0)->hasInt32()) {
+ replaceWithIdentity(
+ m_value->child(0)->asInt32() ? m_value->child(1) : m_value->child(2));
+ break;
+ }
+
+ // Turn this: Select(Equal(x, 0), a, b)
+ // Into this: Select(x, b, a)
+ if (m_value->child(0)->opcode() == Equal && m_value->child(0)->child(1)->isInt(0)) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ std::swap(m_value->child(1), m_value->child(2));
+ m_changed = true;
+ break;
+ }
+
+ // Turn this: Select(BitXor(bool, 1), a, b)
+ // Into this: Select(bool, b, a)
+ if (m_value->child(0)->opcode() == BitXor
+ && m_value->child(0)->child(1)->isInt32(1)
+ && m_value->child(0)->child(0)->returnsBool()) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ std::swap(m_value->child(1), m_value->child(2));
+ m_changed = true;
+ break;
+ }
+
+ // Turn this: Select(BitAnd(bool, xyz1), a, b)
+ // Into this: Select(bool, a, b)
+ if (m_value->child(0)->opcode() == BitAnd
+ && m_value->child(0)->child(1)->hasInt()
+ && m_value->child(0)->child(1)->asInt() & 1
+ && m_value->child(0)->child(0)->returnsBool()) {
+ m_value->child(0) = m_value->child(0)->child(0);
+ m_changed = true;
+ break;
+ }
+
+ // Turn this: Select(stuff, x, x)
+ // Into this: x
+ if (m_value->child(1) == m_value->child(2)) {
+ replaceWithIdentity(m_value->child(1));
+ break;
+ }
+ break;
+
+ case Load8Z:
+ case Load8S:
+ case Load16Z:
+ case Load16S:
+ case Load:
+ case Store8:
+ case Store16:
+ case Store: {
+ Value* address = m_value->lastChild();
+ MemoryValue* memory = m_value->as<MemoryValue>();
+
+ // Turn this: Load(Add(address, offset1), offset = offset2)
+ // Into this: Load(address, offset = offset1 + offset2)
+ //
+ // Also turns this: Store(value, Add(address, offset1), offset = offset2)
+ // Into this: Store(value, address, offset = offset1 + offset2)
+ if (address->opcode() == Add && address->child(1)->hasIntPtr()) {
+ intptr_t offset = address->child(1)->asIntPtr();
+ if (!sumOverflows<intptr_t>(offset, memory->offset())) {
+ offset += memory->offset();
+ int32_t smallOffset = static_cast<int32_t>(offset);
+ if (smallOffset == offset) {
+ address = address->child(0);
+ memory->lastChild() = address;
+ memory->setOffset(smallOffset);
+ m_changed = true;
+ }
+ }
+ }
+
+ // Turn this: Load(constant1, offset = constant2)
+ // Into this: Load(constant1 + constant2)
+ //
+ // This is a fun canonicalization. It purely regresses naively generated code. We rely
+ // on constant materialization to be smart enough to materialize this constant the smart
+ // way. We want this canonicalization because we want to know if two memory accesses see
+ // the same address.
+ if (memory->offset()) {
+ if (Value* newAddress = address->addConstant(m_proc, memory->offset())) {
+ m_insertionSet.insertValue(m_index, newAddress);
+ address = newAddress;
+ memory->lastChild() = newAddress;
+ memory->setOffset(0);
+ m_changed = true;
+ }
+ }
+
+ break;
+ }
+
+ case CCall: {
+ // Turn this: Call(fmod, constant1, constant2)
+ // Into this: fcall-constant(constant1, constant2)
+ double(*fmodDouble)(double, double) = fmod;
+ if (m_value->type() == Double
+ && m_value->numChildren() == 3
+ && m_value->child(0)->isIntPtr(reinterpret_cast<intptr_t>(fmodDouble))
+ && m_value->child(1)->type() == Double
+ && m_value->child(2)->type() == Double) {
+ replaceWithNewValue(m_value->child(1)->modConstant(m_proc, m_value->child(2)));
+ }
+ break;
+ }
+ case Equal:
+ handleCommutativity();
+
+ // Turn this: Equal(bool, 0)
+ // Into this: BitXor(bool, 1)
+ if (m_value->child(0)->returnsBool() && m_value->child(1)->isInt32(0)) {
+ replaceWithNew<Value>(
+ BitXor, m_value->origin(), m_value->child(0),
+ m_insertionSet.insert<Const32Value>(m_index, m_value->origin(), 1));
+ break;
+ }
+
+ // Turn this Equal(bool, 1)
+ // Into this: bool
+ if (m_value->child(0)->returnsBool() && m_value->child(1)->isInt32(1)) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: Equal(const1, const2)
+ // Into this: const1 == const2
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->equalConstant(m_value->child(1))));
+ break;
+
+ case NotEqual:
+ handleCommutativity();
+
+ if (m_value->child(0)->returnsBool()) {
+ // Turn this: NotEqual(bool, 0)
+ // Into this: bool
+ if (m_value->child(1)->isInt32(0)) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ // Turn this: NotEqual(bool, 1)
+ // Into this: Equal(bool, 0)
+ if (m_value->child(1)->isInt32(1)) {
+ replaceWithNew<Value>(
+ Equal, m_value->origin(), m_value->child(0),
+ m_insertionSet.insertIntConstant(m_index, m_value->origin(), Int32, 0));
+ break;
+ }
+ }
+
+ // Turn this: NotEqual(const1, const2)
+ // Into this: const1 != const2
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->notEqualConstant(m_value->child(1))));
+ break;
+
+ case LessThan:
+ // FIXME: We could do a better job of canonicalizing integer comparisons.
+ // https://bugs.webkit.org/show_bug.cgi?id=150958
+
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->lessThanConstant(m_value->child(1))));
+ break;
+
+ case GreaterThan:
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->greaterThanConstant(m_value->child(1))));
+ break;
+
+ case LessEqual:
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->lessEqualConstant(m_value->child(1))));
+ break;
+
+ case GreaterEqual:
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->greaterEqualConstant(m_value->child(1))));
+ break;
+
+ case Above:
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->aboveConstant(m_value->child(1))));
+ break;
+
+ case Below:
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->belowConstant(m_value->child(1))));
+ break;
+
+ case AboveEqual:
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->aboveEqualConstant(m_value->child(1))));
+ break;
+
+ case BelowEqual:
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(0)->belowEqualConstant(m_value->child(1))));
+ break;
+
+ case EqualOrUnordered:
+ handleCommutativity();
+
+ // Turn this: Equal(const1, const2)
+ // Into this: isunordered(const1, const2) || const1 == const2.
+ // Turn this: Equal(value, const_NaN)
+ // Into this: 1.
+ replaceWithNewValue(
+ m_proc.addBoolConstant(
+ m_value->origin(),
+ m_value->child(1)->equalOrUnorderedConstant(m_value->child(0))));
+ break;
+
+ case CheckAdd: {
+ if (replaceWithNewValue(m_value->child(0)->checkAddConstant(m_proc, m_value->child(1))))
+ break;
+
+ handleCommutativity();
+
+ if (m_value->child(1)->isInt(0)) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ IntRange leftRange = rangeFor(m_value->child(0));
+ IntRange rightRange = rangeFor(m_value->child(1));
+ if (!leftRange.couldOverflowAdd(rightRange, m_value->type())) {
+ replaceWithNewValue(
+ m_proc.add<Value>(Add, m_value->origin(), m_value->child(0), m_value->child(1)));
+ break;
+ }
+ break;
+ }
+
+ case CheckSub: {
+ if (replaceWithNewValue(m_value->child(0)->checkSubConstant(m_proc, m_value->child(1))))
+ break;
+
+ if (m_value->child(1)->isInt(0)) {
+ replaceWithIdentity(m_value->child(0));
+ break;
+ }
+
+ if (Value* negatedConstant = m_value->child(1)->checkNegConstant(m_proc)) {
+ m_insertionSet.insertValue(m_index, negatedConstant);
+ m_value->as<CheckValue>()->convertToAdd();
+ m_value->child(1) = negatedConstant;
+ m_changed = true;
+ break;
+ }
+
+ IntRange leftRange = rangeFor(m_value->child(0));
+ IntRange rightRange = rangeFor(m_value->child(1));
+ if (!leftRange.couldOverflowSub(rightRange, m_value->type())) {
+ replaceWithNewValue(
+ m_proc.add<Value>(Sub, m_value->origin(), m_value->child(0), m_value->child(1)));
+ break;
+ }
+ break;
+ }
+
+ case CheckMul: {
+ if (replaceWithNewValue(m_value->child(0)->checkMulConstant(m_proc, m_value->child(1))))
+ break;
+
+ handleCommutativity();
+
+ if (m_value->child(1)->hasInt()) {
+ bool modified = true;
+ switch (m_value->child(1)->asInt()) {
+ case 0:
+ replaceWithNewValue(m_proc.addIntConstant(m_value, 0));
+ break;
+ case 1:
+ replaceWithIdentity(m_value->child(0));
+ break;
+ case 2:
+ m_value->as<CheckValue>()->convertToAdd();
+ m_value->child(1) = m_value->child(0);
+ m_changed = true;
+ break;
+ default:
+ modified = false;
+ break;
+ }
+ if (modified)
+ break;
+ }
+
+ IntRange leftRange = rangeFor(m_value->child(0));
+ IntRange rightRange = rangeFor(m_value->child(1));
+ if (!leftRange.couldOverflowMul(rightRange, m_value->type())) {
+ replaceWithNewValue(
+ m_proc.add<Value>(Mul, m_value->origin(), m_value->child(0), m_value->child(1)));
+ break;
+ }
+ break;
+ }
+
+ case Check: {
+ CheckValue* checkValue = m_value->as<CheckValue>();
+
+ if (checkValue->child(0)->isLikeZero()) {
+ checkValue->replaceWithNop();
+ m_changed = true;
+ break;
+ }
+
+ if (checkValue->child(0)->isLikeNonZero()) {
+ PatchpointValue* patchpoint =
+ m_insertionSet.insert<PatchpointValue>(m_index, Void, checkValue->origin());
+
+ patchpoint->effects = Effects();
+ patchpoint->effects.reads = HeapRange::top();
+ patchpoint->effects.exitsSideways = true;
+
+ for (unsigned i = 1; i < checkValue->numChildren(); ++i)
+ patchpoint->append(checkValue->constrainedChild(i));
+
+ patchpoint->setGenerator(checkValue->generator());
+
+ // Replace the rest of the block with an Oops.
+ for (unsigned i = m_index + 1; i < m_block->size() - 1; ++i)
+ m_block->at(i)->replaceWithNop();
+ m_block->last()->as<ControlValue>()->convertToOops();
+ m_block->last()->setOrigin(checkValue->origin());
+
+ // Replace ourselves last.
+ checkValue->replaceWithNop();
+ m_changedCFG = true;
+ break;
+ }
+
+ if (checkValue->child(0)->opcode() == NotEqual
+ && checkValue->child(0)->child(1)->isInt(0)) {
+ checkValue->child(0) = checkValue->child(0)->child(0);
+ m_changed = true;
+ }
+
+ // If we are checking some bounded-size SSA expression that leads to a Select that
+ // has a constant as one of its results, then turn the Select into a Branch and split
+ // the code between the Check and the Branch. For example, this:
+ //
+ // @a = Select(@p, @x, 42)
+ // @b = Add(@a, 35)
+ // Check(@b)
+ //
+ // becomes this:
+ //
+ // Branch(@p, #truecase, #falsecase)
+ //
+ // BB#truecase:
+ // @b_truecase = Add(@x, 35)
+ // Check(@b_truecase)
+ // Upsilon(@x, ^a)
+ // Upsilon(@b_truecase, ^b)
+ // Jump(#continuation)
+ //
+ // BB#falsecase:
+ // @b_falsecase = Add(42, 35)
+ // Check(@b_falsecase)
+ // Upsilon(42, ^a)
+ // Upsilon(@b_falsecase, ^b)
+ // Jump(#continuation)
+ //
+ // BB#continuation:
+ // @a = Phi()
+ // @b = Phi()
+ //
+ // The goal of this optimization is to kill a lot of code in one of those basic
+ // blocks. This is pretty much guaranteed since one of those blocks will replace all
+ // uses of the Select with a constant, and that constant will be transitively used
+ // from the check.
+ static const unsigned selectSpecializationBound = 3;
+ Value* select = findRecentNodeMatching(
+ m_value->child(0), selectSpecializationBound,
+ [&] (Value* value) -> bool {
+ return value->opcode() == Select
+ && (value->child(1)->isConstant() && value->child(2)->isConstant());
+ });
+ if (select) {
+ specializeSelect(select);
+ break;
+ }
+ break;
+ }
+
+ case Branch: {
+ ControlValue* branch = m_value->as<ControlValue>();
+
+ // Turn this: Branch(NotEqual(x, 0))
+ // Into this: Branch(x)
+ if (branch->child(0)->opcode() == NotEqual && branch->child(0)->child(1)->isInt(0)) {
+ branch->child(0) = branch->child(0)->child(0);
+ m_changed = true;
+ }
+
+ // Turn this: Branch(Equal(x, 0), then, else)
+ // Into this: Branch(x, else, then)
+ if (branch->child(0)->opcode() == Equal && branch->child(0)->child(1)->isInt(0)) {
+ branch->child(0) = branch->child(0)->child(0);
+ std::swap(branch->taken(), branch->notTaken());
+ m_changed = true;
+ }
+
+ // Turn this: Branch(BitXor(bool, 1), then, else)
+ // Into this: Branch(bool, else, then)
+ if (branch->child(0)->opcode() == BitXor
+ && branch->child(0)->child(1)->isInt32(1)
+ && branch->child(0)->child(0)->returnsBool()) {
+ branch->child(0) = branch->child(0)->child(0);
+ std::swap(branch->taken(), branch->notTaken());
+ m_changed = true;
+ }
+
+ // Turn this: Branch(BitAnd(bool, xyb1), then, else)
+ // Into this: Branch(bool, then, else)
+ if (branch->child(0)->opcode() == BitAnd
+ && branch->child(0)->child(1)->hasInt()
+ && branch->child(0)->child(1)->asInt() & 1
+ && branch->child(0)->child(0)->returnsBool()) {
+ branch->child(0) = branch->child(0)->child(0);
+ m_changed = true;
+ }
+
+ TriState triState = branch->child(0)->asTriState();
+
+ // Turn this: Branch(0, then, else)
+ // Into this: Jump(else)
+ if (triState == FalseTriState) {
+ branch->taken().block()->removePredecessor(m_block);
+ branch->convertToJump(branch->notTaken().block());
+ m_changedCFG = true;
+ break;
+ }
+
+ // Turn this: Branch(not 0, then, else)
+ // Into this: Jump(then)
+ if (triState == TrueTriState) {
+ branch->notTaken().block()->removePredecessor(m_block);
+ branch->convertToJump(branch->taken().block());
+ m_changedCFG = true;
+ break;
+ }
+
+ // If a check for the same property dominates us, we can kill the branch. This sort
+ // of makes sense here because it's cheap, but hacks like this show that we're going
+ // to need SCCP.
+ Value* check = m_pureCSE.findMatch(
+ ValueKey(Check, Void, branch->child(0)), m_block, *m_dominators);
+ if (check) {
+ // The Check would have side-exited if child(0) was non-zero. So, it must be
+ // zero here.
+ branch->taken().block()->removePredecessor(m_block);
+ branch->convertToJump(branch->notTaken().block());
+ m_changedCFG = true;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ // Find a node that:
+ // - functor(node) returns true.
+ // - it's reachable from the given node via children.
+ // - it's in the last "bound" slots in the current basic block.
+ // This algorithm is optimized under the assumption that the bound is small.
+ template<typename Functor>
+ Value* findRecentNodeMatching(Value* start, unsigned bound, const Functor& functor)
+ {
+ unsigned startIndex = bound < m_index ? m_index - bound : 0;
+ Value* result = nullptr;
+ start->walk(
+ [&] (Value* value) -> Value::WalkStatus {
+ bool found = false;
+ for (unsigned i = startIndex; i <= m_index; ++i) {
+ if (m_block->at(i) == value)
+ found = true;
+ }
+ if (!found)
+ return Value::IgnoreChildren;
+
+ if (functor(value)) {
+ result = value;
+ return Value::Stop;
+ }
+
+ return Value::Continue;
+ });
+ return result;
+ }
+
+ // This specializes a sequence of code up to a Select. This doesn't work when we're at a
+ // terminal. It would be cool to fix that eventually. The main problem is that instead of
+ // splitting the block, we should just insert the then/else blocks. We'll have to create
+ // double the Phis and double the Upsilons. It'll probably be the sort of optimization that
+ // we want to do only after we've done loop optimizations, since this will *definitely*
+ // obscure things. In fact, even this simpler form of select specialization will possibly
+ // obscure other optimizations. It would be great to have two modes of strength reduction,
+ // one that does obscuring optimizations and runs late, and another that does not do
+ // obscuring optimizations and runs early.
+ // FIXME: Make select specialization handle branches.
+ // FIXME: Have a form of strength reduction that does no obscuring optimizations and runs
+ // early.
+ void specializeSelect(Value* source)
+ {
+ if (verbose)
+ dataLog("Specializing select: ", deepDump(m_proc, source), "\n");
+
+ // This mutates startIndex to account for the fact that m_block got the front of it
+ // chopped off.
+ BasicBlock* predecessor =
+ m_blockInsertionSet.splitForward(m_block, m_index, &m_insertionSet);
+
+ // Splitting will commit the insertion set, which changes the exact position of the
+ // source. That's why we do the search after splitting.
+ unsigned startIndex = UINT_MAX;
+ for (unsigned i = predecessor->size(); i--;) {
+ if (predecessor->at(i) == source) {
+ startIndex = i;
+ break;
+ }
+ }
+
+ RELEASE_ASSERT(startIndex != UINT_MAX);
+
+ // By BasicBlock convention, caseIndex == 0 => then, caseIndex == 1 => else.
+ static const unsigned numCases = 2;
+ BasicBlock* cases[numCases];
+ for (unsigned i = 0; i < numCases; ++i)
+ cases[i] = m_blockInsertionSet.insertBefore(m_block);
+
+ HashMap<Value*, Value*> mappings[2];
+
+ // Save things we want to know about the source.
+ Value* predicate = source->child(0);
+
+ for (unsigned i = 0; i < numCases; ++i)
+ mappings[i].add(source, source->child(1 + i));
+
+ auto cloneValue = [&] (Value* value) {
+ ASSERT(value != source);
+
+ for (unsigned i = 0; i < numCases; ++i) {
+ Value* clone = m_proc.clone(value);
+ for (Value*& child : clone->children()) {
+ if (Value* newChild = mappings[i].get(child))
+ child = newChild;
+ }
+ if (value->type() != Void)
+ mappings[i].add(value, clone);
+
+ cases[i]->append(clone);
+ if (value->type() != Void)
+ cases[i]->appendNew<UpsilonValue>(m_proc, value->origin(), clone, value);
+ }
+
+ value->replaceWithPhi();
+ };
+
+ // The jump that the splitter inserted is of no use to us.
+ predecessor->removeLast(m_proc);
+
+ // Hance the source, it's special.
+ for (unsigned i = 0; i < numCases; ++i) {
+ cases[i]->appendNew<UpsilonValue>(
+ m_proc, source->origin(), source->child(1 + i), source);
+ }
+ source->replaceWithPhi();
+ m_insertionSet.insertValue(m_index, source);
+
+ // Now handle all values between the source and the check.
+ for (unsigned i = startIndex + 1; i < predecessor->size(); ++i) {
+ Value* value = predecessor->at(i);
+ value->owner = nullptr;
+
+ cloneValue(value);
+
+ if (value->type() != Void)
+ m_insertionSet.insertValue(m_index, value);
+ else
+ m_proc.deleteValue(value);
+ }
+
+ // Finally, deal with the check.
+ cloneValue(m_value);
+
+ // Remove the values from the predecessor.
+ predecessor->values().resize(startIndex);
+
+ predecessor->appendNew<ControlValue>(
+ m_proc, Branch, source->origin(), predicate,
+ FrequentedBlock(cases[0]), FrequentedBlock(cases[1]));
+
+ for (unsigned i = 0; i < numCases; ++i) {
+ cases[i]->appendNew<ControlValue>(
+ m_proc, Jump, m_value->origin(), FrequentedBlock(m_block));
+ }
+
+ m_changed = true;
+
+ predecessor->updatePredecessorsAfter();
+ }
+
+ // Turn this: Add(constant, value)
+ // Into this: Add(value, constant)
+ //
+ // Also:
+ // Turn this: Add(value1, value2)
+ // Into this: Add(value2, value1)
+ // If we decide that value2 coming first is the canonical ordering.
+ void handleCommutativity()
+ {
+ // Note that we have commutative operations that take more than two children. Those operations may
+ // commute their first two children while leaving the rest unaffected.
+ ASSERT(m_value->numChildren() >= 2);
+
+ // Leave it alone if the right child is a constant.
+ if (m_value->child(1)->isConstant())
+ return;
+
+ if (m_value->child(0)->isConstant()) {
+ std::swap(m_value->child(0), m_value->child(1));
+ m_changed = true;
+ return;
+ }
+
+ // Sort the operands. This is an important canonicalization. We use the index instead of
+ // the address to make this at least slightly deterministic.
+ if (m_value->child(0)->index() > m_value->child(1)->index()) {
+ std::swap(m_value->child(0), m_value->child(1));
+ m_changed = true;
+ return;
+ }
+ }
+
+ // FIXME: This should really be a forward analysis. Instead, we uses a bounded-search backwards
+ // analysis.
+ IntRange rangeFor(Value* value, unsigned timeToLive = 5)
+ {
+ if (!timeToLive)
+ return IntRange::top(value->type());
+
+ switch (value->opcode()) {
+ case Const32:
+ case Const64: {
+ int64_t intValue = value->asInt();
+ return IntRange(intValue, intValue);
+ }
+
+ case BitAnd:
+ if (value->child(1)->hasInt())
+ return IntRange::rangeForMask(value->child(1)->asInt(), value->type());
+ break;
+
+ case SShr:
+ if (value->child(1)->hasInt32()) {
+ return rangeFor(value->child(0), timeToLive - 1).sShr(
+ value->child(1)->asInt32(), value->type());
+ }
+ break;
+
+ case ZShr:
+ if (value->child(1)->hasInt32()) {
+ return rangeFor(value->child(0), timeToLive - 1).zShr(
+ value->child(1)->asInt32(), value->type());
+ }
+ break;
+
+ case Shl:
+ if (value->child(1)->hasInt32()) {
+ return rangeFor(value->child(0), timeToLive - 1).shl(
+ value->child(1)->asInt32(), value->type());
+ }
+ break;
+
+ case Add:
+ return rangeFor(value->child(0), timeToLive - 1).add(
+ rangeFor(value->child(1), timeToLive - 1), value->type());
+
+ case Sub:
+ return rangeFor(value->child(0), timeToLive - 1).sub(
+ rangeFor(value->child(1), timeToLive - 1), value->type());
+
+ case Mul:
+ return rangeFor(value->child(0), timeToLive - 1).mul(
+ rangeFor(value->child(1), timeToLive - 1), value->type());
+
+ default:
+ break;
+ }
+
+ return IntRange::top(value->type());
+ }
+
+ template<typename ValueType, typename... Arguments>
+ void replaceWithNew(Arguments... arguments)
+ {
+ replaceWithNewValue(m_proc.add<ValueType>(arguments...));
+ }
+
+ bool replaceWithNewValue(Value* newValue)
+ {
+ if (!newValue)
+ return false;
+ m_insertionSet.insertValue(m_index, newValue);
+ m_value->replaceWithIdentity(newValue);
+ m_changed = true;
+ return true;
+ }
+
+ void replaceWithIdentity(Value* newValue)
+ {
+ m_value->replaceWithIdentity(newValue);
+ m_changed = true;
+ }
+
+ bool handleShiftAmount()
+ {
+ // Shift anything by zero is identity.
+ if (m_value->child(1)->isInt32(0)) {
+ replaceWithIdentity(m_value->child(0));
+ return true;
+ }
+
+ // The shift already masks its shift amount. If the shift amount is being masked by a
+ // redundant amount, then remove the mask. For example,
+ // Turn this: Shl(@x, BitAnd(@y, 63))
+ // Into this: Shl(@x, @y)
+ unsigned mask = sizeofType(m_value->type()) * 8 - 1;
+ if (m_value->child(1)->opcode() == BitAnd
+ && m_value->child(1)->child(1)->hasInt32()
+ && (m_value->child(1)->child(1)->asInt32() & mask) == mask) {
+ m_value->child(1) = m_value->child(1)->child(0);
+ m_changed = true;
+ // Don't need to return true, since we're still the same shift, and we can still cascade
+ // through other optimizations.
+ }
+
+ return false;
+ }
+
+ void replaceIfRedundant()
+ {
+ m_changed |= m_pureCSE.process(m_value, *m_dominators);
+ }
+
+ void simplifyCFG()
+ {
+ if (verbose) {
+ dataLog("Before simplifyCFG:\n");
+ dataLog(m_proc);
+ }
+
+ // We have three easy simplification rules:
+ //
+ // 1) If a successor is a block that just jumps to another block, then jump directly to
+ // that block.
+ //
+ // 2) If all successors are the same and the operation has no effects, then use a jump
+ // instead.
+ //
+ // 3) If you jump to a block that is not you and has one predecessor, then merge.
+ //
+ // Note that because of the first rule, this phase may introduce critical edges. That's fine.
+ // If you need broken critical edges, then you have to break them yourself.
+
+ // Note that this relies on predecessors being at least conservatively correct. It's fine for
+ // predecessors to mention a block that isn't actually a predecessor. It's *not* fine for a
+ // predecessor to be omitted. We assert as much in the loop. In practice, we precisely preserve
+ // predecessors during strength reduction since that minimizes the total number of fixpoint
+ // iterations needed to kill a lot of code.
+
+ for (BasicBlock* block : m_proc) {
+ if (verbose)
+ dataLog("Considering block ", *block, ":\n");
+
+ checkPredecessorValidity();
+
+ // We don't care about blocks that don't have successors.
+ if (!block->numSuccessors())
+ continue;
+
+ // First check if any of the successors of this block can be forwarded over.
+ for (BasicBlock*& successor : block->successorBlocks()) {
+ if (successor != block
+ && successor->size() == 1
+ && successor->last()->opcode() == Jump) {
+ BasicBlock* newSuccessor = successor->successorBlock(0);
+ if (newSuccessor != successor) {
+ if (verbose) {
+ dataLog(
+ "Replacing ", pointerDump(block), "->", pointerDump(successor),
+ " with ", pointerDump(block), "->", pointerDump(newSuccessor),
+ "\n");
+ }
+ // Note that we do not do replacePredecessor() because the block we're
+ // skipping will still have newSuccessor as its successor.
+ newSuccessor->addPredecessor(block);
+ successor = newSuccessor;
+ m_changedCFG = true;
+ }
+ }
+ }
+
+ // Now check if the block's terminal can be replaced with a jump.
+ if (block->numSuccessors() > 1) {
+ // The terminal must not have weird effects.
+ Effects effects = block->last()->effects();
+ effects.terminal = false;
+ if (!effects.mustExecute()) {
+ // All of the successors must be the same.
+ bool allSame = true;
+ BasicBlock* firstSuccessor = block->successorBlock(0);
+ for (unsigned i = 1; i < block->numSuccessors(); ++i) {
+ if (block->successorBlock(i) != firstSuccessor) {
+ allSame = false;
+ break;
+ }
+ }
+ if (allSame) {
+ if (verbose) {
+ dataLog(
+ "Changing ", pointerDump(block), "'s terminal to a Jump.\n");
+ }
+ block->last()->as<ControlValue>()->convertToJump(firstSuccessor);
+ m_changedCFG = true;
+ }
+ }
+ }
+
+ // Finally handle jumps to a block with one predecessor.
+ if (block->numSuccessors() == 1) {
+ BasicBlock* successor = block->successorBlock(0);
+ if (successor != block && successor->numPredecessors() == 1) {
+ RELEASE_ASSERT(successor->predecessor(0) == block);
+
+ // We can merge the two blocks, because the predecessor only jumps to the successor
+ // and the successor is only reachable from the predecessor.
+
+ // Remove the terminal.
+ Value* value = block->values().takeLast();
+ Origin jumpOrigin = value->origin();
+ RELEASE_ASSERT(value->as<ControlValue>());
+ m_proc.deleteValue(value);
+
+ // Append the full contents of the successor to the predecessor.
+ block->values().appendVector(successor->values());
+
+ // Make sure that the successor has nothing left in it. Make sure that the block
+ // has a terminal so that nobody chokes when they look at it.
+ successor->values().resize(0);
+ successor->appendNew<ControlValue>(m_proc, Oops, jumpOrigin);
+
+ // Ensure that predecessors of block's new successors know what's up.
+ for (BasicBlock* newSuccessor : block->successorBlocks())
+ newSuccessor->replacePredecessor(successor, block);
+
+ if (verbose) {
+ dataLog(
+ "Merged ", pointerDump(block), "->", pointerDump(successor), "\n");
+ }
+
+ m_changedCFG = true;
+ }
+ }
+ }
+
+ if (m_changedCFG && verbose) {
+ dataLog("B3 after simplifyCFG:\n");
+ dataLog(m_proc);
+ }
+ }
+
+ void checkPredecessorValidity()
+ {
+ if (!shouldValidateIRAtEachPhase())
+ return;
+
+ for (BasicBlock* block : m_proc) {
+ for (BasicBlock* successor : block->successorBlocks())
+ RELEASE_ASSERT(successor->containsPredecessor(block));
+ }
+ }
+
+ void killDeadCode()
+ {
+ GraphNodeWorklist<Value*, IndexSet<Value>> worklist;
+ Vector<UpsilonValue*, 64> upsilons;
+ for (BasicBlock* block : m_proc) {
+ for (Value* value : *block) {
+ Effects effects;
+ // We don't care about effects of SSA operations, since we model them more
+ // accurately than the effects() method does.
+ if (value->opcode() != Phi && value->opcode() != Upsilon)
+ effects = value->effects();
+
+ if (effects.mustExecute())
+ worklist.push(value);
+
+ if (UpsilonValue* upsilon = value->as<UpsilonValue>())
+ upsilons.append(upsilon);
+ }
+ }
+ for (;;) {
+ while (Value* value = worklist.pop()) {
+ for (Value* child : value->children())
+ worklist.push(child);
+ }
+
+ bool didPush = false;
+ for (size_t upsilonIndex = 0; upsilonIndex < upsilons.size(); ++upsilonIndex) {
+ UpsilonValue* upsilon = upsilons[upsilonIndex];
+ if (worklist.saw(upsilon->phi())) {
+ worklist.push(upsilon);
+ upsilons[upsilonIndex--] = upsilons.last();
+ upsilons.takeLast();
+ didPush = true;
+ }
+ }
+ if (!didPush)
+ break;
+ }
+
+ IndexSet<Variable> liveVariables;
+
+ for (BasicBlock* block : m_proc) {
+ size_t sourceIndex = 0;
+ size_t targetIndex = 0;
+ while (sourceIndex < block->size()) {
+ Value* value = block->at(sourceIndex++);
+ if (worklist.saw(value)) {
+ if (VariableValue* variableValue = value->as<VariableValue>())
+ liveVariables.add(variableValue->variable());
+ block->at(targetIndex++) = value;
+ } else {
+ m_proc.deleteValue(value);
+
+ // It's not entirely clear if this is needed. I think it makes sense to have
+ // this force a rerun of the fixpoint for now, since that will make it easier
+ // to do peephole optimizations: removing dead code will make the peephole
+ // easier to spot.
+ m_changed = true;
+ }
+ }
+ block->values().resize(targetIndex);
+ }
+
+ for (Variable* variable : m_proc.variables()) {
+ if (!liveVariables.contains(variable))
+ m_proc.deleteVariable(variable);
+ }
+ }
+
+ void simplifySSA()
+ {
+ // This runs Aycock and Horspool's algorithm on our Phi functions [1]. For most CFG patterns,
+ // this can take a suboptimal arrangement of Phi functions and make it optimal, as if you had
+ // run Cytron, Ferrante, Rosen, Wegman, and Zadeck. It's only suboptimal for irreducible
+ // CFGs. In practice, that doesn't matter, since we expect clients of B3 to run their own SSA
+ // conversion before lowering to B3, and in the case of the DFG, that conversion uses Cytron
+ // et al. In that context, this algorithm is intended to simplify Phi functions that were
+ // made redundant by prior CFG simplification. But according to Aycock and Horspool's paper,
+ // this algorithm is good enough that a B3 client could just give us maximal Phi's (i.e. Phi
+ // for each variable at each basic block) and we will make them optimal.
+ // [1] http://pages.cpsc.ucalgary.ca/~aycock/papers/ssa.ps
+
+ // Aycock and Horspool prescribe two rules that are to be run to fixpoint:
+ //
+ // 1) If all of the Phi's children are the same (i.e. it's one child referenced from one or
+ // more Upsilons), then replace all uses of the Phi with the one child.
+ //
+ // 2) If all of the Phi's children are either the Phi itself or exactly one other child, then
+ // replace all uses of the Phi with the one other child.
+ //
+ // Rule (2) subsumes rule (1), so we can just run (2). We only run one fixpoint iteration
+ // here. This premise is that in common cases, this will only find optimization opportunities
+ // as a result of CFG simplification and usually CFG simplification will only do one round
+ // of block merging per ReduceStrength fixpoint iteration, so it's OK for this to only do one
+ // round of Phi merging - since Phis are the value analogue of blocks.
+
+ PhiChildren phiChildren(m_proc);
+
+ for (Value* phi : phiChildren.phis()) {
+ Value* otherChild = nullptr;
+ bool ok = true;
+ for (Value* child : phiChildren[phi].values()) {
+ if (child == phi)
+ continue;
+ if (child == otherChild)
+ continue;
+ if (!otherChild) {
+ otherChild = child;
+ continue;
+ }
+ ok = false;
+ break;
+ }
+ if (!ok)
+ continue;
+ if (!otherChild) {
+ // Wow, this would be super weird. It probably won't happen, except that things could
+ // get weird as a consequence of stepwise simplifications in the strength reduction
+ // fixpoint.
+ continue;
+ }
+
+ // Turn the Phi into an Identity and turn the Upsilons into Nops.
+ m_changed = true;
+ for (Value* upsilon : phiChildren[phi])
+ upsilon->replaceWithNop();
+ phi->replaceWithIdentity(otherChild);
+ }
+ }
+
+ Procedure& m_proc;
+ InsertionSet m_insertionSet;
+ BlockInsertionSet m_blockInsertionSet;
+ BasicBlock* m_block { nullptr };
+ unsigned m_index { 0 };
+ Value* m_value { nullptr };
+ Dominators* m_dominators { nullptr };
+ PureCSE m_pureCSE;
+ bool m_changed { false };
+ bool m_changedCFG { false };
+};
+
+} // anonymous namespace
+
+bool reduceStrength(Procedure& proc)
+{
+ PhaseScope phaseScope(proc, "reduceStrength");
+ ReduceStrength reduceStrength(proc);
+ return reduceStrength.run();
+}
+
+} } // namespace JSC::B3
+
+#endif // ENABLE(B3_JIT)
+