Bug 894813 - IonMonkey: Implement dynamic range analysis checking. r=nbp

This commit is contained in:
Dan Gohman 2013-08-16 14:09:49 -07:00
parent 0e6eeaf7da
commit 84c78a5607
9 changed files with 216 additions and 43 deletions

View File

@ -7304,5 +7304,85 @@ CodeGenerator::visitAsmJSCheckOverRecursed(LAsmJSCheckOverRecursed *lir)
return true;
}
bool
CodeGenerator::visitRangeAssert(LRangeAssert *ins)
{
Register input = ToRegister(ins->input());
Range *r = ins->range();
// Check the lower bound.
if (r->lower() != INT32_MIN) {
Label success;
masm.branch32(Assembler::GreaterThanOrEqual, input, Imm32(r->lower()), &success);
masm.breakpoint();
masm.bind(&success);
}
// Check the upper bound.
if (r->upper() != INT32_MAX) {
Label success;
masm.branch32(Assembler::LessThanOrEqual, input, Imm32(r->upper()), &success);
masm.breakpoint();
masm.bind(&success);
}
// For r->isDecimal() and r->exponent(), there's nothing to check, because
// if we ended up in the integer range checking code, the value is already
// in an integer register in the integer range.
return true;
}
bool
CodeGenerator::visitDoubleRangeAssert(LDoubleRangeAssert *ins)
{
FloatRegister input = ToFloatRegister(ins->input());
FloatRegister temp = ToFloatRegister(ins->temp());
Range *r = ins->range();
// Check the lower bound.
if (!r->isLowerInfinite()) {
Label success;
masm.loadConstantDouble(r->lower(), temp);
if (r->isUpperInfinite())
masm.branchDouble(Assembler::DoubleUnordered, input, input, &success);
masm.branchDouble(Assembler::DoubleGreaterThanOrEqual, input, temp, &success);
masm.breakpoint();
masm.bind(&success);
}
// Check the upper bound.
if (!r->isUpperInfinite()) {
Label success;
masm.loadConstantDouble(r->upper(), temp);
if (r->isLowerInfinite())
masm.branchDouble(Assembler::DoubleUnordered, input, input, &success);
masm.branchDouble(Assembler::DoubleLessThanOrEqual, input, temp, &success);
masm.breakpoint();
masm.bind(&success);
}
// This code does not yet check r->isDecimal(). This would require new
// assembler interfaces to make rounding instructions available.
if (!r->isInfinite()) {
// Check the bounds implied by the maximum exponent.
Label exponentLoOk;
masm.loadConstantDouble(pow(2, r->exponent() + 1), temp);
masm.branchDouble(Assembler::DoubleUnordered, input, input, &exponentLoOk);
masm.branchDouble(Assembler::DoubleLessThanOrEqual, input, temp, &exponentLoOk);
masm.breakpoint();
masm.bind(&exponentLoOk);
Label exponentHiOk;
masm.loadConstantDouble(-pow(2, r->exponent() + 1), temp);
masm.branchDouble(Assembler::DoubleUnordered, input, input, &exponentHiOk);
masm.branchDouble(Assembler::DoubleGreaterThanOrEqual, input, temp, &exponentHiOk);
masm.breakpoint();
masm.bind(&exponentHiOk);
}
return true;
}
} // namespace ion
} // namespace js

View File

@ -302,6 +302,9 @@ class CodeGenerator : public CodeGeneratorSpecific
bool visitNameIC(OutOfLineUpdateCache *ool, NameIC *ic);
bool visitCallsiteCloneIC(OutOfLineUpdateCache *ool, CallsiteCloneIC *ic);
bool visitRangeAssert(LRangeAssert *ins);
bool visitDoubleRangeAssert(LDoubleRangeAssert *ins);
IonScriptCounts *extractUnassociatedScriptCounts() {
IonScriptCounts *counts = unassociatedScriptCounts_;
unassociatedScriptCounts_ = NULL; // prevent delete in dtor

View File

@ -77,6 +77,12 @@ struct IonOptions
// Default: true
bool rangeAnalysis;
// Whether to enable extra code to perform dynamic validation of
// RangeAnalysis results.
//
// Default: false
bool checkRangeAnalysis;
// Toggles whether Unreachable Code Elimination is performed.
//
// Default: true
@ -212,6 +218,7 @@ struct IonOptions
inlining(true),
edgeCaseAnalysis(true),
rangeAnalysis(true),
checkRangeAnalysis(false),
uce(true),
eaa(true),
parallelCompilation(false),

View File

@ -7,6 +7,7 @@
#ifndef jit_LIR_Common_h
#define jit_LIR_Common_h
#include "RangeAnalysis.h"
#include "jit/shared/Assembler-shared.h"
// This file declares LIR instructions that are common to every platform.
@ -4942,6 +4943,55 @@ class LAsmJSCheckOverRecursed : public LInstructionHelper<0, 0, 0>
}
};
class LRangeAssert : public LInstructionHelper<0, 1, 0>
{
Range range_;
public:
LIR_HEADER(RangeAssert)
LRangeAssert(const LAllocation &input, Range r)
: range_(r)
{
setOperand(0, input);
}
const LAllocation *input() {
return getOperand(0);
}
Range *range() {
return &range_;
}
};
class LDoubleRangeAssert : public LInstructionHelper<0, 1, 1>
{
Range range_;
public:
LIR_HEADER(DoubleRangeAssert)
LDoubleRangeAssert(const LAllocation &input, const LDefinition &temp, Range r)
: range_(r)
{
setOperand(0, input);
setTemp(0, temp);
}
const LAllocation *input() {
return getOperand(0);
}
const LDefinition *temp() {
return getTemp(0);
}
Range *range() {
return &range_;
}
};
} // namespace ion
} // namespace js

