blob: 306ea5d6b974e93380e19b89074705e9c8348f64 [file] [log] [blame]
/*
* Copyright (C) 2013-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 "DFGBackwardsPropagationPhase.h"
#if ENABLE(DFG_JIT)
#include "DFGBlockMapInlines.h"
#include "DFGGraph.h"
#include "DFGPhase.h"
#include "JSCJSValueInlines.h"
#include <wtf/MathExtras.h>
namespace JSC { namespace DFG {
// This phase is run at the end of BytecodeParsing, so the graph isn't in a fully formed state.
// For example, we can't access the predecessor list of any basic blocks yet.
class BackwardsPropagationPhase {
public:
BackwardsPropagationPhase(Graph& graph)
: m_graph(graph)
, m_flagsAtHead(graph)
{
}
bool run()
{
for (BasicBlock* block : m_graph.blocksInNaturalOrder()) {
m_flagsAtHead[block] = Operands<NodeFlags>(OperandsLike, m_graph.block(0)->variablesAtHead);
m_flagsAtHead[block].fill(0);
}
bool changed;
do {
changed = false;
for (BlockIndex blockIndex = m_graph.numBlocks(); blockIndex--;) {
BasicBlock* block = m_graph.block(blockIndex);
if (!block)
continue;
{
unsigned numSuccessors = block->numSuccessors();
if (!numSuccessors) {
m_currentFlags = Operands<NodeFlags>(OperandsLike, m_graph.block(0)->variablesAtHead);
m_currentFlags.fill(0);
} else {
m_currentFlags = m_flagsAtHead[block->successor(0)];
for (unsigned i = 1; i < numSuccessors; ++i) {
BasicBlock* successor = block->successor(i);
for (size_t i = 0; i < m_currentFlags.size(); ++i)
m_currentFlags[i] |= m_flagsAtHead[successor][i];
}
}
}
// Prevent a tower of overflowing additions from creating a value that is out of the
// safe 2^48 range.
m_allowNestedOverflowingAdditions = block->size() < (1 << 16);
for (unsigned indexInBlock = block->size(); indexInBlock--;)
propagate(block->at(indexInBlock));
if (m_flagsAtHead[block] != m_currentFlags) {
m_flagsAtHead[block] = m_currentFlags;
changed = true;
}
}
} while (changed);
return true;
}
private:
bool isNotNegZero(Node* node)
{
if (!node->isNumberConstant())
return false;
double value = node->asNumber();
return (value || 1.0 / value > 0.0);
}
bool isNotPosZero(Node* node)
{
if (!node->isNumberConstant())
return false;
double value = node->asNumber();
return (value || 1.0 / value < 0.0);
}
// Tests if the absolute value is strictly less than the power of two.
template<int power>
bool isWithinPowerOfTwoForConstant(Node* node)
{
JSValue immediateValue = node->asJSValue();
if (!immediateValue.isNumber())
return false;
double immediate = immediateValue.asNumber();
return immediate > -(static_cast<int64_t>(1) << power) && immediate < (static_cast<int64_t>(1) << power);
}
template<int power>
bool isWithinPowerOfTwoNonRecursive(Node* node)
{
if (!node->isNumberConstant())
return false;
return isWithinPowerOfTwoForConstant<power>(node);
}
template<int power>
bool isWithinPowerOfTwo(Node* node)
{
switch (node->op()) {
case DoubleConstant:
case JSConstant:
case Int52Constant: {
return isWithinPowerOfTwoForConstant<power>(node);
}
case ValueBitAnd:
case ArithBitAnd: {
if (power > 31)
return true;
return isWithinPowerOfTwoNonRecursive<power>(node->child1().node())
|| isWithinPowerOfTwoNonRecursive<power>(node->child2().node());
}
case ArithBitOr:
case ArithBitXor:
case ValueBitOr:
case ValueBitXor:
case ValueBitLShift:
case ArithBitLShift: {
return power > 31;
}
case ArithBitRShift:
case ValueBitRShift:
case BitURShift: {
if (power > 31)
return true;
Node* shiftAmount = node->child2().node();
if (!node->isNumberConstant())
return false;
JSValue immediateValue = shiftAmount->asJSValue();
if (!immediateValue.isInt32())
return false;
return immediateValue.asInt32() > 32 - power;
}
default:
return false;
}
}
template<int power>
bool isWithinPowerOfTwo(Edge edge)
{
return isWithinPowerOfTwo<power>(edge.node());
}
static bool mergeFlags(NodeFlags& flagsRef, NodeFlags newFlags)
{
return checkAndSet(flagsRef, flagsRef | newFlags);
}
bool mergeDefaultFlags(Node* node)
{
bool changed = false;
if (node->flags() & NodeHasVarArgs) {
for (unsigned childIdx = node->firstChild();
childIdx < node->firstChild() + node->numChildren();
childIdx++) {
if (!!m_graph.m_varArgChildren[childIdx])
changed |= m_graph.m_varArgChildren[childIdx]->mergeFlags(NodeBytecodeUsesAsValue);
}
} else {
if (!node->child1())
return changed;
changed |= node->child1()->mergeFlags(NodeBytecodeUsesAsValue);
if (!node->child2())
return changed;
changed |= node->child2()->mergeFlags(NodeBytecodeUsesAsValue);
if (!node->child3())
return changed;
changed |= node->child3()->mergeFlags(NodeBytecodeUsesAsValue);
}
return changed;
}
static constexpr NodeFlags VariableIsUsed = 1 << (1 + WTF::getMSBSetConstexpr(NodeBytecodeBackPropMask));
static_assert(!(VariableIsUsed & NodeBytecodeBackPropMask));
static_assert(VariableIsUsed > NodeBytecodeBackPropMask, "Verify the above doesn't overflow");
void propagate(Node* node)
{
NodeFlags flags = node->flags() & NodeBytecodeBackPropMask;
switch (node->op()) {
case GetLocal: {
VariableAccessData* variableAccessData = node->variableAccessData();
flags |= m_currentFlags.operand(variableAccessData->operand());
flags |= VariableIsUsed;
m_currentFlags.operand(variableAccessData->operand()) = flags;
break;
}
case SetLocal: {
VariableAccessData* variableAccessData = node->variableAccessData();
Operand operand = variableAccessData->operand();
NodeFlags flags = m_currentFlags.operand(operand);
if (!(flags & VariableIsUsed))
break;
flags &= NodeBytecodeBackPropMask;
flags &= ~NodeBytecodeUsesAsInt; // We don't care about cross-block uses-as-int.
variableAccessData->mergeFlags(flags);
// We union with NodeBytecodeUsesAsNumber to account for the fact that control flow may cause overflows that our modeling can't handle.
// For example, a loop where we always add a constant value.
node->child1()->mergeFlags(flags | NodeBytecodeUsesAsNumber);
m_currentFlags.operand(operand) = 0;
break;
}
case Flush: {
VariableAccessData* variableAccessData = node->variableAccessData();
mergeFlags(m_currentFlags.operand(variableAccessData->operand()), NodeBytecodeUsesAsValue | VariableIsUsed);
break;
}
case PhantomLocal: {
VariableAccessData* variableAccessData = node->variableAccessData();
mergeFlags(m_currentFlags.operand(variableAccessData->operand()), VariableIsUsed);
break;
}
case MovHint:
case Check:
case CheckVarargs:
break;
case ValueBitNot:
case ArithBitNot: {
flags |= NodeBytecodeUsesAsInt;
flags &= ~(NodeBytecodeUsesAsNumber | NodeBytecodeNeedsNegZero | NodeBytecodeUsesAsOther);
flags &= ~NodeBytecodeUsesAsArrayIndex;
node->child1()->mergeFlags(flags);
break;
}
case ArithBitAnd:
case ArithBitOr:
case ArithBitXor:
case ValueBitAnd:
case ValueBitOr:
case ValueBitXor:
case ValueBitLShift:
case ArithBitLShift:
case ArithBitRShift:
case ValueBitRShift:
case BitURShift:
case ArithIMul: {
flags |= NodeBytecodeUsesAsInt;
flags &= ~(NodeBytecodeUsesAsNumber | NodeBytecodeNeedsNegZero | NodeBytecodeUsesAsOther);
flags &= ~NodeBytecodeUsesAsArrayIndex;
node->child1()->mergeFlags(flags);
node->child2()->mergeFlags(flags);
break;
}
case StringCharAt:
case StringCharCodeAt:
case StringCodePointAt: {
node->child1()->mergeFlags(NodeBytecodeUsesAsValue);
node->child2()->mergeFlags(NodeBytecodeUsesAsValue | NodeBytecodeUsesAsInt | NodeBytecodeUsesAsArrayIndex);
break;
}
case StringSlice: {
node->child1()->mergeFlags(NodeBytecodeUsesAsValue);
node->child2()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeUsesAsInt | NodeBytecodeUsesAsArrayIndex);
if (node->child3())
node->child3()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeUsesAsInt | NodeBytecodeUsesAsArrayIndex);
break;
}
case ArraySlice: {
m_graph.varArgChild(node, 0)->mergeFlags(NodeBytecodeUsesAsValue);
if (node->numChildren() == 2)
m_graph.varArgChild(node, 1)->mergeFlags(NodeBytecodeUsesAsValue);
else if (node->numChildren() == 3) {
m_graph.varArgChild(node, 1)->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeUsesAsInt | NodeBytecodeUsesAsArrayIndex);
m_graph.varArgChild(node, 2)->mergeFlags(NodeBytecodeUsesAsValue);
} else if (node->numChildren() == 4) {
m_graph.varArgChild(node, 1)->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeUsesAsInt | NodeBytecodeUsesAsArrayIndex);
m_graph.varArgChild(node, 2)->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeUsesAsInt | NodeBytecodeUsesAsArrayIndex);
m_graph.varArgChild(node, 3)->mergeFlags(NodeBytecodeUsesAsValue);
}
break;
}
case UInt32ToNumber: {
node->child1()->mergeFlags(flags);
break;
}
case ValueAdd: {
if (isNotNegZero(node->child1().node()) || isNotNegZero(node->child2().node()))
flags &= ~NodeBytecodeNeedsNegZero;
if (node->child1()->hasNumericResult() || node->child2()->hasNumericResult() || node->child1()->hasNumberResult() || node->child2()->hasNumberResult())
flags &= ~NodeBytecodeUsesAsOther;
if (!isWithinPowerOfTwo<32>(node->child1()) && !isWithinPowerOfTwo<32>(node->child2()))
flags |= NodeBytecodeUsesAsNumber;
if (!m_allowNestedOverflowingAdditions)
flags |= NodeBytecodeUsesAsNumber;
node->child1()->mergeFlags(flags);
node->child2()->mergeFlags(flags);
break;
}
case ArithAdd: {
flags &= ~NodeBytecodeUsesAsOther;
if (isNotNegZero(node->child1().node()) || isNotNegZero(node->child2().node()))
flags &= ~NodeBytecodeNeedsNegZero;
if (!isWithinPowerOfTwo<32>(node->child1()) && !isWithinPowerOfTwo<32>(node->child2()))
flags |= NodeBytecodeUsesAsNumber;
if (!m_allowNestedOverflowingAdditions)
flags |= NodeBytecodeUsesAsNumber;
node->child1()->mergeFlags(flags);
node->child2()->mergeFlags(flags);
break;
}
case ArithClz32: {
flags &= ~(NodeBytecodeUsesAsNumber | NodeBytecodeNeedsNegZero | NodeBytecodeUsesAsOther | ~NodeBytecodeUsesAsArrayIndex);
flags |= NodeBytecodeUsesAsInt;
node->child1()->mergeFlags(flags);
break;
}
case ArithSub: {
flags &= ~NodeBytecodeUsesAsOther;
if (isNotNegZero(node->child1().node()) || isNotPosZero(node->child2().node()))
flags &= ~NodeBytecodeNeedsNegZero;
if (!isWithinPowerOfTwo<32>(node->child1()) && !isWithinPowerOfTwo<32>(node->child2()))
flags |= NodeBytecodeUsesAsNumber;
if (!m_allowNestedOverflowingAdditions)
flags |= NodeBytecodeUsesAsNumber;
node->child1()->mergeFlags(flags);
node->child2()->mergeFlags(flags);
break;
}
case ArithNegate: {
flags &= ~NodeBytecodeUsesAsOther;
node->child1()->mergeFlags(flags);
break;
}
case Inc:
case Dec: {
flags &= ~NodeBytecodeNeedsNegZero;
flags &= ~NodeBytecodeUsesAsOther;
if (!isWithinPowerOfTwo<32>(node->child1()))
flags |= NodeBytecodeUsesAsNumber;
if (!m_allowNestedOverflowingAdditions)
flags |= NodeBytecodeUsesAsNumber;
node->child1()->mergeFlags(flags);
break;
}
case ValueMul:
case ArithMul: {
// As soon as a multiply happens, we can easily end up in the part
// of the double domain where the point at which you do truncation
// can change the outcome. So, ArithMul always forces its inputs to
// check for overflow. Additionally, it will have to check for overflow
// itself unless we can prove that there is no way for the values
// produced to cause double rounding.
if (!isWithinPowerOfTwo<22>(node->child1().node())
&& !isWithinPowerOfTwo<22>(node->child2().node()))
flags |= NodeBytecodeUsesAsNumber;
node->mergeFlags(flags);
flags |= NodeBytecodeUsesAsNumber | NodeBytecodeNeedsNegZero;
flags &= ~NodeBytecodeUsesAsOther;
node->child1()->mergeFlags(flags);
node->child2()->mergeFlags(flags);
break;
}
case ValueDiv:
case ArithDiv: {
flags |= NodeBytecodeUsesAsNumber | NodeBytecodeNeedsNegZero;
flags &= ~NodeBytecodeUsesAsOther;
node->child1()->mergeFlags(flags);
node->child2()->mergeFlags(flags);
break;
}
case ValueMod:
case ArithMod: {
flags |= NodeBytecodeUsesAsNumber;
flags &= ~NodeBytecodeUsesAsOther;
node->child1()->mergeFlags(flags);
node->child2()->mergeFlags(flags & ~NodeBytecodeNeedsNegZero);
break;
}
case EnumeratorGetByVal:
case GetByVal: {
m_graph.varArgChild(node, 0)->mergeFlags(NodeBytecodeUsesAsValue);
m_graph.varArgChild(node, 1)->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeUsesAsInt | NodeBytecodeUsesAsArrayIndex);
break;
}
case NewTypedArray:
case NewArrayWithSize: {
// Negative zero is not observable. NaN versus undefined are only observable
// in that you would get a different exception message. So, like, whatever: we
// claim here that NaN v. undefined is observable.
node->child1()->mergeFlags(NodeBytecodeUsesAsInt | NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeUsesAsArrayIndex);
break;
}
case ToString:
case CallStringConstructor: {
node->child1()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther);
break;
}
case ToPrimitive:
case ToNumber:
case ToNumeric:
case CallNumberConstructor: {
node->child1()->mergeFlags(flags);
break;
}
case CompareLess:
case CompareLessEq:
case CompareGreater:
case CompareGreaterEq:
case CompareBelow:
case CompareBelowEq:
case CompareEq:
case CompareStrictEq: {
node->child1()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther);
node->child2()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther);
break;
}
case PutByValDirect:
case PutByVal: {
m_graph.varArgChild(node, 0)->mergeFlags(NodeBytecodeUsesAsValue);
m_graph.varArgChild(node, 1)->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther | NodeBytecodeUsesAsInt | NodeBytecodeUsesAsArrayIndex);
m_graph.varArgChild(node, 2)->mergeFlags(NodeBytecodeUsesAsValue);
break;
}
case Switch: {
SwitchData* data = node->switchData();
switch (data->kind) {
case SwitchImm:
// We don't need NodeBytecodeNeedsNegZero because if the cases are all integers
// then -0 and 0 are treated the same. We don't need NodeBytecodeUsesAsOther
// because if all of the cases are integers then NaN and undefined are
// treated the same (i.e. they will take default).
node->child1()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsInt);
break;
case SwitchChar: {
// We don't need NodeBytecodeNeedsNegZero because if the cases are all strings
// then -0 and 0 are treated the same. We don't need NodeBytecodeUsesAsOther
// because if all of the cases are single-character strings then NaN
// and undefined are treated the same (i.e. they will take default).
node->child1()->mergeFlags(NodeBytecodeUsesAsNumber);
break;
}
case SwitchString:
// We don't need NodeBytecodeNeedsNegZero because if the cases are all strings
// then -0 and 0 are treated the same.
node->child1()->mergeFlags(NodeBytecodeUsesAsNumber | NodeBytecodeUsesAsOther);
break;
case SwitchCell:
// There is currently no point to being clever here since this is used for switching
// on objects.
mergeDefaultFlags(node);
break;
}
break;
}
case Identity:
// This would be trivial to handle but we just assert that we cannot see these yet.
RELEASE_ASSERT_NOT_REACHED();
break;
// Note: ArithSqrt, ArithUnary and other math intrinsics don't have special
// rules in here because they are always followed by Phantoms to signify that if the
// method call speculation fails, the bytecode may use the arguments in arbitrary ways.
// This corresponds to that possibility of someone doing something like:
// Math.sin = function(x) { doArbitraryThingsTo(x); }
default:
mergeDefaultFlags(node);
break;
}
}
Graph& m_graph;
bool m_allowNestedOverflowingAdditions;
BlockMap<Operands<NodeFlags>> m_flagsAtHead;
Operands<NodeFlags> m_currentFlags;
};
void performBackwardsPropagation(Graph& graph)
{
BackwardsPropagationPhase(graph).run();
}
} } // namespace JSC::DFG
#endif // ENABLE(DFG_JIT)