From a1f0784e66b1586ae40b107d28abb01230caf6f9 Mon Sep 17 00:00:00 2001 From: "Nicolas B. Pierron" Date: Tue, 18 Jul 2017 12:08:22 +0000 Subject: [PATCH] Bug 1364908 - IonMonkey: Add LoadElementFromSate to support argument[x] in inlined functions. r=jandem --- js/public/TrackedOptimizationInfo.h | 3 +- .../tests/ion/inlining/inline-getelem-args.js | 59 +++++ js/src/jit/CodeGenerator.cpp | 233 ++++++++++++++++++ js/src/jit/CodeGenerator.h | 9 + js/src/jit/IonBuilder.cpp | 93 +++++-- js/src/jit/IonBuilder.h | 6 +- js/src/jit/LIR.h | 8 + js/src/jit/Lowering.cpp | 73 ++++++ js/src/jit/Lowering.h | 2 + js/src/jit/MIR.cpp | 22 ++ js/src/jit/MIR.h | 61 ++++- js/src/jit/MOpcodes.h | 2 + js/src/jit/TypePolicy.cpp | 2 + js/src/jit/arm64/Assembler-arm64.h | 6 + js/src/jit/shared/LIR-shared.h | 41 +++ js/src/jit/shared/LOpcodes-shared.h | 1 + js/src/jit/shared/Lowering-shared-inl.h | 6 +- js/src/jit/shared/Lowering-shared.h | 6 +- 18 files changed, 606 insertions(+), 27 deletions(-) create mode 100644 js/src/jit-test/tests/ion/inlining/inline-getelem-args.js diff --git a/js/public/TrackedOptimizationInfo.h b/js/public/TrackedOptimizationInfo.h index 64164c3169cc..b50d302a6e21 100644 --- a/js/public/TrackedOptimizationInfo.h +++ b/js/public/TrackedOptimizationInfo.h @@ -42,7 +42,8 @@ namespace JS { _(GetElem_TypedArray) \ _(GetElem_String) \ _(GetElem_Arguments) \ - _(GetElem_ArgumentsInlined) \ + _(GetElem_ArgumentsInlinedConstant) \ + _(GetElem_ArgumentsInlinedSwitch) \ _(GetElem_InlineCache) \ \ _(SetElem_TypedObject) \ diff --git a/js/src/jit-test/tests/ion/inlining/inline-getelem-args.js b/js/src/jit-test/tests/ion/inlining/inline-getelem-args.js new file mode 100644 index 000000000000..41acaf6a04dd --- /dev/null +++ b/js/src/jit-test/tests/ion/inlining/inline-getelem-args.js @@ -0,0 +1,59 @@ +function cat() { + var res = ""; + for (var i = 0; i < arguments.length; i++) + res += arguments[i]; + return res; +} + +function cat_m1(ion) { + var res = ""; + for (var i = (ion ? -1 : 0); i < arguments.length; i++) + res += arguments[i]; + return res; +} + +function cat_p1(ion) { + var res = ""; + for (var i = 0; i < arguments.length + (ion ? 1 : 0); i++) + res += arguments[i]; + return res; +} + +function sum() { + var res = 0; + for (var i = 0; i < arguments.length; i++) + res += arguments[i]; + return res; +} + +function wrapper() { + var res; + var i6 = { valueOf: () => 6 }, + i8 = 8.5, + s2 = { toString: () => "2" }, + s4 = {}, + s6 = "6", + s7 = undefined, + s8 = null; + for (var b = true; b; b = !inIon()) { + res = sum(1,2,3,4,5,i6,7,i8,9,10); + assertEq(res, 55.5); + + res = cat(true,s2,3,s4,5,s6,s7,s8); + assertEq(res, "true23[object Object]56undefinednull"); + + ion = inIon(); + if (typeof ion !== "boolean") break; + res = cat_m1(ion,1,s2,3,s4,5,s6,s7,s8); + if (ion) assertEq(res, "undefinedtrue123[object Object]56undefinednull"); + else assertEq(res, "false123[object Object]56undefinednull"); + + ion = inIon(); + if (typeof ion !== "boolean") break; + res = cat_p1(ion,1,s2,3,s4,5,s6,s7,s8); + if (ion) assertEq(res, "true123[object Object]56undefinednullundefined"); + else assertEq(res, "false123[object Object]56undefinednull"); + } +} + +wrapper(); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index a44acbcaae24..ffc3118fed0c 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -42,6 +42,7 @@ #include "jit/MoveEmitter.h" #include "jit/RangeAnalysis.h" #include "jit/SharedICHelpers.h" +#include "jit/StackSlotAllocator.h" #include "vm/AsyncFunction.h" #include "vm/AsyncIteration.h" #include "vm/MatchPairs.h" @@ -11023,6 +11024,238 @@ CodeGenerator::visitLoadTypedArrayElementHole(LLoadTypedArrayElementHole* lir) masm.bind(&done); } +template +class OutOfLineSwitch : public OutOfLineCodeBase +{ + using LabelsVector = Vector; + using CodeLabelsVector = Vector; + LabelsVector labels_; + CodeLabelsVector codeLabels_; + CodeLabel start_; + bool isOutOfLine_; + + void accept(CodeGenerator* codegen) { + codegen->visitOutOfLineSwitch(this); + } + + public: + explicit OutOfLineSwitch(TempAllocator& alloc) + : labels_(alloc), + codeLabels_(alloc), + isOutOfLine_(false) + {} + + CodeLabel* start() { + return &start_; + } + + CodeLabelsVector& codeLabels() { + return codeLabels_; + } + LabelsVector& labels() { + return labels_; + } + + void jumpToCodeEntries(MacroAssembler& masm, Register index, Register temp) { + Register base; + if (tableType == SwitchTableType::Inline) { +#if defined(JS_CODEGEN_ARM) + base = ::js::jit::pc; +#else + MOZ_CRASH("NYI: SwitchTableType::Inline"); +#endif + } else { +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) + MOZ_CRASH("NYI: SwitchTableType::OutOfLine"); +#else + masm.mov(start_.patchAt(), temp); + base = temp; +#endif + } + BaseIndex jumpTarget(base, index, ScalePointer); + masm.branchToComputedAddress(jumpTarget); + } + + // Register an entry in the switch table. + void addTableEntry(MacroAssembler& masm) { + if ((!isOutOfLine_ && tableType == SwitchTableType::Inline) || + (isOutOfLine_ && tableType == SwitchTableType::OutOfLine)) + { + CodeLabel cl; + masm.writeCodePointer(cl.patchAt()); + masm.propagateOOM(codeLabels_.append(mozilla::Move(cl))); + } + } + // Register the code, to which the table will jump to. + void addCodeEntry(MacroAssembler& masm) { + Label entry; + masm.bind(&entry); + masm.propagateOOM(labels_.append(mozilla::Move(entry))); + } + + void setOutOfLine() { + isOutOfLine_ = true; + } +}; + +template +void +CodeGenerator::visitOutOfLineSwitch(OutOfLineSwitch* jumpTable) +{ + jumpTable->setOutOfLine(); + if (tableType == SwitchTableType::OutOfLine) { +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) + MOZ_CRASH("NYI: SwitchTableType::OutOfLine"); +#else + masm.haltingAlign(sizeof(void*)); + masm.use(jumpTable->start()->target()); + masm.addCodeLabel(*jumpTable->start()); +#endif + } + + // Add table entries if the table is inlined. + auto& labels = jumpTable->labels(); + for (size_t i = 0, e = labels.length(); i < e; i++) + jumpTable->addTableEntry(masm); + + auto& codeLabels = jumpTable->codeLabels(); + for (size_t i = 0, e = codeLabels.length(); i < e; i++) { + // The entries of the jump table need to be absolute addresses and thus + // must be patched after codegen is finished. + auto& cl = codeLabels[i]; + cl.target()->bind(labels[i].offset()); + masm.addCodeLabel(cl); + } +} + +template void CodeGenerator::visitOutOfLineSwitch(OutOfLineSwitch* jumpTable); +template void CodeGenerator::visitOutOfLineSwitch(OutOfLineSwitch* jumpTable); + +void +CodeGenerator::visitLoadElementFromStateV(LLoadElementFromStateV* lir) +{ + Register index = ToRegister(lir->index()); + Register temp0 = ToRegister(lir->temp0()); +#ifdef JS_NUNBOX32 + Register temp1 = ToRegister(lir->temp1()); +#endif + FloatRegister tempD = ToFloatRegister(lir->tempD()); + ValueOperand out = ToOutValue(lir); + + // For each element, load it and box it. + MArgumentState* array = lir->array()->toArgumentState(); + Label join; + + // Jump to the code which is loading the element, based on its index. +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) + auto* jumpTable = new (alloc()) OutOfLineSwitch(alloc()); +#else + auto* jumpTable = new (alloc()) OutOfLineSwitch(alloc()); +#endif + + { +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) + // Inhibit pools within the following sequence because we are indexing into + // a pc relative table. The region will have one instruction for ma_ldr, one + // for breakpoint, and each table case takes one word. + AutoForbidPools afp(&masm, 1 + 1 + array->numElements()); +#endif + jumpTable->jumpToCodeEntries(masm, index, temp0); + + // Add table entries if the table is inlined. + for (size_t i = 0, e = array->numElements(); i < e; i++) + jumpTable->addTableEntry(masm); + } + + // Add inlined code for loading arguments from where they are allocated. + for (size_t i = 0, e = array->numElements(); i < e; i++) { + MDefinition* elem = array->getElement(i); + ConstantOrRegister input; + + jumpTable->addCodeEntry(masm); + Register typeReg = Register::Invalid(); + const LAllocation* a = lir->getOperand(1 + BOX_PIECES * i); + if (a->isBogus()) { + if (elem->type() == MIRType::Null) { + input = NullValue(); + } else if (elem->type() == MIRType::Undefined) { + input = UndefinedValue(); + } else if (elem->isConstant() && elem->isEmittedAtUses()) { + input = elem->toConstant()->toJSValue(); + } else { + MOZ_CRASH("Unsupported element constant allocation."); + } + } else if (a->isMemory()) { + if (elem->type() == MIRType::Double) { + masm.loadDouble(ToAddress(a), tempD); + input = TypedOrValueRegister(elem->type(), AnyRegister(tempD)); + } else if (elem->type() == MIRType::Value) { + typeReg = temp0; + masm.loadPtr(ToAddress(a), temp0); +#ifdef JS_PUNBOX64 + input = TypedOrValueRegister(ValueOperand(temp0)); +#endif + } else { + typeReg = temp0; + size_t width = StackSlotAllocator::width(LDefinition::TypeFrom(elem->type())); + if (width == 4) + masm.load32(ToAddress(a), temp0); + else if (width == 8) + masm.loadPtr(ToAddress(a), temp0); + else + MOZ_CRASH("Unsupported load size"); + input = TypedOrValueRegister(elem->type(), AnyRegister(typeReg)); + } + } else if (a->isGeneralReg()) { + typeReg = ToRegister(a); + input = TypedOrValueRegister(elem->type(), AnyRegister(typeReg)); +#ifdef JS_PUNBOX64 + if (elem->type() != MIRType::Value) + input = TypedOrValueRegister(elem->type(), AnyRegister(typeReg)); + else + input = TypedOrValueRegister(ValueOperand(typeReg)); +#else + if (elem->type() != MIRType::Value) + input = TypedOrValueRegister(elem->type(), AnyRegister(typeReg)); +#endif + } else if (a->isFloatReg()) { + input = TypedOrValueRegister(elem->type(), AnyRegister(ToFloatRegister(a))); + } else if (a->isConstantValue()) { + input = a->toConstant()->toJSValue(); + } else { + MOZ_CRASH("Unsupported element allocation."); + } + +#ifdef JS_NUNBOX32 + if (elem->type() == MIRType::Value) { + static_assert(TYPE_INDEX == 0, "Unexpected type allocation index"); + static_assert(PAYLOAD_INDEX == 1, "Unexpected payload allocation index"); + const LAllocation* a1 = lir->getOperand(1 + BOX_PIECES * i + 1); + MOZ_ASSERT(!a1->isBogus()); + MOZ_ASSERT(typeReg != Register::Invalid()); + if (a1->isMemory()) { + masm.loadPtr(ToAddress(a1), temp1); + input = TypedOrValueRegister(ValueOperand(typeReg, temp1)); + } else if (a1->isGeneralReg()) { + input = TypedOrValueRegister(ValueOperand(typeReg, ToRegister(a1))); + } else { + MOZ_CRASH("Unsupported Value allocation."); + } + } else { + MOZ_ASSERT(lir->getOperand(1 + BOX_PIECES * i + 1)->isBogus()); + } +#endif + masm.moveValue(input, out); + + // For the last entry, fall-through. + if (i + 1 < e) + masm.jump(&join); + } + + addOutOfLineCode(jumpTable, lir->mir()); + masm.bind(&join); +} + template static inline void StoreToTypedArray(MacroAssembler& masm, Scalar::Type writeType, const LAllocation* value, diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 75add0667a3f..e08314fcbc4a 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -33,6 +33,12 @@ namespace js { namespace jit { +enum class SwitchTableType { + Inline, + OutOfLine +}; + +template class OutOfLineSwitch; class OutOfLineTestObject; class OutOfLineNewArray; class OutOfLineNewObject; @@ -307,6 +313,9 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitLoadUnboxedPointerV(LLoadUnboxedPointerV* lir); void visitLoadUnboxedPointerT(LLoadUnboxedPointerT* lir); void visitUnboxObjectOrNull(LUnboxObjectOrNull* lir); + template + void visitOutOfLineSwitch(OutOfLineSwitch* ool); + void visitLoadElementFromStateV(LLoadElementFromStateV* lir); void visitStoreElementT(LStoreElementT* lir); void visitStoreElementV(LStoreElementV* lir); template void emitStoreElementHoleT(T* lir); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 6532f1e8621d..007c0d2e4976 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -7735,13 +7735,20 @@ IonBuilder::jsop_getelem() if (emitted) return Ok(); - trackOptimizationAttempt(TrackedStrategy::GetElem_ArgumentsInlined); - MOZ_TRY(getElemTryArgumentsInlined(&emitted, obj, index)); + trackOptimizationAttempt(TrackedStrategy::GetElem_ArgumentsInlinedConstant); + MOZ_TRY(getElemTryArgumentsInlinedConstant(&emitted, obj, index)); if (emitted) return Ok(); - if (script()->argumentsHasVarBinding()) + trackOptimizationAttempt(TrackedStrategy::GetElem_ArgumentsInlinedSwitch); + MOZ_TRY(getElemTryArgumentsInlinedIndex(&emitted, obj, index)); + if (emitted) + return Ok(); + + if (script()->argumentsHasVarBinding()) { + trackOptimizationOutcome(TrackedOutcome::NoTypeInfo); return abort(AbortReason::Disable, "Type is not definitely lazy arguments."); + } } obj = maybeUnboxForPropertyAccess(obj); @@ -8393,7 +8400,7 @@ IonBuilder::getElemTryArguments(bool* emitted, MDefinition* obj, MDefinition* in } AbortReasonOr -IonBuilder::getElemTryArgumentsInlined(bool* emitted, MDefinition* obj, MDefinition* index) +IonBuilder::getElemTryArgumentsInlinedConstant(bool* emitted, MDefinition* obj, MDefinition* index) { MOZ_ASSERT(*emitted == false); @@ -8403,31 +8410,83 @@ IonBuilder::getElemTryArgumentsInlined(bool* emitted, MDefinition* obj, MDefinit if (obj->type() != MIRType::MagicOptimizedArguments) return Ok(); + MConstant* indexConst = index->maybeConstantValue(); + if (!indexConst || indexConst->type() != MIRType::Int32) + return Ok(); + // Emit inlined arguments. obj->setImplicitlyUsedUnchecked(); MOZ_ASSERT(!info().argsObjAliasesFormals()); // When the id is constant, we can just return the corresponding inlined argument - MConstant* indexConst = index->maybeConstantValue(); - if (indexConst && indexConst->type() == MIRType::Int32) { - MOZ_ASSERT(inliningDepth_ > 0); + MOZ_ASSERT(inliningDepth_ > 0); - int32_t id = indexConst->toInt32(); - index->setImplicitlyUsedUnchecked(); + int32_t id = indexConst->toInt32(); + index->setImplicitlyUsedUnchecked(); - if (id < (int32_t)inlineCallInfo_->argc() && id >= 0) - current->push(inlineCallInfo_->getArg(id)); - else - pushConstant(UndefinedValue()); + if (id < (int32_t)inlineCallInfo_->argc() && id >= 0) + current->push(inlineCallInfo_->getArg(id)); + else + pushConstant(UndefinedValue()); - trackOptimizationSuccess(); - *emitted = true; + trackOptimizationSuccess(); + *emitted = true; + return Ok(); +} + +AbortReasonOr +IonBuilder::getElemTryArgumentsInlinedIndex(bool* emitted, MDefinition* obj, MDefinition* index) +{ + MOZ_ASSERT(*emitted == false); + + if (inliningDepth_ == 0) return Ok(); + + if (obj->type() != MIRType::MagicOptimizedArguments) + return Ok(); + + if (!IsNumberType(index->type())) + return Ok(); + + // Currently, we do not support any arguments vector larger than 10, as this + // is being translated into code at the call site, and it would be better to + // store the arguments contiguously on the stack. + if (inlineCallInfo_->argc() > 10) { + trackOptimizationOutcome(TrackedOutcome::CantInlineBound); + return abort(AbortReason::Disable, "NYI get argument element with too many arguments"); } - // inlined not constant not supported, yet. - return abort(AbortReason::Disable, "NYI inlined not constant get argument element"); + // Emit inlined arguments. + obj->setImplicitlyUsedUnchecked(); + + MOZ_ASSERT(!info().argsObjAliasesFormals()); + + // Ensure index is an integer. + MInstruction* idInt32 = MToInt32::New(alloc(), index); + current->add(idInt32); + index = idInt32; + + // Bailout if we read more than the number of actual arguments. This bailout + // cannot re-enter because reading out of bounds arguments will disable the + // lazy arguments optimization for this script, when this code would be + // executed in Baseline. (see GetElemOptimizedArguments) + index = addBoundsCheck(index, constantInt(inlineCallInfo_->argc())); + + // Get an instruction to represent the state of the argument vector. + MInstruction* args = MArgumentState::New(alloc().fallible(), inlineCallInfo_->argv()); + if (!args) + return abort(AbortReason::Alloc); + current->add(args); + + // Select a value to pick from a vector. + MInstruction* load = MLoadElementFromState::New(alloc(), args, index); + current->add(load); + current->push(load); + + trackOptimizationSuccess(); + *emitted = true; + return Ok(); } AbortReasonOr diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 8e4acedb6807..a3209691645f 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -427,8 +427,10 @@ class IonBuilder AbortReasonOr getElemTryTypedObject(bool* emitted, MDefinition* obj, MDefinition* index); AbortReasonOr getElemTryString(bool* emitted, MDefinition* obj, MDefinition* index); AbortReasonOr getElemTryArguments(bool* emitted, MDefinition* obj, MDefinition* index); - AbortReasonOr getElemTryArgumentsInlined(bool* emitted, MDefinition* obj, - MDefinition* index); + AbortReasonOr getElemTryArgumentsInlinedConstant(bool* emitted, MDefinition* obj, + MDefinition* index); + AbortReasonOr getElemTryArgumentsInlinedIndex(bool* emitted, MDefinition* obj, + MDefinition* index); AbortReasonOr getElemAddCache(MDefinition* obj, MDefinition* index); AbortReasonOr getElemTryScalarElemOfTypedObject(bool* emitted, MDefinition* obj, diff --git a/js/src/jit/LIR.h b/js/src/jit/LIR.h index 8d27bd308988..8e64fd3b1fb8 100644 --- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -1177,6 +1177,14 @@ class LVariadicInstruction : public details::LInstructionFixedDefsTempsHelper diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 30f0887fa0ef..e22d74fdc346 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -3235,6 +3235,73 @@ LIRGenerator::visitLoadUnboxedString(MLoadUnboxedString* ins) define(lir, ins); } +void +LIRGenerator::visitLoadElementFromState(MLoadElementFromState* ins) +{ + MOZ_ASSERT(ins->index()->type() == MIRType::Int32); + + LDefinition temp1 = LDefinition::BogusTemp(); +#ifdef JS_NUNBOX32 + temp1 = temp(); +#endif + LLoadElementFromStateV* lir = new(alloc()) LLoadElementFromStateV(temp(), temp1, tempDouble()); + MOZ_ASSERT(ins->array()->isArgumentState(), + "LIRGenerator::visitLoadElementFromState: Unsupported state object"); + MArgumentState* array = ins->array()->toArgumentState(); + + // 1 -- for the index as a register + // BOX_PIECES * array->numElements() -- for using as operand all the + // elements of the inlined array. + size_t numOperands = 1 + BOX_PIECES * array->numElements(); + if (!lir->init(alloc(), numOperands)) { + abort(AbortReason::Alloc, "OOM: LIRGenerator::visitLoadElementFromState"); + return; + } + lir->setOperand(0, useRegister(ins->index())); // index + for (size_t i = 0, e = array->numElements(); i < e; i++) { + MDefinition* elem = array->getElement(i); + if (elem->isConstant() && elem->isEmittedAtUses()) { + lir->setOperand(1 + BOX_PIECES * i, LAllocation()); +#ifdef JS_NUNBOX32 + lir->setOperand(1 + BOX_PIECES * i + 1, LAllocation()); +#endif + continue; + } + + switch (array->getElement(i)->type()) { + case MIRType::Value: + lir->setBoxOperand(1 + BOX_PIECES * i, useBox(elem, LUse::ANY)); + break; + // Anything which can be boxed: + case MIRType::Boolean: + case MIRType::Int32: + case MIRType::Double: + case MIRType::Object: + case MIRType::String: + case MIRType::Symbol: + lir->setOperand(1 + BOX_PIECES * i, use(elem)); +#ifdef JS_NUNBOX32 + // Bogus second operand. + lir->setOperand(1 + BOX_PIECES * i + 1, LAllocation()); +#endif + break; + case MIRType::Null: + case MIRType::Undefined: + // Bogus operand, as these can be inlined. + lir->setOperand(1 + BOX_PIECES * i, LAllocation()); +#ifdef JS_NUNBOX32 + lir->setOperand(1 + BOX_PIECES * i + 1, LAllocation()); +#endif + break; + default: + MOZ_CRASH("LIRGenerator::visitLoadElementFromState: Unsupported element type."); + return; + } + } + + defineBox(lir, ins); +} + void LIRGenerator::visitStoreElement(MStoreElement* ins) { @@ -5130,6 +5197,12 @@ LIRGenerator::visitArrayState(MArrayState* objState) MOZ_CRASH("Unexpected ArrayState node during Lowering."); } +void +LIRGenerator::visitArgumentState(MArgumentState* objState) +{ + // ArgumentState nodes are always inlined at their uses. +} + void LIRGenerator::visitUnknownValue(MUnknownValue* ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index d26af49cb6c0..47f9807a3262 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -232,6 +232,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitLoadElementHole(MLoadElementHole* ins); void visitLoadUnboxedObjectOrNull(MLoadUnboxedObjectOrNull* ins); void visitLoadUnboxedString(MLoadUnboxedString* ins); + void visitLoadElementFromState(MLoadElementFromState* ins); void visitStoreElement(MStoreElement* ins); void visitStoreElementHole(MStoreElementHole* ins); void visitFallibleStoreElement(MFallibleStoreElement* ins); @@ -325,6 +326,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitBeta(MBeta* ins); void visitObjectState(MObjectState* ins); void visitArrayState(MArrayState* ins); + void visitArgumentState(MArgumentState* ins); void visitUnknownValue(MUnknownValue* ins); void visitLexicalCheck(MLexicalCheck* ins); void visitThrowRuntimeLexicalError(MThrowRuntimeLexicalError* ins); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index a4238da6d143..06f24c65ff93 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -5098,6 +5098,28 @@ MArrayState::Copy(TempAllocator& alloc, MArrayState* state) return res; } +MArgumentState* +MArgumentState::New(TempAllocator::Fallible view, const MDefinitionVector& args) +{ + MArgumentState* res = new(view.alloc) MArgumentState(); + if (!res || !res->init(view.alloc, args.length())) + return nullptr; + for (size_t i = 0, e = args.length(); i < e; i++) + res->initOperand(i, args[i]); + return res; +} + +MArgumentState* +MArgumentState::Copy(TempAllocator& alloc, MArgumentState* state) +{ + MArgumentState* res = new(alloc) MArgumentState(); + if (!res || !res->init(alloc, state->numElements())) + return nullptr; + for (size_t i = 0, e = res->numOperands(); i < e; i++) + res->initOperand(i, state->getOperand(i)); + return res; +} + MNewArray::MNewArray(CompilerConstraintList* constraints, uint32_t length, MConstant* templateConst, gc::InitialHeap initialHeap, jsbytecode* pc, bool vmCall) : MUnaryInstruction(templateConst), diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index bdfd84e0885c..3e6fe011a000 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -3957,6 +3957,41 @@ class MArrayState } }; +// Hold the arguments of an inlined frame. At the moment this class is not +// recovered on bailout as it does not have an implementation and it should +// be inlined at all its uses. +class MArgumentState + : public MVariadicInstruction, + public NoFloatPolicyAfter<0>::Data +{ + private: + explicit MArgumentState() { + setResultType(MIRType::Object); + setMovable(); + } + + public: + INSTRUCTION_HEADER(ArgumentState) + + static MArgumentState* New(TempAllocator::Fallible view, const MDefinitionVector& args); + static MArgumentState* Copy(TempAllocator& alloc, MArgumentState* state); + + size_t numElements() const { + return numOperands(); + } + + MDefinition* getElement(uint32_t index) const { + return getOperand(index); + } + + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + AliasSet getAliasSet() const override { + return AliasSet::None(); + } +}; + // Setting __proto__ in an object literal. class MMutateProto : public MAryInstruction<2>, @@ -9656,6 +9691,30 @@ class MStoreElementCommon } }; +// This instruction is used to load an element of a non-escaped inlined array. +class MLoadElementFromState + : public MBinaryInstruction, + public SingleObjectPolicy::Data +{ + MLoadElementFromState(MDefinition* array, MDefinition* index) + : MBinaryInstruction(array, index) + { + MOZ_ASSERT(array->isArgumentState()); + MOZ_ASSERT(index->type() == MIRType::Int32); + setResultType(MIRType::Value); + setMovable(); + } + + public: + INSTRUCTION_HEADER(LoadElementFromState) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, array), (1, index)); + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } +}; + // Store a value to a dense array slots vector. class MStoreElement : public MAryInstruction<3>, @@ -12691,7 +12750,7 @@ class MArgumentsLength : public MNullaryInstruction AliasSet getAliasSet() const override { // Arguments |length| cannot be mutated by Ion Code. return AliasSet::None(); - } + } void computeRange(TempAllocator& alloc) override; diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index c6858c7434f9..2f5902815d9e 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -144,6 +144,7 @@ namespace jit { _(NewStringObject) \ _(ObjectState) \ _(ArrayState) \ + _(ArgumentState) \ _(InitElem) \ _(InitElemGetterSetter) \ _(MutateProto) \ @@ -217,6 +218,7 @@ namespace jit { _(LoadUnboxedScalar) \ _(LoadUnboxedObjectOrNull) \ _(LoadUnboxedString) \ + _(LoadElementFromState) \ _(StoreElement) \ _(StoreElementHole) \ _(FallibleStoreElement) \ diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 409a6cc732ae..8d4aca26291d 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -587,6 +587,7 @@ NoFloatPolicyAfter::adjustInputs(TempAllocator& alloc, MInstruction* de return true; } +template bool NoFloatPolicyAfter<0>::adjustInputs(TempAllocator& alloc, MInstruction* def); template bool NoFloatPolicyAfter<1>::adjustInputs(TempAllocator& alloc, MInstruction* def); template bool NoFloatPolicyAfter<2>::adjustInputs(TempAllocator& alloc, MInstruction* def); @@ -1268,6 +1269,7 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) _(MixPolicy, StringPolicy<1> >) \ _(MixPolicy, BoxPolicy<1> >) \ _(NoFloatPolicy<0>) \ + _(NoFloatPolicyAfter<0>) \ _(NoFloatPolicyAfter<1>) \ _(NoFloatPolicyAfter<2>) \ _(ObjectPolicy<0>) \ diff --git a/js/src/jit/arm64/Assembler-arm64.h b/js/src/jit/arm64/Assembler-arm64.h index 01ef93a3842b..4843f6204604 100644 --- a/js/src/jit/arm64/Assembler-arm64.h +++ b/js/src/jit/arm64/Assembler-arm64.h @@ -387,6 +387,12 @@ class Assembler : public vixl::Assembler LabelBase* label = absoluteLabel; label->bind(off.getOffset()); } + void writeCodePointer(CodeOffset* label) { + uintptr_t x = LabelBase::INVALID_OFFSET; + BufferOffset off = EmitData(&x, sizeof(uintptr_t)); + label->bind(off.getOffset()); + } + void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, const Disassembler::HeapAccess& heapAccess) diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 7b56b6f85502..dbd8d091bf77 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -5744,6 +5744,47 @@ class LUnboxObjectOrNull : public LInstructionHelper<1, 1, 0> } }; +// Load an element from a non-allocated entity represented by its state object +// such as ArgumentState. The elements of the state object are set as operands +// of this variadic LIR instruction and inlined in the code generated for this +// instruction. +// +// Each element is represented with BOX_PIECES allocations, even if 1 (typed +// register) or 0 (constants) is enough. In such case, the unused allocations +// would be bogus. +class LLoadElementFromStateV : public LVariadicInstruction +{ + public: + LIR_HEADER(LoadElementFromStateV) + + LLoadElementFromStateV(const LDefinition& temp0, const LDefinition& temp1, + const LDefinition& tempD) + { + setTemp(0, temp0); + setTemp(1, temp1); + setTemp(2, tempD); + } + + const MLoadElementFromState* mir() const { + return mir_->toLoadElementFromState(); + } + const LAllocation* index() { + return getOperand(0); + } + const LDefinition* temp0() { + return getTemp(0); + } + const LDefinition* temp1() { + return getTemp(1); + } + const LDefinition* tempD() { + return getTemp(2); + } + MDefinition* array() { + return mir()->array(); + } +}; + // Store a boxed value to a dense array's element vector. class LStoreElementV : public LInstructionHelper<0, 2 + BOX_PIECES, 0> { diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index 97959e788cf3..7dd722160fcf 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -283,6 +283,7 @@ _(LoadUnboxedScalar) \ _(LoadUnboxedPointerV) \ _(LoadUnboxedPointerT) \ + _(LoadElementFromStateV) \ _(UnboxObjectOrNull) \ _(StoreElementV) \ _(StoreElementT) \ diff --git a/js/src/jit/shared/Lowering-shared-inl.h b/js/src/jit/shared/Lowering-shared-inl.h index a89c95887816..e2c8e996bc4a 100644 --- a/js/src/jit/shared/Lowering-shared-inl.h +++ b/js/src/jit/shared/Lowering-shared-inl.h @@ -159,9 +159,9 @@ LIRGeneratorShared::defineInt64ReuseInput(LInstructionHelper void -LIRGeneratorShared::defineBox(LInstructionHelper* lir, MDefinition* mir, - LDefinition::Policy policy) +template void +LIRGeneratorShared::defineBox(details::LInstructionFixedDefsTempsHelper* lir, + MDefinition* mir, LDefinition::Policy policy) { // Call instructions should use defineReturn. MOZ_ASSERT(!lir->isCall()); diff --git a/js/src/jit/shared/Lowering-shared.h b/js/src/jit/shared/Lowering-shared.h index b3ecd6e9be0f..a4f14c68d5bc 100644 --- a/js/src/jit/shared/Lowering-shared.h +++ b/js/src/jit/shared/Lowering-shared.h @@ -157,9 +157,9 @@ class LIRGeneratorShared : public MDefinitionVisitor inline void defineFixed(LInstructionHelper<1, Ops, Temps>* lir, MDefinition* mir, const LAllocation& output); - template - inline void defineBox(LInstructionHelper* lir, MDefinition* mir, - LDefinition::Policy policy = LDefinition::REGISTER); + template + inline void defineBox(details::LInstructionFixedDefsTempsHelper* lir, + MDefinition* mir, LDefinition::Policy policy = LDefinition::REGISTER); template inline void defineInt64(LInstructionHelper* lir, MDefinition* mir,