From 4a8cc281cc96ee1cbb0744b6bacf0de1fe072a68 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Wed, 21 May 2014 07:51:59 -0700 Subject: [PATCH] Bug 1000605 - Prevent division's truncation after removal of Math functions. r=sunfish,bbouvier --- js/src/jit-test/tests/ion/bug1000605.js | 43 +++++++++++++++++++++++++ js/src/jit/Lowering.cpp | 6 ++++ js/src/jit/Lowering.h | 1 + js/src/jit/MCallOptimize.cpp | 27 ++++++++++++++-- js/src/jit/MIR.h | 39 ++++++++++++++++++++++ js/src/jit/MOpcodes.h | 1 + js/src/jit/ParallelSafetyAnalysis.cpp | 1 + js/src/jit/RangeAnalysis.cpp | 23 +++++++++++++ 8 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 js/src/jit-test/tests/ion/bug1000605.js diff --git a/js/src/jit-test/tests/ion/bug1000605.js b/js/src/jit-test/tests/ion/bug1000605.js new file mode 100644 index 00000000000..c0f7c1ceb3c --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1000605.js @@ -0,0 +1,43 @@ +setJitCompilerOption("baseline.usecount.trigger", 0); +setJitCompilerOption("ion.usecount.trigger", 0); + +function ceil(a, b) { + return Math.ceil((a | 0) / (b | 0)) | 0; +} +function floor(a, b) { + return Math.floor((a | 0) / (b | 0)) | 0; +} +function round(a, b) { + return Math.round((a | 0) / (b | 0)) | 0; +} +function intdiv(a, b) { + return ((a | 0) / (b | 0)) | 0; +} + +// Always rounds up. +assertEq(ceil(5, 5), 1); +assertEq(ceil(4, 3), 2); +assertEq(ceil(5, 3), 2); +assertEq(ceil(-4, 3), -1); +assertEq(ceil(-5, 3), -1); + +// Always rounds down. +assertEq(floor(5, 5), 1); +assertEq(floor(4, 3), 1); +assertEq(floor(5, 3), 1); +assertEq(floor(-4, 3), -2); +assertEq(floor(-5, 3), -2); + +// Always rounds towards the nearest. +assertEq(round(5, 5), 1); +assertEq(round(4, 3), 1); +assertEq(round(5, 3), 2); +assertEq(round(-4, 3), -1); +assertEq(round(-5, 3), -2); + +// Always rounds towards zero. +assertEq(intdiv(5, 5), 1); +assertEq(intdiv(4, 3), 1); +assertEq(intdiv(5, 3), 1); +assertEq(intdiv(-4, 3), -1); +assertEq(intdiv(-5, 3), -1); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 96c3f932ec5..e0f928ba495 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -1638,6 +1638,12 @@ LIRGenerator::visitNop(MNop *nop) return true; } +bool +LIRGenerator::visitLimitedTruncate(MLimitedTruncate *nop) +{ + return redefine(nop, nop->input()); +} + bool LIRGenerator::visitOsrEntry(MOsrEntry *entry) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 238b77646c0..d9368bc0908 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -141,6 +141,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitStart(MStart *start); bool visitOsrEntry(MOsrEntry *entry); bool visitNop(MNop *nop); + bool visitLimitedTruncate(MLimitedTruncate *nop); bool visitOsrValue(MOsrValue *value); bool visitOsrScopeChain(MOsrScopeChain *object); bool visitOsrReturnValue(MOsrReturnValue *value); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 0419475f3b8..ebe376a6f0a 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -651,7 +651,14 @@ IonBuilder::inlineMathFloor(CallInfo &callInfo) // Math.floor(int(x)) == int(x) if (argType == MIRType_Int32 && returnType == MIRType_Int32) { callInfo.setImplicitlyUsedUnchecked(); - current->push(callInfo.getArg(0)); + // The int operand may be something which bails out if the actual value + // is not in the range of the result type of the MIR. We need to tell + // the optimizer to preserve this bailout even if the final result is + // fully truncated. + MLimitedTruncate *ins = MLimitedTruncate::New(alloc(), callInfo.getArg(0), + MDefinition::IndirectTruncate); + current->add(ins); + current->push(ins); return InliningStatus_Inlined; } @@ -689,7 +696,14 @@ IonBuilder::inlineMathCeil(CallInfo &callInfo) // Math.ceil(int(x)) == int(x) if (argType == MIRType_Int32 && returnType == MIRType_Int32) { callInfo.setImplicitlyUsedUnchecked(); - current->push(callInfo.getArg(0)); + // The int operand may be something which bails out if the actual value + // is not in the range of the result type of the MIR. We need to tell + // the optimizer to preserve this bailout even if the final result is + // fully truncated. + MLimitedTruncate *ins = MLimitedTruncate::New(alloc(), callInfo.getArg(0), + MDefinition::IndirectTruncate); + current->add(ins); + current->push(ins); return InliningStatus_Inlined; } @@ -719,7 +733,14 @@ IonBuilder::inlineMathRound(CallInfo &callInfo) // Math.round(int(x)) == int(x) if (argType == MIRType_Int32 && returnType == MIRType_Int32) { callInfo.setImplicitlyUsedUnchecked(); - current->push(callInfo.getArg(0)); + // The int operand may be something which bails out if the actual value + // is not in the range of the result type of the MIR. We need to tell + // the optimizer to preserve this bailout even if the final result is + // fully truncated. + MLimitedTruncate *ins = MLimitedTruncate::New(alloc(), callInfo.getArg(0), + MDefinition::IndirectTruncate); + current->add(ins); + current->push(ins); return InliningStatus_Inlined; } diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 2ecf218a142..177fd83864c 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -1013,6 +1013,45 @@ class MNop : public MNullaryInstruction } }; +// No-op instruction. This cannot be moved or eliminated, and is intended for +// protecting the input against follow-up optimization. +class MLimitedTruncate : public MUnaryInstruction +{ + public: + TruncateKind truncate_; + TruncateKind truncateLimit_; + + protected: + MLimitedTruncate(MDefinition *input, TruncateKind limit) + : MUnaryInstruction(input), + truncate_(NoTruncate), + truncateLimit_(limit) + { + setResultType(input->type()); + setResultTypeSet(input->resultTypeSet()); + } + + public: + INSTRUCTION_HEADER(LimitedTruncate) + static MLimitedTruncate *New(TempAllocator &alloc, MDefinition *input, TruncateKind kind) { + return new(alloc) MLimitedTruncate(input, kind); + } + + AliasSet getAliasSet() const { + return AliasSet::None(); + } + + void computeRange(TempAllocator &alloc); + bool truncate(TruncateKind kind); + TruncateKind operandTruncateKind(size_t index) const; + TruncateKind truncateKind() const { + return truncate_; + } + void setTruncateKind(TruncateKind kind) { + truncate_ = kind; + } +}; + // A constant js::Value. class MConstant : public MNullaryInstruction { diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 00de788a4f7..19d9f4b6836 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -101,6 +101,7 @@ namespace jit { _(Start) \ _(OsrEntry) \ _(Nop) \ + _(LimitedTruncate) \ _(RegExp) \ _(RegExpExec) \ _(RegExpTest) \ diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index ffdd88b5716..a10113c32f2 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -194,6 +194,7 @@ class ParallelSafetyVisitor : public MInstructionVisitor SAFE_OP(Start) UNSAFE_OP(OsrEntry) SAFE_OP(Nop) + SAFE_OP(LimitedTruncate) UNSAFE_OP(RegExp) CUSTOM_OP(Lambda) UNSAFE_OP(LambdaArrow) diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 893cc261957..6b6b49cb86d 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1384,6 +1384,13 @@ MToInt32::computeRange(TempAllocator &alloc) setRange(output); } +void +MLimitedTruncate::computeRange(TempAllocator &alloc) +{ + Range *output = new(alloc) Range(input()); + setRange(output); +} + static Range *GetTypedArrayRange(TempAllocator &alloc, int type) { switch (type) { @@ -2283,6 +2290,16 @@ MLoadTypedArrayElementStatic::truncate(TruncateKind kind) return false; } +bool +MLimitedTruncate::truncate(TruncateKind kind) +{ + setTruncateKind(kind); + setResultType(MIRType_Int32); + if (kind >= IndirectTruncate && range()) + range()->wrapAroundToInt32(); + return false; +} + MDefinition::TruncateKind MDefinition::operandTruncateKind(size_t index) const { @@ -2304,6 +2321,12 @@ MBinaryBitwiseInstruction::operandTruncateKind(size_t index) const return Truncate; } +MDefinition::TruncateKind +MLimitedTruncate::operandTruncateKind(size_t index) const +{ + return Min(truncateKind(), truncateLimit_); +} + MDefinition::TruncateKind MAdd::operandTruncateKind(size_t index) const {