Bug 998580 - IonMonkey: Generalize RangeAnalysis truncation to handle other kinds of paths to integer types. r=nbp

This commit is contained in:
Dan Gohman 2014-05-05 15:02:02 -07:00
parent 5450f5e784
commit b3dc5ddcb3
3 changed files with 228 additions and 193 deletions

View File

@ -1621,7 +1621,7 @@ MAdd::fallible() const
{
// the add is fallible if range analysis does not say that it is finite, AND
// either the truncation analysis shows that there are non-truncated uses.
if (isTruncated())
if (truncateKind() >= IndirectTruncate)
return false;
if (range() && range()->hasInt32Bounds())
return false;
@ -1632,7 +1632,7 @@ bool
MSub::fallible() const
{
// see comment in MAdd::fallible()
if (isTruncated())
if (truncateKind() >= IndirectTruncate)
return false;
if (range() && range()->hasInt32Bounds())
return false;

View File

@ -412,8 +412,44 @@ class MDefinition : public MNode
virtual void analyzeEdgeCasesForward();
virtual void analyzeEdgeCasesBackward();
virtual bool truncate();
virtual bool isOperandTruncated(size_t index) const;
// When a floating-point value is used by nodes which would prefer to
// recieve integer inputs, we may be able to help by computing our result
// into an integer directly.
//
// A value can be truncated in 4 differents ways:
// 1. Ignore Infinities (x / 0 --> 0).
// 2. Ignore overflow (INT_MIN / -1 == (INT_MAX + 1) --> INT_MIN)
// 3. Ignore negative zeros. (-0 --> 0)
// 4. Ignore remainder. (3 / 4 --> 0)
//
// Indirect truncation is used to represent that we are interested in the
// truncated result, but only if it can safely flow into operations which
// are computed modulo 2^32, such as (2) and (3). Infinities are not safe,
// as they would have absorbed other math operations. Remainders are not
// safe, as fractions can be scaled up by multiplication.
//
// Division is a particularly interesting node here because it covers all 4
// cases even when its own operands are integers.
//
// Note that these enum values are ordered from least value-modifying to
// most value-modifying, and code relies on this ordering.
enum TruncateKind {
// No correction.
NoTruncate = 0,
// An integer is desired, but we can't skip bailout checks.
TruncateAfterBailouts = 1,
// The value will be truncated after some arithmetic (see above).
IndirectTruncate = 2,
// Direct and infallible truncation to int32.
Truncate = 3
};
// Apply the given truncate to this node itself.
virtual bool truncate(TruncateKind kind);
// Determine what kind of truncate this node prefers for the operand at the
// given index.
virtual TruncateKind operandTruncateKind(size_t index) const;
// Compute an absolute or symbolic range for the value of this node.
virtual void computeRange(TempAllocator &alloc) {
@ -977,7 +1013,7 @@ class MConstant : public MNullaryInstruction
}
void computeRange(TempAllocator &alloc);
bool truncate();
bool truncate(TruncateKind kind);
bool canProduceFloat32() const;
};
@ -2375,8 +2411,8 @@ class MCompare
void trySpecializeFloat32(TempAllocator &alloc);
bool isFloat32Commutative() const { return true; }
bool truncate();
bool isOperandTruncated(size_t index) const;
bool truncate(TruncateKind kind);
TruncateKind operandTruncateKind(size_t index) const;
# ifdef DEBUG
bool isConsistentFloat32Use(MUse *use) const {
@ -2905,8 +2941,10 @@ class MToDouble
private:
ConversionKind conversion_;
TruncateKind implicitTruncate_;
MToDouble(MDefinition *def, ConversionKind conversion = NonStringPrimitives)
: MUnaryInstruction(def), conversion_(conversion)
: MUnaryInstruction(def), conversion_(conversion), implicitTruncate_(NoTruncate)
{
setResultType(MIRType_Double);
setMovable();
@ -2946,12 +2984,19 @@ class MToDouble
}
void computeRange(TempAllocator &alloc);
bool truncate();
bool isOperandTruncated(size_t index) const;
bool truncate(TruncateKind kind);
TruncateKind operandTruncateKind(size_t index) const;
#ifdef DEBUG
bool isConsistentFloat32Use(MUse *use) const { return true; }
#endif
TruncateKind truncateKind() const {
return implicitTruncate_;
}
void setTruncateKind(TruncateKind kind) {
implicitTruncate_ = Max(implicitTruncate_, kind);
}
};
// Converts a primitive (either typed or untyped) to a float32. If the input is
@ -3171,7 +3216,7 @@ class MTruncateToInt32 : public MUnaryInstruction
}
void computeRange(TempAllocator &alloc);
bool isOperandTruncated(size_t index) const;
TruncateKind operandTruncateKind(size_t index) const;
# ifdef DEBUG
bool isConsistentFloat32Use(MUse *use) const {
return true;
@ -3349,7 +3394,7 @@ class MBinaryBitwiseInstruction
return AliasSet::None();
}
bool isOperandTruncated(size_t index) const;
TruncateKind operandTruncateKind(size_t index) const;
};
class MBitAnd : public MBinaryBitwiseInstruction
@ -3524,14 +3569,14 @@ class MBinaryArithInstruction
// This optimization happens when the multiplication cannot be truncated
// even if all uses are truncating its result, such as when the range
// analysis detect a precision loss in the multiplication.
bool implicitTruncate_;
TruncateKind implicitTruncate_;
void inferFallback(BaselineInspector *inspector, jsbytecode *pc);
public:
MBinaryArithInstruction(MDefinition *left, MDefinition *right)
: MBinaryInstruction(left, right),
implicitTruncate_(false)
implicitTruncate_(NoTruncate)
{
setMovable();
}
@ -3566,10 +3611,13 @@ class MBinaryArithInstruction
}
bool isTruncated() const {
return implicitTruncate_ == Truncate;
}
TruncateKind truncateKind() const {
return implicitTruncate_;
}
void setTruncated(bool truncate) {
implicitTruncate_ = truncate;
void setTruncateKind(TruncateKind kind) {
implicitTruncate_ = Max(implicitTruncate_, kind);
}
};
@ -4017,7 +4065,7 @@ class MAdd : public MBinaryArithInstruction
add->specialization_ = type;
add->setResultType(type);
if (type == MIRType_Int32) {
add->setTruncated(true);
add->setTruncateKind(Truncate);
add->setCommutative();
}
return add;
@ -4031,8 +4079,8 @@ class MAdd : public MBinaryArithInstruction
bool fallible() const;
void computeRange(TempAllocator &alloc);
bool truncate();
bool isOperandTruncated(size_t index) const;
bool truncate(TruncateKind kind);
TruncateKind operandTruncateKind(size_t index) const;
bool writeRecoverData(CompactBufferWriter &writer) const;
bool canRecoverOnBailout() const {
@ -4060,7 +4108,7 @@ class MSub : public MBinaryArithInstruction
sub->specialization_ = type;
sub->setResultType(type);
if (type == MIRType_Int32)
sub->setTruncated(true);
sub->setTruncateKind(Truncate);
return sub;
}
@ -4072,8 +4120,8 @@ class MSub : public MBinaryArithInstruction
bool fallible() const;
void computeRange(TempAllocator &alloc);
bool truncate();
bool isOperandTruncated(size_t index) const;
bool truncate(TruncateKind kind);
TruncateKind operandTruncateKind(size_t index) const;
};
class MMul : public MBinaryArithInstruction
@ -4100,7 +4148,7 @@ class MMul : public MBinaryArithInstruction
// This implements the required behavior for Math.imul, which
// can never fail and always truncates its output to int32.
canBeNegativeZero_ = false;
setTruncated(true);
setTruncateKind(Truncate);
setCommutative();
}
JS_ASSERT_IF(mode != Integer, mode == Normal);
@ -4165,8 +4213,8 @@ class MMul : public MBinaryArithInstruction
bool isFloat32Commutative() const { return true; }
void computeRange(TempAllocator &alloc);
bool truncate();
bool isOperandTruncated(size_t index) const;
bool truncate(TruncateKind kind);
TruncateKind operandTruncateKind(size_t index) const;
Mode mode() const { return mode_; }
};
@ -4179,32 +4227,13 @@ class MDiv : public MBinaryArithInstruction
bool canBeNegativeDividend_;
bool unsigned_;
// A Division can be truncated in 4 differents ways:
// 1. Ignore Infinities (x / 0 --> 0).
// 2. Ignore overflow (INT_MIN / -1 == (INT_MAX + 1) --> INT_MIN)
// 3. Ignore negative zeros. (-0 --> 0)
// 4. Ignore remainder. (3 / 4 --> 0)
//
// isTruncatedIndirectly is used to represent that we are interested in the
// truncated result, but only if they it can safely flow in operations which
// are computed modulo 2^32, such as (2) and (3).
//
// A division can return either Infinities (1) or a remainder (4) when both
// operands are integers. Infinities are not safe, as they would have
// absorbed other math operations. Remainders are not safe, as multiple can
// add up to integers. This implies that we need to distinguish between a
// division which is truncated directly (isTruncated) or which flow into
// truncated operations (isTruncatedIndirectly).
bool isTruncatedIndirectly_;
MDiv(MDefinition *left, MDefinition *right, MIRType type)
: MBinaryArithInstruction(left, right),
canBeNegativeZero_(true),
canBeNegativeOverflow_(true),
canBeDivideByZero_(true),
canBeNegativeDividend_(true),
unsigned_(false),
isTruncatedIndirectly_(false)
unsigned_(false)
{
if (type != MIRType_Value)
specialization_ = type;
@ -4225,7 +4254,7 @@ class MDiv : public MBinaryArithInstruction
MDiv *div = new(alloc) MDiv(left, right, type);
div->unsigned_ = unsignd;
if (type == MIRType_Int32)
div->setTruncated(true);
div->setTruncateKind(Truncate);
return div;
}
@ -4261,10 +4290,7 @@ class MDiv : public MBinaryArithInstruction
}
bool isTruncatedIndirectly() const {
return isTruncatedIndirectly_;
}
void setTruncatedIndirectly(bool truncate) {
isTruncatedIndirectly_ = truncate;
return truncateKind() >= IndirectTruncate;
}
bool canTruncateInfinities() const {
@ -4284,7 +4310,7 @@ class MDiv : public MBinaryArithInstruction
void computeRange(TempAllocator &alloc);
bool fallible() const;
bool truncate();
bool truncate(TruncateKind kind);
void collectRangeInfoPreTrunc();
};
@ -4314,7 +4340,7 @@ class MMod : public MBinaryArithInstruction
MMod *mod = new(alloc) MMod(left, right, type);
mod->unsigned_ = unsignd;
if (type == MIRType_Int32)
mod->setTruncated(true);
mod->setTruncateKind(Truncate);
return mod;
}
@ -4338,7 +4364,7 @@ class MMod : public MBinaryArithInstruction
bool fallible() const;
void computeRange(TempAllocator &alloc);
bool truncate();
bool truncate(TruncateKind kind);
void collectRangeInfoPreTrunc();
};
@ -6543,7 +6569,7 @@ class MLoadTypedArrayElementStatic
}
void computeRange(TempAllocator &alloc);
bool truncate();
bool truncate(TruncateKind kind);
bool canProduceFloat32() const { return typedArray_->type() == ScalarTypeDescr::TYPE_FLOAT32; }
};
@ -6608,7 +6634,7 @@ class MStoreTypedArrayElement
void setRacy() {
racy_ = true;
}
bool isOperandTruncated(size_t index) const;
TruncateKind operandTruncateKind(size_t index) const;
bool canConsumeFloat32(MUse *use) const {
return use->index() == 2 && arrayType_ == ScalarTypeDescr::TYPE_FLOAT32;
@ -6676,7 +6702,7 @@ class MStoreTypedArrayElementHole
AliasSet getAliasSet() const {
return AliasSet::Store(AliasSet::TypedArrayElement);
}
bool isOperandTruncated(size_t index) const;
TruncateKind operandTruncateKind(size_t index) const;
bool canConsumeFloat32(MUse *use) const {
return use->index() == 3 && arrayType_ == ScalarTypeDescr::TYPE_FLOAT32;
@ -6723,7 +6749,7 @@ class MStoreTypedArrayElementStatic :
AliasSet getAliasSet() const {
return AliasSet::Store(AliasSet::TypedArrayElement);
}
bool isOperandTruncated(size_t index) const;
TruncateKind operandTruncateKind(size_t index) const;
bool canConsumeFloat32(MUse *use) const {
return use->index() == 1 && typedArray_->type() == ScalarTypeDescr::TYPE_FLOAT32;

View File

@ -2136,14 +2136,14 @@ Range::wrapAroundToBoolean()
}
bool
MDefinition::truncate()
MDefinition::truncate(TruncateKind kind)
{
// No procedure defined for truncating this instruction.
return false;
}
bool
MConstant::truncate()
MConstant::truncate(TruncateKind kind)
{
if (!value_.isDouble())
return false;
@ -2158,15 +2158,15 @@ MConstant::truncate()
}
bool
MAdd::truncate()
MAdd::truncate(TruncateKind kind)
{
// Remember analysis, needed for fallible checks.
setTruncated(true);
setTruncateKind(kind);
if (type() == MIRType_Double || type() == MIRType_Int32) {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
if (range())
if (kind >= IndirectTruncate && range())
range()->wrapAroundToInt32();
return true;
}
@ -2175,15 +2175,15 @@ MAdd::truncate()
}
bool
MSub::truncate()
MSub::truncate(TruncateKind kind)
{
// Remember analysis, needed for fallible checks.
setTruncated(true);
setTruncateKind(kind);
if (type() == MIRType_Double || type() == MIRType_Int32) {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
if (range())
if (kind >= IndirectTruncate && range())
range()->wrapAroundToInt32();
return true;
}
@ -2192,54 +2192,39 @@ MSub::truncate()
}
bool
MMul::truncate()
MMul::truncate(TruncateKind kind)
{
// Remember analysis, needed to remove negative zero checks.
setTruncated(true);
setTruncateKind(kind);
if (type() == MIRType_Double || type() == MIRType_Int32) {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
setCanBeNegativeZero(false);
if (range())
range()->wrapAroundToInt32();
return true;
}
return false;
}
bool
MDiv::truncate()
{
// Remember analysis, needed to remove negative zero checks.
setTruncatedIndirectly(true);
// Check if this division only flows in bitwise instructions.
if (!isTruncated()) {
bool allUsesExplictlyTruncate = true;
for (MUseDefIterator use(this); allUsesExplictlyTruncate && use; use++) {
switch (use.def()->op()) {
case MDefinition::Op_BitAnd:
case MDefinition::Op_BitOr:
case MDefinition::Op_BitXor:
case MDefinition::Op_Lsh:
case MDefinition::Op_Rsh:
case MDefinition::Op_Ursh:
break;
default:
allUsesExplictlyTruncate = false;
}
if (kind >= IndirectTruncate) {
setCanBeNegativeZero(false);
if (range())
range()->wrapAroundToInt32();
}
if (allUsesExplictlyTruncate)
setTruncated(true);
return true;
}
// Divisions where the lhs and rhs are unsigned and the result is
// truncated can be lowered more efficiently.
if (specialization() == MIRType_Int32 && tryUseUnsignedOperands()) {
unsigned_ = true;
return false;
}
bool
MDiv::truncate(TruncateKind kind)
{
setTruncateKind(kind);
if (type() == MIRType_Double || type() == MIRType_Int32) {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
// Divisions where the lhs and rhs are unsigned and the result is
// truncated can be lowered more efficiently.
if (tryUseUnsignedOperands())
unsigned_ = true;
return true;
}
@ -2248,14 +2233,19 @@ MDiv::truncate()
}
bool
MMod::truncate()
MMod::truncate(TruncateKind kind)
{
// Remember analysis, needed to remove negative zero checks.
setTruncated(true);
setTruncateKind(kind);
// As for division, handle unsigned modulus with a truncated result.
if (specialization() == MIRType_Int32 && tryUseUnsignedOperands()) {
unsigned_ = true;
if (type() == MIRType_Double || type() == MIRType_Int32) {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
if (tryUseUnsignedOperands())
unsigned_ = true;
return true;
}
@ -2264,90 +2254,103 @@ MMod::truncate()
}
bool
MToDouble::truncate()
MToDouble::truncate(TruncateKind kind)
{
JS_ASSERT(type() == MIRType_Double);
setTruncateKind(kind);
// We use the return type to flag that this MToDouble should be replaced by
// a MTruncateToInt32 when modifying the graph.
setResultType(MIRType_Int32);
if (range())
range()->wrapAroundToInt32();
if (kind >= IndirectTruncate) {
if (range())
range()->wrapAroundToInt32();
}
return true;
}
bool
MLoadTypedArrayElementStatic::truncate()
MLoadTypedArrayElementStatic::truncate(TruncateKind kind)
{
setInfallible();
return false;
}
bool
MDefinition::isOperandTruncated(size_t index) const
MDefinition::TruncateKind
MDefinition::operandTruncateKind(size_t index) const
{
return false;
// Generic routine: We don't know anything.
return NoTruncate;
}
MDefinition::TruncateKind
MTruncateToInt32::operandTruncateKind(size_t index) const
{
// This operator is an explicit truncate to int32.
return Truncate;
}
MDefinition::TruncateKind
MBinaryBitwiseInstruction::operandTruncateKind(size_t index) const
{
// The bitwise operators truncate to int32.
return Truncate;
}
MDefinition::TruncateKind
MAdd::operandTruncateKind(size_t index) const
{
// This operator is doing some arithmetic. If its result is truncated,
// it's an indirect truncate for its operands.
return Min(truncateKind(), IndirectTruncate);
}
MDefinition::TruncateKind
MSub::operandTruncateKind(size_t index) const
{
// See the comment in MAdd::operandTruncateKind.
return Min(truncateKind(), IndirectTruncate);
}
MDefinition::TruncateKind
MMul::operandTruncateKind(size_t index) const
{
// See the comment in MAdd::operandTruncateKind.
return Min(truncateKind(), IndirectTruncate);
}
MDefinition::TruncateKind
MToDouble::operandTruncateKind(size_t index) const
{
// MToDouble propagates its truncate kind to its operand.
return truncateKind();
}
MDefinition::TruncateKind
MStoreTypedArrayElement::operandTruncateKind(size_t index) const
{
// An integer store truncates the stored value.
return index == 2 && !isFloatArray() ? Truncate : NoTruncate;
}
MDefinition::TruncateKind
MStoreTypedArrayElementHole::operandTruncateKind(size_t index) const
{
// An integer store truncates the stored value.
return index == 3 && !isFloatArray() ? Truncate : NoTruncate;
}
MDefinition::TruncateKind
MStoreTypedArrayElementStatic::operandTruncateKind(size_t index) const
{
// An integer store truncates the stored value.
return index == 1 && !isFloatArray() ? Truncate : NoTruncate;
}
bool
MTruncateToInt32::isOperandTruncated(size_t index) const
{
return true;
}
bool
MBinaryBitwiseInstruction::isOperandTruncated(size_t index) const
{
return true;
}
bool
MAdd::isOperandTruncated(size_t index) const
{
return isTruncated();
}
bool
MSub::isOperandTruncated(size_t index) const
{
return isTruncated();
}
bool
MMul::isOperandTruncated(size_t index) const
{
return isTruncated();
}
bool
MToDouble::isOperandTruncated(size_t index) const
{
// The return type is used to flag that we are replacing this Double by a
// Truncate of its operand if needed.
return type() == MIRType_Int32;
}
bool
MStoreTypedArrayElement::isOperandTruncated(size_t index) const
{
return index == 2 && !isFloatArray();
}
bool
MStoreTypedArrayElementHole::isOperandTruncated(size_t index) const
{
return index == 3 && !isFloatArray();
}
bool
MStoreTypedArrayElementStatic::isOperandTruncated(size_t index) const
{
return index == 1 && !isFloatArray();
}
bool
MCompare::truncate()
MCompare::truncate(TruncateKind kind)
{
if (!isDoubleComparison())
return false;
@ -2359,32 +2362,34 @@ MCompare::truncate()
compareType_ = Compare_Int32;
// Truncating the operands won't change their value, but it will change
// their type, which we need because we now expect integer inputs.
// Truncating the operands won't change their value because we don't force a
// truncation, but it will change their type, which we need because we
// now expect integer inputs.
truncateOperands_ = true;
return true;
}
bool
MCompare::isOperandTruncated(size_t index) const
MDefinition::TruncateKind
MCompare::operandTruncateKind(size_t index) const
{
// If we're doing an int32 comparison on operands which were previously
// floating-point, convert them!
JS_ASSERT_IF(truncateOperands_, isInt32Comparison());
return truncateOperands_;
return truncateOperands_ ? TruncateAfterBailouts : NoTruncate;
}
// Ensure that all observables uses can work with a truncated
// version of the |candidate|'s result.
static bool
AllUsesTruncate(MInstruction *candidate)
// Examine all the users of |candidate| and determine the most aggressive
// truncate kind that satisfies all of them.
static MDefinition::TruncateKind
ComputeRequestedTruncateKind(MInstruction *candidate)
{
// If the value naturally produces an int32 value (before bailout checks)
// that needs no conversion, we don't have to worry about resume points
// seeing truncated values.
bool needsConversion = !candidate->range() || !candidate->range()->isInt32();
MDefinition::TruncateKind kind = MDefinition::Truncate;
for (MUseIterator use(candidate->usesBegin()); use != candidate->usesEnd(); use++) {
if (!use->consumer()->isDefinition()) {
// We can only skip testing resume points, if all original uses are
@ -2393,24 +2398,27 @@ AllUsesTruncate(MInstruction *candidate)
// value, and any bailout with a truncated value might lead an
// incorrect value.
if (candidate->isUseRemoved() && needsConversion)
return false;
kind = Min(kind, MDefinition::TruncateAfterBailouts);
continue;
}
if (!use->consumer()->toDefinition()->isOperandTruncated(use->index()))
return false;
MDefinition *consumer = use->consumer()->toDefinition();
MDefinition::TruncateKind consumerKind = consumer->operandTruncateKind(use->index());
kind = Min(kind, consumerKind);
if (kind == MDefinition::NoTruncate)
break;
}
return true;
return kind;
}
static bool
CanTruncate(MInstruction *candidate)
static MDefinition::TruncateKind
ComputeTruncateKind(MInstruction *candidate)
{
// Compare operations might coerce its inputs to int32 if the ranges are
// correct. So we do not need to check if all uses are coerced.
if (candidate->isCompare())
return true;
return MDefinition::TruncateAfterBailouts;
// Set truncated flag if range analysis ensure that it has no
// rounding errors and no fractional part. Note that we can't use
@ -2425,10 +2433,10 @@ CanTruncate(MInstruction *candidate)
canHaveRoundingErrors = false;
if (canHaveRoundingErrors)
return false;
return MDefinition::NoTruncate;
// Ensure all observable uses are truncated.
return AllUsesTruncate(candidate);
return ComputeRequestedTruncateKind(candidate);
}
static void
@ -2455,7 +2463,7 @@ AdjustTruncatedInputs(TempAllocator &alloc, MInstruction *truncated)
{
MBasicBlock *block = truncated->block();
for (size_t i = 0, e = truncated->numOperands(); i < e; i++) {
if (!truncated->isOperandTruncated(i))
if (truncated->operandTruncateKind(i) == MDefinition::NoTruncate)
continue;
MDefinition *input = truncated->getOperand(i);
@ -2515,11 +2523,12 @@ RangeAnalysis::truncate()
default:;
}
if (!CanTruncate(*iter))
MDefinition::TruncateKind kind = ComputeTruncateKind(*iter);
if (kind == MDefinition::NoTruncate)
continue;
// Truncate this instruction if possible.
if (!iter->truncate())
if (!iter->truncate(kind))
continue;
// Delay updates of inputs/outputs to avoid creating node which