/* * 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; const unsigned giveUpThreshold = 50; int64_t clampedSumImpl() { return 0; } template int64_t clampedSumImpl(int left, Args... args) { return static_cast(left) + clampedSumImpl(args...); } template int clampedSum(Args... args) { int64_t result = clampedSumImpl(args...); return static_cast(std::min( static_cast(std::numeric_limits::max()), std::max( static_cast(std::numeric_limits::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::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(m_offset, -1)) return Relationship(); return Relationship(m_left, m_right, GreaterThan, m_offset - 1); case GreaterThan: if (sumOverflows(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(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(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, thisother-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 && // thisa-1 and ia-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 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(m_offset, -1)) return Relationship(); if (sumOverflows(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(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::min(); int minRightValue = std::numeric_limits::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::max(); int maxRightValue = std::numeric_limits::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(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 ab-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 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(thisRight, m_offset)) return; if (sumOverflows(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(std::max( static_cast(1) + upper - static_cast(thisRight), static_cast(-1))); functor(Relationship(m_left, m_right, LessThan, offset)); } if (upper <= otherRight) { int offset = static_cast(std::max( static_cast(1) + upper - static_cast(otherRight), static_cast(-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(std::min( static_cast(-1) + lower - static_cast(thisRight), static_cast(1))); functor(Relationship(m_left, m_right, GreaterThan, offset)); } if (lower >= otherRight) { int offset = static_cast(std::min( static_cast(-1) + lower - static_cast(otherRight), static_cast(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(thisEffectiveRight), static_cast(otherEffectiveRight) - 1)); return; } case GreaterThan: { // Opposite of the LessThan case, above. // This is: @x == thisEffectiveRight // Other is: @x > otherEffectiveRight makeLower(std::min( static_cast(thisEffectiveRight), static_cast(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(thisEffectiveRight) - 1, static_cast(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(thisEffectiveRight) + 1, static_cast(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> 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) { ++m_iterations; if (m_iterations >= giveUpThreshold) { // This case is not necessarily wrong but it can be a sign that this phase // does not converge. // If you hit this assertion for a legitimate case, update the giveUpThreshold // to the smallest values that converges. ASSERT_NOT_REACHED(); // In release, 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 (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::min(); int maxValue = std::numeric_limits::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::min()) node->setArithMode(Arith::Unchecked); changed = true; break; } if (minValue > std::numeric_limits::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::min(); int maxValue = std::numeric_limits::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(minValue, node->child2()->asInt32()) || sumOverflows(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::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 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(std::numeric_limits::max(), -offset, 1)) { setRelationship( Relationship::safeCreate( node->child1().node(), m_zero, Relationship::LessThan, std::numeric_limits::max() - offset + 1), 0); } // If we have "add: @value + 1" then we know that @add >= min + 1, i.e. that // @add > min. if (!sumOverflows(std::numeric_limits::min(), offset, -1)) { setRelationship( Relationship( node, m_zero, Relationship::GreaterThan, std::numeric_limits::min() + offset - 1), 0); } } if (offset < 0 && offset != std::numeric_limits::min()) { // If we have "add: @value - 1" then we know that @value >= min + 1, i.e. that // @value > min. if (!sumOverflows(std::numeric_limits::min(), offset, -1)) { setRelationship( Relationship::safeCreate( node->child1().node(), m_zero, Relationship::GreaterThan, std::numeric_limits::min() + offset - 1), 0); } // If we have "add: @value + 1" then we know that @add <= max - 1, i.e. that // @add < max. if (!sumOverflows(std::numeric_limits::max(), -offset, 1)) { setRelationship( Relationship( node, m_zero, Relationship::LessThan, std::numeric_limits::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 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()); Vector& 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 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::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 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 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 constantRelationshipsAtHead; for (Relationship& relationshipAtHead : entry.value) { if (relationshipAtHead.right()->isInt32Constant()) constantRelationshipsAtHead.append(relationshipAtHead); } Vector 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"); 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 (Node* node : toRemove) m_relationshipsAtHead[target].remove(node); return changed; } Vector sortedRelationships(const RelationshipMap& relationships) { Vector result; for (auto& entry : relationships) result.appendVector(entry.value); std::sort(result.begin(), result.end()); return result; } Vector sortedRelationships() { return sortedRelationships(m_relationships); } Node* m_zero; RelationshipMap m_relationships; BlockSet m_seenBlocks; BlockMap m_relationshipsAtHead; InsertionSet m_insertionSet; unsigned m_iterations { 0 }; }; } // anonymous namespace bool performIntegerRangeOptimization(Graph& graph) { return runPhase(graph); } } } // namespace JSC::DFG #endif // ENABLE(DFG_JIT)