View File

@ -245,7 +245,9 @@
_(AsmJSPassStackArg) \
_(AsmJSCall) \
_(AsmJSCheckOverRecursed) \
_(CheckInterruptPar)
_(CheckInterruptPar) \
_(RangeAssert) \
_(DoubleRangeAssert)
#if defined(JS_CPU_X86)
# include "jit/x86/LOpcodes-x86.h"

View File

@ -2943,6 +2943,25 @@ LIRGenerator::visitInstruction(MInstruction *ins)
return false;
}
// Check the computed range for this instruction, if the option is set. Note
// that this code is quite invasive; it adds numerous additional
// instructions for each MInstruction with a computed range, and it uses
// registers, so it also affects register allocation.
if (js_IonOptions.checkRangeAnalysis) {
if (Range *r = ins->range()) {
switch (ins->type()) {
case MIRType_Int32:
add(new LRangeAssert(useRegisterAtStart(ins), *r));
break;
case MIRType_Double:
add(new LDoubleRangeAssert(useRegister(ins), tempFloat(), *r));
break;
default:
break;
}
}
}
return true;
}

View File

@ -161,7 +161,7 @@ RangeAnalysis::addBetaNobes()
} else if (right->isConstant() && right->toConstant()->value().isInt32()) {
bound = right->toConstant()->value().toInt32();
val = left;
} else {
} else if (left->type() == MIRType_Int32 && right->type() == MIRType_Int32) {
MDefinition *smaller = NULL;
MDefinition *greater = NULL;
if (jsop == JSOP_LT) {
@ -173,22 +173,22 @@ RangeAnalysis::addBetaNobes()
}
if (smaller && greater) {
MBeta *beta;
beta = MBeta::New(smaller, new Range(JSVAL_INT_MIN, JSVAL_INT_MAX-1,
smaller->type() != MIRType_Int32,
Range::MaxDoubleExponent));
beta = MBeta::New(smaller, new Range(JSVAL_INT_MIN, JSVAL_INT_MAX-1));
block->insertBefore(*block->begin(), beta);
replaceDominatedUsesWith(smaller, beta, block);
IonSpew(IonSpew_Range, "Adding beta node for smaller %d", smaller->id());
beta = MBeta::New(greater, new Range(JSVAL_INT_MIN+1, JSVAL_INT_MAX,
greater->type() != MIRType_Int32,
Range::MaxDoubleExponent));
beta = MBeta::New(greater, new Range(JSVAL_INT_MIN+1, JSVAL_INT_MAX));
block->insertBefore(*block->begin(), beta);
replaceDominatedUsesWith(greater, beta, block);
IonSpew(IonSpew_Range, "Adding beta node for greater %d", greater->id());
}
continue;
} else {
continue;
}
// At this point, one of the operands if the compare is a constant, and
// val is the other operand.
JS_ASSERT(val);
@ -853,6 +853,12 @@ MConstant::computeRange()
// Safe double comparisons, because there is no precision loss.
int64_t l = integral - ((rest < 0) ? 1 : 0);
int64_t h = integral + ((rest > 0) ? 1 : 0);
// If we adjusted into a new exponent range, adjust exp accordingly.
if ((rest < 0 && (l == INT64_MIN || IsPowerOfTwo(Abs(l)))) ||
(rest > 0 && (h == INT64_MIN || IsPowerOfTwo(Abs(h)))))
{
++exp;
}
setRange(new Range(l, h, (rest != 0), exp));
} else {
// This double has a precision loss. This also mean that it cannot
@ -984,8 +990,8 @@ MAbs::computeRange()
Range other(getOperand(0));
setRange(Range::abs(&other));
if (implicitTruncate_ && !range()->isInt32())
setRange(new Range(INT32_MIN, INT32_MAX));
if (implicitTruncate_)
range()->wrapAroundToInt32();
}
void
@ -1009,8 +1015,8 @@ MAdd::computeRange()
Range *next = Range::add(&left, &right);
setRange(next);
if (isTruncated() && !range()->isInt32())
setRange(new Range(INT32_MIN, INT32_MAX));
if (isTruncated())
range()->wrapAroundToInt32();
}
void
@ -1023,8 +1029,8 @@ MSub::computeRange()
Range *next = Range::sub(&left, &right);
setRange(next);
if (isTruncated() && !range()->isInt32())
setRange(new Range(INT32_MIN, INT32_MAX));
if (isTruncated())
range()->wrapAroundToInt32();
}
void
@ -1039,8 +1045,8 @@ MMul::computeRange()
setRange(Range::mul(&left, &right));
// Truncated multiplications could overflow in both directions
if (isTruncated() && !range()->isInt32())
setRange(new Range(INT32_MIN, INT32_MAX));
if (isTruncated())
range()->wrapAroundToInt32();
}
void
@ -1056,6 +1062,10 @@ MMod::computeRange()
if (lhs.isInfinite() || rhs.isInfinite())
return;
// If RHS can be zero, the result can be NaN.
if (rhs.lower() <= 0 && rhs.upper() >= 0)
return;
// Math.abs(lhs % rhs) == Math.abs(lhs) % Math.abs(rhs).
// First, the absolute value of the result will always be less than the
// absolute value of rhs. (And if rhs is zero, the result is NaN).
@ -1663,15 +1673,15 @@ MAdd::truncate()
// Remember analysis, needed for fallible checks.
setTruncated(true);
// Modify the instruction if needed.
if (type() != MIRType_Double)
return false;
if (type() == MIRType_Double || type() == MIRType_Int32) {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
if (range())
range()->wrapAroundToInt32();
return true;
}
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
if (range())
range()->wrapAroundToInt32();
return true;
return false;
}
bool
@ -1680,15 +1690,15 @@ MSub::truncate()
// Remember analysis, needed for fallible checks.
setTruncated(true);
// Modify the instruction if needed.
if (type() != MIRType_Double)
return false;
if (type() == MIRType_Double || type() == MIRType_Int32) {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
if (range())
range()->wrapAroundToInt32();
return true;
}
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
if (range())
range()->wrapAroundToInt32();
return true;
return false;
}
bool
@ -1697,22 +1707,16 @@ MMul::truncate()
// Remember analysis, needed to remove negative zero checks.
setTruncated(true);
// Modify the instruction.
bool truncated = type() == MIRType_Int32;
if (type() == MIRType_Double) {
if (type() == MIRType_Double || type() == MIRType_Int32) {
specialization_ = MIRType_Int32;
setResultType(MIRType_Int32);
truncated = true;
JS_ASSERT(range());
}
if (truncated && range()) {
range()->wrapAroundToInt32();
setTruncated(true);
setCanBeNegativeZero(false);
if (range())
range()->wrapAroundToInt32();
return true;
}
return truncated;
return false;
}
bool

