Bug 1608771 - Part 1, BigInt<>I64 conversion for inlined calls r=wingo,lth

This is part 1 of implementing the Wasm BigInt<>I64 conversion proposal for inlined Ion to Wasm calls.

This part implements Ion MIR and LIR instructions that are needed for conversion between BigInts and I64.

Differential Revision: https://phabricator.services.mozilla.com/D65233
This commit is contained in:
Asumu Takikawa 2020-04-22 20:12:41 +00:00
Родитель ebf7dd1da8
Коммит fb18e7c404
14 изменённых файлов: 484 добавлений и 0 удалений

Просмотреть файл

@ -4626,6 +4626,126 @@ void CodeGenerator::visitToNumeric(LToNumeric* lir) {
masm.bind(ool->rejoin());
}
void CodeGenerator::visitBooleanToInt64(LBooleanToInt64* lir) {
Register input = ToRegister(lir->input());
Register64 output = ToOutRegister64(lir);
masm.move32To64ZeroExtend(input, output);
}
void CodeGenerator::emitStringToInt64(LInstruction* lir, Register input,
Register temp, Register64 output) {
masm.reserveStack(sizeof(uint64_t));
masm.moveStackPtrTo(temp);
pushArg(temp);
pushArg(input);
using Fn = bool (*)(JSContext*, HandleString, uint64_t*, bool*);
callVM<Fn, DoStringToInt64>(lir);
masm.load64(Address(masm.getStackPointer(), 0), output);
masm.freeStack(sizeof(uint64_t));
bailoutIfFalseBool(ReturnReg, lir->snapshot());
}
void CodeGenerator::visitStringToInt64(LStringToInt64* lir) {
Register input = ToRegister(lir->input());
Register temp = ToTempRegisterOrInvalid(lir->temp());
Register64 output = ToOutRegister64(lir);
MOZ_ASSERT(lir->mir()->input()->type() == MIRType::String);
emitStringToInt64(lir, input, temp, output);
}
void CodeGenerator::visitValueToInt64(LValueToInt64* lir) {
ValueOperand input = ToValue(lir, LValueToInt64::Input);
Register64 output = ToOutRegister64(lir);
Register scratch = ToTempRegisterOrInvalid(lir->temp());
Register tag = masm.extractTag(input, scratch);
Label fail, done;
bool maybeBigInt = lir->mir()->input()->mightBeType(MIRType::BigInt);
bool maybeBool = lir->mir()->input()->mightBeType(MIRType::Boolean);
bool maybeString = lir->mir()->input()->mightBeType(MIRType::String);
int checks = int(maybeBigInt) + int(maybeBool) + int(maybeString);
if (checks == 0) {
// Bail on other types.
masm.jump(&fail);
} else {
// BigInt.
if (maybeBigInt) {
Label notBigInt;
masm.branchTestBigInt(Assembler::NotEqual, tag, &notBigInt);
masm.unboxBigInt(input, scratch);
Register bigint = input.scratchReg();
masm.movePtr(scratch, bigint);
masm.loadBigInt64(bigint, output);
masm.jump(&done);
masm.bind(&notBigInt);
}
// Boolean
if (maybeBool) {
Label notBoolean;
masm.branchTestBoolean(Assembler::NotEqual, tag, &notBoolean);
Register unboxed = ToTempUnboxRegister(lir->tempToUnbox());
unboxed = masm.extractBoolean(input, unboxed);
masm.move32To64ZeroExtend(unboxed, output);
masm.jump(&done);
masm.bind(&notBoolean);
}
// String
if (maybeString) {
masm.branchTestString(Assembler::NotEqual, tag, &fail);
#ifdef JS_NUNBOX32
Register unboxed = input.payloadReg();
#else
Register unboxed = ToTempUnboxRegister(lir->tempToUnbox());
#endif
masm.unboxString(input, unboxed);
emitStringToInt64(lir, unboxed, scratch, output);
masm.jump(&done);
}
}
bailoutFrom(&fail, lir->snapshot());
masm.bind(&done);
}
void CodeGenerator::visitTruncateBigIntToInt64(LTruncateBigIntToInt64* lir) {
Register operand = ToRegister(lir->input());
Register64 output = ToOutRegister64(lir);
MOZ_ASSERT(lir->mir()->input()->type() == MIRType::BigInt);
masm.loadBigInt64(operand, output);
}
void CodeGenerator::visitInt64ToBigInt(LInt64ToBigInt* lir) {
MOZ_ASSERT(lir->mir()->input()->type() == MIRType::Int64);
Register64 input = ToRegister64(lir->getInt64Operand(LInt64ToBigInt::Input));
Register temp = ToTempRegisterOrInvalid(lir->temp1());
Register bigint = ToTempRegisterOrInvalid(lir->temp2());
Register output = ToRegister(lir->getDef(0));
#if JS_BITS_PER_WORD == 32
using Fn = BigInt* (*)(JSContext*, uint32_t, uint32_t);
OutOfLineCode* ool = oolCallVM<Fn, jit::CreateBigIntFromInt64>(
lir, ArgList(input.low, input.high), StoreRegisterTo(output));
#else
using Fn = BigInt* (*)(JSContext*, uint64_t);
OutOfLineCode* ool = oolCallVM<Fn, jit::CreateBigIntFromInt64>(
lir, ArgList(input), StoreRegisterTo(output));
#endif
masm.newGCBigInt(bigint, temp, ool->entry(), true);
masm.initializeBigInt64(Scalar::BigInt64, bigint, input);
masm.movePtr(bigint, output);
masm.bind(ool->rejoin());
}
void CodeGenerator::visitTypeBarrierV(LTypeBarrierV* lir) {
ValueOperand operand = ToValue(lir, LTypeBarrierV::Input);
Register unboxScratch = ToTempRegisterOrInvalid(lir->unboxTemp());

Просмотреть файл

@ -265,6 +265,9 @@ class CodeGenerator final : public CodeGeneratorSpecific {
template <class OrderedHashTable>
void emitLoadIteratorValues(Register result, Register temp, Register front);
void emitStringToInt64(LInstruction* lir, Register input, Register temp,
Register64 output);
template <size_t NumDefs>
void emitIonToWasmCallBase(LIonToWasmCallBase<NumDefs>* lir);

Просмотреть файл

@ -2200,6 +2200,66 @@ void LIRGenerator::visitTruncateToInt32(MTruncateToInt32* truncate) {
}
}
void LIRGenerator::visitToInt64(MToInt64* ins) {
MDefinition* opd = ins->input();
switch (opd->type()) {
case MIRType::Value: {
LValueToInt64* lir =
new (alloc()) LValueToInt64(useBox(opd), temp(), tempToUnbox());
assignSnapshot(lir, Bailout_NonPrimitiveInput);
defineInt64(lir, ins);
assignSafepoint(lir, ins);
break;
}
case MIRType::Boolean: {
LBooleanToInt64* lir =
new (alloc()) LBooleanToInt64(useRegisterAtStart(opd));
defineInt64(lir, ins);
assignSafepoint(lir, ins);
break;
}
case MIRType::String: {
LStringToInt64* lir =
new (alloc()) LStringToInt64(useRegister(opd), temp());
// May bail out on parse failure.
assignSnapshot(lir, Bailout_NonPrimitiveInput);
defineInt64(lir, ins);
assignSafepoint(lir, ins);
break;
}
// An Int64 may be passed here from a BigInt to Int64 conversion.
case MIRType::Int64: {
redefine(ins, opd);
break;
}
default:
// Undefined, Null, Number, and Symbol throw.
// Objects may be effectful.
// BigInt operands are eliminated by the type policy.
MOZ_CRASH("unexpected type");
}
}
void LIRGenerator::visitTruncateBigIntToInt64(MTruncateBigIntToInt64* ins) {
MOZ_ASSERT(ins->input()->type() == MIRType::BigInt);
LTruncateBigIntToInt64* lir =
new (alloc()) LTruncateBigIntToInt64(useRegister(ins->input()));
defineInt64(lir, ins);
}
void LIRGenerator::visitInt64ToBigInt(MInt64ToBigInt* ins) {
MOZ_ASSERT(ins->input()->type() == MIRType::Int64);
LInt64ToBigInt* lir =
new (alloc()) LInt64ToBigInt(useInt64(ins->input()), temp(), temp());
define(lir, ins);
assignSafepoint(lir, ins);
}
void LIRGenerator::visitWasmTruncateToInt32(MWasmTruncateToInt32* ins) {
MDefinition* input = ins->input();
switch (input->type()) {

Просмотреть файл

@ -3725,6 +3725,52 @@ MDefinition* MToNumeric::foldsTo(TempAllocator& alloc) {
return this;
}
MDefinition* MTruncateBigIntToInt64::foldsTo(TempAllocator& alloc) {
MDefinition* input = getOperand(0);
if (input->isBox()) {
input = input->getOperand(0);
}
// If the operand converts an I64 to BigInt, drop both conversions.
if (input->isInt64ToBigInt()) {
return input->getOperand(0);
}
// Fold this operation if the input operand is constant.
if (input->isConstant()) {
return MConstant::NewInt64(
alloc, BigInt::toInt64(input->toConstant()->toBigInt()));
}
return this;
}
MDefinition* MToInt64::foldsTo(TempAllocator& alloc) {
MDefinition* input = getOperand(0);
if (input->isBox()) {
input = input->getOperand(0);
}
// When the input is an Int64 already, just return it.
if (input->type() == MIRType::Int64) {
return input;
}
// Fold this operation if the input operand is constant.
if (input->isConstant()) {
switch (input->type()) {
case MIRType::Boolean:
return MConstant::NewInt64(alloc, input->toConstant()->toBoolean());
default:
break;
}
}
return this;
}
MDefinition* MToNumberInt32::foldsTo(TempAllocator& alloc) {
MDefinition* input = getOperand(0);

Просмотреть файл

@ -4224,6 +4224,74 @@ class MTruncateToInt32 : public MUnaryInstruction, public ToInt32Policy::Data {
ALLOW_CLONE(MTruncateToInt32)
};
// Takes a Value or typed input and returns a suitable Int64 using the
// ToBigInt algorithm, possibly calling out to the VM for string, etc inputs.
class MToInt64 : public MUnaryInstruction, public ToInt64Policy::Data {
explicit MToInt64(MDefinition* arg) : MUnaryInstruction(classOpcode, arg) {
setResultType(MIRType::Int64);
setGuard(); // May bail on non-Bool, non-BigInt, or invalid Strings.
setMovable();
}
public:
INSTRUCTION_HEADER(ToInt64)
TRIVIAL_NEW_WRAPPERS
void computeRange(TempAllocator& alloc) override;
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins);
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
MDefinition* foldsTo(TempAllocator& alloc) override;
ALLOW_CLONE(MToInt64)
};
// Takes a BigInt pointer and returns its toInt64 value.
class MTruncateBigIntToInt64 : public MUnaryInstruction,
public NoTypePolicy::Data {
explicit MTruncateBigIntToInt64(MDefinition* arg)
: MUnaryInstruction(classOpcode, arg) {
MOZ_ASSERT(arg->type() == MIRType::BigInt);
setResultType(MIRType::Int64);
}
public:
INSTRUCTION_HEADER(TruncateBigIntToInt64)
TRIVIAL_NEW_WRAPPERS
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins);
}
MDefinition* foldsTo(TempAllocator& alloc) override;
ALLOW_CLONE(MTruncateBigIntToInt64)
};
// Takes an Int64 and returns a fresh BigInt pointer.
class MInt64ToBigInt : public MUnaryInstruction, public NoTypePolicy::Data {
explicit MInt64ToBigInt(MDefinition* arg)
: MUnaryInstruction(classOpcode, arg) {
MOZ_ASSERT(arg->type() == MIRType::Int64);
setResultType(MIRType::BigInt);
}
public:
INSTRUCTION_HEADER(Int64ToBigInt)
TRIVIAL_NEW_WRAPPERS
bool congruentTo(const MDefinition* ins) const override {
return congruentIfOperandsEqual(ins);
}
AliasSet getAliasSet() const override { return AliasSet::None(); }
ALLOW_CLONE(MInt64ToBigInt)
};
// Converts any type to a string
class MToString : public MUnaryInstruction, public ToStringPolicy::Data {
public:

Просмотреть файл

@ -2631,6 +2631,16 @@ void MacroAssembler::Push(JSValueType type, Register reg) {
framePushed_ += sizeof(Value);
}
void MacroAssembler::Push(const Register64 reg) {
#if JS_BITS_PER_WORD == 64
Push(reg.reg);
#else
MOZ_ASSERT(MOZ_LITTLE_ENDIAN(), "Big-endian not supported.");
Push(reg.high);
Push(reg.low);
#endif
}
void MacroAssembler::PushValue(const Address& addr) {
MOZ_ASSERT(addr.base != getStackPointer());
pushValue(addr);

Просмотреть файл

@ -387,6 +387,7 @@ class MacroAssembler : public MacroAssemblerSpecific {
void Push(const ValueOperand& val);
void Push(const Value& val);
void Push(JSValueType type, Register reg);
void Push(const Register64 reg);
void PushValue(const Address& addr);
void PushEmptyRooted(VMFunctionData::RootType rootType);
inline CodeOffset PushWithPatch(ImmWord word);

Просмотреть файл

@ -1707,6 +1707,10 @@ void MTruncateToInt32::computeRange(TempAllocator& alloc) {
setRange(output);
}
void MToInt64::computeRange(TempAllocator& alloc) {
setRange(new (alloc) Range(getOperand(0)));
}
void MToNumeric::computeRange(TempAllocator& alloc) {
setRange(new (alloc) Range(getOperand(0)));
}

Просмотреть файл

@ -830,6 +830,35 @@ bool ToStringPolicy::staticAdjustInputs(TempAllocator& alloc,
return true;
}
bool ToInt64Policy::staticAdjustInputs(TempAllocator& alloc,
MInstruction* ins) {
MOZ_ASSERT(ins->isToInt64());
MDefinition* input = ins->getOperand(0);
MIRType type = input->type();
switch (type) {
case MIRType::BigInt: {
auto* replace = MTruncateBigIntToInt64::New(alloc, input);
ins->block()->insertBefore(ins, replace);
ins->replaceOperand(0, replace);
break;
}
// No need for boxing for these types, because they are handled specially
// when this instruction is lowered to LIR.
case MIRType::Boolean:
case MIRType::String:
case MIRType::Int64:
case MIRType::Value:
break;
default:
ins->replaceOperand(0, BoxAt(alloc, ins, ins->getOperand(0)));
break;
}
return true;
}
template <unsigned Op>
bool ObjectPolicy<Op>::staticAdjustInputs(TempAllocator& alloc,
MInstruction* ins) {
@ -1152,6 +1181,7 @@ bool TypedArrayIndexPolicy::adjustInputs(TempAllocator& alloc,
_(ToDoublePolicy) \
_(ToInt32Policy) \
_(ToStringPolicy) \
_(ToInt64Policy) \
_(TypeBarrierPolicy) \
_(TypedArrayIndexPolicy)

Просмотреть файл

@ -336,6 +336,19 @@ class ToStringPolicy final : public TypePolicy {
}
};
// Box non-Boolean, non-String, non-BigInt as input to a ToInt64 instruction.
class ToInt64Policy final : public TypePolicy {
public:
constexpr ToInt64Policy() = default;
EMPTY_DATA_;
static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc,
MInstruction* ins);
MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc,
MInstruction* ins) const override {
return staticAdjustInputs(alloc, ins);
}
};
template <unsigned Op>
class ObjectPolicy final : public TypePolicy {
public:

Просмотреть файл

@ -89,6 +89,7 @@ namespace jit {
_(CopyElementsForWrite, js::NativeObject::CopyElementsForWrite) \
_(CopyLexicalEnvironmentObject, js::jit::CopyLexicalEnvironmentObject) \
_(CreateAsyncFromSyncIterator, js::CreateAsyncFromSyncIterator) \
_(CreateBigIntFromInt64, js::jit::CreateBigIntFromInt64) \
_(CreateGenerator, js::jit::CreateGenerator) \
_(CreateThisForFunctionWithProto, js::CreateThisForFunctionWithProto) \
_(CreateThisFromIC, js::jit::CreateThisFromIC) \
@ -115,6 +116,7 @@ namespace jit {
_(DoCallFallback, js::jit::DoCallFallback) \
_(DoConcatStringObject, js::jit::DoConcatStringObject) \
_(DoSpreadCallFallback, js::jit::DoSpreadCallFallback) \
_(DoStringToInt64, js::jit::DoStringToInt64) \
_(DoToNumeric, js::jit::DoToNumeric) \
_(DoTypeUpdateFallback, js::jit::DoTypeUpdateFallback) \
_(EnterWith, js::jit::EnterWith) \

Просмотреть файл

@ -2004,6 +2004,32 @@ void* AllocateBigIntNoGC(JSContext* cx, bool requestMinorGC) {
return js::AllocateBigInt<NoGC>(cx, gc::TenuredHeap);
}
#if JS_BITS_PER_WORD == 32
BigInt* CreateBigIntFromInt64(JSContext* cx, uint32_t low, uint32_t high) {
int64_t n = ((uint64_t)high << 32) + low;
return js::BigInt::createFromInt64(cx, n);
}
#else
BigInt* CreateBigIntFromInt64(JSContext* cx, uint64_t i64) {
return js::BigInt::createFromInt64(cx, i64);
}
#endif
bool DoStringToInt64(JSContext* cx, HandleString str, uint64_t* res,
bool* parseSuccess) {
BigInt* bi;
*parseSuccess = true;
JS_TRY_VAR_OR_RETURN_FALSE(cx, bi, js::StringToBigInt(cx, str));
if (!bi) {
*parseSuccess = false;
return true;
}
*res = js::BigInt::toUint64(bi);
return true;
}
template <EqualityKind Kind>
bool BigIntEqual(BigInt* x, BigInt* y) {
AutoUnsafeCallWithABI unsafe;

Просмотреть файл

@ -715,6 +715,10 @@ struct OutParamToDataType<uint32_t*> {
static const DataType result = Type_Int32;
};
template <>
struct OutParamToDataType<uint64_t*> {
static const DataType result = Type_Pointer;
};
template <>
struct OutParamToDataType<uint8_t**> {
static const DataType result = Type_Pointer;
};
@ -1128,6 +1132,13 @@ bool IsPossiblyWrappedTypedArray(JSContext* cx, JSObject* obj, bool* result);
bool DoToNumeric(JSContext* cx, HandleValue arg, MutableHandleValue ret);
void* AllocateBigIntNoGC(JSContext* cx, bool requestMinorGC);
#if JS_BITS_PER_WORD == 32
BigInt* CreateBigIntFromInt64(JSContext* cx, uint32_t low, uint32_t high);
#else
BigInt* CreateBigIntFromInt64(JSContext* cx, uint64_t i64);
#endif
bool DoStringToInt64(JSContext* cx, HandleString str, uint64_t* res,
bool* parseSuccess);
template <EqualityKind Kind>
bool BigIntEqual(BigInt* x, BigInt* y);

Просмотреть файл

@ -5493,6 +5493,96 @@ class LToNumeric : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 0> {
static const size_t Input = 0;
const MToNumeric* mir() const { return mir_->toToNumeric(); }
const LDefinition* temp() { return getTemp(0); }
};
// Convert a Boolean to an Int64, following ToBigInt.
class LBooleanToInt64 : public LInstructionHelper<INT64_PIECES, 1, 0> {
public:
LIR_HEADER(BooleanToInt64)
explicit LBooleanToInt64(const LAllocation& input)
: LInstructionHelper(classOpcode) {
setOperand(Input, input);
}
static const size_t Input = 0;
const MToInt64* mir() const { return mir_->toToInt64(); }
};
// Convert a String to an Int64, following ToBigInt.
class LStringToInt64 : public LInstructionHelper<INT64_PIECES, 1, 1> {
public:
LIR_HEADER(StringToInt64)
explicit LStringToInt64(const LAllocation& input, const LDefinition& temp)
: LInstructionHelper(classOpcode) {
setOperand(Input, input);
setTemp(0, temp);
}
static const size_t Input = 0;
const MToInt64* mir() const { return mir_->toToInt64(); }
const LDefinition* temp() { return getTemp(0); }
};
// Simulate ToBigInt on a Value and produce a matching Int64.
class LValueToInt64 : public LInstructionHelper<INT64_PIECES, BOX_PIECES, 2> {
public:
LIR_HEADER(ValueToInt64)
explicit LValueToInt64(const LBoxAllocation& input, const LDefinition& temp,
const LDefinition& tempToUnbox)
: LInstructionHelper(classOpcode) {
setBoxOperand(Input, input);
setTemp(0, temp);
setTemp(1, tempToUnbox);
}
static const size_t Input = 0;
const MToInt64* mir() const { return mir_->toToInt64(); }
const LDefinition* temp() { return getTemp(0); }
const LDefinition* tempToUnbox() { return getTemp(1); }
};
// Truncate a BigInt to an unboxed int64.
class LTruncateBigIntToInt64 : public LInstructionHelper<INT64_PIECES, 1, 0> {
public:
LIR_HEADER(TruncateBigIntToInt64)
explicit LTruncateBigIntToInt64(const LAllocation& input)
: LInstructionHelper(classOpcode) {
setOperand(Input, input);
}
static const size_t Input = 0;
const MTruncateBigIntToInt64* mir() const {
return mir_->toTruncateBigIntToInt64();
}
};
// Create a new BigInt* from an unboxed int64.
class LInt64ToBigInt : public LInstructionHelper<1, INT64_PIECES, 2> {
public:
LIR_HEADER(Int64ToBigInt)
explicit LInt64ToBigInt(const LInt64Allocation& input,
const LDefinition& temp1, const LDefinition& temp2)
: LInstructionHelper(classOpcode) {
setInt64Operand(Input, input);
setTemp(0, temp1);
setTemp(1, temp2);
}
static const size_t Input = 0;
const MInt64ToBigInt* mir() const { return mir_->toInt64ToBigInt(); }
const LDefinition* temp1() { return getTemp(0); }
const LDefinition* temp2() { return getTemp(1); }
};
// Guard that a value is in a TypeSet.