mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 17:55:50 +00:00
Bug 1383436 - Part 8: Add MToIntegerInt32 to specialise ToInteger operations. r=jandem
The code is mostly based on the existing MToNumberInt32 class. Differential Revision: https://phabricator.services.mozilla.com/D37291 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
933a1e83a2
commit
a4821747b3
@ -863,6 +863,23 @@ CodeGenerator::CodeGenerator(MIRGenerator* gen, LIRGraph* graph,
|
||||
|
||||
CodeGenerator::~CodeGenerator() { js_delete(scriptCounts_); }
|
||||
|
||||
class OutOfLineZeroIfNaN : public OutOfLineCodeBase<CodeGenerator> {
|
||||
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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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()));
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user