diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index e38b79e0d5a2..2aac70a0819f 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -863,6 +863,23 @@ CodeGenerator::CodeGenerator(MIRGenerator* gen, LIRGraph* graph, CodeGenerator::~CodeGenerator() { js_delete(scriptCounts_); } +class OutOfLineZeroIfNaN : public OutOfLineCodeBase { + LInstruction* lir_; + FloatRegister input_; + Register output_; + + public: + OutOfLineZeroIfNaN(LInstruction* lir, FloatRegister input, Register output) + : lir_(lir), input_(input), output_(output) {} + + void accept(CodeGenerator* codegen) override { + codegen->visitOutOfLineZeroIfNaN(this); + } + LInstruction* lir() const { return lir_; } + FloatRegister input() const { return input_; } + Register output() const { return output_; } +}; + void CodeGenerator::visitValueToInt32(LValueToInt32* lir) { ValueOperand operand = ToValue(lir, LValueToInt32::Input); Register output = ToRegister(lir->output()); @@ -871,6 +888,8 @@ void CodeGenerator::visitValueToInt32(LValueToInt32* lir) { MDefinition* input; if (lir->mode() == LValueToInt32::NORMAL) { input = lir->mirNormal()->input(); + } else if (lir->mode() == LValueToInt32::TRUNCATE_NOWRAP) { + input = lir->mirTruncateNoWrap()->input(); } else { input = lir->mirTruncate()->input(); } @@ -901,6 +920,13 @@ void CodeGenerator::visitValueToInt32(LValueToInt32* lir) { oolDouble->entry(), stringReg, temp, output, &fails); masm.bind(oolDouble->rejoin()); + } else if (lir->mode() == LValueToInt32::TRUNCATE_NOWRAP) { + auto* ool = new (alloc()) OutOfLineZeroIfNaN(lir, temp, output); + addOutOfLineCode(ool, lir->mir()); + + masm.truncateNoWrapValueToInt32(operand, input, temp, output, ool->entry(), + &fails); + masm.bind(ool->rejoin()); } else { masm.convertValueToInt32(operand, input, temp, output, &fails, lir->mirNormal()->canBeNegativeZero(), @@ -910,6 +936,27 @@ void CodeGenerator::visitValueToInt32(LValueToInt32* lir) { bailoutFrom(&fails, lir->snapshot()); } +void CodeGenerator::visitOutOfLineZeroIfNaN(OutOfLineZeroIfNaN* ool) { + FloatRegister input = ool->input(); + Register output = ool->output(); + + // NaN triggers the failure path for branchTruncateDoubleToInt32() on x86, + // x64, and ARM64, so handle it here. In all other cases bail out. + + Label fails; + if (input.isSingle()) { + masm.branchFloat(Assembler::DoubleOrdered, input, input, &fails); + } else { + masm.branchDouble(Assembler::DoubleOrdered, input, input, &fails); + } + + // ToInteger(NaN) is 0. + masm.move32(Imm32(0), output); + masm.jump(ool->rejoin()); + + bailoutFrom(&fails, ool->lir()->snapshot()); +} + void CodeGenerator::visitValueToDouble(LValueToDouble* lir) { MToDouble* mir = lir->mir(); ValueOperand operand = ToValue(lir, LValueToDouble::Input); @@ -1069,6 +1116,28 @@ void CodeGenerator::visitFloat32ToInt32(LFloat32ToInt32* lir) { bailoutFrom(&fail, lir->snapshot()); } +void CodeGenerator::visitDoubleToIntegerInt32(LDoubleToIntegerInt32* lir) { + FloatRegister input = ToFloatRegister(lir->input()); + Register output = ToRegister(lir->output()); + + auto* ool = new (alloc()) OutOfLineZeroIfNaN(lir, input, output); + addOutOfLineCode(ool, lir->mir()); + + masm.branchTruncateDoubleToInt32(input, output, ool->entry()); + masm.bind(ool->rejoin()); +} + +void CodeGenerator::visitFloat32ToIntegerInt32(LFloat32ToIntegerInt32* lir) { + FloatRegister input = ToFloatRegister(lir->input()); + Register output = ToRegister(lir->output()); + + auto* ool = new (alloc()) OutOfLineZeroIfNaN(lir, input, output); + addOutOfLineCode(ool, lir->mir()); + + masm.branchTruncateFloat32ToInt32(input, output, ool->entry()); + masm.bind(ool->rejoin()); +} + void CodeGenerator::emitOOLTestObject(Register objreg, Label* ifEmulatesUndefined, Label* ifDoesntEmulateUndefined, diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 0eaf191b0886..42994c764cf9 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -62,6 +62,7 @@ class OutOfLineRegExpPrototypeOptimizable; class OutOfLineRegExpInstanceOptimizable; class OutOfLineLambdaArrow; class OutOfLineNaNToZero; +class OutOfLineZeroIfNaN; class CodeGenerator final : public CodeGeneratorSpecific { void generateArgumentsChecks(bool assert = false); @@ -129,6 +130,7 @@ class CodeGenerator final : public CodeGeneratorSpecific { void visitOutOfLineIsConstructor(OutOfLineIsConstructor* ool); void visitOutOfLineNaNToZero(OutOfLineNaNToZero* ool); + void visitOutOfLineZeroIfNaN(OutOfLineZeroIfNaN* ool); void visitCheckOverRecursedFailure(CheckOverRecursedFailure* ool); diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index cf96a5d76d8b..c46978ef3201 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -428,8 +428,9 @@ enum class IntConversionBehavior { // will fail if the resulting int32 isn't strictly equal to the input. Normal, // Succeeds on -0: converts to 0. NegativeZeroCheck, // Fails on -0. - // These two will convert the input to an int32 with loss of precision. + // These three will convert the input to an int32 with loss of precision. Truncate, + TruncateNoWrap, ClampToUint8, }; diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index bd24d62d3b21..518f170710f8 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2110,6 +2110,56 @@ void LIRGenerator::visitToNumberInt32(MToNumberInt32* convert) { } } +void LIRGenerator::visitToIntegerInt32(MToIntegerInt32* convert) { + MDefinition* opd = convert->input(); + + switch (opd->type()) { + case MIRType::Value: { + auto* lir = new (alloc()) LValueToInt32(useBox(opd), tempDouble(), temp(), + LValueToInt32::TRUNCATE_NOWRAP); + assignSnapshot(lir, Bailout_NonPrimitiveInput); + define(lir, convert); + assignSafepoint(lir, convert); + break; + } + + case MIRType::Undefined: + case MIRType::Null: + define(new (alloc()) LInteger(0), convert); + break; + + case MIRType::Boolean: + case MIRType::Int32: + redefine(convert, opd); + break; + + case MIRType::Float32: { + auto* lir = new (alloc()) LFloat32ToIntegerInt32(useRegister(opd)); + assignSnapshot(lir, Bailout_Overflow); + define(lir, convert); + break; + } + + case MIRType::Double: { + auto* lir = new (alloc()) LDoubleToIntegerInt32(useRegister(opd)); + assignSnapshot(lir, Bailout_Overflow); + define(lir, convert); + break; + } + + case MIRType::String: + case MIRType::Symbol: + case MIRType::BigInt: + case MIRType::Object: + // Objects might be effectful. Symbols and BigInts throw. + // Strings are complicated - we don't handle them yet. + MOZ_CRASH("ToIntegerInt32 invalid input type"); + + default: + MOZ_CRASH("unexpected type"); + } +} + void LIRGenerator::visitToNumeric(MToNumeric* ins) { MOZ_ASSERT(ins->input()->type() == MIRType::Value); LToNumeric* lir = new (alloc()) LToNumeric(useBoxAtStart(ins->input())); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index d45ecb756b6d..fff4cfc0018f 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -3911,6 +3911,45 @@ MDefinition* MToNumberInt32::foldsTo(TempAllocator& alloc) { return this; } +MDefinition* MToIntegerInt32::foldsTo(TempAllocator& alloc) { + MDefinition* input = getOperand(0); + + // Fold this operation if the input operand is constant. + if (input->isConstant()) { + switch (input->type()) { + case MIRType::Undefined: + case MIRType::Null: + return MConstant::New(alloc, Int32Value(0)); + case MIRType::Boolean: + return MConstant::New(alloc, + Int32Value(input->toConstant()->toBoolean())); + case MIRType::Int32: + return MConstant::New(alloc, + Int32Value(input->toConstant()->toInt32())); + case MIRType::Float32: + case MIRType::Double: { + double result = JS::ToInteger(input->toConstant()->numberToDouble()); + int32_t ival; + // Only the value within the range of Int32 can be substituted as + // constant. + if (mozilla::NumberEqualsInt32(result, &ival)) { + return MConstant::New(alloc, Int32Value(ival)); + } + break; + } + default: + break; + } + } + + // See the comment in |MToNumberInt32::foldsTo|. + if (input->type() == MIRType::Int32 && !IsUint32Type(input)) { + return input; + } + + return this; +} + void MToNumberInt32::analyzeEdgeCasesBackward() { if (!NeedNegativeZeroCheck(this)) { setCanBeNegativeZero(false); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index de99c9416dfd..06c34c70862f 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -4112,6 +4112,50 @@ class MToNumberInt32 : public MUnaryInstruction, public ToInt32Policy::Data { ALLOW_CLONE(MToNumberInt32) }; +// Applies ECMA's ToInteger on a primitive (either typed or untyped) and expects +// the result to be precisely representable as an Int32, otherwise bails. +// +// NB: Negative zero doesn't lead to a bailout, but instead will be treated the +// same as positive zero for this operation. +// +// If the input is not primitive at runtime, a bailout occurs. If the input +// cannot be converted to an int32 without loss (i.e. 2e10 or Infinity) then a +// bailout occurs. +class MToIntegerInt32 : public MUnaryInstruction, public ToInt32Policy::Data { + explicit MToIntegerInt32(MDefinition* def) + : MUnaryInstruction(classOpcode, def) { + setResultType(MIRType::Int32); + setMovable(); + + // An object might have "valueOf", which means it is effectful. + // ToInteger(symbol) and ToInteger(BigInt) throw. + if (def->mightBeType(MIRType::Object) || + def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { + setGuard(); + } + } + + public: + INSTRUCTION_HEADER(ToIntegerInt32) + TRIVIAL_NEW_WRAPPERS + + MDefinition* foldsTo(TempAllocator& alloc) override; + + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + + AliasSet getAliasSet() const override { return AliasSet::None(); } + void computeRange(TempAllocator& alloc) override; + +#ifdef DEBUG + bool isConsistentFloat32Use(MUse* use) const override { return true; } +#endif + + ALLOW_CLONE(MToIntegerInt32) +}; + // Converts a value or typed input to a truncated int32, for use with bitwise // operations. This is an infallible ValueToECMAInt32. class MTruncateToInt32 : public MUnaryInstruction, public ToInt32Policy::Data { diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 097216cf06ff..a5b51e7a40a7 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -2140,6 +2140,10 @@ void MacroAssembler::convertDoubleToInt(FloatRegister src, Register output, branchTruncateDoubleMaybeModUint32(src, output, truncateFail ? truncateFail : fail); break; + case IntConversionBehavior::TruncateNoWrap: + branchTruncateDoubleToInt32(src, output, + truncateFail ? truncateFail : fail); + break; case IntConversionBehavior::ClampToUint8: // Clamping clobbers the input register, so use a temp. if (src != temp) { @@ -2184,6 +2188,7 @@ void MacroAssembler::convertValueToInt( break; case IntConversionBehavior::Truncate: + case IntConversionBehavior::TruncateNoWrap: case IntConversionBehavior::ClampToUint8: maybeBranchTestType(MIRType::Null, maybeInput, tag, &isNull); if (handleStrings) { diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 8ff062b5ae0a..86690d7a57a5 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -3141,6 +3141,16 @@ class MacroAssembler : public MacroAssemblerSpecific { temp, output, fail); } + // Truncates, i.e. removes any fractional parts, but doesn't wrap around to + // the int32 range. + void truncateNoWrapValueToInt32(ValueOperand value, MDefinition* input, + FloatRegister temp, Register output, + Label* truncateDoubleSlow, Label* fail) { + convertValueToInt(value, input, nullptr, nullptr, truncateDoubleSlow, + InvalidReg, temp, output, fail, + IntConversionBehavior::TruncateNoWrap); + } + // Convenience functions for clamping values to uint8. void clampValueToUint8(ValueOperand value, MDefinition* input, Label* handleStringEntry, Label* handleStringRejoin, diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 56182ea30d03..23020c5cee4f 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1206,6 +1206,21 @@ Range* Range::NaNToZero(TempAllocator& alloc, const Range* op) { return copy; } +Range* Range::toIntegerInt32(TempAllocator& alloc, const Range* op) { + Range* copy = new (alloc) Range(*op); + copy->canHaveFractionalPart_ = ExcludesFractionalParts; + if (copy->canBeNaN()) { + copy->max_exponent_ = Range::IncludesInfinity; + if (!copy->canBeZero()) { + Range zero; + zero.setDoubleSingleton(0); + copy->unionWith(&zero); + } + } + copy->refineToExcludeNegativeZero(); + return copy; +} + bool Range::negativeZeroMul(const Range* lhs, const Range* rhs) { // The result can only be negative zero if both sides are finite and they // have differing signs. @@ -1707,6 +1722,11 @@ void MToNumberInt32::computeRange(TempAllocator& alloc) { setRange(new (alloc) Range(getOperand(0))); } +void MToIntegerInt32::computeRange(TempAllocator& alloc) { + Range other(input()); + setRange(Range::toIntegerInt32(alloc, &other)); +} + void MLimitedTruncate::computeRange(TempAllocator& alloc) { Range* output = new (alloc) Range(input()); setRange(output); diff --git a/js/src/jit/RangeAnalysis.h b/js/src/jit/RangeAnalysis.h index 548ade4c98ae..b0831147c69e 100644 --- a/js/src/jit/RangeAnalysis.h +++ b/js/src/jit/RangeAnalysis.h @@ -467,6 +467,7 @@ class Range : public TempObject { static Range* ceil(TempAllocator& alloc, const Range* op); static Range* sign(TempAllocator& alloc, const Range* op); static Range* NaNToZero(TempAllocator& alloc, const Range* op); + static Range* toIntegerInt32(TempAllocator& alloc, const Range* op); static MOZ_MUST_USE bool negativeZeroMul(const Range* lhs, const Range* rhs); diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 1fb8a7d3067a..124bbd29c25d 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -787,7 +787,8 @@ bool ToDoublePolicy::staticAdjustInputs(TempAllocator& alloc, bool ToInt32Policy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) { - MOZ_ASSERT(ins->isToNumberInt32() || ins->isTruncateToInt32()); + MOZ_ASSERT(ins->isToNumberInt32() || ins->isTruncateToInt32() || + ins->isToIntegerInt32()); IntConversionInputKind conversion = IntConversionInputKind::Any; if (ins->isToNumberInt32()) { @@ -804,7 +805,9 @@ bool ToInt32Policy::staticAdjustInputs(TempAllocator& alloc, return true; case MIRType::Undefined: // No need for boxing when truncating. - if (ins->isTruncateToInt32()) { + // Also no need for boxing when performing ToInteger, because + // ToInteger(undefined) = ToInteger(NaN) = 0. + if (ins->isTruncateToInt32() || ins->isToIntegerInt32()) { return true; } break; diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index a6c98f9b7680..505969902764 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -2962,7 +2962,7 @@ class LValueToFloat32 : public LInstructionHelper<1, BOX_PIECES, 0> { // This instruction requires a temporary float register. class LValueToInt32 : public LInstructionHelper<1, BOX_PIECES, 2> { public: - enum Mode { NORMAL, TRUNCATE }; + enum Mode { NORMAL, TRUNCATE, TRUNCATE_NOWRAP }; private: Mode mode_; @@ -2979,7 +2979,9 @@ class LValueToInt32 : public LInstructionHelper<1, BOX_PIECES, 2> { } const char* extraName() const { - return mode() == NORMAL ? "Normal" : "Truncate"; + return mode() == NORMAL + ? "Normal" + : mode() == TRUNCATE ? "Truncate" : "TruncateNoWrap"; } static const size_t Input = 0; @@ -2995,6 +2997,10 @@ class LValueToInt32 : public LInstructionHelper<1, BOX_PIECES, 2> { MOZ_ASSERT(mode_ == TRUNCATE); return mir_->toTruncateToInt32(); } + MToIntegerInt32* mirTruncateNoWrap() const { + MOZ_ASSERT(mode_ == TRUNCATE_NOWRAP); + return mir_->toToIntegerInt32(); + } MInstruction* mir() const { return mir_->toInstruction(); } }; @@ -3030,6 +3036,40 @@ class LFloat32ToInt32 : public LInstructionHelper<1, 1, 0> { MToNumberInt32* mir() const { return mir_->toToNumberInt32(); } }; +// Truncates a double to an int32. +// Input: floating-point register +// Output: 32-bit integer +// Bailout: if the double when converted to an integer exceeds the int32 +// bounds. No bailout for NaN or negative zero. +class LDoubleToIntegerInt32 : public LInstructionHelper<1, 1, 0> { + public: + LIR_HEADER(DoubleToIntegerInt32) + + explicit LDoubleToIntegerInt32(const LAllocation& in) + : LInstructionHelper(classOpcode) { + setOperand(0, in); + } + + MToIntegerInt32* mir() const { return mir_->toToIntegerInt32(); } +}; + +// Truncates a float to an int32. +// Input: floating-point register +// Output: 32-bit integer +// Bailout: if the double when converted to an integer exceeds the int32 +// bounds. No bailout for NaN or negative zero. +class LFloat32ToIntegerInt32 : public LInstructionHelper<1, 1, 0> { + public: + LIR_HEADER(Float32ToIntegerInt32) + + explicit LFloat32ToIntegerInt32(const LAllocation& in) + : LInstructionHelper(classOpcode) { + setOperand(0, in); + } + + MToIntegerInt32* mir() const { return mir_->toToIntegerInt32(); } +}; + // Convert a double to a truncated int32. // Input: floating-point register // Output: 32-bit integer