View File

@ -174,6 +174,9 @@ class Range : public TempObject {
symbolicLower_(NULL),
symbolicUpper_(NULL)
{
JS_ASSERT(e >= (h == INT64_MIN ? MaxDoubleExponent : mozilla::FloorLog2(mozilla::Abs(h))));
JS_ASSERT(e >= (l == INT64_MIN ? MaxDoubleExponent : mozilla::FloorLog2(mozilla::Abs(l))));
setLowerInit(l);
setUpperInit(h);
rectifyExponent();

View File

@ -5092,6 +5092,9 @@ ProcessArgs(JSContext *cx, JSObject *obj_, OptionParser *op)
return OptionFailure("ion-range-analysis", str);
}
if (op->getBoolOption("ion-check-range-analysis"))
ion::js_IonOptions.checkRangeAnalysis = true;
if (const char *str = op->getStringOption("ion-inlining")) {
if (strcmp(str, "on") == 0)
ion::js_IonOptions.inlining = true;
@ -5377,6 +5380,8 @@ main(int argc, char **argv, char **envp)
"Find edge cases where Ion can avoid bailouts (default: on, off to disable)")
|| !op.addStringOption('\0', "ion-range-analysis", "on/off",
"Range analysis (default: off, on to enable)")
|| !op.addBoolOption('\0', "ion-check-range-analysis",
"Range analysis checking")
|| !op.addStringOption('\0', "ion-inlining", "on/off",
"Inline methods where possible (default: on, off to disable)")
|| !op.addStringOption('\0', "ion-osr", "on/off",