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:
André Bargull 2019-10-22 09:37:06 +00:00
parent 933a1e83a2
commit a4821747b3
12 changed files with 289 additions and 5 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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,
};

View File

@ -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()));

View File

@ -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);

View File

@ -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 {

View File

@ -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) {

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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