view modules/javafx.web/src/main/native/Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp @ 11290:a1fb556cdd7d

8219362: Update to 608.1 version of WebKit Reviewed-by: kcr, jvos
author arajkumar
date Sat, 29 Jun 2019 15:45:39 +0530
parents db2c977a840b
children
line wrap: on
line source
/*
 * 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 "DFGIntegerRangeOptimizationPhase.h"

#if ENABLE(DFG_JIT)

#include "DFGBlockMapInlines.h"
#include "DFGBlockSet.h"
#include "DFGGraph.h"
#include "DFGInsertionSet.h"
#include "DFGNodeFlowProjection.h"
#include "DFGPhase.h"
#include "DFGPredictionPropagationPhase.h"
#include "DFGVariableAccessDataDump.h"
#include "JSCInlines.h"

namespace JSC { namespace DFG {

namespace {

namespace DFGIntegerRangeOptimizationPhaseInternal {
static const bool verbose = false;
}
const unsigned giveUpThreshold = 50;

int64_t clampedSumImpl() { return 0; }

template<typename... Args>
int64_t clampedSumImpl(int left, Args... args)
{
    return static_cast<int64_t>(left) + clampedSumImpl(args...);
}

template<typename... Args>
int clampedSum(Args... args)
{
    int64_t result = clampedSumImpl(args...);
    return static_cast<int>(std::min(
        static_cast<int64_t>(std::numeric_limits<int>::max()),
        std::max(
            static_cast<int64_t>(std::numeric_limits<int>::min()),
            result)));
}

bool isGeneralOffset(int offset)
{
    return offset >= -1 && offset <= 1;
}

class Relationship {
public:
    enum Kind {
        LessThan,
        Equal,
        NotEqual,
        GreaterThan
    };

    // Some relationships provide more information than others. When a relationship provides more
    // information, it is less vague.
    static unsigned vagueness(Kind kind)
    {
        switch (kind) {
        case Equal:
            return 0;
        case LessThan:
        case GreaterThan:
            return 1;
        case NotEqual:
            return 2;
        }
        RELEASE_ASSERT_NOT_REACHED();
        return 0;
    }

    static const unsigned minVagueness = 0;
    static const unsigned maxVagueness = 2;

    static Kind flipped(Kind kind)
    {
        switch (kind) {
        case LessThan:
            return GreaterThan;
        case Equal:
            return Equal;
        case NotEqual:
            return NotEqual;
        case GreaterThan:
            return LessThan;
        }
        RELEASE_ASSERT_NOT_REACHED();
        return kind;
    }

    Relationship()
        : m_left(nullptr)
        , m_right(nullptr)
        , m_kind(Equal)
        , m_offset(0)
    {
    }

    Relationship(NodeFlowProjection left, NodeFlowProjection right, Kind kind, int offset = 0)
        : m_left(left)
        , m_right(right)
        , m_kind(kind)
        , m_offset(offset)
    {
        RELEASE_ASSERT(m_left);
        RELEASE_ASSERT(m_right);
        RELEASE_ASSERT(m_left != m_right);
    }

    static Relationship safeCreate(NodeFlowProjection left, NodeFlowProjection right, Kind kind, int offset = 0)
    {
        if (!left.isStillValid() || !right.isStillValid() || left == right)
            return Relationship();
        return Relationship(left, right, kind, offset);
    }

    explicit operator bool() const { return !!m_left; }

    NodeFlowProjection left() const { return m_left; }
    NodeFlowProjection right() const { return m_right; }
    Kind kind() const { return m_kind; }
    int offset() const { return m_offset; }

    unsigned vagueness() const { return vagueness(kind()); }

    Relationship flipped() const
    {
        if (!*this)
            return Relationship();

        // This should return Relationship() if -m_offset overflows. For example:
        //
        //     @a > @b - 2**31
        //
        // If we flip it we get:
        //
        //     @b < @a + 2**31
        //
        // Except that the sign gets flipped since it's INT_MIN:
        //
        //     @b < @a - 2**31
        //
        // And that makes no sense. To see how little sense it makes, consider:
        //
        //     @a > @zero - 2**31
        //
        // We would flip it to mean:
        //
        //     @zero < @a - 2**31
        //
        // Which is absurd.

        if (m_offset == std::numeric_limits<int>::min())
            return Relationship();

        return Relationship(m_right, m_left, flipped(m_kind), -m_offset);
    }

    Relationship inverse() const
    {
        if (!*this)
            return *this;

        switch (m_kind) {
        case Equal:
            return Relationship(m_left, m_right, NotEqual, m_offset);
        case NotEqual:
            return Relationship(m_left, m_right, Equal, m_offset);
        case LessThan:
            if (sumOverflows<int>(m_offset, -1))
                return Relationship();
            return Relationship(m_left, m_right, GreaterThan, m_offset - 1);
        case GreaterThan:
            if (sumOverflows<int>(m_offset, 1))
                return Relationship();
            return Relationship(m_left, m_right, LessThan, m_offset + 1);
        }

        RELEASE_ASSERT_NOT_REACHED();
    }

    bool isCanonical() const { return m_left < m_right; }

    Relationship canonical() const
    {
        if (isCanonical())
            return *this;
        return flipped();
    }

    bool sameNodesAs(const Relationship& other) const
    {
        return m_left == other.m_left
            && m_right == other.m_right;
    }

    bool isEquivalentTo(const Relationship& other) const
    {
        if (m_left != other.m_left || m_kind != other.m_kind)
            return false;

        if (*this == other)
            return true;

        if (m_right->isInt32Constant() && other.m_right->isInt32Constant())
            return (m_right->asInt32() + m_offset) == (other.m_right->asInt32() + other.m_offset);
        return false;
    }

    bool operator==(const Relationship& other) const
    {
        return sameNodesAs(other)
            && m_kind == other.m_kind
            && m_offset == other.m_offset;
    }

    bool operator!=(const Relationship& other) const
    {
        return !(*this == other);
    }

    bool operator<(const Relationship& other) const
    {
        if (m_left != other.m_left)
            return m_left < other.m_left;
        if (m_right != other.m_right)
            return m_right < other.m_right;
        if (m_kind != other.m_kind)
            return m_kind < other.m_kind;
        return m_offset < other.m_offset;
    }

    // If possible, returns a form of this relationship where the given node is the left
    // side. Returns a null relationship if this relationship cannot say anything about this
    // node.
    Relationship forNode(NodeFlowProjection node) const
    {
        if (m_left == node)
            return *this;
        if (m_right == node)
            return flipped();
        return Relationship();
    }

    void setLeft(NodeFlowProjection left)
    {
        RELEASE_ASSERT(left != m_right);
        m_left = left;
    }
    bool addToOffset(int offset)
    {
        if (sumOverflows<int>(m_offset, offset))
            return false;
        m_offset += offset;
        return true;
    }

    // Attempts to create relationships that summarize the union of this relationship and
    // the other relationship. Relationships are returned by calling the functor with the newly
    // created relationships. No relationships are created to indicate TOP. This is used
    // for merging the current relationship-at-head for some pair of nodes and a new
    // relationship-at-head being proposed by a predecessor. We wish to create a new
    // relationship that is true whenever either of them are true, which ensuring that we don't
    // do this forever. Anytime we create a relationship that is not equal to either of the
    // previous ones, we will cause the analysis fixpoint to reexecute.
    //
    // If *this and other are identical, we just pass it to the functor.
    //
    // If they are different, we pick from a finite set of "general" relationships:
    //
    // Eq: this == other + C, where C is -1, 0, or 1.
    // Lt: this < other + C, where C is -1, 0, or 1.
    // Gt: this > other + C, where C is -1, 0, or 1.
    // Ne: this != other + C, where C is -1, 0, or 1.
    // TOP: the null relationship.
    //
    // Constraining C to -1,0,1 is necessary to ensure that the set of general relationships is
    // finite. This finite set of relationships forms a pretty simple lattice where a
    // relA->relB means "relB is more general than relA". For example, this<other+1 is more
    // general than this==other. I'll leave it as an exercise for the reader to see that a
    // graph between the 13 general relationships is indeed a lattice. The fact that the set of
    // general relationships is a finite lattice ensures monotonicity of the fixpoint, since
    // any merge over not-identical relationships returns a relationship that is closer to the
    // TOP relationship than either of the original relationships. Here's how convergence is
    // achieved for any pair of relationships over the same nodes:
    //
    // - If they are identical, then returning *this means that we won't be responsible for
    //   causing another fixpoint iteration. Once all merges reach this point, we're done.
    //
    // - If they are different, then we pick the most constraining of the 13 general
    //   relationships that is true if either *this or other are true. This means that if the
    //   relationships are not identical, the merged relationship will be closer to TOP than
    //   either of the originals. Returning a different relationship means that we will be
    //   responsible for the fixpoint to reloop, but we can only do this at most 13 times since
    //   that's how "deep" the general relationship lattice is.
    //
    // Note that C being constrained to -1,0,1 also ensures that we never have to return a
    // combination of Lt and Gt, as in for example this<other+C && this>other-D. The only possible
    // values of C and D where this would work are -1 and 1, but in that case we just say
    // this==other. That said, the logic for merging two == relationships, like this==other+C ||
    // this==other+D is to attempt to create these two relationships: this>other+min(C,D)-1 &&
    // this<other+max(C,D)+1. But only one of these relationships will belong to the set of general
    // relationships.
    //
    // Here's an example of this in action:
    //
    // for (var i = a; ; ++i) { }
    //
    // Without C being constrained to -1,0,1, we could end up looping forever: first we'd say
    // that i==a, then we might say that i<a+2, then i<a+3, then i<a+4, etc. We won't do this
    // because i<a+2 is not a valid general relationship: so when we merge i==a from the first
    // iteration and i==a+1 from the second iteration, we create i>a-1 and i<a+2 but then
    // realize that only i>a-1 is a valid general relationship. This gives us exactly what we
    // want: a statement that i>=a.
    //
    // However, this may return a pair of relationships when merging relationships involving
    // constants. For example, if given:
    //
    //     @x == @c
    //     @x == @d
    //
    // where @c and @d are constants, then this may pass two relationships to the functor:
    //
    //     @x > min(@c, @d) - 1
    //     @x < max(@c, @d) + 1
    //
    // This still allows for convergence, because just as when merging relationships over
    // variables, this always picks from a set of general relationships. Hence although this may
    // produce two relationships as a result of the merge, the total number of relationships that
    // can be present at head of block is limited by O(graph.size^2).
    template<typename Functor>
    void merge(const Relationship& other, const Functor& functor) const
    {
        // Handle the super obvious case first.
        if (*this == other) {
            functor(*this);
            return;
        }

        if (m_left != other.m_left)
            return;

        if (m_right != other.m_right) {
            mergeConstantsImpl(other, functor);
            return;
        }

        ASSERT(sameNodesAs(other));

        // This does some interesting permutations to reduce the amount of duplicate code. For
        // example:
        //
        // initially: @a != @b, @a > @b
        //            @b != @a, @b < @a
        //            @b < @a, @b != @a
        //   finally: @b != a, @b < @a
        //
        // Another example:
        //
        // initially: @a < @b, @a != @b
        //   finally: @a != @b, @a < @b

        Relationship a = *this;
        Relationship b = other;
        bool needFlip = false;

        // Get rid of GreaterThan.
        if (a.m_kind == GreaterThan || b.m_kind == GreaterThan) {
            a = a.flipped();
            b = b.flipped();

            // In rare cases, we might not be able to flip. Just give up on life in those
            // cases.
            if (!a || !b)
                return;

            needFlip = true;

            // If we still have GreaterThan, then it means that we started with @a < @b and
            // @a > @b. That's pretty much always a tautology; we don't attempt to do smart
            // things for that case for now.
            if (a.m_kind == GreaterThan || b.m_kind == GreaterThan)
                return;
        }

        // Make sure that if we have a LessThan, then it's first.
        if (b.m_kind == LessThan)
            std::swap(a, b);

        // Make sure that if we have a NotEqual, then it's first.
        if (b.m_kind == NotEqual)
            std::swap(a, b);

        Relationship result = a.mergeImpl(b);
        if (!result)
            return;

        if (needFlip)
            result = result.flipped();

        functor(result);
    }

    // Attempts to construct one Relationship that adequately summarizes the intersection of
    // this and other. Returns a null relationship if the filtration should be expressed as two
    // different relationships. Returning null is always safe because relationship lists in
    // this phase always imply intersection. So, you could soundly skip calling this method and
    // just put both relationships into the list. But, that could lead the fixpoint to diverge.
    // Hence this will attempt to combine the two relationships into one as a convergence hack.
    // In some cases, it will do something conservative. It's always safe for this to return
    // *this, or to return other. It'll do that sometimes, mainly to accelerate convergence for
    // things that we don't think are important enough to slow down the analysis.
    Relationship filter(const Relationship& other) const
    {
        // We are only interested in merging relationships over the same nodes.
        ASSERT(sameNodesAs(other));

        if (*this == other)
            return *this;

        // From here we can assume that the two relationships are not identical. Usually we use
        // this to assume that we have different offsets anytime that everything but the offset
        // is identical.

        // We want equality to take precedent over everything else, and we don't want multiple
        // independent claims of equality. That would just be a contradiction. When it does
        // happen, we will be conservative in the sense that we will pick one.
        if (m_kind == Equal)
            return *this;
        if (other.m_kind == Equal)
            return other;

        // Useful helper for flipping.
        auto filterFlipped = [&] () -> Relationship {
            // If we cannot flip, then just conservatively return *this.
            Relationship a = flipped();
            Relationship b = other.flipped();
            if (!a || !b)
                return *this;
            Relationship result = a.filter(b);
            if (!result)
                return Relationship();
            result = result.flipped();
            if (!result)
                return *this;
            return result;
        };

        if (m_kind == NotEqual) {
            if (other.m_kind == NotEqual) {
                // We could do something smarter here. We could even keep both NotEqual's. We
                // would need to make sure that we correctly collapsed them when merging. But
                // for now, we just pick one of them and hope for the best.
                return *this;
            }

            if (other.m_kind == GreaterThan) {
                // Implement this in terms of NotEqual.filter(LessThan).
                return filterFlipped();
            }

            ASSERT(other.m_kind == LessThan);
            // We have two claims:
            //     @a != @b + C
            //     @a  < @b + D
            //
            // If C >= D, then the NotEqual is redundant.
            // If C < D - 1, then we could keep both, but for now we just keep the LessThan.
            // If C == D - 1, then the LessThan can be turned into:
            //
            //     @a < @b + C
            //
            // Note that C == this.m_offset, D == other.m_offset.

            if (m_offset == other.m_offset - 1)
                return Relationship(m_left, m_right, LessThan, m_offset);

            return other;
        }

        if (other.m_kind == NotEqual)
            return other.filter(*this);

        if (m_kind == LessThan) {
            if (other.m_kind == LessThan) {
                return Relationship(
                    m_left, m_right, LessThan, std::min(m_offset, other.m_offset));
            }

            ASSERT(other.m_kind == GreaterThan);
            if (sumOverflows<int>(m_offset, -1))
                return Relationship();
            if (sumOverflows<int>(other.m_offset, 1))
                return Relationship();
            if (m_offset - 1 == other.m_offset + 1)
                return Relationship(m_left, m_right, Equal, m_offset - 1);

            return Relationship();
        }

        ASSERT(m_kind == GreaterThan);
        return filterFlipped();
    }

    // Come up with a relationship that is the best description of this && other, provided that left() is
    // the same and right() is a constant. Also requires that this is at least as vague as other. It may
    // return this or it may return something else, but whatever it returns, it will have the same nodes as
    // this. This is not automatically done by filter() because it currently only makes sense to call this
    // during a very particular part of setOneSide().
    Relationship filterConstant(const Relationship& other) const
    {
        ASSERT(m_left == other.m_left);
        ASSERT(m_right->isInt32Constant());
        ASSERT(other.m_right->isInt32Constant());
        ASSERT(vagueness() >= other.vagueness());

        if (vagueness() == other.vagueness())
            return *this;

        int thisRight = m_right->asInt32();
        int otherRight = other.m_right->asInt32();

        // Ignore funny business.
        if (sumOverflows<int>(otherRight, other.m_offset))
            return *this;

        int otherEffectiveRight = otherRight + other.m_offset;

        switch (other.m_kind) {
        case Equal:
            // Return a version of *this that is Equal to other's constant.
            return Relationship(m_left, m_right, Equal, otherEffectiveRight - thisRight);

        case LessThan:
        case GreaterThan:
            ASSERT(m_kind == NotEqual);
            // We could do smart things here. But we don't currently have an example of when it would be
            // valuable. Note that you have to be careful. We could refine NotEqual to LessThan, but only
            // if the LessThan subsumes the NotEqual.
            return *this;

        case NotEqual:
            RELEASE_ASSERT_NOT_REACHED();
            return Relationship();
        }

        RELEASE_ASSERT_NOT_REACHED();
        return Relationship();
    }

    int minValueOfLeft() const
    {
        if (m_left->isInt32Constant())
            return m_left->asInt32();

        if (m_kind == LessThan || m_kind == NotEqual)
            return std::numeric_limits<int>::min();

        int minRightValue = std::numeric_limits<int>::min();
        if (m_right->isInt32Constant())
            minRightValue = m_right->asInt32();

        if (m_kind == GreaterThan)
            return clampedSum(minRightValue, m_offset, 1);
        ASSERT(m_kind == Equal);
        return clampedSum(minRightValue, m_offset);
    }

    int maxValueOfLeft() const
    {
        if (m_left->isInt32Constant())
            return m_left->asInt32();

        if (m_kind == GreaterThan || m_kind == NotEqual)
            return std::numeric_limits<int>::max();

        int maxRightValue = std::numeric_limits<int>::max();
        if (m_right->isInt32Constant())
            maxRightValue = m_right->asInt32();

        if (m_kind == LessThan)
            return clampedSum(maxRightValue, m_offset, -1);
        ASSERT(m_kind == Equal);
        return clampedSum(maxRightValue, m_offset);
    }

    void dump(PrintStream& out) const
    {
        // This prints out the relationship without any whitespace, like @x<@y+42. This
        // optimizes for the clarity of a list of relationships. It's easier to read something
        // like [@1<@2+3, @4==@5-6] than it would be if there was whitespace inside the
        // relationships.

        if (!*this) {
            out.print("null");
            return;
        }

        out.print(m_left);
        switch (m_kind) {
        case LessThan:
            out.print("<");
            break;
        case Equal:
            out.print("==");
            break;
        case NotEqual:
            out.print("!=");
            break;
        case GreaterThan:
            out.print(">");
            break;
        }
        out.print(m_right);
        if (m_offset > 0)
            out.print("+", m_offset);
        else if (m_offset < 0)
            out.print("-", -static_cast<int64_t>(m_offset));
    }

private:
    Relationship mergeImpl(const Relationship& other) const
    {
        ASSERT(sameNodesAs(other));
        ASSERT(m_kind != GreaterThan);
        ASSERT(other.m_kind != GreaterThan);
        ASSERT(*this != other);

        // The purpose of this method is to guarantee that:
        //
        // - We avoid having more than one Relationship over the same two nodes. Therefore, if
        //   the merge could be expressed as two Relationships, we prefer to instead pick the
        //   less precise single Relationship form even if that means TOP.
        //
        // - If the difference between two Relationships is just the m_offset, then we create a
        //   Relationship that has an offset of -1, 0, or 1. This is an essential convergence
        //   hack. We need -1 and 1 to support <= and >=.

        // From here we can assume that the two relationships are not identical. Usually we use
        // this to assume that we have different offsets anytime that everything but the offset
        // is identical.

        if (m_kind == NotEqual) {
            if (other.m_kind == NotEqual)
                return Relationship(); // Different offsets, so tautology.

            if (other.m_kind == Equal) {
                if (m_offset != other.m_offset) {
                    // Saying that you might be B when you've already said that you're anything
                    // but A, where A and B are different, is a tautology. You could just say
                    // that you're anything but A. Adding "(a == b + 1)" to "(a != b + 5)" has
                    // no value.
                    return *this;
                }
                // Otherwise, same offsets: we're saying that you're either A or you're not
                // equal to A.

                return Relationship();
            }

            RELEASE_ASSERT(other.m_kind == LessThan);
            // We have these claims, and we're merging them:
            //     @a != @b + C
            //     @a < @b + D
            //
            // If we have C == D, then the merge is clearly just the NotEqual.
            // If we have C < D, then the merge is a tautology.
            // If we have C > D, then we could keep both claims, but we are cheap, so we
            // don't. We just use the NotEqual.

            if (m_offset < other.m_offset)
                return Relationship();

            return *this;
        }

        if (m_kind == LessThan) {
            if (other.m_kind == LessThan) {
                // Figure out what offset to select to merge them. The appropriate offsets are
                // -1, 0, or 1.

                // First figure out what offset we'd like to use.
                int bestOffset = std::max(m_offset, other.m_offset);

                // We have something like @a < @b + 2. We can't represent this under the
                // -1,0,1 rule.
                if (isGeneralOffset(bestOffset))
                    return Relationship(m_left, m_right, LessThan, std::max(bestOffset, -1));

                return Relationship();
            }

            // The only thing left is Equal. We would have eliminated the GreaterThan's, and
            // if we merge LessThan and NotEqual, the NotEqual always comes first.
            RELEASE_ASSERT(other.m_kind == Equal);

            // This is the really interesting case. We have:
            //
            //     @a < @b + C
            //
            // and:
            //
            //     @a == @b + D
            //
            // Therefore we'd like to return:
            //
            //     @a < @b + max(C, D + 1)

            int bestOffset = std::max(m_offset, other.m_offset + 1);

            // We have something like @a < @b + 2. We can't do it.
            if (isGeneralOffset(bestOffset))
                return Relationship(m_left, m_right, LessThan, std::max(bestOffset, -1));

            return Relationship();
        }

        // The only thing left is Equal, since we would have gotten rid of the GreaterThan's.
        RELEASE_ASSERT(m_kind == Equal);

        // We would never see NotEqual, because those always come first. We would never
        // see GreaterThan, because we would have eliminated those. We would never see
        // LessThan, because those always come first.

        RELEASE_ASSERT(other.m_kind == Equal);
        // We have @a == @b + C and @a == @b + D, where C != D. Turn this into some
        // inequality that involves a constant that is -1,0,1. Note that we will never have
        // lessThan and greaterThan because the constants are constrained to -1,0,1. The only
        // way for both of them to be valid is a<b+1 and a>b-1, but then we would have said
        // a==b.

        Relationship lessThan;
        Relationship greaterThan;

        int lessThanEqOffset = std::max(m_offset, other.m_offset);
        if (lessThanEqOffset >= -2 && lessThanEqOffset <= 0) {
            lessThan = Relationship(
                m_left, other.m_right, LessThan, lessThanEqOffset + 1);

            ASSERT(isGeneralOffset(lessThan.offset()));
        }

        int greaterThanEqOffset = std::min(m_offset, other.m_offset);
        if (greaterThanEqOffset >= 0 && greaterThanEqOffset <= 2) {
            greaterThan = Relationship(
                m_left, other.m_right, GreaterThan, greaterThanEqOffset - 1);

            ASSERT(isGeneralOffset(greaterThan.offset()));
        }

        if (lessThan) {
            // Both relationships cannot be valid; see above.
            RELEASE_ASSERT(!greaterThan);

            return lessThan;
        }

        return greaterThan;
    }

    template<typename Functor>
    void mergeConstantsImpl(const Relationship& other, const Functor& functor) const
    {
        ASSERT(m_left == other.m_left);

        // Only deal with constant right.
        if (!m_right->isInt32Constant() || !other.m_right->isInt32Constant())
            return;

        // What follows is a fairly conservative merge. We could tune this phase to come up with
        // all possible inequalities between variables and constants, but we focus mainly on cheap
        // cases for now.

        // Here are some of the arrangements we can merge usefully assuming @c < @d:
        //
        //     @x == @c || @x == @d   =>   @x >= c && @x <= @d
        //     @x >= @c || @x <= @d   =>   TOP
        //     @x == @c || @x != @d   =>   @x != @d

        int thisRight = m_right->asInt32();
        int otherRight = other.m_right->asInt32();

        // Ignore funny business.
        if (sumOverflows<int>(thisRight, m_offset))
            return;
        if (sumOverflows<int>(otherRight, other.m_offset))
            return;

        int thisEffectiveRight = thisRight + m_offset;
        int otherEffectiveRight = otherRight + other.m_offset;

        auto makeUpper = [&] (int64_t upper) {
            if (upper <= thisRight) {
                // We want m_right + offset to be equal to upper. Hence we want offset to cancel
                // with m_right. But there's more to it, since we want +1 to turn the LessThan into
                // a LessThanOrEqual, and we want to make sure we don't end up with non-general
                // offsets.
                int offset = static_cast<int>(std::max(
                    static_cast<int64_t>(1) + upper - static_cast<int64_t>(thisRight),
                    static_cast<int64_t>(-1)));
                functor(Relationship(m_left, m_right, LessThan, offset));
            }
            if (upper <= otherRight) {
                int offset = static_cast<int>(std::max(
                    static_cast<int64_t>(1) + upper - static_cast<int64_t>(otherRight),
                    static_cast<int64_t>(-1)));
                functor(Relationship(m_left, other.m_right, LessThan, offset));
            }
        };
        auto makeLower = [&] (int64_t lower) {
            if (lower >= thisRight) {
                // We want m_right + offset to be equal to lower. Hence we want offset to cancel with
                // m_right. But there's more to it, since we want -1 to turn the GreaterThan into a
                // GreaterThanOrEqual, and we want to make sure we don't end up with non-general
                // offsets.
                int offset = static_cast<int>(std::min(
                    static_cast<int64_t>(-1) + lower - static_cast<int64_t>(thisRight),
                    static_cast<int64_t>(1)));
                functor(Relationship(m_left, m_right, GreaterThan, offset));
            }
            if (lower >= otherRight) {
                int offset = static_cast<int>(std::min(
                    static_cast<int64_t>(-1) + lower - static_cast<int64_t>(otherRight),
                    static_cast<int64_t>(1)));
                functor(Relationship(m_left, other.m_right, GreaterThan, offset));
            }
        };

        switch (m_kind) {
        case Equal: {
            switch (other.m_kind) {
            case Equal: {
                if (thisEffectiveRight == otherEffectiveRight) {
                    // This probably won't arise often. We can keep whichever relationship is general.
                    if (isGeneralOffset(m_offset))
                        functor(*this);
                    if (isGeneralOffset(other.m_offset))
                        functor(other);
                    return;
                }

                // What follows is the only case where a merge will create more rules than what it
                // started with. This is fine for convergence because the LessThan/GreaterThan
                // rules that this creates are general (i.e. have small offsets) and they never
                // spawn more rules upon subsequent merging.

                makeUpper(std::max(thisEffectiveRight, otherEffectiveRight));
                makeLower(std::min(thisEffectiveRight, otherEffectiveRight));
                return;
            }

            case LessThan: {
                // Either the LessThan condition subsumes the equality, or the LessThan condition
                // and equality merge together to create a looser LessThan condition.

                // This is @x == thisEffectiveRight
                // Other is: @x < otherEffectiveRight

                // We want to create @x <= upper. Figure out the value of upper.
                makeUpper(std::max(
                    static_cast<int64_t>(thisEffectiveRight),
                    static_cast<int64_t>(otherEffectiveRight) - 1));
                return;
            }

            case GreaterThan: {
                // Opposite of the LessThan case, above.

                // This is: @x == thisEffectiveRight
                // Other is: @x > otherEffectiveRight

                makeLower(std::min(
                    static_cast<int64_t>(thisEffectiveRight),
                    static_cast<int64_t>(otherEffectiveRight) + 1));
                return;
            }

            case NotEqual: {
                // We keep the NotEqual so long as it doesn't contradict our Equal.
                if (otherEffectiveRight == thisEffectiveRight)
                    return;

                // But, we only keep the NotEqual if it is general. This simplifies reasoning about
                // convergence: merging never introduces a new rule unless that rule is general.
                if (!isGeneralOffset(other.m_offset))
                    return;

                functor(other);
                return;
            } }

            RELEASE_ASSERT_NOT_REACHED();
            return;
        }

        case LessThan: {
            switch (other.m_kind) {
            case Equal: {
                other.mergeConstantsImpl(*this, functor);
                return;
            }

            case LessThan: {
                makeUpper(std::max(
                    static_cast<int64_t>(thisEffectiveRight) - 1,
                    static_cast<int64_t>(otherEffectiveRight) - 1));
                return;
            }

            case GreaterThan: {
                // We have a claim that @x > @c || @x < @d. If @d > @c, this is the tautology. If
                // @d <= @c, it's sort of uninteresting. Just ignore this.
                return;
            }

            case NotEqual: {
                // We have a claim that @x < @c || @x != @d. This isn't interesting.
                return;
            } }

            RELEASE_ASSERT_NOT_REACHED();
            return;
        }

        case GreaterThan: {
            switch (other.m_kind) {
            case Equal: {
                other.mergeConstantsImpl(*this, functor);
                return;
            }

            case LessThan: {
                // Not interesting, see above.
                return;
            }

            case GreaterThan: {
                makeLower(std::min(
                    static_cast<int64_t>(thisEffectiveRight) + 1,
                    static_cast<int64_t>(otherEffectiveRight) + 1));
                return;
            }

            case NotEqual: {
                // Not interesting, see above.
                return;
            } }

            RELEASE_ASSERT_NOT_REACHED();
            return;
        }

        case NotEqual: {
            if (other.m_kind == Equal)
                other.mergeConstantsImpl(*this, functor);
            return;
        } }

        RELEASE_ASSERT_NOT_REACHED();
    }

    NodeFlowProjection m_left;
    NodeFlowProjection m_right;
    Kind m_kind;
    int m_offset; // This offset can be arbitrarily large.
};

typedef HashMap<NodeFlowProjection, Vector<Relationship>> RelationshipMap;

class IntegerRangeOptimizationPhase : public Phase {
public:
    IntegerRangeOptimizationPhase(Graph& graph)
        : Phase(graph, "integer range optimization")
        , m_zero(nullptr)
        , m_relationshipsAtHead(graph)
        , m_insertionSet(graph)
    {
    }

    bool run()
    {
        ASSERT(m_graph.m_form == SSA);

        // Before we do anything, make sure that we have a zero constant at the top.
        m_zero = m_insertionSet.insertConstant(0, m_graph.block(0)->at(0)->origin, jsNumber(0));
        m_insertionSet.execute(m_graph.block(0));

        if (DFGIntegerRangeOptimizationPhaseInternal::verbose) {
            dataLog("Graph before integer range optimization:\n");
            m_graph.dump();
        }

        // This performs a fixpoint over the blocks in reverse post-order. Logically, we
        // maintain a list of relationships at each point in the program. The list should be
        // read as an intersection. For example if we have {rel1, rel2, ..., relN}, you should
        // read this as:
        //
        //     TOP && rel1 && rel2 && ... && relN
        //
        // This allows us to express things like:
        //
        //     @a > @b - 42 && @a < @b + 25
        //
        // But not things like:
        //
        //     @a < @b - 42 || @a > @b + 25
        //
        // We merge two lists by merging each relationship in one list with each relationship
        // in the other list. Merging two relationships will yield a relationship list; as with
        // all such lists it is an intersection. Merging relationships over different variables
        // always yields the empty list (i.e. TOP). This merge style is sound because if we
        // have:
        //
        //     (A && B && C) || (D && E && F)
        //
        // Then a valid merge is just one that will return true if A, B, C are all true, or
        // that will return true if D, E, F are all true. Our merge style essentially does:
        //
        //     (A || D) && (A || E) && (A || F) && (B || D) && (B || E) && (B || F) &&
        //         (C || D) && (C || E) && (C || F)
        //
        // If A && B && C is true, then this returns true. If D && E && F is true, this also
        // returns true.
        //
        // While this appears at first like a kind of expression explosion, in practice it
        // isn't. The code that handles this knows that the merge of two relationships over
        // different variables is TOP (i.e. the empty list). For example if A above is @a < @b
        // and B above is @c > @d, where @a, @b, @c, and @d are different nodes, the merge will
        // yield nothing. In fact, the merge algorithm will skip such merges entirely because
        // the relationship lists are actually keyed by node.
        //
        // Note that it's always safe to drop any of relationship from the relationship list.
        // This merely increases the likelihood of the "expression" yielding true, i.e. being
        // closer to TOP. Optimizations are only performed if we can establish that the
        // expression implied by the relationship list is false for all of those cases where
        // some check would have failed.
        //
        // There is no notion of BOTTOM because we treat blocks that haven't had their
        // state-at-head set as a special case: we just transfer all live relationships to such
        // a block. After the head of a block is set, we perform the merging as above. In all
        // other places where we would ordinarily need BOTTOM, we approximate it by having some
        // non-BOTTOM relationship.

        BlockList postOrder = m_graph.blocksInPostOrder();

        // This loop analyzes the IR to give us m_relationshipsAtHead for each block. This
        // may reexecute blocks many times, but it is guaranteed to converge. The state of
        // the relationshipsAtHead over any pair of nodes converge monotonically towards the
        // TOP relationship (i.e. no relationships in the relationship list). The merge rule
        // when between the current relationshipsAtHead and the relationships being propagated
        // from a predecessor ensures monotonicity by converting disagreements into one of a
        // small set of "general" relationships. There are 12 such relationships, plus TOP. See
        // the comment above Relationship::merge() for details.
        bool changed = true;
        while (changed) {
            ++m_iterations;
            if (m_iterations >= giveUpThreshold) {
                // This case is not necessarily wrong but it can be a sign that this phase
                // does not converge. The value giveUpThreshold was chosen emperically based on
                // current tests and real world JS.
                // If you hit this case for a legitimate reason, update the giveUpThreshold
                // to the smallest values that converges.

                // Do not risk holding the thread for too long since this phase is really slow.
                return false;
            }

            changed = false;
            for (unsigned postOrderIndex = postOrder.size(); postOrderIndex--;) {
                BasicBlock* block = postOrder[postOrderIndex];
                DFG_ASSERT(
                    m_graph, nullptr,
                    block == m_graph.block(0) || m_seenBlocks.contains(block));

                m_relationships = m_relationshipsAtHead[block];

                for (auto* node : *block) {
                    if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                        dataLog("Analysis: at ", node, ": ", listDump(sortedRelationships()), "\n");
                    executeNode(node);
                }

                // Now comes perhaps the most important piece of cleverness: if we Branch, and
                // the predicate involves some relation over integers, we propagate different
                // information to the taken and notTaken paths. This handles:
                // - Branch on int32.
                // - Branch on LogicalNot on int32.
                // - Branch on compare on int32's.
                // - Branch on LogicalNot of compare on int32's.
                Node* terminal = block->terminal();
                bool alreadyMerged = false;
                if (terminal->op() == Branch) {
                    Relationship relationshipForTrue;
                    BranchData* branchData = terminal->branchData();

                    bool invert = false;
                    if (terminal->child1()->op() == LogicalNot) {
                        terminal = terminal->child1().node();
                        invert = true;
                    }

                    if (terminal->child1().useKind() == Int32Use) {
                        relationshipForTrue = Relationship::safeCreate(
                            terminal->child1().node(), m_zero, Relationship::NotEqual, 0);
                    } else {
                        // FIXME: Handle CompareBelow and CompareBelowEq.
                        Node* compare = terminal->child1().node();
                        switch (compare->op()) {
                        case CompareEq:
                        case CompareStrictEq:
                        case CompareLess:
                        case CompareLessEq:
                        case CompareGreater:
                        case CompareGreaterEq: {
                            if (!compare->isBinaryUseKind(Int32Use))
                                break;

                            switch (compare->op()) {
                            case CompareEq:
                            case CompareStrictEq:
                                relationshipForTrue = Relationship::safeCreate(
                                    compare->child1().node(), compare->child2().node(),
                                    Relationship::Equal, 0);
                                break;
                            case CompareLess:
                                relationshipForTrue = Relationship::safeCreate(
                                    compare->child1().node(), compare->child2().node(),
                                    Relationship::LessThan, 0);
                                break;
                            case CompareLessEq:
                                relationshipForTrue = Relationship::safeCreate(
                                    compare->child1().node(), compare->child2().node(),
                                    Relationship::LessThan, 1);
                                break;
                            case CompareGreater:
                                relationshipForTrue = Relationship::safeCreate(
                                    compare->child1().node(), compare->child2().node(),
                                    Relationship::GreaterThan, 0);
                                break;
                            case CompareGreaterEq:
                                relationshipForTrue = Relationship::safeCreate(
                                    compare->child1().node(), compare->child2().node(),
                                    Relationship::GreaterThan, -1);
                                break;
                            default:
                                DFG_CRASH(m_graph, compare, "Invalid comparison node type");
                                break;
                            }
                            break;
                        }

                        default:
                            break;
                        }
                    }

                    if (invert)
                        relationshipForTrue = relationshipForTrue.inverse();

                    if (relationshipForTrue) {
                        RelationshipMap forTrue = m_relationships;
                        RelationshipMap forFalse = m_relationships;

                        if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                            dataLog("Dealing with true:\n");
                        setRelationship(forTrue, relationshipForTrue);
                        if (Relationship relationshipForFalse = relationshipForTrue.inverse()) {
                            if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                                dataLog("Dealing with false:\n");
                            setRelationship(forFalse, relationshipForFalse);
                        }

                        changed |= mergeTo(forTrue, branchData->taken.block);
                        changed |= mergeTo(forFalse, branchData->notTaken.block);
                        alreadyMerged = true;
                    }
                }

                if (!alreadyMerged) {
                    for (BasicBlock* successor : block->successors())
                        changed |= mergeTo(m_relationships, successor);
                }
            }
        }

        // Now we transform the code based on the results computed in the previous loop.
        changed = false;
        for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
            m_relationships = m_relationshipsAtHead[block];
            for (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
                Node* node = block->at(nodeIndex);
                if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                    dataLog("Transformation: at ", node, ": ", listDump(sortedRelationships()), "\n");

                // This ends up being pretty awkward to write because we need to decide if we
                // optimize by using the relationships before the operation, but we need to
                // call executeNode() before we optimize.
                switch (node->op()) {
                case ArithAbs: {
                    if (node->child1().useKind() != Int32Use)
                        break;

                    auto iter = m_relationships.find(node->child1().node());
                    if (iter == m_relationships.end())
                        break;

                    int minValue = std::numeric_limits<int>::min();
                    int maxValue = std::numeric_limits<int>::max();
                    for (Relationship relationship : iter->value) {
                        minValue = std::max(minValue, relationship.minValueOfLeft());
                        maxValue = std::min(maxValue, relationship.maxValueOfLeft());
                    }

                    executeNode(block->at(nodeIndex));

                    if (minValue >= 0) {
                        node->convertToIdentityOn(node->child1().node());
                        changed = true;
                        break;
                    }
                    bool absIsUnchecked = !shouldCheckOverflow(node->arithMode());
                    if (maxValue < 0 || (absIsUnchecked && maxValue <= 0)) {
                        node->convertToArithNegate();
                        if (absIsUnchecked || minValue > std::numeric_limits<int>::min())
                            node->setArithMode(Arith::Unchecked);
                        changed = true;
                        break;
                    }
                    if (minValue > std::numeric_limits<int>::min()) {
                        node->setArithMode(Arith::Unchecked);
                        changed = true;
                        break;
                    }

                    break;
                }
                case ArithAdd: {
                    if (!node->isBinaryUseKind(Int32Use))
                        break;
                    if (node->arithMode() != Arith::CheckOverflow)
                        break;
                    if (!node->child2()->isInt32Constant())
                        break;

                    auto iter = m_relationships.find(node->child1().node());
                    if (iter == m_relationships.end())
                        break;

                    int minValue = std::numeric_limits<int>::min();
                    int maxValue = std::numeric_limits<int>::max();
                    for (Relationship relationship : iter->value) {
                        minValue = std::max(minValue, relationship.minValueOfLeft());
                        maxValue = std::min(maxValue, relationship.maxValueOfLeft());
                    }

                    if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                        dataLog("    minValue = ", minValue, ", maxValue = ", maxValue, "\n");

                    if (sumOverflows<int>(minValue, node->child2()->asInt32()) ||
                        sumOverflows<int>(maxValue, node->child2()->asInt32()))
                        break;

                    if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                        dataLog("    It's in bounds.\n");

                    executeNode(block->at(nodeIndex));
                    node->setArithMode(Arith::Unchecked);
                    changed = true;
                    break;
                }

                case CheckInBounds: {
                    auto iter = m_relationships.find(node->child1().node());
                    if (iter == m_relationships.end())
                        break;

                    bool nonNegative = false;
                    bool lessThanLength = false;
                    for (Relationship relationship : iter->value) {
                        if (relationship.minValueOfLeft() >= 0)
                            nonNegative = true;

                        if (relationship.right() == node->child2().node()) {
                            if (relationship.kind() == Relationship::Equal
                                && relationship.offset() < 0)
                                lessThanLength = true;

                            if (relationship.kind() == Relationship::LessThan
                                && relationship.offset() <= 0)
                                lessThanLength = true;
                        }
                    }

                    if (nonNegative && lessThanLength) {
                        executeNode(block->at(nodeIndex));
                        node->convertToIdentityOn(m_zero);
                        changed = true;
                    }
                    break;
                }

                case GetByVal: {
                    if (node->arrayMode().type() != Array::Undecided)
                        break;

                    auto iter = m_relationships.find(m_graph.varArgChild(node, 1).node());
                    if (iter == m_relationships.end())
                        break;

                    int minValue = std::numeric_limits<int>::min();
                    for (Relationship relationship : iter->value)
                        minValue = std::max(minValue, relationship.minValueOfLeft());

                    if (minValue < 0)
                        break;

                    executeNode(block->at(nodeIndex));
                    m_graph.convertToConstant(node, jsUndefined());
                    changed = true;
                    break;
                }

                default:
                    break;
                }

                executeNode(block->at(nodeIndex));
            }
        }

        return changed;
    }

private:
    void executeNode(Node* node)
    {
        switch (node->op()) {
        case CheckInBounds: {
            setRelationship(Relationship::safeCreate(node->child1().node(), node->child2().node(), Relationship::LessThan));
            setRelationship(Relationship::safeCreate(node->child1().node(), m_zero, Relationship::GreaterThan, -1));
            break;
        }

        case ArithAbs: {
            if (node->child1().useKind() != Int32Use)
                break;
            setRelationship(Relationship(node, m_zero, Relationship::GreaterThan, -1));
            break;
        }

        case ArithAdd: {
            // We're only interested in int32 additions and we currently only know how to
            // handle the non-wrapping ones.
            if (!node->isBinaryUseKind(Int32Use))
                break;

            // FIXME: We could handle the unchecked arithmetic case. We just do it don't right
            // now.
            if (node->arithMode() != Arith::CheckOverflow)
                break;

            // Handle add: @value + constant.
            if (!node->child2()->isInt32Constant())
                break;

            int offset = node->child2()->asInt32();

            // We add a relationship for @add == @value + constant, and then we copy the
            // relationships for @value. This gives us a one-deep view of @value's existing
            // relationships, which matches the one-deep search in setRelationship().

            setRelationship(
                Relationship(node, node->child1().node(), Relationship::Equal, offset));

            auto iter = m_relationships.find(node->child1().node());
            if (iter != m_relationships.end()) {
                Vector<Relationship> toAdd;
                for (Relationship relationship : iter->value) {
                    // We have:
                    //     add: ArithAdd(@x, C)
                    //     @x op @y + D
                    //
                    // The following certainly holds:
                    //     @x == @add - C
                    //
                    // Which allows us to substitute:
                    //     @add - C op @y + D
                    //
                    // And then carry the C over:
                    //     @add op @y + D + C

                    Relationship newRelationship = relationship;
                    ASSERT(newRelationship.left() == node->child1().node());

                    if (newRelationship.right() == node)
                        continue;
                    newRelationship.setLeft(node);
                    if (newRelationship.addToOffset(offset))
                        toAdd.append(newRelationship);
                }
                for (Relationship relationship : toAdd)
                    setRelationship(relationship, 0);
            }

            // Now we want to establish that both the input and the output of the addition are
            // within a particular range of integers.

            if (offset > 0) {
                // If we have "add: @value + 1" then we know that @value <= max - 1, i.e. that
                // @value < max.
                if (!sumOverflows<int>(std::numeric_limits<int>::max(), -offset, 1)) {
                    setRelationship(
                        Relationship::safeCreate(
                            node->child1().node(), m_zero, Relationship::LessThan,
                            std::numeric_limits<int>::max() - offset + 1),
                        0);
                }

                // If we have "add: @value + 1" then we know that @add >= min + 1, i.e. that
                // @add > min.
                if (!sumOverflows<int>(std::numeric_limits<int>::min(), offset, -1)) {
                    setRelationship(
                        Relationship(
                            node, m_zero, Relationship::GreaterThan,
                            std::numeric_limits<int>::min() + offset - 1),
                        0);
                }
            }

            if (offset < 0 && offset != std::numeric_limits<int>::min()) {
                // If we have "add: @value - 1" then we know that @value >= min + 1, i.e. that
                // @value > min.
                if (!sumOverflows<int>(std::numeric_limits<int>::min(), offset, -1)) {
                    setRelationship(
                        Relationship::safeCreate(
                            node->child1().node(), m_zero, Relationship::GreaterThan,
                            std::numeric_limits<int>::min() + offset - 1),
                        0);
                }

                // If we have "add: @value + 1" then we know that @add <= max - 1, i.e. that
                // @add < max.
                if (!sumOverflows<int>(std::numeric_limits<int>::max(), -offset, 1)) {
                    setRelationship(
                        Relationship(
                            node, m_zero, Relationship::LessThan,
                            std::numeric_limits<int>::max() - offset + 1),
                        0);
                }
            }
            break;
        }

        case GetArrayLength:
        case GetVectorLength: {
            setRelationship(Relationship(node, m_zero, Relationship::GreaterThan, -1));
            break;
        }

        case Upsilon: {
            setEquivalence(
                node->child1().node(),
                NodeFlowProjection(node->phi(), NodeFlowProjection::Shadow));
            break;
        }

        case Phi: {
            setEquivalence(
                NodeFlowProjection(node, NodeFlowProjection::Shadow),
                node);
            break;
        }

        default:
            break;
        }
    }

    void setEquivalence(NodeFlowProjection oldNode, NodeFlowProjection newNode)
    {
        setRelationship(Relationship::safeCreate(oldNode, newNode, Relationship::Equal, 0));

        auto iter = m_relationships.find(oldNode);
        if (iter != m_relationships.end()) {
            Vector<Relationship> toAdd;
            for (Relationship relationship : iter->value) {
                Relationship newRelationship = relationship;
                // Avoid creating any kind of self-relationship.
                if (newNode.node() == newRelationship.right().node())
                    continue;
                newRelationship.setLeft(newNode);
                toAdd.append(newRelationship);
            }
            for (Relationship relationship : toAdd)
                setRelationship(relationship);
        }
    }

    void setRelationship(Relationship relationship, unsigned timeToLive = 1)
    {
        setRelationship(m_relationships, relationship, timeToLive);
    }

    void setRelationship(
        RelationshipMap& relationshipMap, Relationship relationship, unsigned timeToLive = 1)
    {
        setOneSide(relationshipMap, relationship, timeToLive);
        setOneSide(relationshipMap, relationship.flipped(), timeToLive);
    }

    void setOneSide(
        RelationshipMap& relationshipMap, Relationship relationship, unsigned timeToLive = 1)
    {
        if (!relationship)
            return;

        if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
            dataLog("    Setting: ", relationship, " (ttl = ", timeToLive, ")\n");

        auto result = relationshipMap.add(
            relationship.left(), Vector<Relationship>());
        Vector<Relationship>& relationships = result.iterator->value;

        if (relationship.right()->isInt32Constant()) {
            // We want to do some work to refine relationships over constants. This is necessary because
            // when we introduce a constant into the IR, we don't automatically create relationships
            // between that constant and the other constants. That means that when we do introduce
            // relationships between a non-constant and a constant, we need to check the other
            // relationships between that non-constant and other constants to see if we can make some
            // refinements. Possible constant statement filtrations:
            //
            // - @x == @c and @x != @d, where @c > @d:
            //       @x == @c and @x > @d
            //
            // but actually we are more aggressive:
            //
            // - @x == @c and @x op @d where @c == @d + k
            //       @x == @c and @x == @d + k
            //
            // And this is also possible:
            //
            // - @x > @c and @x != @d where @c == @d + k and k >= 0
            //
            //       @x > @c and @x > @d + k
            //
            // So, here's what we should do depending on the kind of relationship we're introducing:
            //
            // Equal constant: Find all LessThan, NotEqual, and GreaterThan constant operations and refine
            //     them to be Equal constant. Don't worry about contradictions.
            //
            // LessThan, GreaterThan constant: See if there is any Equal constant, and if so, refine to
            //     that. Otherwise, find all NotEqual constant operations and refine them to be LessThan or
            //     GreaterThan constant if possible.
            //
            // NotEqual constant: See if there is any Equal constant, and if so, refine to that. Otherwise,
            //     see if there is any LessThan or GreaterThan constant operation, and if so, attempt to
            //     refine to that.
            //
            // Seems that the key thing is to have a filterConstant() operation that returns a refined
            // version of *this based on other. The code here accomplishes this by using the vagueness
            // index (Relationship::vagueness()) to first find less vague relationships and refine this one
            // using them, and then find more vague relationships and refine those to this.

            if (relationship.vagueness() != Relationship::minVagueness) {
                // We're not minimally vague (maximally specific), so try to refine ourselves based on what
                // we already know.
                for (Relationship& otherRelationship : relationships) {
                    if (otherRelationship.vagueness() < relationship.vagueness()
                        && otherRelationship.right()->isInt32Constant()) {
                        Relationship newRelationship = relationship.filterConstant(otherRelationship);
                        if (DFGIntegerRangeOptimizationPhaseInternal::verbose && newRelationship != relationship)
                            dataLog("      Refined to: ", newRelationship, " based on ", otherRelationship, "\n");
                        relationship = newRelationship;
                    }
                }
            }

            if (relationship.vagueness() != Relationship::maxVagueness) {
                // We're not maximally value (minimally specific), so try to refine other relationships
                // based on this one.
                for (Relationship& otherRelationship : relationships) {
                    if (otherRelationship.vagueness() > relationship.vagueness()
                        && otherRelationship.right()->isInt32Constant()) {
                        Relationship newRelationship = otherRelationship.filterConstant(relationship);
                        if (DFGIntegerRangeOptimizationPhaseInternal::verbose && newRelationship != otherRelationship)
                            dataLog("      Refined ", otherRelationship, " to: ", newRelationship, "\n");
                        otherRelationship = newRelationship;
                    }
                }
            }
        }

        Vector<Relationship> toAdd;
        bool found = false;
        for (Relationship& otherRelationship : relationships) {
            if (otherRelationship.sameNodesAs(relationship)) {
                if (Relationship filtered = otherRelationship.filter(relationship)) {
                    ASSERT(filtered.left() == relationship.left());
                    otherRelationship = filtered;
                    found = true;
                }
            }

            // FIXME: Also add filtration over statements about constants. For example, if we have
            // @x == @c and @x != @d, where @d > @c, then we want to turn @x != @d into @x < @d.

            if (timeToLive && otherRelationship.kind() == Relationship::Equal) {
                if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                    dataLog("      Considering: ", otherRelationship, "\n");

                // We have:
                //     @a op @b + C
                //     @a == @c + D
                //
                // This implies:
                //     @c + D op @b + C
                //     @c op @b + C - D
                //
                // Where: @a == relationship.left(), @b == relationship.right(),
                // @a == otherRelationship.left(), @c == otherRelationship.right().

                if (otherRelationship.offset() != std::numeric_limits<int>::min()) {
                    Relationship newRelationship = relationship;
                    if (newRelationship.right() != otherRelationship.right()) {
                        newRelationship.setLeft(otherRelationship.right());
                        if (newRelationship.addToOffset(-otherRelationship.offset()))
                            toAdd.append(newRelationship);
                    }
                }
            }
        }

        if (!found)
            relationships.append(relationship);

        for (Relationship anotherRelationship : toAdd) {
            ASSERT(timeToLive);
            setOneSide(relationshipMap, anotherRelationship, timeToLive - 1);
        }
    }

    bool mergeTo(RelationshipMap& relationshipMap, BasicBlock* target)
    {
        if (DFGIntegerRangeOptimizationPhaseInternal::verbose) {
            dataLog("Merging to ", pointerDump(target), ":\n");
            dataLog("    Incoming: ", listDump(sortedRelationships(relationshipMap)), "\n");
            dataLog("    At head: ", listDump(sortedRelationships(m_relationshipsAtHead[target])), "\n");
        }

        if (m_seenBlocks.add(target)) {
            // This is a new block. We copy subject to liveness pruning.
            auto isLive = [&] (NodeFlowProjection node) {
                if (node == m_zero)
                    return true;
                return target->ssa->liveAtHead.contains(node);
            };

            for (auto& entry : relationshipMap) {
                if (!isLive(entry.key))
                    continue;

                Vector<Relationship> values;
                for (Relationship relationship : entry.value) {
                    ASSERT(relationship.left() == entry.key);
                    if (isLive(relationship.right())) {
                        if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                            dataLog("  Propagating ", relationship, "\n");
                        values.append(relationship);
                    }
                }

                std::sort(values.begin(), values.end());
                m_relationshipsAtHead[target].add(entry.key, values);
            }
            return true;
        }

        // Merge by intersecting. We have no notion of BOTTOM, so we use the omission of
        // relationships for a pair of nodes to mean TOP. The reason why we don't need BOTTOM
        // is (1) we just overapproximate contradictions and (2) a value never having been
        // assigned would only happen if we have not processed the node's predecessor. We
        // shouldn't process blocks until we have processed the block's predecessor because we
        // are using reverse postorder.
        Vector<NodeFlowProjection> toRemove;
        bool changed = false;
        for (auto& entry : m_relationshipsAtHead[target]) {
            auto iter = relationshipMap.find(entry.key);
            if (iter == relationshipMap.end()) {
                toRemove.append(entry.key);
                changed = true;
                continue;
            }

            Vector<Relationship> constantRelationshipsAtHead;
            for (Relationship& relationshipAtHead : entry.value) {
                if (relationshipAtHead.right()->isInt32Constant())
                    constantRelationshipsAtHead.append(relationshipAtHead);
            }

            Vector<Relationship> mergedRelationships;
            for (Relationship targetRelationship : entry.value) {
                for (Relationship sourceRelationship : iter->value) {
                    if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                        dataLog("  Merging ", targetRelationship, " and ", sourceRelationship, ":\n");
                    targetRelationship.merge(
                        sourceRelationship,
                        [&] (Relationship newRelationship) {
                            if (DFGIntegerRangeOptimizationPhaseInternal::verbose)
                                dataLog("    Got ", newRelationship, "\n");

                            if (newRelationship.right()->isInt32Constant()) {
                                // We can produce a relationship with a constant equivalent to
                                // an existing relationship yet of a different form. For example:
                                //
                                //     @a == @b(42) + 0
                                //     @a == @c(41) + 1
                                //
                                // We do not want to perpetually switch between those two forms,
                                // so we always prefer the one already at head.

                                for (Relationship& existingRelationshipAtHead : constantRelationshipsAtHead) {
                                    if (existingRelationshipAtHead.isEquivalentTo(newRelationship)) {
                                        newRelationship = existingRelationshipAtHead;
                                        break;
                                    }
                                }
                            }

                            // We need to filter() to avoid exponential explosion of identical
                            // relationships. We do this here to avoid making setOneSide() do
                            // more work, since we expect setOneSide() will be called more
                            // frequently. Here's an example. At some point someone might start
                            // with two relationships like @a > @b - C and @a < @b + D. Then
                            // someone does a setRelationship() passing something that turns
                            // both of these into @a == @b. Now we have @a == @b duplicated.
                            // Let's say that this duplicate @a == @b ends up at the head of a
                            // loop. If we didn't have this rule, then the loop would propagate
                            // duplicate @a == @b's onto the existing duplicate @a == @b's.
                            // There would be four pairs of @a == @b, each of which would
                            // create a new @a == @b. Now we'd have four of these duplicates
                            // and the next time around we'd have 8, then 16, etc. We avoid
                            // this here by doing this filtration. That might be a bit of
                            // overkill, since it's probably just the identical duplicate
                            // relationship case we want' to avoid. But, I'll keep this until
                            // we have evidence that this is a performance problem. Remember -
                            // we are already dealing with a list that is pruned down to
                            // relationships with identical left operand. It shouldn't be a
                            // large list.
                            bool found = false;
                            for (Relationship& existingRelationship : mergedRelationships) {
                                if (existingRelationship.sameNodesAs(newRelationship)) {
                                    Relationship filtered =
                                        existingRelationship.filter(newRelationship);
                                    if (filtered) {
                                        existingRelationship = filtered;
                                        found = true;
                                        break;
                                    }
                                }
                            }

                            if (!found)
                                mergedRelationships.append(newRelationship);
                        });
                }
            }
            std::sort(mergedRelationships.begin(), mergedRelationships.end());
            if (entry.value == mergedRelationships)
                continue;

            entry.value = mergedRelationships;
            changed = true;
        }
        for (NodeFlowProjection node : toRemove)
            m_relationshipsAtHead[target].remove(node);

        return changed;
    }

    Vector<Relationship> sortedRelationships(const RelationshipMap& relationships)
    {
        Vector<Relationship> result;
        for (auto& entry : relationships)
            result.appendVector(entry.value);
        std::sort(result.begin(), result.end());
        return result;
    }

    Vector<Relationship> sortedRelationships()
    {
        return sortedRelationships(m_relationships);
    }

    Node* m_zero;
    RelationshipMap m_relationships;
    BlockSet m_seenBlocks;
    BlockMap<RelationshipMap> m_relationshipsAtHead;
    InsertionSet m_insertionSet;

    unsigned m_iterations { 0 };
};

} // anonymous namespace

bool performIntegerRangeOptimization(Graph& graph)
{
    return runPhase<IntegerRangeOptimizationPhase>(graph);
}

} } // namespace JSC::DFG

#endif // ENABLE(DFG_JIT)