summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp
diff options
context:
space:
mode:
authorKonstantin Tokarev <annulen@yandex.ru>2016-08-25 19:20:41 +0300
committerKonstantin Tokarev <annulen@yandex.ru>2017-02-02 12:30:55 +0000
commit6882a04fb36642862b11efe514251d32070c3d65 (patch)
treeb7959826000b061fd5ccc7512035c7478742f7b0 /Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp
parentab6df191029eeeb0b0f16f127d553265659f739e (diff)
downloadqtwebkit-6882a04fb36642862b11efe514251d32070c3d65.tar.gz
Imported QtWebKit TP3 (git b57bc6801f1876c3220d5a4bfea33d620d477443)
Change-Id: I3b1d8a2808782c9f34d50240000e20cb38d3680f Reviewed-by: Konstantin Tokarev <annulen@yandex.ru>
Diffstat (limited to 'Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp')
-rw-r--r--Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp1780
1 files changed, 1780 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp b/Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp
new file mode 100644
index 000000000..02aa134d8
--- /dev/null
+++ b/Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp
@@ -0,0 +1,1780 @@
+/*
+ * Copyright (C) 2015 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 "DFGPhase.h"
+#include "DFGPredictionPropagationPhase.h"
+#include "DFGVariableAccessDataDump.h"
+#include "JSCInlines.h"
+
+namespace JSC { namespace DFG {
+
+namespace {
+
+const bool verbose = false;
+
+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(Node* left, Node* 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(Node* left, Node* right, Kind kind, int offset = 0)
+ {
+ if (!left || !right || left == right)
+ return Relationship();
+ return Relationship(left, right, kind, offset);
+ }
+
+ explicit operator bool() const { return m_left; }
+
+ Node* left() const { return m_left; }
+ Node* 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 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(Node* node) const
+ {
+ if (m_left == node)
+ return *this;
+ if (m_right == node)
+ return flipped();
+ return Relationship();
+ }
+
+ void setLeft(Node* 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 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();
+ }
+
+ Node* m_left;
+ Node* m_right;
+ Kind m_kind;
+ int m_offset; // This offset can be arbitrarily large.
+};
+
+typedef HashMap<Node*, 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.
+ for (Node* node : *m_graph.block(0)) {
+ if (node->isInt32Constant() && !node->asInt32()) {
+ m_zero = node;
+ break;
+ }
+ }
+ if (!m_zero) {
+ m_zero = m_insertionSet.insertConstant(0, m_graph.block(0)->at(0)->origin, jsNumber(0));
+ m_insertionSet.execute(m_graph.block(0));
+ }
+
+ if (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 intersction. 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 relationshis, plus TOP. See
+ // the comment above Relationship::merge() for details.
+ bool changed = true;
+ while (changed) {
+ 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 (unsigned nodeIndex = 0; nodeIndex < block->size(); ++nodeIndex) {
+ Node* node = block->at(nodeIndex);
+ if (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 {
+ 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 (verbose)
+ dataLog("Dealing with true:\n");
+ setRelationship(forTrue, relationshipForTrue);
+ if (Relationship relationshipForFalse = relationshipForTrue.inverse()) {
+ if (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 (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;
+ }
+ if (maxValue <= 0) {
+ node->convertToArithNegate();
+ if (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 (verbose)
+ dataLog(" minValue = ", minValue, ", maxValue = ", maxValue, "\n");
+
+ if (sumOverflows<int>(minValue, node->child2()->asInt32()) ||
+ sumOverflows<int>(maxValue, node->child2()->asInt32()))
+ break;
+
+ if (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()) {
+ 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->remove();
+ changed = true;
+ }
+ break;
+ }
+
+ case GetByVal: {
+ if (node->arrayMode().type() != Array::Undecided)
+ break;
+
+ auto iter = m_relationships.find(node->child2().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: {
+ setRelationship(Relationship(node, m_zero, Relationship::GreaterThan, -1));
+ break;
+ }
+
+ case Upsilon: {
+ setRelationship(
+ Relationship::safeCreate(
+ node->child1().node(), node->phi(), Relationship::Equal, 0));
+
+ auto iter = m_relationships.find(node->child1().node());
+ if (iter != m_relationships.end()) {
+ Vector<Relationship> toAdd;
+ for (Relationship relationship : iter->value) {
+ Relationship newRelationship = relationship;
+ if (node->phi() == newRelationship.right())
+ continue;
+ newRelationship.setLeft(node->phi());
+ toAdd.append(newRelationship);
+ }
+ for (Relationship relationship : toAdd)
+ setRelationship(relationship);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ 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 (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 (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 (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 (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 (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 = [&] (Node* 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 (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<Node*> 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> mergedRelationships;
+ for (Relationship targetRelationship : entry.value) {
+ for (Relationship sourceRelationship : iter->value) {
+ if (verbose)
+ dataLog(" Merging ", targetRelationship, " and ", sourceRelationship, ":\n");
+ targetRelationship.merge(
+ sourceRelationship,
+ [&] (Relationship newRelationship) {
+ if (verbose)
+ dataLog(" Got ", newRelationship, "\n");
+
+ // 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 (Node* 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;
+};
+
+} // anonymous namespace
+
+bool performIntegerRangeOptimization(Graph& graph)
+{
+ SamplingRegion samplingRegion("DFG Integer Range Optimization Phase");
+ return runPhase<IntegerRangeOptimizationPhase>(graph);
+}
+
+} } // namespace JSC::DFG
+
+#endif // ENABLE(DFG_JIT)
+