| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "jit/BaselineIC.h" |
| |
| #include "mozilla/DebugOnly.h" |
| #include "mozilla/SizePrintfMacros.h" |
| #include "mozilla/TemplateLib.h" |
| |
| #include "jslibmath.h" |
| #include "jstypes.h" |
| |
| #include "builtin/Eval.h" |
| #include "builtin/SIMD.h" |
| #include "jit/BaselineDebugModeOSR.h" |
| #include "jit/BaselineJIT.h" |
| #include "jit/JitSpewer.h" |
| #include "jit/Linker.h" |
| #include "jit/Lowering.h" |
| #ifdef JS_ION_PERF |
| # include "jit/PerfSpewer.h" |
| #endif |
| #include "jit/SharedICHelpers.h" |
| #include "jit/VMFunctions.h" |
| #include "js/Conversions.h" |
| #include "js/TraceableVector.h" |
| #include "vm/Opcodes.h" |
| #include "vm/TypedArrayCommon.h" |
| |
| #include "jsboolinlines.h" |
| #include "jsscriptinlines.h" |
| |
| #include "jit/JitFrames-inl.h" |
| #include "jit/MacroAssembler-inl.h" |
| #include "jit/shared/Lowering-shared-inl.h" |
| #include "vm/Interpreter-inl.h" |
| #include "vm/ScopeObject-inl.h" |
| #include "vm/StringObject-inl.h" |
| #include "vm/UnboxedObject-inl.h" |
| |
| using mozilla::DebugOnly; |
| |
| namespace js { |
| namespace jit { |
| |
| // |
| // WarmUpCounter_Fallback |
| // |
| |
| static bool |
| EnsureCanEnterIon(JSContext* cx, ICWarmUpCounter_Fallback* stub, BaselineFrame* frame, |
| HandleScript script, jsbytecode* pc, void** jitcodePtr) |
| { |
| MOZ_ASSERT(jitcodePtr); |
| MOZ_ASSERT(!*jitcodePtr); |
| |
| bool isLoopEntry = (JSOp(*pc) == JSOP_LOOPENTRY); |
| |
| MethodStatus stat; |
| if (isLoopEntry) { |
| MOZ_ASSERT(LoopEntryCanIonOsr(pc)); |
| JitSpew(JitSpew_BaselineOSR, " Compile at loop entry!"); |
| stat = CanEnterAtBranch(cx, script, frame, pc); |
| } else if (frame->isFunctionFrame()) { |
| JitSpew(JitSpew_BaselineOSR, " Compile function from top for later entry!"); |
| stat = CompileFunctionForBaseline(cx, script, frame); |
| } else { |
| return true; |
| } |
| |
| if (stat == Method_Error) { |
| JitSpew(JitSpew_BaselineOSR, " Compile with Ion errored!"); |
| return false; |
| } |
| |
| if (stat == Method_CantCompile) |
| JitSpew(JitSpew_BaselineOSR, " Can't compile with Ion!"); |
| else if (stat == Method_Skipped) |
| JitSpew(JitSpew_BaselineOSR, " Skipped compile with Ion!"); |
| else if (stat == Method_Compiled) |
| JitSpew(JitSpew_BaselineOSR, " Compiled with Ion!"); |
| else |
| MOZ_CRASH("Invalid MethodStatus!"); |
| |
| // Failed to compile. Reset warm-up counter and return. |
| if (stat != Method_Compiled) { |
| // TODO: If stat == Method_CantCompile, insert stub that just skips the |
| // warm-up counter entirely, instead of resetting it. |
| bool bailoutExpected = script->hasIonScript() && script->ionScript()->bailoutExpected(); |
| if (stat == Method_CantCompile || bailoutExpected) { |
| JitSpew(JitSpew_BaselineOSR, " Reset WarmUpCounter cantCompile=%s bailoutExpected=%s!", |
| stat == Method_CantCompile ? "yes" : "no", |
| bailoutExpected ? "yes" : "no"); |
| script->resetWarmUpCounter(); |
| } |
| return true; |
| } |
| |
| if (isLoopEntry) { |
| IonScript* ion = script->ionScript(); |
| MOZ_ASSERT(cx->runtime()->spsProfiler.enabled() == ion->hasProfilingInstrumentation()); |
| MOZ_ASSERT(ion->osrPc() == pc); |
| |
| JitSpew(JitSpew_BaselineOSR, " OSR possible!"); |
| *jitcodePtr = ion->method()->raw() + ion->osrEntryOffset(); |
| } |
| |
| return true; |
| } |
| |
| // |
| // The following data is kept in a temporary heap-allocated buffer, stored in |
| // JitRuntime (high memory addresses at top, low at bottom): |
| // |
| // +----->+=================================+ -- <---- High Address |
| // | | | | |
| // | | ...BaselineFrame... | |-- Copy of BaselineFrame + stack values |
| // | | | | |
| // | +---------------------------------+ | |
| // | | | | |
| // | | ...Locals/Stack... | | |
| // | | | | |
| // | +=================================+ -- |
| // | | Padding(Maybe Empty) | |
| // | +=================================+ -- |
| // +------|-- baselineFrame | |-- IonOsrTempData |
| // | jitcode | | |
| // +=================================+ -- <---- Low Address |
| // |
| // A pointer to the IonOsrTempData is returned. |
| |
| struct IonOsrTempData |
| { |
| void* jitcode; |
| uint8_t* baselineFrame; |
| }; |
| |
| static IonOsrTempData* |
| PrepareOsrTempData(JSContext* cx, ICWarmUpCounter_Fallback* stub, BaselineFrame* frame, |
| HandleScript script, jsbytecode* pc, void* jitcode) |
| { |
| size_t numLocalsAndStackVals = frame->numValueSlots(); |
| |
| // Calculate the amount of space to allocate: |
| // BaselineFrame space: |
| // (sizeof(Value) * (numLocals + numStackVals)) |
| // + sizeof(BaselineFrame) |
| // |
| // IonOsrTempData space: |
| // sizeof(IonOsrTempData) |
| |
| size_t frameSpace = sizeof(BaselineFrame) + sizeof(Value) * numLocalsAndStackVals; |
| size_t ionOsrTempDataSpace = sizeof(IonOsrTempData); |
| |
| size_t totalSpace = AlignBytes(frameSpace, sizeof(Value)) + |
| AlignBytes(ionOsrTempDataSpace, sizeof(Value)); |
| |
| IonOsrTempData* info = (IonOsrTempData*)cx->runtime()->getJitRuntime(cx)->allocateOsrTempData(totalSpace); |
| if (!info) |
| return nullptr; |
| |
| memset(info, 0, totalSpace); |
| |
| info->jitcode = jitcode; |
| |
| // Copy the BaselineFrame + local/stack Values to the buffer. Arguments and |
| // |this| are not copied but left on the stack: the Baseline and Ion frame |
| // share the same frame prefix and Ion won't clobber these values. Note |
| // that info->baselineFrame will point to the *end* of the frame data, like |
| // the frame pointer register in baseline frames. |
| uint8_t* frameStart = (uint8_t*)info + AlignBytes(ionOsrTempDataSpace, sizeof(Value)); |
| info->baselineFrame = frameStart + frameSpace; |
| |
| memcpy(frameStart, (uint8_t*)frame - numLocalsAndStackVals * sizeof(Value), frameSpace); |
| |
| JitSpew(JitSpew_BaselineOSR, "Allocated IonOsrTempData at %p", (void*) info); |
| JitSpew(JitSpew_BaselineOSR, "Jitcode is %p", info->jitcode); |
| |
| // All done. |
| return info; |
| } |
| |
| static bool |
| DoWarmUpCounterFallback(JSContext* cx, BaselineFrame* frame, ICWarmUpCounter_Fallback* stub, |
| IonOsrTempData** infoPtr) |
| { |
| MOZ_ASSERT(infoPtr); |
| *infoPtr = nullptr; |
| |
| // A TI OOM will disable TI and Ion. |
| if (!jit::IsIonEnabled(cx)) |
| return true; |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| bool isLoopEntry = JSOp(*pc) == JSOP_LOOPENTRY; |
| |
| MOZ_ASSERT(!isLoopEntry || LoopEntryCanIonOsr(pc)); |
| |
| FallbackICSpew(cx, stub, "WarmUpCounter(%d)", isLoopEntry ? int(script->pcToOffset(pc)) : int(-1)); |
| |
| if (!script->canIonCompile()) { |
| // TODO: ASSERT that ion-compilation-disabled checker stub doesn't exist. |
| // TODO: Clear all optimized stubs. |
| // TODO: Add a ion-compilation-disabled checker IC stub |
| script->resetWarmUpCounter(); |
| return true; |
| } |
| |
| MOZ_ASSERT(!script->isIonCompilingOffThread()); |
| |
| // If Ion script exists, but PC is not at a loop entry, then Ion will be entered for |
| // this script at an appropriate LOOPENTRY or the next time this function is called. |
| if (script->hasIonScript() && !isLoopEntry) { |
| JitSpew(JitSpew_BaselineOSR, "IonScript exists, but not at loop entry!"); |
| // TODO: ASSERT that a ion-script-already-exists checker stub doesn't exist. |
| // TODO: Clear all optimized stubs. |
| // TODO: Add a ion-script-already-exists checker stub. |
| return true; |
| } |
| |
| // Ensure that Ion-compiled code is available. |
| JitSpew(JitSpew_BaselineOSR, |
| "WarmUpCounter for %s:%" PRIuSIZE " reached %d at pc %p, trying to switch to Ion!", |
| script->filename(), script->lineno(), (int) script->getWarmUpCount(), (void*) pc); |
| void* jitcode = nullptr; |
| if (!EnsureCanEnterIon(cx, stub, frame, script, pc, &jitcode)) |
| return false; |
| |
| // Jitcode should only be set here if not at loop entry. |
| MOZ_ASSERT_IF(!isLoopEntry, !jitcode); |
| if (!jitcode) |
| return true; |
| |
| // Prepare the temporary heap copy of the fake InterpreterFrame and actual args list. |
| JitSpew(JitSpew_BaselineOSR, "Got jitcode. Preparing for OSR into ion."); |
| IonOsrTempData* info = PrepareOsrTempData(cx, stub, frame, script, pc, jitcode); |
| if (!info) |
| return false; |
| *infoPtr = info; |
| |
| return true; |
| } |
| |
| typedef bool (*DoWarmUpCounterFallbackFn)(JSContext*, BaselineFrame*, |
| ICWarmUpCounter_Fallback*, IonOsrTempData** infoPtr); |
| static const VMFunction DoWarmUpCounterFallbackInfo = |
| FunctionInfo<DoWarmUpCounterFallbackFn>(DoWarmUpCounterFallback); |
| |
| bool |
| ICWarmUpCounter_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, R1.scratchReg()); |
| |
| Label noCompiledCode; |
| // Call DoWarmUpCounterFallback to compile/check-for Ion-compiled function |
| { |
| // Push IonOsrTempData pointer storage |
| masm.subFromStackPtr(Imm32(sizeof(void*))); |
| masm.push(masm.getStackPointer()); |
| |
| // Push stub pointer. |
| masm.push(ICStubReg); |
| |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| if (!callVM(DoWarmUpCounterFallbackInfo, masm)) |
| return false; |
| |
| // Pop IonOsrTempData pointer. |
| masm.pop(R0.scratchReg()); |
| |
| leaveStubFrame(masm); |
| |
| // If no JitCode was found, then skip just exit the IC. |
| masm.branchPtr(Assembler::Equal, R0.scratchReg(), ImmPtr(nullptr), &noCompiledCode); |
| } |
| |
| // Get a scratch register. |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| Register osrDataReg = R0.scratchReg(); |
| regs.take(osrDataReg); |
| regs.takeUnchecked(OsrFrameReg); |
| |
| Register scratchReg = regs.takeAny(); |
| |
| // At this point, stack looks like: |
| // +-> [...Calling-Frame...] |
| // | [...Actual-Args/ThisV/ArgCount/Callee...] |
| // | [Descriptor] |
| // | [Return-Addr] |
| // +---[Saved-FramePtr] <-- BaselineFrameReg points here. |
| // [...Baseline-Frame...] |
| |
| // Restore the stack pointer to point to the saved frame pointer. |
| masm.moveToStackPtr(BaselineFrameReg); |
| |
| // Discard saved frame pointer, so that the return address is on top of |
| // the stack. |
| masm.pop(scratchReg); |
| |
| #ifdef DEBUG |
| // If profiler instrumentation is on, ensure that lastProfilingFrame is |
| // the frame currently being OSR-ed |
| { |
| Label checkOk; |
| AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); |
| masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &checkOk); |
| masm.loadPtr(AbsoluteAddress((void*)&cx->runtime()->jitActivation), scratchReg); |
| masm.loadPtr(Address(scratchReg, JitActivation::offsetOfLastProfilingFrame()), scratchReg); |
| |
| // It may be the case that we entered the baseline frame with |
| // profiling turned off on, then in a call within a loop (i.e. a |
| // callee frame), turn on profiling, then return to this frame, |
| // and then OSR with profiling turned on. In this case, allow for |
| // lastProfilingFrame to be null. |
| masm.branchPtr(Assembler::Equal, scratchReg, ImmWord(0), &checkOk); |
| |
| masm.branchStackPtr(Assembler::Equal, scratchReg, &checkOk); |
| masm.assumeUnreachable("Baseline OSR lastProfilingFrame mismatch."); |
| masm.bind(&checkOk); |
| } |
| #endif |
| |
| // Jump into Ion. |
| masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, jitcode)), scratchReg); |
| masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, baselineFrame)), OsrFrameReg); |
| masm.jump(scratchReg); |
| |
| // No jitcode available, do nothing. |
| masm.bind(&noCompiledCode); |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| // |
| // TypeUpdate_Fallback |
| // |
| static bool |
| DoTypeUpdateFallback(JSContext* cx, BaselineFrame* frame, ICUpdatedStub* stub, HandleValue objval, |
| HandleValue value) |
| { |
| FallbackICSpew(cx, stub->getChainFallback(), "TypeUpdate(%s)", |
| ICStub::KindString(stub->kind())); |
| |
| RootedScript script(cx, frame->script()); |
| RootedObject obj(cx, &objval.toObject()); |
| RootedId id(cx); |
| |
| switch(stub->kind()) { |
| case ICStub::SetElem_DenseOrUnboxedArray: |
| case ICStub::SetElem_DenseOrUnboxedArrayAdd: { |
| id = JSID_VOID; |
| AddTypePropertyId(cx, obj, id, value); |
| break; |
| } |
| case ICStub::SetProp_Native: |
| case ICStub::SetProp_NativeAdd: |
| case ICStub::SetProp_Unboxed: { |
| MOZ_ASSERT(obj->isNative() || obj->is<UnboxedPlainObject>()); |
| jsbytecode* pc = stub->getChainFallback()->icEntry()->pc(script); |
| if (*pc == JSOP_SETALIASEDVAR || *pc == JSOP_INITALIASEDLEXICAL) |
| id = NameToId(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc)); |
| else |
| id = NameToId(script->getName(pc)); |
| AddTypePropertyId(cx, obj, id, value); |
| break; |
| } |
| case ICStub::SetProp_TypedObject: { |
| MOZ_ASSERT(obj->is<TypedObject>()); |
| jsbytecode* pc = stub->getChainFallback()->icEntry()->pc(script); |
| id = NameToId(script->getName(pc)); |
| if (stub->toSetProp_TypedObject()->isObjectReference()) { |
| // Ignore all values being written except plain objects. Null |
| // is included implicitly in type information for this property, |
| // and non-object non-null values will cause the stub to fail to |
| // match shortly and we will end up doing the assignment in the VM. |
| if (value.isObject()) |
| AddTypePropertyId(cx, obj, id, value); |
| } else { |
| // Ignore undefined values, which are included implicitly in type |
| // information for this property. |
| if (!value.isUndefined()) |
| AddTypePropertyId(cx, obj, id, value); |
| } |
| break; |
| } |
| default: |
| MOZ_CRASH("Invalid stub"); |
| } |
| |
| return stub->addUpdateStubForValue(cx, script, obj, id, value); |
| } |
| |
| typedef bool (*DoTypeUpdateFallbackFn)(JSContext*, BaselineFrame*, ICUpdatedStub*, HandleValue, |
| HandleValue); |
| const VMFunction DoTypeUpdateFallbackInfo = |
| FunctionInfo<DoTypeUpdateFallbackFn>(DoTypeUpdateFallback, NonTailCall); |
| |
| bool |
| ICTypeUpdate_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| // Just store false into R1.scratchReg() and return. |
| masm.move32(Imm32(0), R1.scratchReg()); |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| bool |
| ICTypeUpdate_PrimitiveSet::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label success; |
| if ((flags_ & TypeToFlag(JSVAL_TYPE_INT32)) && !(flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE))) |
| masm.branchTestInt32(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE)) |
| masm.branchTestNumber(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_UNDEFINED)) |
| masm.branchTestUndefined(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_BOOLEAN)) |
| masm.branchTestBoolean(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_STRING)) |
| masm.branchTestString(Assembler::Equal, R0, &success); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_SYMBOL)) |
| masm.branchTestSymbol(Assembler::Equal, R0, &success); |
| |
| // Currently, we will never generate primitive stub checks for object. However, |
| // when we do get to the point where we want to collapse our monitor chains of |
| // objects and singletons down (when they get too long) to a generic "any object" |
| // in coordination with the typeset doing the same thing, this will need to |
| // be re-enabled. |
| /* |
| if (flags_ & TypeToFlag(JSVAL_TYPE_OBJECT)) |
| masm.branchTestObject(Assembler::Equal, R0, &success); |
| */ |
| MOZ_ASSERT(!(flags_ & TypeToFlag(JSVAL_TYPE_OBJECT))); |
| |
| if (flags_ & TypeToFlag(JSVAL_TYPE_NULL)) |
| masm.branchTestNull(Assembler::Equal, R0, &success); |
| |
| EmitStubGuardFailure(masm); |
| |
| // Type matches, load true into R1.scratchReg() and return. |
| masm.bind(&success); |
| masm.mov(ImmWord(1), R1.scratchReg()); |
| EmitReturnFromIC(masm); |
| |
| return true; |
| } |
| |
| bool |
| ICTypeUpdate_SingleObject::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Guard on the object's identity. |
| Register obj = masm.extractObject(R0, R1.scratchReg()); |
| Address expectedObject(ICStubReg, ICTypeUpdate_SingleObject::offsetOfObject()); |
| masm.branchPtr(Assembler::NotEqual, expectedObject, obj, &failure); |
| |
| // Identity matches, load true into R1.scratchReg() and return. |
| masm.mov(ImmWord(1), R1.scratchReg()); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICTypeUpdate_ObjectGroup::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Guard on the object's ObjectGroup. |
| Register obj = masm.extractObject(R0, R1.scratchReg()); |
| masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), R1.scratchReg()); |
| |
| Address expectedGroup(ICStubReg, ICTypeUpdate_ObjectGroup::offsetOfGroup()); |
| masm.branchPtr(Assembler::NotEqual, expectedGroup, R1.scratchReg(), &failure); |
| |
| // Group matches, load true into R1.scratchReg() and return. |
| masm.mov(ImmWord(1), R1.scratchReg()); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| typedef bool (*DoCallNativeGetterFn)(JSContext*, HandleFunction, HandleObject, MutableHandleValue); |
| static const VMFunction DoCallNativeGetterInfo = |
| FunctionInfo<DoCallNativeGetterFn>(DoCallNativeGetter); |
| |
| // |
| // NewArray_Fallback |
| // |
| |
| static bool |
| DoNewArray(JSContext* cx, BaselineFrame* frame, ICNewArray_Fallback* stub, uint32_t length, |
| MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "NewArray"); |
| |
| RootedObject obj(cx); |
| if (stub->templateObject()) { |
| RootedObject templateObject(cx, stub->templateObject()); |
| obj = NewArrayOperationWithTemplate(cx, templateObject); |
| if (!obj) |
| return false; |
| } else { |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| obj = NewArrayOperation(cx, script, pc, length); |
| if (!obj) |
| return false; |
| |
| if (obj && !obj->isSingleton() && !obj->group()->maybePreliminaryObjects()) { |
| JSObject* templateObject = NewArrayOperation(cx, script, pc, length, TenuredObject); |
| if (!templateObject) |
| return false; |
| stub->setTemplateObject(templateObject); |
| } |
| } |
| |
| res.setObject(*obj); |
| return true; |
| } |
| |
| typedef bool(*DoNewArrayFn)(JSContext*, BaselineFrame*, ICNewArray_Fallback*, uint32_t, |
| MutableHandleValue); |
| static const VMFunction DoNewArrayInfo = FunctionInfo<DoNewArrayFn>(DoNewArray, TailCall); |
| |
| bool |
| ICNewArray_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(R0.scratchReg()); // length |
| masm.push(ICStubReg); // stub. |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoNewArrayInfo, masm); |
| } |
| |
| // |
| // NewObject_Fallback |
| // |
| |
| // Unlike typical baseline IC stubs, the code for NewObject_WithTemplate is |
| // specialized for the template object being allocated. |
| static JitCode* |
| GenerateNewObjectWithTemplateCode(JSContext* cx, JSObject* templateObject) |
| { |
| JitContext jctx(cx, nullptr); |
| MacroAssembler masm; |
| #ifdef JS_CODEGEN_ARM |
| masm.setSecondScratchReg(BaselineSecondScratchReg); |
| #endif |
| |
| Label failure; |
| Register objReg = R0.scratchReg(); |
| Register tempReg = R1.scratchReg(); |
| masm.movePtr(ImmGCPtr(templateObject->group()), tempReg); |
| masm.branchTest32(Assembler::NonZero, Address(tempReg, ObjectGroup::offsetOfFlags()), |
| Imm32(OBJECT_FLAG_PRE_TENURE), &failure); |
| masm.branchPtr(Assembler::NotEqual, AbsoluteAddress(cx->compartment()->addressOfMetadataCallback()), |
| ImmWord(0), &failure); |
| masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, &failure); |
| masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0); |
| |
| EmitReturnFromIC(masm); |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| |
| Linker linker(masm); |
| AutoFlushICache afc("GenerateNewObjectWithTemplateCode"); |
| return linker.newCode<CanGC>(cx, BASELINE_CODE); |
| } |
| |
| static bool |
| DoNewObject(JSContext* cx, BaselineFrame* frame, ICNewObject_Fallback* stub, MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "NewObject"); |
| |
| RootedObject obj(cx); |
| |
| RootedObject templateObject(cx, stub->templateObject()); |
| if (templateObject) { |
| MOZ_ASSERT(!templateObject->group()->maybePreliminaryObjects()); |
| obj = NewObjectOperationWithTemplate(cx, templateObject); |
| } else { |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| obj = NewObjectOperation(cx, script, pc); |
| |
| if (obj && !obj->isSingleton() && !obj->group()->maybePreliminaryObjects()) { |
| JSObject* templateObject = NewObjectOperation(cx, script, pc, TenuredObject); |
| if (!templateObject) |
| return false; |
| |
| if (templateObject->is<UnboxedPlainObject>() || |
| !templateObject->as<PlainObject>().hasDynamicSlots()) |
| { |
| JitCode* code = GenerateNewObjectWithTemplateCode(cx, templateObject); |
| if (!code) |
| return false; |
| |
| ICStubSpace* space = |
| ICStubCompiler::StubSpaceForKind(ICStub::NewObject_WithTemplate, script); |
| ICStub* templateStub = ICStub::New<ICNewObject_WithTemplate>(cx, space, code); |
| if (!templateStub) |
| return false; |
| |
| stub->addNewStub(templateStub); |
| } |
| |
| stub->setTemplateObject(templateObject); |
| } |
| } |
| |
| if (!obj) |
| return false; |
| |
| res.setObject(*obj); |
| return true; |
| } |
| |
| typedef bool(*DoNewObjectFn)(JSContext*, BaselineFrame*, ICNewObject_Fallback*, MutableHandleValue); |
| static const VMFunction DoNewObjectInfo = FunctionInfo<DoNewObjectFn>(DoNewObject, TailCall); |
| |
| bool |
| ICNewObject_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(ICStubReg); // stub. |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoNewObjectInfo, masm); |
| } |
| |
| // |
| // ToBool_Fallback |
| // |
| |
| static bool |
| DoToBoolFallback(JSContext* cx, BaselineFrame* frame, ICToBool_Fallback* stub, HandleValue arg, |
| MutableHandleValue ret) |
| { |
| FallbackICSpew(cx, stub, "ToBool"); |
| |
| bool cond = ToBoolean(arg); |
| ret.setBoolean(cond); |
| |
| // Check to see if a new stub should be generated. |
| if (stub->numOptimizedStubs() >= ICToBool_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| MOZ_ASSERT(!arg.isBoolean()); |
| |
| JSScript* script = frame->script(); |
| |
| // Try to generate new stubs. |
| if (arg.isInt32()) { |
| JitSpew(JitSpew_BaselineIC, " Generating ToBool(Int32) stub."); |
| ICToBool_Int32::Compiler compiler(cx); |
| ICStub* int32Stub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!int32Stub) |
| return false; |
| |
| stub->addNewStub(int32Stub); |
| return true; |
| } |
| |
| if (arg.isDouble() && cx->runtime()->jitSupportsFloatingPoint) { |
| JitSpew(JitSpew_BaselineIC, " Generating ToBool(Double) stub."); |
| ICToBool_Double::Compiler compiler(cx); |
| ICStub* doubleStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!doubleStub) |
| return false; |
| |
| stub->addNewStub(doubleStub); |
| return true; |
| } |
| |
| if (arg.isString()) { |
| JitSpew(JitSpew_BaselineIC, " Generating ToBool(String) stub"); |
| ICToBool_String::Compiler compiler(cx); |
| ICStub* stringStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stringStub) |
| return false; |
| |
| stub->addNewStub(stringStub); |
| return true; |
| } |
| |
| if (arg.isNull() || arg.isUndefined()) { |
| ICToBool_NullUndefined::Compiler compiler(cx); |
| ICStub* nilStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!nilStub) |
| return false; |
| |
| stub->addNewStub(nilStub); |
| return true; |
| } |
| |
| if (arg.isObject()) { |
| JitSpew(JitSpew_BaselineIC, " Generating ToBool(Object) stub."); |
| ICToBool_Object::Compiler compiler(cx); |
| ICStub* objStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!objStub) |
| return false; |
| |
| stub->addNewStub(objStub); |
| return true; |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*pf)(JSContext*, BaselineFrame*, ICToBool_Fallback*, HandleValue, |
| MutableHandleValue); |
| static const VMFunction fun = FunctionInfo<pf>(DoToBoolFallback, TailCall); |
| |
| bool |
| ICToBool_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Push arguments. |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(fun, masm); |
| } |
| |
| // |
| // ToBool_Int32 |
| // |
| |
| bool |
| ICToBool_Int32::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| masm.branchTestInt32(Assembler::NotEqual, R0, &failure); |
| |
| Label ifFalse; |
| masm.branchTestInt32Truthy(false, R0, &ifFalse); |
| |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_String |
| // |
| |
| bool |
| ICToBool_String::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| |
| Label ifFalse; |
| masm.branchTestStringTruthy(false, R0, &ifFalse); |
| |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_NullUndefined |
| // |
| |
| bool |
| ICToBool_NullUndefined::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure, ifFalse; |
| masm.branchTestNull(Assembler::Equal, R0, &ifFalse); |
| masm.branchTestUndefined(Assembler::NotEqual, R0, &failure); |
| |
| masm.bind(&ifFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_Double |
| // |
| |
| bool |
| ICToBool_Double::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure, ifTrue; |
| masm.branchTestDouble(Assembler::NotEqual, R0, &failure); |
| masm.unboxDouble(R0, FloatReg0); |
| masm.branchTestDoubleTruthy(true, FloatReg0, &ifTrue); |
| |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifTrue); |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToBool_Object |
| // |
| |
| bool |
| ICToBool_Object::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure, ifFalse, slowPath; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| Register scratch = R1.scratchReg(); |
| masm.branchTestObjectTruthy(false, objReg, scratch, &slowPath, &ifFalse); |
| |
| // If object doesn't emulate undefined, it evaulates to true. |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&ifFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&slowPath); |
| masm.setupUnalignedABICall(scratch); |
| masm.passABIArg(objReg); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::EmulatesUndefined)); |
| masm.convertBoolToInt32(ReturnReg, ReturnReg); |
| masm.xor32(Imm32(1), ReturnReg); |
| masm.tagValue(JSVAL_TYPE_BOOLEAN, ReturnReg, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // ToNumber_Fallback |
| // |
| |
| static bool |
| DoToNumberFallback(JSContext* cx, ICToNumber_Fallback* stub, HandleValue arg, MutableHandleValue ret) |
| { |
| FallbackICSpew(cx, stub, "ToNumber"); |
| ret.set(arg); |
| return ToNumber(cx, ret); |
| } |
| |
| typedef bool (*DoToNumberFallbackFn)(JSContext*, ICToNumber_Fallback*, HandleValue, MutableHandleValue); |
| static const VMFunction DoToNumberFallbackInfo = |
| FunctionInfo<DoToNumberFallbackFn>(DoToNumberFallback, TailCall, PopValues(1)); |
| |
| bool |
| ICToNumber_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| |
| // Push arguments. |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| |
| return tailCallVM(DoToNumberFallbackInfo, masm); |
| } |
| |
| // |
| // GetElem_Fallback |
| // |
| |
| static Shape* |
| LastPropertyForSetProp(JSObject* obj) |
| { |
| if (obj->isNative()) |
| return obj->as<NativeObject>().lastProperty(); |
| |
| if (obj->is<UnboxedPlainObject>()) { |
| UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando(); |
| return expando ? expando->lastProperty() : nullptr; |
| } |
| |
| return nullptr; |
| } |
| |
| static bool |
| IsCacheableSetPropWriteSlot(JSObject* obj, Shape* oldShape, Shape* propertyShape) |
| { |
| // Object shape must not have changed during the property set. |
| if (LastPropertyForSetProp(obj) != oldShape) |
| return false; |
| |
| if (!propertyShape->hasSlot() || |
| !propertyShape->hasDefaultSetter() || |
| !propertyShape->writable()) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| IsCacheableSetPropAddSlot(JSContext* cx, JSObject* obj, Shape* oldShape, |
| jsid id, Shape* propertyShape, size_t* protoChainDepth) |
| { |
| // The property must be the last added property of the object. |
| if (LastPropertyForSetProp(obj) != propertyShape) |
| return false; |
| |
| // Object must be extensible, oldShape must be immediate parent of current shape. |
| if (!obj->nonProxyIsExtensible() || propertyShape->previous() != oldShape) |
| return false; |
| |
| // Basic shape checks. |
| if (propertyShape->inDictionary() || |
| !propertyShape->hasSlot() || |
| !propertyShape->hasDefaultSetter() || |
| !propertyShape->writable()) |
| { |
| return false; |
| } |
| |
| // Watch out for resolve or addProperty hooks. |
| if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj) || |
| obj->getClass()->addProperty) |
| { |
| return false; |
| } |
| |
| size_t chainDepth = 0; |
| // Walk up the object prototype chain and ensure that all prototypes are |
| // native, and that all prototypes have no setter defined on the property. |
| for (JSObject* proto = obj->getProto(); proto; proto = proto->getProto()) { |
| chainDepth++; |
| // if prototype is non-native, don't optimize |
| if (!proto->isNative()) |
| return false; |
| |
| // if prototype defines this property in a non-plain way, don't optimize |
| Shape* protoShape = proto->as<NativeObject>().lookup(cx, id); |
| if (protoShape && !protoShape->hasDefaultSetter()) |
| return false; |
| |
| // Otherwise, if there's no such property, watch out for a resolve hook |
| // that would need to be invoked and thus prevent inlining of property |
| // addition. |
| if (ClassMayResolveId(cx->names(), proto->getClass(), id, proto)) |
| return false; |
| } |
| |
| // Only add a IC entry if the dynamic slots didn't change when the shapes |
| // changed. Need to ensure that a shape change for a subsequent object |
| // won't involve reallocating the slot array. |
| if (NativeObject::dynamicSlotsCount(propertyShape) != NativeObject::dynamicSlotsCount(oldShape)) |
| return false; |
| |
| *protoChainDepth = chainDepth; |
| return true; |
| } |
| |
| static bool |
| IsCacheableSetPropCall(JSContext* cx, JSObject* obj, JSObject* holder, Shape* shape, |
| bool* isScripted, bool* isTemporarilyUnoptimizable) |
| { |
| MOZ_ASSERT(isScripted); |
| |
| if (!shape || !IsCacheableProtoChain(obj, holder)) |
| return false; |
| |
| if (shape->hasSlot() || shape->hasDefaultSetter()) |
| return false; |
| |
| if (!shape->hasSetterValue()) |
| return false; |
| |
| if (!shape->setterValue().isObject() || !shape->setterObject()->is<JSFunction>()) |
| return false; |
| |
| JSFunction* func = &shape->setterObject()->as<JSFunction>(); |
| |
| if (func->isNative()) { |
| *isScripted = false; |
| return true; |
| } |
| |
| if (!func->hasJITCode()) { |
| *isTemporarilyUnoptimizable = true; |
| return false; |
| } |
| |
| *isScripted = true; |
| return true; |
| } |
| |
| template <class T> |
| static bool |
| GetElemNativeStubExists(ICGetElem_Fallback* stub, HandleObject obj, HandleObject holder, |
| Handle<T> key, bool needsAtomize) |
| { |
| bool indirect = (obj.get() != holder.get()); |
| MOZ_ASSERT_IF(indirect, holder->isNative()); |
| |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (iter->kind() != ICStub::GetElem_NativeSlotName && |
| iter->kind() != ICStub::GetElem_NativeSlotSymbol && |
| iter->kind() != ICStub::GetElem_NativePrototypeSlotName && |
| iter->kind() != ICStub::GetElem_NativePrototypeSlotSymbol && |
| iter->kind() != ICStub::GetElem_NativePrototypeCallNativeName && |
| iter->kind() != ICStub::GetElem_NativePrototypeCallNativeSymbol && |
| iter->kind() != ICStub::GetElem_NativePrototypeCallScriptedName && |
| iter->kind() != ICStub::GetElem_NativePrototypeCallScriptedSymbol) |
| { |
| continue; |
| } |
| |
| if (indirect && (iter->kind() != ICStub::GetElem_NativePrototypeSlotName && |
| iter->kind() != ICStub::GetElem_NativePrototypeSlotSymbol && |
| iter->kind() != ICStub::GetElem_NativePrototypeCallNativeName && |
| iter->kind() != ICStub::GetElem_NativePrototypeCallNativeSymbol && |
| iter->kind() != ICStub::GetElem_NativePrototypeCallScriptedName && |
| iter->kind() != ICStub::GetElem_NativePrototypeCallScriptedSymbol)) |
| { |
| continue; |
| } |
| |
| if(mozilla::IsSame<T, JS::Symbol*>::value != |
| static_cast<ICGetElemNativeStub*>(*iter)->isSymbol()) |
| { |
| continue; |
| } |
| |
| ICGetElemNativeStubImpl<T>* getElemNativeStub = |
| reinterpret_cast<ICGetElemNativeStubImpl<T>*>(*iter); |
| if (key != getElemNativeStub->key()) |
| continue; |
| |
| if (ReceiverGuard(obj) != getElemNativeStub->receiverGuard()) |
| continue; |
| |
| // If the new stub needs atomization, and the old stub doesn't atomize, then |
| // an appropriate stub doesn't exist. |
| if (needsAtomize && !getElemNativeStub->needsAtomize()) |
| continue; |
| |
| // For prototype gets, check the holder and holder shape. |
| if (indirect) { |
| if (iter->isGetElem_NativePrototypeSlotName() || |
| iter->isGetElem_NativePrototypeSlotSymbol()) { |
| ICGetElem_NativePrototypeSlot<T>* protoStub = |
| reinterpret_cast<ICGetElem_NativePrototypeSlot<T>*>(*iter); |
| |
| if (holder != protoStub->holder()) |
| continue; |
| |
| if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape()) |
| continue; |
| } else { |
| MOZ_ASSERT(iter->isGetElem_NativePrototypeCallNativeName() || |
| iter->isGetElem_NativePrototypeCallNativeSymbol() || |
| iter->isGetElem_NativePrototypeCallScriptedName() || |
| iter->isGetElem_NativePrototypeCallScriptedSymbol()); |
| |
| ICGetElemNativePrototypeCallStub<T>* protoStub = |
| reinterpret_cast<ICGetElemNativePrototypeCallStub<T>*>(*iter); |
| |
| if (holder != protoStub->holder()) |
| continue; |
| |
| if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape()) |
| continue; |
| } |
| } |
| |
| return true; |
| } |
| return false; |
| } |
| |
| template <class T> |
| static void |
| RemoveExistingGetElemNativeStubs(JSContext* cx, ICGetElem_Fallback* stub, HandleObject obj, |
| HandleObject holder, Handle<T> key, bool needsAtomize) |
| { |
| bool indirect = (obj.get() != holder.get()); |
| MOZ_ASSERT_IF(indirect, holder->isNative()); |
| |
| for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) { |
| switch (iter->kind()) { |
| case ICStub::GetElem_NativeSlotName: |
| case ICStub::GetElem_NativeSlotSymbol: |
| if (indirect) |
| continue; |
| case ICStub::GetElem_NativePrototypeSlotName: |
| case ICStub::GetElem_NativePrototypeSlotSymbol: |
| case ICStub::GetElem_NativePrototypeCallNativeName: |
| case ICStub::GetElem_NativePrototypeCallNativeSymbol: |
| case ICStub::GetElem_NativePrototypeCallScriptedName: |
| case ICStub::GetElem_NativePrototypeCallScriptedSymbol: |
| break; |
| default: |
| continue; |
| } |
| |
| if(mozilla::IsSame<T, JS::Symbol*>::value != |
| static_cast<ICGetElemNativeStub*>(*iter)->isSymbol()) |
| { |
| continue; |
| } |
| |
| ICGetElemNativeStubImpl<T>* getElemNativeStub = |
| reinterpret_cast<ICGetElemNativeStubImpl<T>*>(*iter); |
| if (key != getElemNativeStub->key()) |
| continue; |
| |
| if (ReceiverGuard(obj) != getElemNativeStub->receiverGuard()) |
| continue; |
| |
| // For prototype gets, check the holder and holder shape. |
| if (indirect) { |
| if (iter->isGetElem_NativePrototypeSlotName() || |
| iter->isGetElem_NativePrototypeSlotSymbol()) { |
| ICGetElem_NativePrototypeSlot<T>* protoStub = |
| reinterpret_cast<ICGetElem_NativePrototypeSlot<T>*>(*iter); |
| |
| if (holder != protoStub->holder()) |
| continue; |
| |
| // If the holder matches, but the holder's lastProperty doesn't match, then |
| // this stub is invalid anyway. Unlink it. |
| if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape()) { |
| iter.unlink(cx); |
| continue; |
| } |
| } else { |
| MOZ_ASSERT(iter->isGetElem_NativePrototypeCallNativeName() || |
| iter->isGetElem_NativePrototypeCallNativeSymbol() || |
| iter->isGetElem_NativePrototypeCallScriptedName() || |
| iter->isGetElem_NativePrototypeCallScriptedSymbol()); |
| ICGetElemNativePrototypeCallStub<T>* protoStub = |
| reinterpret_cast<ICGetElemNativePrototypeCallStub<T>*>(*iter); |
| |
| if (holder != protoStub->holder()) |
| continue; |
| |
| // If the holder matches, but the holder's lastProperty doesn't match, then |
| // this stub is invalid anyway. Unlink it. |
| if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape()) { |
| iter.unlink(cx); |
| continue; |
| } |
| } |
| } |
| |
| // If the new stub needs atomization, and the old stub doesn't atomize, then |
| // remove the old stub. |
| if (needsAtomize && !getElemNativeStub->needsAtomize()) { |
| iter.unlink(cx); |
| continue; |
| } |
| |
| // Should never get here, because this means a matching stub exists, and if |
| // a matching stub exists, this procedure should never have been called. |
| MOZ_CRASH("Procedure should never have been called."); |
| } |
| } |
| |
| static bool |
| TypedArrayGetElemStubExists(ICGetElem_Fallback* stub, HandleObject obj) |
| { |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (!iter->isGetElem_TypedArray()) |
| continue; |
| if (obj->maybeShape() == iter->toGetElem_TypedArray()->shape()) |
| return true; |
| } |
| return false; |
| } |
| |
| static bool |
| ArgumentsGetElemStubExists(ICGetElem_Fallback* stub, ICGetElem_Arguments::Which which) |
| { |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (!iter->isGetElem_Arguments()) |
| continue; |
| if (iter->toGetElem_Arguments()->which() == which) |
| return true; |
| } |
| return false; |
| } |
| |
| template <class T> |
| static T |
| getKey(jsid id) |
| { |
| MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol"); |
| return false; |
| } |
| |
| template <> |
| JS::Symbol* getKey<JS::Symbol*>(jsid id) |
| { |
| if (!JSID_IS_SYMBOL(id)) |
| return nullptr; |
| return JSID_TO_SYMBOL(id); |
| } |
| |
| template <> |
| PropertyName* getKey<PropertyName*>(jsid id) |
| { |
| uint32_t dummy; |
| if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy)) |
| return nullptr; |
| return JSID_TO_ATOM(id)->asPropertyName(); |
| } |
| |
| static bool |
| IsOptimizableElementPropertyName(JSContext* cx, HandleValue key, MutableHandleId idp) |
| { |
| if (!key.isString()) |
| return false; |
| |
| // Convert to interned property name. |
| if (!ValueToId<CanGC>(cx, key, idp)) |
| return false; |
| |
| uint32_t dummy; |
| if (!JSID_IS_ATOM(idp) || JSID_TO_ATOM(idp)->isIndex(&dummy)) |
| return false; |
| |
| return true; |
| } |
| |
| template <class T> |
| static bool |
| checkAtomize(HandleValue key) |
| { |
| MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol"); |
| return false; |
| } |
| |
| template <> |
| bool checkAtomize<JS::Symbol*>(HandleValue key) |
| { |
| return false; |
| } |
| |
| template <> |
| bool checkAtomize<PropertyName*>(HandleValue key) |
| { |
| return !key.toString()->isAtom(); |
| } |
| |
| template <class T> |
| static bool |
| TryAttachNativeOrUnboxedGetValueElemStub(JSContext* cx, HandleScript script, jsbytecode* pc, |
| ICGetElem_Fallback* stub, HandleObject obj, |
| HandleValue keyVal, bool* attached) |
| { |
| MOZ_ASSERT(keyVal.isString() || keyVal.isSymbol()); |
| |
| // Convert to id. |
| RootedId id(cx); |
| if (!ValueToId<CanGC>(cx, keyVal, &id)) |
| return false; |
| |
| Rooted<T> key(cx, getKey<T>(id)); |
| if (!key) |
| return true; |
| bool needsAtomize = checkAtomize<T>(keyVal); |
| |
| RootedShape shape(cx); |
| RootedObject holder(cx); |
| if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape)) |
| return false; |
| if (!holder || (holder != obj && !holder->isNative())) |
| return true; |
| |
| // If a suitable stub already exists, nothing else to do. |
| if (GetElemNativeStubExists<T>(stub, obj, holder, key, needsAtomize)) |
| return true; |
| |
| // Remove any existing stubs that may interfere with the new stub being added. |
| RemoveExistingGetElemNativeStubs<T>(cx, stub, obj, holder, key, needsAtomize); |
| |
| ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| |
| if (obj->is<UnboxedPlainObject>() && holder == obj) { |
| const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id); |
| |
| // Once unboxed objects support symbol-keys, we need to change the following accordingly |
| MOZ_ASSERT_IF(!keyVal.isString(), !property); |
| |
| if (property) { |
| if (!cx->runtime()->jitSupportsFloatingPoint) |
| return true; |
| |
| RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName()); |
| ICGetElemNativeCompiler<PropertyName*> compiler(cx, ICStub::GetElem_UnboxedPropertyName, |
| monitorStub, obj, holder, |
| name, |
| ICGetElemNativeStub::UnboxedProperty, |
| needsAtomize, property->offset + |
| UnboxedPlainObject::offsetOfData(), |
| property->type); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| Shape* shape = obj->as<UnboxedPlainObject>().maybeExpando()->lookup(cx, id); |
| if (!shape->hasDefaultGetter() || !shape->hasSlot()) |
| return true; |
| |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset); |
| |
| ICGetElemNativeStub::AccessType acctype = |
| isFixedSlot ? ICGetElemNativeStub::FixedSlot |
| : ICGetElemNativeStub::DynamicSlot; |
| ICGetElemNativeCompiler<T> compiler(cx, getGetElemStubKind<T>(ICStub::GetElem_NativeSlotName), |
| monitorStub, obj, holder, key, |
| acctype, needsAtomize, offset); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| if (!holder->isNative()) |
| return true; |
| |
| if (IsCacheableGetPropReadSlot(obj, holder, shape)) { |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset); |
| |
| ICStub::Kind kind = (obj == holder) ? ICStub::GetElem_NativeSlotName |
| : ICStub::GetElem_NativePrototypeSlotName; |
| kind = getGetElemStubKind<T>(kind); |
| |
| JitSpew(JitSpew_BaselineIC, " Generating GetElem(Native %s%s slot) stub " |
| "(obj=%p, holder=%p, holderShape=%p)", |
| (obj == holder) ? "direct" : "prototype", |
| needsAtomize ? " atomizing" : "", |
| obj.get(), holder.get(), holder->as<NativeObject>().lastProperty()); |
| |
| AccType acctype = isFixedSlot ? ICGetElemNativeStub::FixedSlot |
| : ICGetElemNativeStub::DynamicSlot; |
| ICGetElemNativeCompiler<T> compiler(cx, kind, monitorStub, obj, holder, key, |
| acctype, needsAtomize, offset); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| return true; |
| } |
| |
| template <class T> |
| static bool |
| TryAttachNativeGetAccessorElemStub(JSContext* cx, HandleScript script, jsbytecode* pc, |
| ICGetElem_Fallback* stub, HandleNativeObject obj, |
| HandleValue keyVal, bool* attached, |
| bool* isTemporarilyUnoptimizable) |
| { |
| MOZ_ASSERT(!*attached); |
| MOZ_ASSERT(keyVal.isString() || keyVal.isSymbol()); |
| |
| RootedId id(cx); |
| if (!ValueToId<CanGC>(cx, keyVal, &id)) |
| return false; |
| |
| Rooted<T> key(cx, getKey<T>(id)); |
| if (!key) |
| return true; |
| bool needsAtomize = checkAtomize<T>(keyVal); |
| |
| RootedShape shape(cx); |
| RootedObject baseHolder(cx); |
| if (!EffectlesslyLookupProperty(cx, obj, id, &baseHolder, &shape)) |
| return false; |
| if (!baseHolder || baseHolder->isNative()) |
| return true; |
| |
| HandleNativeObject holder = baseHolder.as<NativeObject>(); |
| |
| bool getterIsScripted = false; |
| if (IsCacheableGetPropCall(cx, obj, baseHolder, shape, &getterIsScripted, |
| isTemporarilyUnoptimizable, /*isDOMProxy=*/false)) |
| { |
| RootedFunction getter(cx, &shape->getterObject()->as<JSFunction>()); |
| |
| // For now, we do not handle own property getters |
| if (obj == holder) |
| return true; |
| |
| // If a suitable stub already exists, nothing else to do. |
| if (GetElemNativeStubExists<T>(stub, obj, holder, key, needsAtomize)) |
| return true; |
| |
| // Remove any existing stubs that may interfere with the new stub being added. |
| RemoveExistingGetElemNativeStubs<T>(cx, stub, obj, holder, key, needsAtomize); |
| |
| ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| ICStub::Kind kind = getterIsScripted ? ICStub::GetElem_NativePrototypeCallScriptedName |
| : ICStub::GetElem_NativePrototypeCallNativeName; |
| kind = getGetElemStubKind<T>(kind); |
| |
| if (getterIsScripted) { |
| JitSpew(JitSpew_BaselineIC, |
| " Generating GetElem(Native %s%s call scripted %s:%" PRIuSIZE ") stub " |
| "(obj=%p, shape=%p, holder=%p, holderShape=%p)", |
| (obj == holder) ? "direct" : "prototype", |
| needsAtomize ? " atomizing" : "", |
| getter->nonLazyScript()->filename(), getter->nonLazyScript()->lineno(), |
| obj.get(), obj->lastProperty(), holder.get(), holder->lastProperty()); |
| } else { |
| JitSpew(JitSpew_BaselineIC, |
| " Generating GetElem(Native %s%s call native) stub " |
| "(obj=%p, shape=%p, holder=%p, holderShape=%p)", |
| (obj == holder) ? "direct" : "prototype", |
| needsAtomize ? " atomizing" : "", |
| obj.get(), obj->lastProperty(), holder.get(), holder->lastProperty()); |
| } |
| |
| AccType acctype = getterIsScripted ? ICGetElemNativeStub::ScriptedGetter |
| : ICGetElemNativeStub::NativeGetter; |
| ICGetElemNativeCompiler<T> compiler(cx, kind, monitorStub, obj, holder, key, acctype, |
| needsAtomize, getter, script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| IsPrimitiveArrayTypedObject(JSObject* obj) |
| { |
| if (!obj->is<TypedObject>()) |
| return false; |
| TypeDescr& descr = obj->as<TypedObject>().typeDescr(); |
| return descr.is<ArrayTypeDescr>() && |
| descr.as<ArrayTypeDescr>().elementType().is<ScalarTypeDescr>(); |
| } |
| |
| static Scalar::Type |
| PrimitiveArrayTypedObjectType(JSObject* obj) |
| { |
| MOZ_ASSERT(IsPrimitiveArrayTypedObject(obj)); |
| TypeDescr& descr = obj->as<TypedObject>().typeDescr(); |
| return descr.as<ArrayTypeDescr>().elementType().as<ScalarTypeDescr>().type(); |
| } |
| |
| static Scalar::Type |
| TypedThingElementType(JSObject* obj) |
| { |
| return IsAnyTypedArray(obj) |
| ? AnyTypedArrayType(obj) |
| : PrimitiveArrayTypedObjectType(obj); |
| } |
| |
| static bool |
| TypedThingRequiresFloatingPoint(JSObject* obj) |
| { |
| Scalar::Type type = TypedThingElementType(obj); |
| return type == Scalar::Uint32 || |
| type == Scalar::Float32 || |
| type == Scalar::Float64; |
| } |
| |
| static bool |
| IsNativeDenseElementAccess(HandleObject obj, HandleValue key) |
| { |
| if (obj->isNative() && key.isInt32() && key.toInt32() >= 0 && !IsAnyTypedArray(obj.get())) |
| return true; |
| return false; |
| } |
| |
| static bool |
| IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key) |
| { |
| if (!obj->isNative() && !obj->is<UnboxedArrayObject>()) |
| return false; |
| if (key.isInt32() && key.toInt32() >= 0 && !IsAnyTypedArray(obj.get())) |
| return true; |
| return false; |
| } |
| |
| static bool |
| TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_Fallback* stub, |
| HandleValue lhs, HandleValue rhs, HandleValue res, bool* attached) |
| { |
| // Check for String[i] => Char accesses. |
| if (lhs.isString() && rhs.isInt32() && res.isString() && |
| !stub->hasStub(ICStub::GetElem_String)) |
| { |
| // NoSuchMethod handling doesn't apply to string targets. |
| |
| JitSpew(JitSpew_BaselineIC, " Generating GetElem(String[Int32]) stub"); |
| ICGetElem_String::Compiler compiler(cx); |
| ICStub* stringStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!stringStub) |
| return false; |
| |
| stub->addNewStub(stringStub); |
| *attached = true; |
| return true; |
| } |
| |
| if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS) && rhs.isInt32() && |
| !ArgumentsGetElemStubExists(stub, ICGetElem_Arguments::Magic)) |
| { |
| JitSpew(JitSpew_BaselineIC, " Generating GetElem(MagicArgs[Int32]) stub"); |
| ICGetElem_Arguments::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| ICGetElem_Arguments::Magic); |
| ICStub* argsStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!argsStub) |
| return false; |
| |
| stub->addNewStub(argsStub); |
| *attached = true; |
| return true; |
| } |
| |
| // Otherwise, GetElem is only optimized on objects. |
| if (!lhs.isObject()) |
| return true; |
| RootedObject obj(cx, &lhs.toObject()); |
| |
| // Check for ArgumentsObj[int] accesses |
| if (obj->is<ArgumentsObject>() && rhs.isInt32()) { |
| ICGetElem_Arguments::Which which = ICGetElem_Arguments::Mapped; |
| if (obj->is<UnmappedArgumentsObject>()) |
| which = ICGetElem_Arguments::Unmapped; |
| if (!ArgumentsGetElemStubExists(stub, which)) { |
| JitSpew(JitSpew_BaselineIC, " Generating GetElem(ArgsObj[Int32]) stub"); |
| ICGetElem_Arguments::Compiler compiler( |
| cx, stub->fallbackMonitorStub()->firstMonitorStub(), which); |
| ICStub* argsStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!argsStub) |
| return false; |
| |
| stub->addNewStub(argsStub); |
| *attached = true; |
| return true; |
| } |
| } |
| |
| // Check for NativeObject[int] dense accesses. |
| if (IsNativeDenseElementAccess(obj, rhs)) { |
| JitSpew(JitSpew_BaselineIC, " Generating GetElem(Native[Int32] dense) stub"); |
| ICGetElem_Dense::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| obj->as<NativeObject>().lastProperty()); |
| ICStub* denseStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!denseStub) |
| return false; |
| |
| stub->addNewStub(denseStub); |
| *attached = true; |
| return true; |
| } |
| |
| // Check for NativeObject[id] and UnboxedPlainObject[id] shape-optimizable accesses. |
| if (obj->isNative() || obj->is<UnboxedPlainObject>()) { |
| RootedScript rootedScript(cx, script); |
| if (rhs.isString()) { |
| if (!TryAttachNativeOrUnboxedGetValueElemStub<PropertyName*>(cx, rootedScript, pc, stub, |
| obj, rhs, attached)) |
| { |
| return false; |
| } |
| } else if (rhs.isSymbol()) { |
| if (!TryAttachNativeOrUnboxedGetValueElemStub<JS::Symbol*>(cx, rootedScript, pc, stub, |
| obj, rhs, attached)) |
| { |
| return false; |
| } |
| } |
| if (*attached) |
| return true; |
| script = rootedScript; |
| } |
| |
| // Check for UnboxedArray[int] accesses. |
| if (obj->is<UnboxedArrayObject>() && rhs.isInt32() && rhs.toInt32() >= 0) { |
| JitSpew(JitSpew_BaselineIC, " Generating GetElem(UnboxedArray[Int32]) stub"); |
| ICGetElem_UnboxedArray::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| obj->group()); |
| ICStub* unboxedStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!unboxedStub) |
| return false; |
| |
| stub->addNewStub(unboxedStub); |
| *attached = true; |
| return true; |
| } |
| |
| // Check for TypedArray[int] => Number and TypedObject[int] => Number accesses. |
| if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) && |
| rhs.isNumber() && |
| res.isNumber() && |
| !TypedArrayGetElemStubExists(stub, obj)) |
| { |
| if (!cx->runtime()->jitSupportsFloatingPoint && |
| (TypedThingRequiresFloatingPoint(obj) || rhs.isDouble())) |
| { |
| return true; |
| } |
| |
| // Don't attach typed object stubs if they might be neutered, as the |
| // stub will always bail out. |
| if (IsPrimitiveArrayTypedObject(obj) && cx->compartment()->neuteredTypedObjects) |
| return true; |
| |
| JitSpew(JitSpew_BaselineIC, " Generating GetElem(TypedArray[Int32]) stub"); |
| ICGetElem_TypedArray::Compiler compiler(cx, obj->maybeShape(), TypedThingElementType(obj)); |
| ICStub* typedArrayStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!typedArrayStub) |
| return false; |
| |
| stub->addNewStub(typedArrayStub); |
| *attached = true; |
| return true; |
| } |
| |
| // GetElem operations on non-native objects cannot be cached by either |
| // Baseline or Ion. Indicate this in the cache so that Ion does not |
| // generate a cache for this op. |
| if (!obj->isNative()) |
| stub->noteNonNativeAccess(); |
| |
| // GetElem operations which could access negative indexes generally can't |
| // be optimized without the potential for bailouts, as we can't statically |
| // determine that an object has no properties on such indexes. |
| if (rhs.isNumber() && rhs.toNumber() < 0) |
| stub->noteNegativeIndex(); |
| |
| return true; |
| } |
| |
| static bool |
| DoGetElemFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_, HandleValue lhs, |
| HandleValue rhs, MutableHandleValue res) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICGetElem_Fallback*> stub(frame, stub_); |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(frame->script()); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "GetElem(%s)", CodeName[op]); |
| |
| MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM); |
| |
| // Don't pass lhs directly, we need it when generating stubs. |
| RootedValue lhsCopy(cx, lhs); |
| |
| bool isOptimizedArgs = false; |
| if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS)) { |
| // Handle optimized arguments[i] access. |
| if (!GetElemOptimizedArguments(cx, frame, &lhsCopy, rhs, res, &isOptimizedArgs)) |
| return false; |
| if (isOptimizedArgs) |
| TypeScript::Monitor(cx, frame->script(), pc, res); |
| } |
| |
| bool attached = false; |
| if (stub->numOptimizedStubs() >= ICGetElem_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| stub->noteUnoptimizableAccess(); |
| attached = true; |
| } |
| |
| // Try to attach an optimized getter stub. |
| bool isTemporarilyUnoptimizable = false; |
| if (!attached && lhs.isObject() && lhs.toObject().isNative()){ |
| if (rhs.isString()) { |
| RootedScript rootedScript(cx, frame->script()); |
| RootedNativeObject obj(cx, &lhs.toObject().as<NativeObject>()); |
| if (!TryAttachNativeGetAccessorElemStub<PropertyName*>(cx, rootedScript, pc, stub, |
| obj, rhs, &attached, |
| &isTemporarilyUnoptimizable)) |
| { |
| return false; |
| } |
| script = rootedScript; |
| } else if (rhs.isSymbol()) { |
| RootedScript rootedScript(cx, frame->script()); |
| RootedNativeObject obj(cx, &lhs.toObject().as<NativeObject>()); |
| if (!TryAttachNativeGetAccessorElemStub<JS::Symbol*>(cx, rootedScript, pc, stub, |
| obj, rhs, &attached, |
| &isTemporarilyUnoptimizable)) |
| { |
| return false; |
| } |
| script = rootedScript; |
| } |
| } |
| |
| if (!isOptimizedArgs) { |
| if (!GetElementOperation(cx, op, &lhsCopy, rhs, res)) |
| return false; |
| TypeScript::Monitor(cx, frame->script(), pc, res); |
| } |
| |
| // Check if debug mode toggling made the stub invalid. |
| if (stub.invalid()) |
| return true; |
| |
| // Add a type monitor stub for the resulting value. |
| if (!stub->addMonitorStubForValue(cx, frame->script(), res, |
| ICStubCompiler::Engine::Baseline)) |
| { |
| return false; |
| } |
| |
| if (attached) |
| return true; |
| |
| // Try to attach an optimized stub. |
| if (!TryAttachGetElemStub(cx, frame->script(), pc, stub, lhs, rhs, res, &attached)) |
| return false; |
| |
| if (!attached && !isTemporarilyUnoptimizable) |
| stub->noteUnoptimizableAccess(); |
| |
| return true; |
| } |
| |
| typedef bool (*DoGetElemFallbackFn)(JSContext*, BaselineFrame*, ICGetElem_Fallback*, |
| HandleValue, HandleValue, MutableHandleValue); |
| static const VMFunction DoGetElemFallbackInfo = |
| FunctionInfo<DoGetElemFallbackFn>(DoGetElemFallback, TailCall, PopValues(2)); |
| |
| bool |
| ICGetElem_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| |
| // Restore the tail call register. |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoGetElemFallbackInfo, masm); |
| } |
| |
| // |
| // GetElem_NativeSlot |
| // |
| |
| static bool |
| DoAtomizeString(JSContext* cx, HandleString string, MutableHandleValue result) |
| { |
| JitSpew(JitSpew_BaselineIC, " AtomizeString called"); |
| |
| RootedValue key(cx, StringValue(string)); |
| |
| // Convert to interned property name. |
| RootedId id(cx); |
| if (!ValueToId<CanGC>(cx, key, &id)) |
| return false; |
| |
| if (!JSID_IS_ATOM(id)) { |
| result.set(key); |
| return true; |
| } |
| |
| result.set(StringValue(JSID_TO_ATOM(id))); |
| return true; |
| } |
| |
| typedef bool (*DoAtomizeStringFn)(JSContext*, HandleString, MutableHandleValue); |
| static const VMFunction DoAtomizeStringInfo = FunctionInfo<DoAtomizeStringFn>(DoAtomizeString); |
| |
| template <class T> |
| bool |
| ICGetElemNativeCompiler<T>::emitCallNative(MacroAssembler& masm, Register objReg) |
| { |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| regs.takeUnchecked(objReg); |
| regs.takeUnchecked(ICTailCallReg); |
| |
| enterStubFrame(masm, regs.getAny()); |
| |
| // Push object. |
| masm.push(objReg); |
| |
| // Push native callee. |
| masm.loadPtr(Address(ICStubReg, ICGetElemNativeGetterStub<T>::offsetOfGetter()), objReg); |
| masm.push(objReg); |
| |
| regs.add(objReg); |
| |
| // Call helper. |
| if (!callVM(DoCallNativeGetterInfo, masm)) |
| return false; |
| |
| leaveStubFrame(masm); |
| |
| return true; |
| } |
| |
| template <class T> |
| bool |
| ICGetElemNativeCompiler<T>::emitCallScripted(MacroAssembler& masm, Register objReg) |
| { |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| regs.takeUnchecked(objReg); |
| regs.takeUnchecked(ICTailCallReg); |
| |
| // Enter stub frame. |
| enterStubFrame(masm, regs.getAny()); |
| |
| // Align the stack such that the JitFrameLayout is aligned on |
| // JitStackAlignment. |
| masm.alignJitStackBasedOnNArgs(0); |
| |
| // Push |this| for getter (target object). |
| { |
| ValueOperand val = regs.takeAnyValue(); |
| masm.tagValue(JSVAL_TYPE_OBJECT, objReg, val); |
| masm.Push(val); |
| regs.add(val); |
| } |
| |
| regs.add(objReg); |
| |
| Register callee = regs.takeAny(); |
| masm.loadPtr(Address(ICStubReg, ICGetElemNativeGetterStub<T>::offsetOfGetter()), callee); |
| |
| // Push argc, callee, and descriptor. |
| { |
| Register callScratch = regs.takeAny(); |
| EmitBaselineCreateStubFrameDescriptor(masm, callScratch); |
| masm.Push(Imm32(0)); // ActualArgc is 0 |
| masm.Push(callee); |
| masm.Push(callScratch); |
| regs.add(callScratch); |
| } |
| |
| Register code = regs.takeAnyExcluding(ArgumentsRectifierReg); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code); |
| masm.loadBaselineOrIonRaw(code, code, nullptr); |
| |
| Register scratch = regs.takeAny(); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), scratch); |
| masm.branch32(Assembler::Equal, scratch, Imm32(0), &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| MOZ_ASSERT(ArgumentsRectifierReg != code); |
| |
| JitCode* argumentsRectifier = |
| cx->runtime()->jitRuntime()->getArgumentsRectifier(); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), code); |
| masm.loadPtr(Address(code, JitCode::offsetOfCode()), code); |
| masm.movePtr(ImmWord(0), ArgumentsRectifierReg); |
| } |
| |
| masm.bind(&noUnderflow); |
| masm.callJit(code); |
| |
| leaveStubFrame(masm, true); |
| |
| return true; |
| } |
| |
| template <class T> |
| bool |
| ICGetElemNativeCompiler<T>::emitCheckKey(MacroAssembler& masm, Label& failure) |
| { |
| MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol"); |
| return false; |
| } |
| |
| template <> |
| bool |
| ICGetElemNativeCompiler<JS::Symbol*>::emitCheckKey(MacroAssembler& masm, Label& failure) |
| { |
| MOZ_ASSERT(!needsAtomize_); |
| masm.branchTestSymbol(Assembler::NotEqual, R1, &failure); |
| Address symbolAddr(ICStubReg, ICGetElemNativeStubImpl<JS::Symbol*>::offsetOfKey()); |
| Register symExtract = masm.extractObject(R1, ExtractTemp1); |
| masm.branchPtr(Assembler::NotEqual, symbolAddr, symExtract, &failure); |
| return true; |
| } |
| |
| template <> |
| bool |
| ICGetElemNativeCompiler<PropertyName*>::emitCheckKey(MacroAssembler& masm, Label& failure) |
| { |
| masm.branchTestString(Assembler::NotEqual, R1, &failure); |
| // Check key identity. Don't automatically fail if this fails, since the incoming |
| // key maybe a non-interned string. Switch to a slowpath vm-call based check. |
| Address nameAddr(ICStubReg, ICGetElemNativeStubImpl<PropertyName*>::offsetOfKey()); |
| Register strExtract = masm.extractString(R1, ExtractTemp1); |
| |
| // If needsAtomize_ is true, and the string is not already an atom, then atomize the |
| // string before proceeding. |
| if (needsAtomize_) { |
| Label skipAtomize; |
| |
| // If string is already an atom, skip the atomize. |
| masm.branchTest32(Assembler::NonZero, |
| Address(strExtract, JSString::offsetOfFlags()), |
| Imm32(JSString::ATOM_BIT), |
| &skipAtomize); |
| |
| // Stow R0. |
| EmitStowICValues(masm, 1); |
| |
| enterStubFrame(masm, R0.scratchReg()); |
| |
| // Atomize the string into a new value. |
| masm.push(strExtract); |
| if (!callVM(DoAtomizeStringInfo, masm)) |
| return false; |
| |
| // Atomized string is now in JSReturnOperand (R0). |
| // Leave stub frame, move atomized string into R1. |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| leaveStubFrame(masm); |
| masm.moveValue(JSReturnOperand, R1); |
| |
| // Unstow R0 |
| EmitUnstowICValues(masm, 1); |
| |
| // Extract string from R1 again. |
| DebugOnly<Register> strExtract2 = masm.extractString(R1, ExtractTemp1); |
| MOZ_ASSERT(Register(strExtract2) == strExtract); |
| |
| masm.bind(&skipAtomize); |
| } |
| |
| // Key has been atomized if necessary. Do identity check on string pointer. |
| masm.branchPtr(Assembler::NotEqual, nameAddr, strExtract, &failure); |
| return true; |
| } |
| |
| template <class T> |
| bool |
| ICGetElemNativeCompiler<T>::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| Label failurePopR1; |
| bool popR1 = false; |
| |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox object. |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| |
| // Check object shape/group. |
| GuardReceiverObject(masm, ReceiverGuard(obj_), objReg, scratchReg, |
| ICGetElemNativeStub::offsetOfReceiverGuard(), &failure); |
| |
| // Since this stub sometimes enters a stub frame, we manually set this to true (lie). |
| #ifdef DEBUG |
| entersStubFrame_ = true; |
| #endif |
| |
| if (!emitCheckKey(masm, failure)) |
| return false; |
| |
| Register holderReg; |
| if (obj_ == holder_) { |
| holderReg = objReg; |
| |
| if (obj_->is<UnboxedPlainObject>() && acctype_ != ICGetElemNativeStub::UnboxedProperty) { |
| // The property will be loaded off the unboxed expando. |
| masm.push(R1.scratchReg()); |
| popR1 = true; |
| holderReg = R1.scratchReg(); |
| masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); |
| } |
| } else { |
| // Shape guard holder. |
| if (regs.empty()) { |
| masm.push(R1.scratchReg()); |
| popR1 = true; |
| holderReg = R1.scratchReg(); |
| } else { |
| holderReg = regs.takeAny(); |
| } |
| |
| if (kind == ICStub::GetElem_NativePrototypeCallNativeName || |
| kind == ICStub::GetElem_NativePrototypeCallNativeSymbol || |
| kind == ICStub::GetElem_NativePrototypeCallScriptedName || |
| kind == ICStub::GetElem_NativePrototypeCallScriptedSymbol) |
| { |
| masm.loadPtr(Address(ICStubReg, |
| ICGetElemNativePrototypeCallStub<T>::offsetOfHolder()), |
| holderReg); |
| masm.loadPtr(Address(ICStubReg, |
| ICGetElemNativePrototypeCallStub<T>::offsetOfHolderShape()), |
| scratchReg); |
| } else { |
| masm.loadPtr(Address(ICStubReg, |
| ICGetElem_NativePrototypeSlot<T>::offsetOfHolder()), |
| holderReg); |
| masm.loadPtr(Address(ICStubReg, |
| ICGetElem_NativePrototypeSlot<T>::offsetOfHolderShape()), |
| scratchReg); |
| } |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratchReg, |
| popR1 ? &failurePopR1 : &failure); |
| } |
| |
| if (acctype_ == ICGetElemNativeStub::DynamicSlot || |
| acctype_ == ICGetElemNativeStub::FixedSlot) |
| { |
| masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub<T>::offsetOfOffset()), |
| scratchReg); |
| |
| // Load from object. |
| if (acctype_ == ICGetElemNativeStub::DynamicSlot) |
| masm.addPtr(Address(holderReg, NativeObject::offsetOfSlots()), scratchReg); |
| else |
| masm.addPtr(holderReg, scratchReg); |
| |
| Address valAddr(scratchReg, 0); |
| masm.loadValue(valAddr, R0); |
| if (popR1) |
| masm.addToStackPtr(ImmWord(sizeof(size_t))); |
| |
| } else if (acctype_ == ICGetElemNativeStub::UnboxedProperty) { |
| masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub<T>::offsetOfOffset()), |
| scratchReg); |
| masm.loadUnboxedProperty(BaseIndex(objReg, scratchReg, TimesOne), unboxedType_, |
| TypedOrValueRegister(R0)); |
| if (popR1) |
| masm.addToStackPtr(ImmWord(sizeof(size_t))); |
| } else { |
| MOZ_ASSERT(acctype_ == ICGetElemNativeStub::NativeGetter || |
| acctype_ == ICGetElemNativeStub::ScriptedGetter); |
| MOZ_ASSERT(kind == ICStub::GetElem_NativePrototypeCallNativeName || |
| kind == ICStub::GetElem_NativePrototypeCallNativeSymbol || |
| kind == ICStub::GetElem_NativePrototypeCallScriptedName || |
| kind == ICStub::GetElem_NativePrototypeCallScriptedSymbol); |
| |
| if (acctype_ == ICGetElemNativeStub::NativeGetter) { |
| // If calling a native getter, there is no chance of failure now. |
| |
| // GetElem key (R1) is no longer needed. |
| if (popR1) |
| masm.addToStackPtr(ImmWord(sizeof(size_t))); |
| |
| emitCallNative(masm, objReg); |
| |
| } else { |
| MOZ_ASSERT(acctype_ == ICGetElemNativeStub::ScriptedGetter); |
| |
| // Load function in scratchReg and ensure that it has a jit script. |
| masm.loadPtr(Address(ICStubReg, ICGetElemNativeGetterStub<T>::offsetOfGetter()), |
| scratchReg); |
| masm.branchIfFunctionHasNoScript(scratchReg, popR1 ? &failurePopR1 : &failure); |
| masm.loadPtr(Address(scratchReg, JSFunction::offsetOfNativeOrScript()), scratchReg); |
| masm.loadBaselineOrIonRaw(scratchReg, scratchReg, popR1 ? &failurePopR1 : &failure); |
| |
| // At this point, we are guaranteed to successfully complete. |
| if (popR1) |
| masm.addToStackPtr(Imm32(sizeof(size_t))); |
| |
| emitCallScripted(masm, objReg); |
| } |
| } |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| if (popR1) { |
| masm.bind(&failurePopR1); |
| masm.pop(R1.scratchReg()); |
| } |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| |
| return true; |
| } |
| |
| // |
| // GetElem_String |
| // |
| |
| bool |
| ICGetElem_String::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox string in R0. |
| Register str = masm.extractString(R0, ExtractTemp0); |
| |
| // Check for non-linear strings. |
| masm.branchIfRope(str, &failure); |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Bounds check. |
| masm.branch32(Assembler::BelowOrEqual, Address(str, JSString::offsetOfLength()), |
| key, &failure); |
| |
| // Get char code. |
| masm.loadStringChar(str, key, scratchReg); |
| |
| // Check if char code >= UNIT_STATIC_LIMIT. |
| masm.branch32(Assembler::AboveOrEqual, scratchReg, Imm32(StaticStrings::UNIT_STATIC_LIMIT), |
| &failure); |
| |
| // Load static string. |
| masm.movePtr(ImmPtr(&cx->staticStrings().unitStaticTable), str); |
| masm.loadPtr(BaseIndex(str, scratchReg, ScalePointer), str); |
| |
| // Return. |
| masm.tagValue(JSVAL_TYPE_STRING, str, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetElem_Dense |
| // |
| |
| bool |
| ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and shape guard. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICGetElem_Dense::offsetOfShape()), scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Load obj->elements. |
| masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg); |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Bounds check. |
| Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength()); |
| masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure); |
| |
| // Hole check and load value. |
| BaseObjectElementIndex element(scratchReg, key); |
| masm.branchTestMagic(Assembler::Equal, element, &failure); |
| |
| // Load value from element location. |
| masm.loadValue(element, R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetElem_UnboxedArray |
| // |
| |
| bool |
| ICGetElem_UnboxedArray::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and group guard. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICGetElem_UnboxedArray::offsetOfGroup()), scratchReg); |
| masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Bounds check. |
| masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), |
| scratchReg); |
| masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg); |
| masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure); |
| |
| // Load obj->elements. |
| masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg); |
| |
| // Load value. |
| size_t width = UnboxedTypeSize(elementType_); |
| BaseIndex addr(scratchReg, key, ScaleFromElemWidth(width)); |
| masm.loadUnboxedProperty(addr, elementType_, R0); |
| |
| // Only monitor the result if its type might change. |
| if (elementType_ == JSVAL_TYPE_OBJECT) |
| EmitEnterTypeMonitorIC(masm); |
| else |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetElem_TypedArray |
| // |
| |
| static void |
| LoadTypedThingLength(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result) |
| { |
| switch (layout) { |
| case Layout_TypedArray: |
| masm.unboxInt32(Address(obj, TypedArrayObject::lengthOffset()), result); |
| break; |
| case Layout_OutlineTypedObject: |
| case Layout_InlineTypedObject: |
| masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), result); |
| masm.loadPtr(Address(result, ObjectGroup::offsetOfAddendum()), result); |
| masm.unboxInt32(Address(result, ArrayTypeDescr::offsetOfLength()), result); |
| break; |
| default: |
| MOZ_CRASH(); |
| } |
| } |
| |
| bool |
| ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| |
| if (layout_ != Layout_TypedArray) |
| CheckForNeuteredTypedObject(cx, masm, &failure); |
| |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and shape guard. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICGetElem_TypedArray::offsetOfShape()), scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Ensure the index is an integer. |
| if (cx->runtime()->jitSupportsFloatingPoint) { |
| Label isInt32; |
| masm.branchTestInt32(Assembler::Equal, R1, &isInt32); |
| { |
| // If the index is a double, try to convert it to int32. It's okay |
| // to convert -0 to 0: the shape check ensures the object is a typed |
| // array so the difference is not observable. |
| masm.branchTestDouble(Assembler::NotEqual, R1, &failure); |
| masm.unboxDouble(R1, FloatReg0); |
| masm.convertDoubleToInt32(FloatReg0, scratchReg, &failure, /* negZeroCheck = */false); |
| masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R1); |
| } |
| masm.bind(&isInt32); |
| } else { |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| } |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Bounds check. |
| LoadTypedThingLength(masm, layout_, obj, scratchReg); |
| masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure); |
| |
| // Load the elements vector. |
| LoadTypedThingData(masm, layout_, obj, scratchReg); |
| |
| // Load the value. |
| BaseIndex source(scratchReg, key, ScaleFromElemWidth(Scalar::byteSize(type_))); |
| masm.loadFromTypedArray(type_, source, R0, false, scratchReg, &failure); |
| |
| // Todo: Allow loading doubles from uint32 arrays, but this requires monitoring. |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // GetElem_Arguments |
| // |
| bool |
| ICGetElem_Arguments::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| if (which_ == ICGetElem_Arguments::Magic) { |
| // Ensure that this is a magic arguments value. |
| masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure); |
| |
| // Ensure that frame has not loaded different arguments object since. |
| masm.branchTest32(Assembler::NonZero, |
| Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()), |
| Imm32(BaselineFrame::HAS_ARGS_OBJ), |
| &failure); |
| |
| // Ensure that index is an integer. |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| Register idx = masm.extractInt32(R1, ExtractTemp1); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| // Load num actual arguments |
| Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs()); |
| masm.loadPtr(actualArgs, scratch); |
| |
| // Ensure idx < argc |
| masm.branch32(Assembler::AboveOrEqual, idx, scratch, &failure); |
| |
| // Load argval |
| masm.movePtr(BaselineFrameReg, scratch); |
| masm.addPtr(Imm32(BaselineFrame::offsetOfArg(0)), scratch); |
| BaseValueIndex element(scratch, idx); |
| masm.loadValue(element, R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| MOZ_ASSERT(which_ == ICGetElem_Arguments::Mapped || |
| which_ == ICGetElem_Arguments::Unmapped); |
| |
| const Class* clasp = (which_ == ICGetElem_Arguments::Mapped) |
| ? &MappedArgumentsObject::class_ |
| : &UnmappedArgumentsObject::class_; |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Guard on input being an arguments object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| masm.branchTestObjClass(Assembler::NotEqual, objReg, scratchReg, clasp, &failure); |
| |
| // Guard on index being int32 |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| Register idxReg = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Get initial ArgsObj length value. |
| masm.unboxInt32(Address(objReg, ArgumentsObject::getInitialLengthSlotOffset()), scratchReg); |
| |
| // Test if length has been overridden. |
| masm.branchTest32(Assembler::NonZero, |
| scratchReg, |
| Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT), |
| &failure); |
| |
| // Length has not been overridden, ensure that R1 is an integer and is <= length. |
| masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratchReg); |
| masm.branch32(Assembler::AboveOrEqual, idxReg, scratchReg, &failure); |
| |
| // Length check succeeded, now check the correct bit. We clobber potential type regs |
| // now. Inputs will have to be reconstructed if we fail after this point, but that's |
| // unlikely. |
| Label failureReconstructInputs; |
| regs = availableGeneralRegs(0); |
| regs.takeUnchecked(objReg); |
| regs.takeUnchecked(idxReg); |
| regs.take(scratchReg); |
| Register argData = regs.takeAny(); |
| Register tempReg = regs.takeAny(); |
| |
| // Load ArgumentsData |
| masm.loadPrivate(Address(objReg, ArgumentsObject::getDataSlotOffset()), argData); |
| |
| // Load deletedBits bitArray pointer into scratchReg |
| masm.loadPtr(Address(argData, offsetof(ArgumentsData, deletedBits)), scratchReg); |
| |
| // In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord) |
| masm.movePtr(idxReg, tempReg); |
| const uint32_t shift = mozilla::tl::FloorLog2<(sizeof(size_t) * JS_BITS_PER_BYTE)>::value; |
| MOZ_ASSERT(shift == 5 || shift == 6); |
| masm.rshiftPtr(Imm32(shift), tempReg); |
| masm.loadPtr(BaseIndex(scratchReg, tempReg, ScaleFromElemWidth(sizeof(size_t))), scratchReg); |
| |
| // Don't bother testing specific bit, if any bit is set in the word, fail. |
| masm.branchPtr(Assembler::NotEqual, scratchReg, ImmPtr(nullptr), &failureReconstructInputs); |
| |
| // Load the value. use scratchReg and tempReg to form a ValueOperand to load into. |
| masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), argData); |
| regs.add(scratchReg); |
| regs.add(tempReg); |
| ValueOperand tempVal = regs.takeAnyValue(); |
| masm.loadValue(BaseValueIndex(argData, idxReg), tempVal); |
| |
| // Makesure that this is not a FORWARD_TO_CALL_SLOT magic value. |
| masm.branchTestMagic(Assembler::Equal, tempVal, &failureReconstructInputs); |
| |
| // Copy value from temp to R0. |
| masm.moveValue(tempVal, R0); |
| |
| // Type-check result |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failed, but inputs are deconstructed into object and int, and need to be |
| // reconstructed into values. |
| masm.bind(&failureReconstructInputs); |
| masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0); |
| masm.tagValue(JSVAL_TYPE_INT32, idxReg, R1); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // SetElem_Fallback |
| // |
| |
| static bool |
| SetElemAddHasSameShapes(ICSetElem_DenseOrUnboxedArrayAdd* stub, JSObject* obj) |
| { |
| static const size_t MAX_DEPTH = ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH; |
| ICSetElem_DenseOrUnboxedArrayAddImpl<MAX_DEPTH>* nstub = stub->toImplUnchecked<MAX_DEPTH>(); |
| |
| if (obj->maybeShape() != nstub->shape(0)) |
| return false; |
| |
| JSObject* proto = obj->getProto(); |
| for (size_t i = 0; i < stub->protoChainDepth(); i++) { |
| if (!proto->isNative()) |
| return false; |
| if (proto->as<NativeObject>().lastProperty() != nstub->shape(i + 1)) |
| return false; |
| proto = obj->getProto(); |
| if (!proto) { |
| if (i != stub->protoChainDepth() - 1) |
| return false; |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool |
| DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind, |
| ICSetElem_Fallback* stub, HandleObject obj) |
| { |
| MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArray || |
| kind == ICStub::SetElem_DenseOrUnboxedArrayAdd); |
| |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (kind == ICStub::SetElem_DenseOrUnboxedArray && iter->isSetElem_DenseOrUnboxedArray()) { |
| ICSetElem_DenseOrUnboxedArray* nstub = iter->toSetElem_DenseOrUnboxedArray(); |
| if (obj->maybeShape() == nstub->shape() && obj->getGroup(cx) == nstub->group()) |
| return true; |
| } |
| |
| if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) { |
| ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd(); |
| if (obj->getGroup(cx) == nstub->group() && SetElemAddHasSameShapes(nstub, obj)) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool |
| TypedArraySetElemStubExists(ICSetElem_Fallback* stub, HandleObject obj, bool expectOOB) |
| { |
| for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (!iter->isSetElem_TypedArray()) |
| continue; |
| ICSetElem_TypedArray* taStub = iter->toSetElem_TypedArray(); |
| if (obj->maybeShape() == taStub->shape() && taStub->expectOutOfBounds() == expectOOB) |
| return true; |
| } |
| return false; |
| } |
| |
| static bool |
| RemoveExistingTypedArraySetElemStub(JSContext* cx, ICSetElem_Fallback* stub, HandleObject obj) |
| { |
| for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) { |
| if (!iter->isSetElem_TypedArray()) |
| continue; |
| |
| if (obj->maybeShape() != iter->toSetElem_TypedArray()->shape()) |
| continue; |
| |
| // TypedArraySetElem stubs are only removed using this procedure if |
| // being replaced with one that expects out of bounds index. |
| MOZ_ASSERT(!iter->toSetElem_TypedArray()->expectOutOfBounds()); |
| iter.unlink(cx); |
| return true; |
| } |
| return false; |
| } |
| |
| static bool |
| CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index, |
| Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength, |
| bool* isAddingCaseOut, size_t* protoDepthOut) |
| { |
| uint32_t initLength = GetAnyBoxedOrUnboxedInitializedLength(obj); |
| uint32_t capacity = GetAnyBoxedOrUnboxedCapacity(obj); |
| |
| *isAddingCaseOut = false; |
| *protoDepthOut = 0; |
| |
| // Some initial sanity checks. |
| if (initLength < oldInitLength || capacity < oldCapacity) |
| return false; |
| |
| // Unboxed arrays need to be able to emit floating point code. |
| if (obj->is<UnboxedArrayObject>() && !obj->runtimeFromMainThread()->jitSupportsFloatingPoint) |
| return false; |
| |
| Shape* shape = obj->maybeShape(); |
| |
| // Cannot optimize if the shape changed. |
| if (oldShape != shape) |
| return false; |
| |
| // Cannot optimize if the capacity changed. |
| if (oldCapacity != capacity) |
| return false; |
| |
| // Cannot optimize if the index doesn't fit within the new initialized length. |
| if (index >= initLength) |
| return false; |
| |
| // Cannot optimize if the value at position after the set is a hole. |
| if (obj->isNative() && !obj->as<NativeObject>().containsDenseElement(index)) |
| return false; |
| |
| // At this point, if we know that the initLength did not change, then |
| // an optimized set is possible. |
| if (oldInitLength == initLength) |
| return true; |
| |
| // If it did change, ensure that it changed specifically by incrementing by 1 |
| // to accomodate this particular indexed set. |
| if (oldInitLength + 1 != initLength) |
| return false; |
| if (index != oldInitLength) |
| return false; |
| |
| // The checks are not complete. The object may have a setter definition, |
| // either directly, or via a prototype, or via the target object for a prototype |
| // which is a proxy, that handles a particular integer write. |
| // Scan the prototype and shape chain to make sure that this is not the case. |
| if (obj->isIndexed()) |
| return false; |
| JSObject* curObj = obj->getProto(); |
| while (curObj) { |
| ++*protoDepthOut; |
| if (!curObj->isNative() || curObj->isIndexed()) |
| return false; |
| curObj = curObj->getProto(); |
| } |
| |
| if (*protoDepthOut > ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH) |
| return false; |
| |
| *isAddingCaseOut = true; |
| return true; |
| } |
| |
| static bool |
| DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_, Value* stack, |
| HandleValue objv, HandleValue index, HandleValue rhs) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICSetElem_Fallback*> stub(frame, stub_); |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "SetElem(%s)", CodeName[JSOp(*pc)]); |
| |
| MOZ_ASSERT(op == JSOP_SETELEM || |
| op == JSOP_STRICTSETELEM || |
| op == JSOP_INITELEM || |
| op == JSOP_INITHIDDENELEM || |
| op == JSOP_INITELEM_ARRAY || |
| op == JSOP_INITELEM_INC); |
| |
| RootedObject obj(cx, ToObjectFromStack(cx, objv)); |
| if (!obj) |
| return false; |
| |
| RootedShape oldShape(cx, obj->maybeShape()); |
| |
| // Check the old capacity |
| uint32_t oldCapacity = 0; |
| uint32_t oldInitLength = 0; |
| if (index.isInt32() && index.toInt32() >= 0) { |
| oldCapacity = GetAnyBoxedOrUnboxedCapacity(obj); |
| oldInitLength = GetAnyBoxedOrUnboxedInitializedLength(obj); |
| } |
| |
| if (op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM) { |
| if (!InitElemOperation(cx, pc, obj, index, rhs)) |
| return false; |
| } else if (op == JSOP_INITELEM_ARRAY) { |
| MOZ_ASSERT(uint32_t(index.toInt32()) <= INT32_MAX, |
| "the bytecode emitter must fail to compile code that would " |
| "produce JSOP_INITELEM_ARRAY with an index exceeding " |
| "int32_t range"); |
| MOZ_ASSERT(uint32_t(index.toInt32()) == GET_UINT32(pc)); |
| if (!InitArrayElemOperation(cx, pc, obj, index.toInt32(), rhs)) |
| return false; |
| } else if (op == JSOP_INITELEM_INC) { |
| if (!InitArrayElemOperation(cx, pc, obj, index.toInt32(), rhs)) |
| return false; |
| } else { |
| if (!SetObjectElement(cx, obj, index, rhs, JSOp(*pc) == JSOP_STRICTSETELEM, script, pc)) |
| return false; |
| } |
| |
| // Don't try to attach stubs that wish to be hidden. We don't know how to |
| // have different enumerability in the stubs for the moment. |
| if (op == JSOP_INITHIDDENELEM) |
| return true; |
| |
| // Overwrite the object on the stack (pushed for the decompiler) with the rhs. |
| MOZ_ASSERT(stack[2] == objv); |
| stack[2] = rhs; |
| |
| // Check if debug mode toggling made the stub invalid. |
| if (stub.invalid()) |
| return true; |
| |
| if (stub->numOptimizedStubs() >= ICSetElem_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| // Try to generate new stubs. |
| if (IsNativeOrUnboxedDenseElementAccess(obj, index) && !rhs.isMagic(JS_ELEMENTS_HOLE)) { |
| bool addingCase; |
| size_t protoDepth; |
| |
| if (CanOptimizeDenseOrUnboxedArraySetElem(obj, index.toInt32(), |
| oldShape, oldCapacity, oldInitLength, |
| &addingCase, &protoDepth)) |
| { |
| RootedShape shape(cx, obj->maybeShape()); |
| RootedObjectGroup group(cx, obj->getGroup(cx)); |
| if (!group) |
| return false; |
| |
| if (addingCase && |
| !DenseOrUnboxedArraySetElemStubExists(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd, |
| stub, obj)) |
| { |
| JitSpew(JitSpew_BaselineIC, |
| " Generating SetElem_DenseOrUnboxedArrayAdd stub " |
| "(shape=%p, group=%p, protoDepth=%u)", |
| shape.get(), group.get(), protoDepth); |
| ICSetElemDenseOrUnboxedArrayAddCompiler compiler(cx, obj, protoDepth); |
| ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| if (compiler.needsUpdateStubs() && |
| !newStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs)) |
| { |
| return false; |
| } |
| |
| stub->addNewStub(newStub); |
| } else if (!addingCase && |
| !DenseOrUnboxedArraySetElemStubExists(cx, |
| ICStub::SetElem_DenseOrUnboxedArray, |
| stub, obj)) |
| { |
| JitSpew(JitSpew_BaselineIC, |
| " Generating SetElem_DenseOrUnboxedArray stub (shape=%p, group=%p)", |
| shape.get(), group.get()); |
| ICSetElem_DenseOrUnboxedArray::Compiler compiler(cx, shape, group); |
| ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| if (compiler.needsUpdateStubs() && |
| !newStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs)) |
| { |
| return false; |
| } |
| |
| stub->addNewStub(newStub); |
| } |
| } |
| |
| return true; |
| } |
| |
| if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) && |
| index.isNumber() && |
| rhs.isNumber()) |
| { |
| if (!cx->runtime()->jitSupportsFloatingPoint && |
| (TypedThingRequiresFloatingPoint(obj) || index.isDouble())) |
| { |
| return true; |
| } |
| |
| bool expectOutOfBounds; |
| double idx = index.toNumber(); |
| if (IsAnyTypedArray(obj)) { |
| expectOutOfBounds = (idx < 0 || idx >= double(AnyTypedArrayLength(obj))); |
| } else { |
| // Typed objects throw on out of bounds accesses. Don't attach |
| // a stub in this case. |
| if (idx < 0 || idx >= double(obj->as<TypedObject>().length())) |
| return true; |
| expectOutOfBounds = false; |
| |
| // Don't attach stubs if typed objects in the compartment might be |
| // neutered, as the stub will always bail out. |
| if (cx->compartment()->neuteredTypedObjects) |
| return true; |
| } |
| |
| if (!TypedArraySetElemStubExists(stub, obj, expectOutOfBounds)) { |
| // Remove any existing TypedArraySetElemStub that doesn't handle out-of-bounds |
| if (expectOutOfBounds) |
| RemoveExistingTypedArraySetElemStub(cx, stub, obj); |
| |
| Shape* shape = obj->maybeShape(); |
| Scalar::Type type = TypedThingElementType(obj); |
| |
| JitSpew(JitSpew_BaselineIC, |
| " Generating SetElem_TypedArray stub (shape=%p, type=%u, oob=%s)", |
| shape, type, expectOutOfBounds ? "yes" : "no"); |
| ICSetElem_TypedArray::Compiler compiler(cx, shape, type, expectOutOfBounds); |
| ICStub* typedArrayStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!typedArrayStub) |
| return false; |
| |
| stub->addNewStub(typedArrayStub); |
| return true; |
| } |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoSetElemFallbackFn)(JSContext*, BaselineFrame*, ICSetElem_Fallback*, Value*, |
| HandleValue, HandleValue, HandleValue); |
| static const VMFunction DoSetElemFallbackInfo = |
| FunctionInfo<DoSetElemFallbackFn>(DoSetElemFallback, TailCall, PopValues(2)); |
| |
| bool |
| ICSetElem_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| // State: R0: object, R1: index, stack: rhs. |
| // For the decompiler, the stack has to be: object, index, rhs, |
| // so we push the index, then overwrite the rhs Value with R0 |
| // and push the rhs value. |
| masm.pushValue(R1); |
| masm.loadValue(Address(masm.getStackPointer(), sizeof(Value)), R1); |
| masm.storeValue(R0, Address(masm.getStackPointer(), sizeof(Value))); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); // RHS |
| |
| // Push index. On x86 and ARM two push instructions are emitted so use a |
| // separate register to store the old stack pointer. |
| masm.moveStackPtrTo(R1.scratchReg()); |
| masm.pushValue(Address(R1.scratchReg(), 2 * sizeof(Value))); |
| masm.pushValue(R0); // Object. |
| |
| // Push pointer to stack values, so that the stub can overwrite the object |
| // (pushed for the decompiler) with the rhs. |
| masm.computeEffectiveAddress(Address(masm.getStackPointer(), 3 * sizeof(Value)), R0.scratchReg()); |
| masm.push(R0.scratchReg()); |
| |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoSetElemFallbackInfo, masm); |
| } |
| |
| void |
| BaselineScript::noteArrayWriteHole(uint32_t pcOffset) |
| { |
| ICEntry& entry = icEntryFromPCOffset(pcOffset); |
| ICFallbackStub* stub = entry.fallbackStub(); |
| |
| if (stub->isSetElem_Fallback()) |
| stub->toSetElem_Fallback()->noteArrayWriteHole(); |
| } |
| |
| // |
| // SetElem_DenseOrUnboxedArray |
| // |
| |
| template <typename T> |
| void |
| EmitUnboxedPreBarrierForBaseline(MacroAssembler &masm, T address, JSValueType type) |
| { |
| if (type == JSVAL_TYPE_OBJECT) |
| EmitPreBarrier(masm, address, MIRType_Object); |
| else if (type == JSVAL_TYPE_STRING) |
| EmitPreBarrier(masm, address, MIRType_String); |
| else |
| MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type)); |
| } |
| |
| bool |
| ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| // R0 = object |
| // R1 = key |
| // Stack = { ... rhs-value, <return-addr>? } |
| Label failure, failurePopR0; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and guard on its group and, if this is a native access, its shape. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArray::offsetOfGroup()), |
| scratchReg); |
| masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure); |
| if (unboxedType_ == JSVAL_TYPE_MAGIC) { |
| masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArray::offsetOfShape()), |
| scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| } |
| |
| if (needsUpdateStubs()) { |
| // Stow both R0 and R1 (object and key) |
| // But R0 and R1 still hold their values. |
| EmitStowICValues(masm, 2); |
| |
| // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR } |
| // Load rhs-value into R0 |
| masm.loadValue(Address(masm.getStackPointer(), 2 * sizeof(Value) + ICStackValueOffset), R0); |
| |
| // Call the type-update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| |
| // Restore object. |
| obj = masm.extractObject(R0, ExtractTemp0); |
| |
| // Trigger post barriers here on the value being written. Fields which |
| // objects can be written to also need update stubs. |
| masm.Push(R1); |
| masm.loadValue(Address(masm.getStackPointer(), sizeof(Value) + ICStackValueOffset), R1); |
| |
| LiveGeneralRegisterSet saveRegs; |
| saveRegs.add(R0); |
| saveRegs.addUnchecked(obj); |
| saveRegs.add(ICStubReg); |
| emitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs); |
| |
| masm.Pop(R1); |
| } |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| if (unboxedType_ == JSVAL_TYPE_MAGIC) { |
| // Set element on a native object. |
| |
| // Load obj->elements in scratchReg. |
| masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg); |
| |
| // Bounds check. |
| Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength()); |
| masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure); |
| |
| // Hole check. |
| BaseIndex element(scratchReg, key, TimesEight); |
| masm.branchTestMagic(Assembler::Equal, element, &failure); |
| |
| // Perform a single test to see if we either need to convert double |
| // elements or clone the copy on write elements in the object. |
| Label noSpecialHandling; |
| Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags()); |
| masm.branchTest32(Assembler::Zero, elementsFlags, |
| Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS | |
| ObjectElements::COPY_ON_WRITE), |
| &noSpecialHandling); |
| |
| // Fail if we need to clone copy on write elements. |
| masm.branchTest32(Assembler::NonZero, elementsFlags, |
| Imm32(ObjectElements::COPY_ON_WRITE), |
| &failure); |
| |
| // Failure is not possible now. Free up registers. |
| regs.add(R0); |
| regs.add(R1); |
| regs.takeUnchecked(obj); |
| regs.takeUnchecked(key); |
| |
| Address valueAddr(masm.getStackPointer(), ICStackValueOffset); |
| |
| // We need to convert int32 values being stored into doubles. In this case |
| // the heap typeset is guaranteed to contain both int32 and double, so it's |
| // okay to store a double. Note that double arrays are only created by |
| // IonMonkey, so if we have no floating-point support Ion is disabled and |
| // there should be no double arrays. |
| if (cx->runtime()->jitSupportsFloatingPoint) |
| masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &noSpecialHandling); |
| else |
| masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support."); |
| |
| masm.bind(&noSpecialHandling); |
| |
| ValueOperand tmpVal = regs.takeAnyValue(); |
| masm.loadValue(valueAddr, tmpVal); |
| EmitPreBarrier(masm, element, MIRType_Value); |
| masm.storeValue(tmpVal, element); |
| } else { |
| // Set element on an unboxed array. |
| |
| // Bounds check. |
| Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()); |
| masm.load32(initLength, scratchReg); |
| masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg); |
| masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure); |
| |
| // Load obj->elements. |
| masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg); |
| |
| // Compute the address being written to. |
| BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_))); |
| |
| EmitUnboxedPreBarrierForBaseline(masm, address, unboxedType_); |
| |
| Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value)); |
| masm.Push(R0); |
| masm.loadValue(valueAddr, R0); |
| masm.storeUnboxedProperty(address, unboxedType_, |
| ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0); |
| masm.Pop(R0); |
| } |
| |
| EmitReturnFromIC(masm); |
| |
| if (failurePopR0.used()) { |
| // Failure case: restore the value of R0 |
| masm.bind(&failurePopR0); |
| masm.popValue(R0); |
| } |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // SetElem_DenseOrUnboxedArrayAdd |
| // |
| |
| ICUpdatedStub* |
| ICSetElemDenseOrUnboxedArrayAddCompiler::getStub(ICStubSpace* space) |
| { |
| Rooted<ShapeVector> shapes(cx, ShapeVector(cx)); |
| if (!shapes.append(obj_->maybeShape())) |
| return nullptr; |
| |
| if (!GetProtoShapes(obj_, protoChainDepth_, &shapes)) |
| return nullptr; |
| |
| JS_STATIC_ASSERT(ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH == 4); |
| |
| ICUpdatedStub* stub = nullptr; |
| switch (protoChainDepth_) { |
| case 0: stub = getStubSpecific<0>(space, shapes); break; |
| case 1: stub = getStubSpecific<1>(space, shapes); break; |
| case 2: stub = getStubSpecific<2>(space, shapes); break; |
| case 3: stub = getStubSpecific<3>(space, shapes); break; |
| case 4: stub = getStubSpecific<4>(space, shapes); break; |
| default: MOZ_CRASH("ProtoChainDepth too high."); |
| } |
| if (!stub || !stub->initUpdatingChain(cx, space)) |
| return nullptr; |
| return stub; |
| } |
| |
| bool |
| ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| // R0 = object |
| // R1 = key |
| // Stack = { ... rhs-value, <return-addr>? } |
| Label failure, failurePopR0, failureUnstow; |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and guard on its group and, if this is a native access, its shape. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArrayAdd::offsetOfGroup()), |
| scratchReg); |
| masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure); |
| if (unboxedType_ == JSVAL_TYPE_MAGIC) { |
| masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArrayAddImpl<0>::offsetOfShape(0)), |
| scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| } |
| |
| // Stow both R0 and R1 (object and key) |
| // But R0 and R1 still hold their values. |
| EmitStowICValues(masm, 2); |
| |
| // We may need to free up some registers. |
| regs = availableGeneralRegs(0); |
| regs.take(R0); |
| regs.take(scratchReg); |
| |
| // Shape guard objects on the proto chain. |
| Register protoReg = regs.takeAny(); |
| for (size_t i = 0; i < protoChainDepth_; i++) { |
| masm.loadObjProto(i == 0 ? obj : protoReg, protoReg); |
| masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow); |
| masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArrayAddImpl<0>::offsetOfShape(i + 1)), |
| scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratchReg, &failureUnstow); |
| } |
| regs.add(protoReg); |
| regs.add(scratchReg); |
| |
| if (needsUpdateStubs()) { |
| // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR } |
| // Load rhs-value in to R0 |
| masm.loadValue(Address(masm.getStackPointer(), 2 * sizeof(Value) + ICStackValueOffset), R0); |
| |
| // Call the type-update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| } |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| |
| // Restore object. |
| obj = masm.extractObject(R0, ExtractTemp0); |
| |
| if (needsUpdateStubs()) { |
| // Trigger post barriers here on the value being written. Fields which |
| // objects can be written to also need update stubs. |
| masm.Push(R1); |
| masm.loadValue(Address(masm.getStackPointer(), sizeof(Value) + ICStackValueOffset), R1); |
| |
| LiveGeneralRegisterSet saveRegs; |
| saveRegs.add(R0); |
| saveRegs.addUnchecked(obj); |
| saveRegs.add(ICStubReg); |
| emitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs); |
| |
| masm.Pop(R1); |
| } |
| |
| // Reset register set. |
| regs = availableGeneralRegs(2); |
| scratchReg = regs.takeAny(); |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| if (unboxedType_ == JSVAL_TYPE_MAGIC) { |
| // Adding element to a native object. |
| |
| // Load obj->elements in scratchReg. |
| masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg); |
| |
| // Bounds check (key == initLength) |
| Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength()); |
| masm.branch32(Assembler::NotEqual, initLength, key, &failure); |
| |
| // Capacity check. |
| Address capacity(scratchReg, ObjectElements::offsetOfCapacity()); |
| masm.branch32(Assembler::BelowOrEqual, capacity, key, &failure); |
| |
| // Check for copy on write elements. |
| Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags()); |
| masm.branchTest32(Assembler::NonZero, elementsFlags, |
| Imm32(ObjectElements::COPY_ON_WRITE), |
| &failure); |
| |
| // Failure is not possible now. Free up registers. |
| regs.add(R0); |
| regs.add(R1); |
| regs.takeUnchecked(obj); |
| regs.takeUnchecked(key); |
| |
| // Increment initLength before write. |
| masm.add32(Imm32(1), initLength); |
| |
| // If length is now <= key, increment length before write. |
| Label skipIncrementLength; |
| Address length(scratchReg, ObjectElements::offsetOfLength()); |
| masm.branch32(Assembler::Above, length, key, &skipIncrementLength); |
| masm.add32(Imm32(1), length); |
| masm.bind(&skipIncrementLength); |
| |
| // Convert int32 values to double if convertDoubleElements is set. In this |
| // case the heap typeset is guaranteed to contain both int32 and double, so |
| // it's okay to store a double. |
| Label dontConvertDoubles; |
| masm.branchTest32(Assembler::Zero, elementsFlags, |
| Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS), |
| &dontConvertDoubles); |
| |
| Address valueAddr(masm.getStackPointer(), ICStackValueOffset); |
| |
| // Note that double arrays are only created by IonMonkey, so if we have no |
| // floating-point support Ion is disabled and there should be no double arrays. |
| if (cx->runtime()->jitSupportsFloatingPoint) |
| masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles); |
| else |
| masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support."); |
| masm.bind(&dontConvertDoubles); |
| |
| // Write the value. No need for pre-barrier since we're not overwriting an old value. |
| ValueOperand tmpVal = regs.takeAnyValue(); |
| BaseIndex element(scratchReg, key, TimesEight); |
| masm.loadValue(valueAddr, tmpVal); |
| masm.storeValue(tmpVal, element); |
| } else { |
| // Adding element to an unboxed array. |
| |
| // Bounds check (key == initLength) |
| Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()); |
| masm.load32(initLengthAddr, scratchReg); |
| masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg); |
| masm.branch32(Assembler::NotEqual, scratchReg, key, &failure); |
| |
| // Capacity check. |
| masm.checkUnboxedArrayCapacity(obj, Int32Key(key), scratchReg, &failure); |
| |
| // Load obj->elements. |
| masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg); |
| |
| // Write the value first, since this can fail. No need for pre-barrier |
| // since we're not overwriting an old value. |
| masm.Push(R0); |
| Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value)); |
| masm.loadValue(valueAddr, R0); |
| BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_))); |
| masm.storeUnboxedProperty(address, unboxedType_, |
| ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0); |
| masm.Pop(R0); |
| |
| // Increment initialized length. |
| masm.add32(Imm32(1), initLengthAddr); |
| |
| // If length is now <= key, increment length. |
| Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength()); |
| Label skipIncrementLength; |
| masm.branch32(Assembler::Above, lengthAddr, key, &skipIncrementLength); |
| masm.add32(Imm32(1), lengthAddr); |
| masm.bind(&skipIncrementLength); |
| } |
| |
| EmitReturnFromIC(masm); |
| |
| if (failurePopR0.used()) { |
| // Failure case: restore the value of R0 |
| masm.bind(&failurePopR0); |
| masm.popValue(R0); |
| masm.jump(&failure); |
| } |
| |
| // Failure case - fail but first unstow R0 and R1 |
| masm.bind(&failureUnstow); |
| EmitUnstowICValues(masm, 2); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // SetElem_TypedArray |
| // |
| |
| // Write an arbitrary value to a typed array or typed object address at dest. |
| // If the value could not be converted to the appropriate format, jump to |
| // failure or failureModifiedScratch. |
| template <typename T> |
| static void |
| StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type, Address value, T dest, |
| Register scratch, Label* failure, Label* failureModifiedScratch) |
| { |
| Label done; |
| |
| if (type == Scalar::Float32 || type == Scalar::Float64) { |
| masm.ensureDouble(value, FloatReg0, failure); |
| if (type == Scalar::Float32) { |
| masm.convertDoubleToFloat32(FloatReg0, ScratchFloat32Reg); |
| masm.storeToTypedFloatArray(type, ScratchFloat32Reg, dest); |
| } else { |
| masm.storeToTypedFloatArray(type, FloatReg0, dest); |
| } |
| } else if (type == Scalar::Uint8Clamped) { |
| Label notInt32; |
| masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); |
| masm.unboxInt32(value, scratch); |
| masm.clampIntToUint8(scratch); |
| |
| Label clamped; |
| masm.bind(&clamped); |
| masm.storeToTypedIntArray(type, scratch, dest); |
| masm.jump(&done); |
| |
| // If the value is a double, clamp to uint8 and jump back. |
| // Else, jump to failure. |
| masm.bind(¬Int32); |
| if (cx->runtime()->jitSupportsFloatingPoint) { |
| masm.branchTestDouble(Assembler::NotEqual, value, failure); |
| masm.unboxDouble(value, FloatReg0); |
| masm.clampDoubleToUint8(FloatReg0, scratch); |
| masm.jump(&clamped); |
| } else { |
| masm.jump(failure); |
| } |
| } else { |
| Label notInt32; |
| masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); |
| masm.unboxInt32(value, scratch); |
| |
| Label isInt32; |
| masm.bind(&isInt32); |
| masm.storeToTypedIntArray(type, scratch, dest); |
| masm.jump(&done); |
| |
| // If the value is a double, truncate and jump back. |
| // Else, jump to failure. |
| masm.bind(¬Int32); |
| if (cx->runtime()->jitSupportsFloatingPoint) { |
| masm.branchTestDouble(Assembler::NotEqual, value, failure); |
| masm.unboxDouble(value, FloatReg0); |
| masm.branchTruncateDouble(FloatReg0, scratch, failureModifiedScratch); |
| masm.jump(&isInt32); |
| } else { |
| masm.jump(failure); |
| } |
| } |
| |
| masm.bind(&done); |
| } |
| |
| bool |
| ICSetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| |
| if (layout_ != Layout_TypedArray) |
| CheckForNeuteredTypedObject(cx, masm, &failure); |
| |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratchReg = regs.takeAny(); |
| |
| // Unbox R0 and shape guard. |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICSetElem_TypedArray::offsetOfShape()), scratchReg); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure); |
| |
| // Ensure the index is an integer. |
| if (cx->runtime()->jitSupportsFloatingPoint) { |
| Label isInt32; |
| masm.branchTestInt32(Assembler::Equal, R1, &isInt32); |
| { |
| // If the index is a double, try to convert it to int32. It's okay |
| // to convert -0 to 0: the shape check ensures the object is a typed |
| // array so the difference is not observable. |
| masm.branchTestDouble(Assembler::NotEqual, R1, &failure); |
| masm.unboxDouble(R1, FloatReg0); |
| masm.convertDoubleToInt32(FloatReg0, scratchReg, &failure, /* negZeroCheck = */false); |
| masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R1); |
| } |
| masm.bind(&isInt32); |
| } else { |
| masm.branchTestInt32(Assembler::NotEqual, R1, &failure); |
| } |
| |
| // Unbox key. |
| Register key = masm.extractInt32(R1, ExtractTemp1); |
| |
| // Bounds check. |
| Label oobWrite; |
| LoadTypedThingLength(masm, layout_, obj, scratchReg); |
| masm.branch32(Assembler::BelowOrEqual, scratchReg, key, |
| expectOutOfBounds_ ? &oobWrite : &failure); |
| |
| // Load the elements vector. |
| LoadTypedThingData(masm, layout_, obj, scratchReg); |
| |
| BaseIndex dest(scratchReg, key, ScaleFromElemWidth(Scalar::byteSize(type_))); |
| Address value(masm.getStackPointer(), ICStackValueOffset); |
| |
| // We need a second scratch register. It's okay to clobber the type tag of |
| // R0 or R1, as long as it's restored before jumping to the next stub. |
| regs = availableGeneralRegs(0); |
| regs.takeUnchecked(obj); |
| regs.takeUnchecked(key); |
| regs.take(scratchReg); |
| Register secondScratch = regs.takeAny(); |
| |
| Label failureModifiedSecondScratch; |
| StoreToTypedArray(cx, masm, type_, value, dest, |
| secondScratch, &failure, &failureModifiedSecondScratch); |
| EmitReturnFromIC(masm); |
| |
| if (failureModifiedSecondScratch.used()) { |
| // Writing to secondScratch may have clobbered R0 or R1, restore them |
| // first. |
| masm.bind(&failureModifiedSecondScratch); |
| masm.tagValue(JSVAL_TYPE_OBJECT, obj, R0); |
| masm.tagValue(JSVAL_TYPE_INT32, key, R1); |
| } |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| |
| if (expectOutOfBounds_) { |
| MOZ_ASSERT(layout_ == Layout_TypedArray); |
| masm.bind(&oobWrite); |
| EmitReturnFromIC(masm); |
| } |
| return true; |
| } |
| |
| // |
| // In_Fallback |
| // |
| |
| static bool |
| TryAttachDenseInStub(JSContext* cx, HandleScript script, ICIn_Fallback* stub, |
| HandleValue key, HandleObject obj, bool* attached) |
| { |
| MOZ_ASSERT(!*attached); |
| |
| if (!IsNativeDenseElementAccess(obj, key)) |
| return true; |
| |
| JitSpew(JitSpew_BaselineIC, " Generating In(Native[Int32] dense) stub"); |
| ICIn_Dense::Compiler compiler(cx, obj->as<NativeObject>().lastProperty()); |
| ICStub* denseStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!denseStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(denseStub); |
| return true; |
| } |
| |
| static bool |
| TryAttachNativeInStub(JSContext* cx, HandleScript script, ICIn_Fallback* stub, |
| HandleValue key, HandleObject obj, bool* attached) |
| { |
| MOZ_ASSERT(!*attached); |
| |
| RootedId id(cx); |
| if (!IsOptimizableElementPropertyName(cx, key, &id)) |
| return true; |
| |
| RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName()); |
| RootedShape shape(cx); |
| RootedObject holder(cx); |
| if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape)) |
| return false; |
| |
| if (IsCacheableGetPropReadSlot(obj, holder, shape)) { |
| ICStub::Kind kind = (obj == holder) ? ICStub::In_Native |
| : ICStub::In_NativePrototype; |
| JitSpew(JitSpew_BaselineIC, " Generating In(Native %s) stub", |
| (obj == holder) ? "direct" : "prototype"); |
| ICInNativeCompiler compiler(cx, kind, obj, holder, name); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| TryAttachNativeInDoesNotExistStub(JSContext* cx, HandleScript script, |
| ICIn_Fallback* stub, HandleValue key, |
| HandleObject obj, bool* attached) |
| { |
| MOZ_ASSERT(!*attached); |
| |
| RootedId id(cx); |
| if (!IsOptimizableElementPropertyName(cx, key, &id)) |
| return true; |
| |
| // Check if does-not-exist can be confirmed on property. |
| RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName()); |
| RootedObject lastProto(cx); |
| size_t protoChainDepth = SIZE_MAX; |
| if (!CheckHasNoSuchProperty(cx, obj, name, &lastProto, &protoChainDepth)) |
| return true; |
| MOZ_ASSERT(protoChainDepth < SIZE_MAX); |
| |
| if (protoChainDepth > ICIn_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH) |
| return true; |
| |
| // Confirmed no-such-property. Add stub. |
| JitSpew(JitSpew_BaselineIC, " Generating In_NativeDoesNotExist stub"); |
| ICInNativeDoesNotExistCompiler compiler(cx, obj, name, protoChainDepth); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| static bool |
| DoInFallback(JSContext* cx, BaselineFrame* frame, ICIn_Fallback* stub_, |
| HandleValue key, HandleValue objValue, MutableHandleValue res) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICIn_Fallback*> stub(frame, stub_); |
| |
| FallbackICSpew(cx, stub, "In"); |
| |
| if (!objValue.isObject()) { |
| ReportValueError(cx, JSMSG_IN_NOT_OBJECT, -1, objValue, nullptr); |
| return false; |
| } |
| |
| RootedObject obj(cx, &objValue.toObject()); |
| |
| bool cond = false; |
| if (!OperatorIn(cx, key, obj, &cond)) |
| return false; |
| res.setBoolean(cond); |
| |
| if (stub.invalid()) |
| return true; |
| |
| if (stub->numOptimizedStubs() >= ICIn_Fallback::MAX_OPTIMIZED_STUBS) |
| return true; |
| |
| if (obj->isNative()) { |
| RootedScript script(cx, frame->script()); |
| bool attached = false; |
| if (cond) { |
| if (!TryAttachDenseInStub(cx, script, stub, key, obj, &attached)) |
| return false; |
| if (attached) |
| return true; |
| if (!TryAttachNativeInStub(cx, script, stub, key, obj, &attached)) |
| return false; |
| if (attached) |
| return true; |
| } else { |
| if (!TryAttachNativeInDoesNotExistStub(cx, script, stub, key, obj, &attached)) |
| return false; |
| if (attached) |
| return true; |
| } |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoInFallbackFn)(JSContext*, BaselineFrame*, ICIn_Fallback*, HandleValue, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoInFallbackInfo = |
| FunctionInfo<DoInFallbackFn>(DoInFallback, TailCall, PopValues(2)); |
| |
| bool |
| ICIn_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| // Sync for the decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoInFallbackInfo, masm); |
| } |
| |
| bool |
| ICInNativeCompiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure, failurePopR0Scratch; |
| |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| // Check key identity. |
| Register strExtract = masm.extractString(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICInNativeStub::offsetOfName()), scratch); |
| masm.branchPtr(Assembler::NotEqual, strExtract, scratch, &failure); |
| |
| // Unbox and shape guard object. |
| Register objReg = masm.extractObject(R1, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICInNativeStub::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| if (kind == ICStub::In_NativePrototype) { |
| // Shape guard holder. Use R0 scrachReg since on x86 there're not enough registers. |
| Register holderReg = R0.scratchReg(); |
| masm.push(R0.scratchReg()); |
| masm.loadPtr(Address(ICStubReg, ICIn_NativePrototype::offsetOfHolder()), |
| holderReg); |
| masm.loadPtr(Address(ICStubReg, ICIn_NativePrototype::offsetOfHolderShape()), |
| scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failurePopR0Scratch); |
| masm.addToStackPtr(Imm32(sizeof(size_t))); |
| } |
| |
| masm.moveValue(BooleanValue(true), R0); |
| |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failurePopR0Scratch); |
| masm.pop(R0.scratchReg()); |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| ICStub* |
| ICInNativeDoesNotExistCompiler::getStub(ICStubSpace* space) |
| { |
| Rooted<ShapeVector> shapes(cx, ShapeVector(cx)); |
| if (!shapes.append(obj_->as<NativeObject>().lastProperty())) |
| return nullptr; |
| |
| if (!GetProtoShapes(obj_, protoChainDepth_, &shapes)) |
| return nullptr; |
| |
| JS_STATIC_ASSERT(ICIn_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH == 8); |
| |
| ICStub* stub = nullptr; |
| switch (protoChainDepth_) { |
| case 0: stub = getStubSpecific<0>(space, shapes); break; |
| case 1: stub = getStubSpecific<1>(space, shapes); break; |
| case 2: stub = getStubSpecific<2>(space, shapes); break; |
| case 3: stub = getStubSpecific<3>(space, shapes); break; |
| case 4: stub = getStubSpecific<4>(space, shapes); break; |
| case 5: stub = getStubSpecific<5>(space, shapes); break; |
| case 6: stub = getStubSpecific<6>(space, shapes); break; |
| case 7: stub = getStubSpecific<7>(space, shapes); break; |
| case 8: stub = getStubSpecific<8>(space, shapes); break; |
| default: MOZ_CRASH("ProtoChainDepth too high."); |
| } |
| if (!stub) |
| return nullptr; |
| return stub; |
| } |
| |
| bool |
| ICInNativeDoesNotExistCompiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure, failurePopR0Scratch; |
| |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| #ifdef DEBUG |
| // Ensure that protoChainDepth_ matches the protoChainDepth stored on the stub. |
| { |
| Label ok; |
| masm.load16ZeroExtend(Address(ICStubReg, ICStub::offsetOfExtra()), scratch); |
| masm.branch32(Assembler::Equal, scratch, Imm32(protoChainDepth_), &ok); |
| masm.assumeUnreachable("Non-matching proto chain depth on stub."); |
| masm.bind(&ok); |
| } |
| #endif // DEBUG |
| |
| // Check key identity. |
| Register strExtract = masm.extractString(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICIn_NativeDoesNotExist::offsetOfName()), scratch); |
| masm.branchPtr(Assembler::NotEqual, strExtract, scratch, &failure); |
| |
| // Unbox and guard against old shape. |
| Register objReg = masm.extractObject(R1, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICIn_NativeDoesNotExist::offsetOfShape(0)), |
| scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure); |
| |
| // Check the proto chain. |
| Register protoReg = R0.scratchReg(); |
| masm.push(R0.scratchReg()); |
| for (size_t i = 0; i < protoChainDepth_; ++i) { |
| masm.loadObjProto(i == 0 ? objReg : protoReg, protoReg); |
| masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failurePopR0Scratch); |
| size_t shapeOffset = ICIn_NativeDoesNotExistImpl<0>::offsetOfShape(i + 1); |
| masm.loadPtr(Address(ICStubReg, shapeOffset), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratch, &failurePopR0Scratch); |
| } |
| masm.addToStackPtr(Imm32(sizeof(size_t))); |
| |
| // Shape and type checks succeeded, ok to proceed. |
| masm.moveValue(BooleanValue(false), R0); |
| |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failurePopR0Scratch); |
| masm.pop(R0.scratchReg()); |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICIn_Dense::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| |
| masm.branchTestInt32(Assembler::NotEqual, R0, &failure); |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| // Unbox and shape guard object. |
| Register obj = masm.extractObject(R1, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICIn_Dense::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, obj, scratch, &failure); |
| |
| // Load obj->elements. |
| masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch); |
| |
| // Unbox key and bounds check. |
| Address initLength(scratch, ObjectElements::offsetOfInitializedLength()); |
| Register key = masm.extractInt32(R0, ExtractTemp0); |
| masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure); |
| |
| // Hole check. |
| JS_STATIC_ASSERT(sizeof(Value) == 8); |
| BaseIndex element(scratch, key, TimesEight); |
| masm.branchTestMagic(Assembler::Equal, element, &failure); |
| |
| masm.moveValue(BooleanValue(true), R0); |
| |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // Try to update existing SetProp setter call stubs for the given holder in |
| // place with a new shape and setter. |
| static bool |
| UpdateExistingSetPropCallStubs(ICSetProp_Fallback* fallbackStub, |
| ICStub::Kind kind, |
| NativeObject* holder, |
| JSObject* receiver, |
| JSFunction* setter) |
| { |
| MOZ_ASSERT(kind == ICStub::SetProp_CallScripted || |
| kind == ICStub::SetProp_CallNative); |
| MOZ_ASSERT(holder); |
| MOZ_ASSERT(receiver); |
| |
| bool isOwnSetter = (holder == receiver); |
| bool foundMatchingStub = false; |
| ReceiverGuard receiverGuard(receiver); |
| for (ICStubConstIterator iter = fallbackStub->beginChainConst(); !iter.atEnd(); iter++) { |
| if (iter->kind() == kind) { |
| ICSetPropCallSetter* setPropStub = static_cast<ICSetPropCallSetter*>(*iter); |
| if (setPropStub->holder() == holder && setPropStub->isOwnSetter() == isOwnSetter) { |
| // If this is an own setter, update the receiver guard as well, |
| // since that's the shape we'll be guarding on. Furthermore, |
| // isOwnSetter() relies on holderShape_ and receiverGuard_ being |
| // the same shape. |
| if (isOwnSetter) |
| setPropStub->receiverGuard().update(receiverGuard); |
| |
| MOZ_ASSERT(setPropStub->holderShape() != holder->lastProperty() || |
| !setPropStub->receiverGuard().matches(receiverGuard), |
| "Why didn't we end up using this stub?"); |
| |
| // We want to update the holder shape to match the new one no |
| // matter what, even if the receiver shape is different. |
| setPropStub->holderShape() = holder->lastProperty(); |
| |
| // Make sure to update the setter, since a shape change might |
| // have changed which setter we want to use. |
| setPropStub->setter() = setter; |
| if (setPropStub->receiverGuard().matches(receiverGuard)) |
| foundMatchingStub = true; |
| } |
| } |
| } |
| |
| return foundMatchingStub; |
| } |
| |
| // Attach an optimized stub for a GETGNAME/CALLGNAME slot-read op. |
| static bool |
| TryAttachGlobalNameValueStub(JSContext* cx, HandleScript script, jsbytecode* pc, |
| ICGetName_Fallback* stub, Handle<ClonedBlockObject*> globalLexical, |
| HandlePropertyName name, bool* attached) |
| { |
| MOZ_ASSERT(globalLexical->isGlobal()); |
| MOZ_ASSERT(!*attached); |
| |
| RootedId id(cx, NameToId(name)); |
| |
| // The property must be found, and it must be found as a normal data property. |
| RootedShape shape(cx, globalLexical->lookup(cx, id)); |
| RootedNativeObject current(cx, globalLexical); |
| while (true) { |
| shape = current->lookup(cx, id); |
| if (shape) |
| break; |
| if (current == globalLexical) { |
| current = &globalLexical->global(); |
| } else { |
| JSObject* proto = current->getProto(); |
| if (!proto || !proto->is<NativeObject>()) |
| return true; |
| current = &proto->as<NativeObject>(); |
| } |
| } |
| |
| // Instantiate this global property, for use during Ion compilation. |
| if (IsIonEnabled(cx)) |
| EnsureTrackPropertyTypes(cx, current, id); |
| |
| if (shape->hasDefaultGetter() && shape->hasSlot()) { |
| |
| // TODO: if there's a previous stub discard it, or just update its Shape + slot? |
| |
| ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| ICStub* newStub; |
| if (current == globalLexical) { |
| MOZ_ASSERT(shape->slot() >= current->numFixedSlots()); |
| uint32_t slot = shape->slot() - current->numFixedSlots(); |
| |
| JitSpew(JitSpew_BaselineIC, " Generating GetName(GlobalName lexical) stub"); |
| ICGetName_GlobalLexical::Compiler compiler(cx, monitorStub, slot); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| } else { |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset); |
| |
| // Check the prototype chain from the global to the current |
| // prototype. Ignore the global lexical scope as it doesn' figure |
| // into the prototype chain. We guard on the global lexical |
| // scope's shape independently. |
| if (!IsCacheableGetPropReadSlot(&globalLexical->global(), current, shape)) |
| return true; |
| |
| JitSpew(JitSpew_BaselineIC, " Generating GetName(GlobalName non-lexical) stub"); |
| ICGetPropNativeCompiler compiler(cx, ICStub::GetName_Global, |
| ICStubCompiler::Engine::Baseline, monitorStub, |
| globalLexical, current, name, isFixedSlot, offset, |
| /* inputDefinitelyObject = */ true); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| } |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| } |
| return true; |
| } |
| |
| // Attach an optimized stub for a GETGNAME/CALLGNAME getter op. |
| static bool |
| TryAttachGlobalNameAccessorStub(JSContext* cx, HandleScript script, jsbytecode* pc, |
| ICGetName_Fallback* stub, Handle<ClonedBlockObject*> globalLexical, |
| HandlePropertyName name, bool* attached, |
| bool* isTemporarilyUnoptimizable) |
| { |
| MOZ_ASSERT(globalLexical->isGlobal()); |
| RootedId id(cx, NameToId(name)); |
| |
| // There must not be a shadowing binding on the global lexical scope. |
| if (globalLexical->lookup(cx, id)) |
| return true; |
| |
| RootedGlobalObject global(cx, &globalLexical->global()); |
| |
| // The property must be found, and it must be found as a normal data property. |
| RootedShape shape(cx); |
| RootedNativeObject current(cx, global); |
| while (true) { |
| shape = current->lookup(cx, id); |
| if (shape) |
| break; |
| JSObject* proto = current->getProto(); |
| if (!proto || !proto->is<NativeObject>()) |
| return true; |
| current = &proto->as<NativeObject>(); |
| } |
| |
| // Instantiate this global property, for use during Ion compilation. |
| if (IsIonEnabled(cx)) |
| EnsureTrackPropertyTypes(cx, current, id); |
| |
| // Try to add a getter stub. We don't handle scripted getters yet; if this |
| // changes we need to make sure IonBuilder::getPropTryCommonGetter (which |
| // requires a Baseline stub) handles non-outerized this objects correctly. |
| bool isScripted; |
| if (IsCacheableGetPropCall(cx, global, current, shape, &isScripted, isTemporarilyUnoptimizable) && |
| !isScripted) |
| { |
| ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| RootedFunction getter(cx, &shape->getterObject()->as<JSFunction>()); |
| |
| // The CallNativeGlobal stub needs to generate 3 shape checks: |
| // |
| // 1. The global lexical scope shape check. |
| // 2. The global object shape check. |
| // 3. The holder shape check. |
| // |
| // 1 is done as the receiver check, as for GETNAME the global lexical scope is in the |
| // receiver position. 2 is done as a manual check that other GetProp stubs don't do. 3 is |
| // done as the holder check per normal. |
| // |
| // In the case the holder is the global object, check 2 is redundant but is not yet |
| // optimized away. |
| JitSpew(JitSpew_BaselineIC, " Generating GetName(GlobalName/NativeGetter) stub"); |
| if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNativeGlobal, current, |
| globalLexical, getter)) |
| { |
| *attached = true; |
| return true; |
| } |
| ICGetPropCallNativeCompiler compiler(cx, ICStub::GetProp_CallNativeGlobal, |
| ICStubCompiler::Engine::Baseline, |
| monitorStub, globalLexical, current, |
| getter, script->pcToOffset(pc), |
| /* outerClass = */ nullptr, |
| /* inputDefinitelyObject = */ true); |
| |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| } |
| return true; |
| } |
| |
| static bool |
| TryAttachScopeNameStub(JSContext* cx, HandleScript script, ICGetName_Fallback* stub, |
| HandleObject initialScopeChain, HandlePropertyName name, bool* attached) |
| { |
| MOZ_ASSERT(!*attached); |
| |
| Rooted<ShapeVector> shapes(cx, ShapeVector(cx)); |
| RootedId id(cx, NameToId(name)); |
| RootedObject scopeChain(cx, initialScopeChain); |
| |
| Shape* shape = nullptr; |
| while (scopeChain) { |
| if (!shapes.append(scopeChain->maybeShape())) |
| return false; |
| |
| if (scopeChain->is<GlobalObject>()) { |
| shape = scopeChain->as<GlobalObject>().lookup(cx, id); |
| if (shape) |
| break; |
| return true; |
| } |
| |
| if (!scopeChain->is<ScopeObject>() || scopeChain->is<DynamicWithObject>()) |
| return true; |
| |
| // Check for an 'own' property on the scope. There is no need to |
| // check the prototype as non-with scopes do not inherit properties |
| // from any prototype. |
| shape = scopeChain->as<NativeObject>().lookup(cx, id); |
| if (shape) |
| break; |
| |
| scopeChain = scopeChain->enclosingScope(); |
| } |
| |
| // We don't handle getters here. When this changes, we need to make sure |
| // IonBuilder::getPropTryCommonGetter (which requires a Baseline stub to |
| // work) handles non-outerized this objects correctly. |
| |
| if (!IsCacheableGetPropReadSlot(scopeChain, scopeChain, shape)) |
| return true; |
| |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset); |
| |
| ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); |
| ICStub* newStub; |
| |
| switch (shapes.length()) { |
| case 1: { |
| ICGetName_Scope<0>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, |
| offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 2: { |
| ICGetName_Scope<1>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, |
| offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 3: { |
| ICGetName_Scope<2>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, |
| offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 4: { |
| ICGetName_Scope<3>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, |
| offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 5: { |
| ICGetName_Scope<4>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, |
| offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 6: { |
| ICGetName_Scope<5>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, |
| offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| case 7: { |
| ICGetName_Scope<6>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, |
| offset); |
| newStub = compiler.getStub(compiler.getStubSpace(script)); |
| break; |
| } |
| default: |
| return true; |
| } |
| |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| static bool |
| DoGetNameFallback(JSContext* cx, BaselineFrame* frame, ICGetName_Fallback* stub_, |
| HandleObject scopeChain, MutableHandleValue res) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICGetName_Fallback*> stub(frame, stub_); |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| mozilla::DebugOnly<JSOp> op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "GetName(%s)", CodeName[JSOp(*pc)]); |
| |
| MOZ_ASSERT(op == JSOP_GETNAME || op == JSOP_GETGNAME); |
| |
| RootedPropertyName name(cx, script->getName(pc)); |
| bool attached = false; |
| bool isTemporarilyUnoptimizable = false; |
| |
| // Attach new stub. |
| if (stub->numOptimizedStubs() >= ICGetName_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with generic stub. |
| attached = true; |
| } |
| |
| if (!attached && IsGlobalOp(JSOp(*pc)) && !script->hasNonSyntacticScope()) { |
| if (!TryAttachGlobalNameAccessorStub(cx, script, pc, stub, |
| scopeChain.as<ClonedBlockObject>(), |
| name, &attached, &isTemporarilyUnoptimizable)) |
| { |
| return false; |
| } |
| } |
| |
| static_assert(JSOP_GETGNAME_LENGTH == JSOP_GETNAME_LENGTH, |
| "Otherwise our check for JSOP_TYPEOF isn't ok"); |
| if (JSOp(pc[JSOP_GETGNAME_LENGTH]) == JSOP_TYPEOF) { |
| if (!GetScopeNameForTypeOf(cx, scopeChain, name, res)) |
| return false; |
| } else { |
| if (!GetScopeName(cx, scopeChain, name, res)) |
| return false; |
| } |
| |
| TypeScript::Monitor(cx, script, pc, res); |
| |
| // Check if debug mode toggling made the stub invalid. |
| if (stub.invalid()) |
| return true; |
| |
| // Add a type monitor stub for the resulting value. |
| if (!stub->addMonitorStubForValue(cx, script, res, ICStubCompiler::Engine::Baseline)) |
| return false; |
| if (attached) |
| return true; |
| |
| if (IsGlobalOp(JSOp(*pc)) && !script->hasNonSyntacticScope()) { |
| Handle<ClonedBlockObject*> globalLexical = scopeChain.as<ClonedBlockObject>(); |
| if (!TryAttachGlobalNameValueStub(cx, script, pc, stub, globalLexical, name, &attached)) |
| return false; |
| } else { |
| if (!TryAttachScopeNameStub(cx, script, stub, scopeChain, name, &attached)) |
| return false; |
| } |
| |
| if (!attached && !isTemporarilyUnoptimizable) |
| stub->noteUnoptimizableAccess(); |
| return true; |
| } |
| |
| typedef bool (*DoGetNameFallbackFn)(JSContext*, BaselineFrame*, ICGetName_Fallback*, |
| HandleObject, MutableHandleValue); |
| static const VMFunction DoGetNameFallbackInfo = FunctionInfo<DoGetNameFallbackFn>(DoGetNameFallback, TailCall); |
| |
| bool |
| ICGetName_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(R0.scratchReg()); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoGetNameFallbackInfo, masm); |
| } |
| |
| bool |
| ICGetName_GlobalLexical::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| Register obj = R0.scratchReg(); |
| Register scratch = R1.scratchReg(); |
| |
| // There's no need to guard on the shape. Lexical bindings are |
| // non-configurable, and this stub cannot be shared across globals. |
| |
| // Load dynamic slot. |
| masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), obj); |
| masm.load32(Address(ICStubReg, ICGetName_GlobalLexical::offsetOfSlot()), scratch); |
| masm.loadValue(BaseIndex(obj, scratch, TimesEight), R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| template <size_t NumHops> |
| bool |
| ICGetName_Scope<NumHops>::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register obj = R0.scratchReg(); |
| Register walker = regs.takeAny(); |
| Register scratch = regs.takeAny(); |
| |
| // Use a local to silence Clang tautological-compare warning if NumHops is 0. |
| size_t numHops = NumHops; |
| |
| for (size_t index = 0; index < NumHops + 1; index++) { |
| Register scope = index ? walker : obj; |
| |
| // Shape guard. |
| masm.loadPtr(Address(ICStubReg, ICGetName_Scope::offsetOfShape(index)), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, scope, scratch, &failure); |
| |
| if (index < numHops) |
| masm.extractObject(Address(scope, ScopeObject::offsetOfEnclosingScope()), walker); |
| } |
| |
| Register scope = NumHops ? walker : obj; |
| |
| if (!isFixedSlot_) { |
| masm.loadPtr(Address(scope, NativeObject::offsetOfSlots()), walker); |
| scope = walker; |
| } |
| |
| masm.load32(Address(ICStubReg, ICGetName_Scope::offsetOfOffset()), scratch); |
| |
| // GETNAME needs to check for uninitialized lexicals. |
| BaseIndex slot(scope, scratch, TimesOne); |
| masm.branchTestMagic(Assembler::Equal, slot, &failure); |
| masm.loadValue(slot, R0); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // BindName_Fallback |
| // |
| |
| static bool |
| DoBindNameFallback(JSContext* cx, BaselineFrame* frame, ICBindName_Fallback* stub, |
| HandleObject scopeChain, MutableHandleValue res) |
| { |
| jsbytecode* pc = stub->icEntry()->pc(frame->script()); |
| mozilla::DebugOnly<JSOp> op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "BindName(%s)", CodeName[JSOp(*pc)]); |
| |
| MOZ_ASSERT(op == JSOP_BINDNAME || op == JSOP_BINDGNAME); |
| |
| RootedPropertyName name(cx, frame->script()->getName(pc)); |
| |
| RootedObject scope(cx); |
| if (!LookupNameUnqualified(cx, name, scopeChain, &scope)) |
| return false; |
| |
| res.setObject(*scope); |
| return true; |
| } |
| |
| typedef bool (*DoBindNameFallbackFn)(JSContext*, BaselineFrame*, ICBindName_Fallback*, |
| HandleObject, MutableHandleValue); |
| static const VMFunction DoBindNameFallbackInfo = |
| FunctionInfo<DoBindNameFallbackFn>(DoBindNameFallback, TailCall); |
| |
| bool |
| ICBindName_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(R0.scratchReg()); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoBindNameFallbackInfo, masm); |
| } |
| |
| // |
| // GetIntrinsic_Fallback |
| // |
| |
| static bool |
| DoGetIntrinsicFallback(JSContext* cx, BaselineFrame* frame, ICGetIntrinsic_Fallback* stub_, |
| MutableHandleValue res) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICGetIntrinsic_Fallback*> stub(frame, stub_); |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| mozilla::DebugOnly<JSOp> op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "GetIntrinsic(%s)", CodeName[JSOp(*pc)]); |
| |
| MOZ_ASSERT(op == JSOP_GETINTRINSIC); |
| |
| if (!GetIntrinsicOperation(cx, pc, res)) |
| return false; |
| |
| // An intrinsic operation will always produce the same result, so only |
| // needs to be monitored once. Attach a stub to load the resulting constant |
| // directly. |
| |
| TypeScript::Monitor(cx, script, pc, res); |
| |
| // Check if debug mode toggling made the stub invalid. |
| if (stub.invalid()) |
| return true; |
| |
| JitSpew(JitSpew_BaselineIC, " Generating GetIntrinsic optimized stub"); |
| ICGetIntrinsic_Constant::Compiler compiler(cx, res); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| typedef bool (*DoGetIntrinsicFallbackFn)(JSContext*, BaselineFrame*, ICGetIntrinsic_Fallback*, |
| MutableHandleValue); |
| static const VMFunction DoGetIntrinsicFallbackInfo = |
| FunctionInfo<DoGetIntrinsicFallbackFn>(DoGetIntrinsicFallback, TailCall); |
| |
| bool |
| ICGetIntrinsic_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoGetIntrinsicFallbackInfo, masm); |
| } |
| |
| bool |
| ICGetIntrinsic_Constant::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| masm.loadValue(Address(ICStubReg, ICGetIntrinsic_Constant::offsetOfValue()), R0); |
| |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| // |
| // SetProp_Fallback |
| // |
| |
| // Attach an optimized property set stub for a SETPROP/SETGNAME/SETNAME op on a |
| // value property. |
| static bool |
| TryAttachSetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc, ICSetProp_Fallback* stub, |
| HandleObject obj, HandleShape oldShape, HandleObjectGroup oldGroup, |
| HandlePropertyName name, HandleId id, HandleValue rhs, bool* attached) |
| { |
| MOZ_ASSERT(!*attached); |
| |
| if (obj->watched()) |
| return true; |
| |
| RootedShape shape(cx); |
| RootedObject holder(cx); |
| if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape)) |
| return false; |
| if (obj != holder) |
| return true; |
| |
| if (!obj->isNative()) { |
| if (obj->is<UnboxedPlainObject>()) { |
| UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando(); |
| if (expando) { |
| shape = expando->lookup(cx, name); |
| if (!shape) |
| return true; |
| } else { |
| return true; |
| } |
| } else { |
| return true; |
| } |
| } |
| |
| size_t chainDepth; |
| if (IsCacheableSetPropAddSlot(cx, obj, oldShape, id, shape, &chainDepth)) { |
| // Don't attach if proto chain depth is too high. |
| if (chainDepth > ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH) |
| return true; |
| |
| // Don't attach if we are adding a property to an object which the new |
| // script properties analysis hasn't been performed for yet, as there |
| // may be a shape change required here afterwards. Pretend we attached |
| // a stub, though, so the access is not marked as unoptimizable. |
| if (oldGroup->newScript() && !oldGroup->newScript()->analyzed()) { |
| *attached = true; |
| return true; |
| } |
| |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset); |
| |
| JitSpew(JitSpew_BaselineIC, " Generating SetProp(NativeObject.ADD) stub"); |
| ICSetPropNativeAddCompiler compiler(cx, obj, oldShape, oldGroup, |
| chainDepth, isFixedSlot, offset); |
| ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| if (IsCacheableSetPropWriteSlot(obj, oldShape, shape)) { |
| // For some property writes, such as the initial overwrite of global |
| // properties, TI will not mark the property as having been |
| // overwritten. Don't attach a stub in this case, so that we don't |
| // execute another write to the property without TI seeing that write. |
| EnsureTrackPropertyTypes(cx, obj, id); |
| if (!PropertyHasBeenMarkedNonConstant(obj, id)) { |
| *attached = true; |
| return true; |
| } |
| |
| bool isFixedSlot; |
| uint32_t offset; |
| GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset); |
| |
| JitSpew(JitSpew_BaselineIC, " Generating SetProp(NativeObject.PROP) stub"); |
| MOZ_ASSERT(LastPropertyForSetProp(obj) == oldShape, |
| "Should this really be a SetPropWriteSlot?"); |
| ICSetProp_Native::Compiler compiler(cx, obj, isFixedSlot, offset); |
| ICSetProp_Native* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) |
| return false; |
| |
| if (IsPreliminaryObject(obj)) |
| newStub->notePreliminaryObject(); |
| else |
| StripPreliminaryObjectStubs(cx, stub); |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| return true; |
| } |
| |
| // Attach an optimized property set stub for a SETPROP/SETGNAME/SETNAME op on |
| // an accessor property. |
| static bool |
| TryAttachSetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc, |
| ICSetProp_Fallback* stub, |
| HandleObject obj, const RootedReceiverGuard& receiverGuard, |
| HandlePropertyName name, |
| HandleId id, HandleValue rhs, bool* attached, |
| bool* isTemporarilyUnoptimizable) |
| { |
| MOZ_ASSERT(!*attached); |
| MOZ_ASSERT(!*isTemporarilyUnoptimizable); |
| |
| if (obj->watched()) |
| return true; |
| |
| RootedShape shape(cx); |
| RootedObject holder(cx); |
| if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape)) |
| return false; |
| |
| bool isScripted = false; |
| bool cacheableCall = IsCacheableSetPropCall(cx, obj, holder, shape, |
| &isScripted, isTemporarilyUnoptimizable); |
| |
| // Try handling scripted setters. |
| if (cacheableCall && isScripted) { |
| RootedFunction callee(cx, &shape->setterObject()->as<JSFunction>()); |
| MOZ_ASSERT(callee->hasScript()); |
| |
| if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallScripted, |
| &holder->as<NativeObject>(), obj, callee)) { |
| *attached = true; |
| return true; |
| } |
| |
| JitSpew(JitSpew_BaselineIC, " Generating SetProp(NativeObj/ScriptedSetter %s:%" PRIuSIZE ") stub", |
| callee->nonLazyScript()->filename(), callee->nonLazyScript()->lineno()); |
| |
| ICSetProp_CallScripted::Compiler compiler(cx, obj, holder, callee, script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| // Try handling JSNative setters. |
| if (cacheableCall && !isScripted) { |
| RootedFunction callee(cx, &shape->setterObject()->as<JSFunction>()); |
| MOZ_ASSERT(callee->isNative()); |
| |
| if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallNative, |
| &holder->as<NativeObject>(), obj, callee)) { |
| *attached = true; |
| return true; |
| } |
| |
| JitSpew(JitSpew_BaselineIC, " Generating SetProp(NativeObj/NativeSetter %p) stub", |
| callee->native()); |
| |
| ICSetProp_CallNative::Compiler compiler(cx, obj, holder, callee, script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| TryAttachUnboxedSetPropStub(JSContext* cx, HandleScript script, |
| ICSetProp_Fallback* stub, HandleId id, |
| HandleObject obj, HandleValue rhs, bool* attached) |
| { |
| MOZ_ASSERT(!*attached); |
| |
| if (!cx->runtime()->jitSupportsFloatingPoint) |
| return true; |
| |
| if (!obj->is<UnboxedPlainObject>()) |
| return true; |
| |
| const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id); |
| if (!property) |
| return true; |
| |
| ICSetProp_Unboxed::Compiler compiler(cx, obj->group(), |
| property->offset + UnboxedPlainObject::offsetOfData(), |
| property->type); |
| ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) |
| return false; |
| |
| stub->addNewStub(newStub); |
| |
| StripPreliminaryObjectStubs(cx, stub); |
| |
| *attached = true; |
| return true; |
| } |
| |
| static bool |
| TryAttachTypedObjectSetPropStub(JSContext* cx, HandleScript script, |
| ICSetProp_Fallback* stub, HandleId id, |
| HandleObject obj, HandleValue rhs, bool* attached) |
| { |
| MOZ_ASSERT(!*attached); |
| |
| if (!cx->runtime()->jitSupportsFloatingPoint) |
| return true; |
| |
| if (!obj->is<TypedObject>()) |
| return true; |
| |
| if (!obj->as<TypedObject>().typeDescr().is<StructTypeDescr>()) |
| return true; |
| Rooted<StructTypeDescr*> structDescr(cx); |
| structDescr = &obj->as<TypedObject>().typeDescr().as<StructTypeDescr>(); |
| |
| size_t fieldIndex; |
| if (!structDescr->fieldIndex(id, &fieldIndex)) |
| return true; |
| |
| Rooted<TypeDescr*> fieldDescr(cx, &structDescr->fieldDescr(fieldIndex)); |
| if (!fieldDescr->is<SimpleTypeDescr>()) |
| return true; |
| |
| uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex); |
| |
| ICSetProp_TypedObject::Compiler compiler(cx, obj->maybeShape(), obj->group(), fieldOffset, |
| &fieldDescr->as<SimpleTypeDescr>()); |
| ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, script, obj, id, rhs)) |
| return false; |
| |
| stub->addNewStub(newStub); |
| |
| *attached = true; |
| return true; |
| } |
| |
| static bool |
| DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_, |
| HandleValue lhs, HandleValue rhs, MutableHandleValue res) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICSetProp_Fallback*> stub(frame, stub_); |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "SetProp(%s)", CodeName[op]); |
| |
| MOZ_ASSERT(op == JSOP_SETPROP || |
| op == JSOP_STRICTSETPROP || |
| op == JSOP_SETNAME || |
| op == JSOP_STRICTSETNAME || |
| op == JSOP_SETGNAME || |
| op == JSOP_STRICTSETGNAME || |
| op == JSOP_INITPROP || |
| op == JSOP_INITLOCKEDPROP || |
| op == JSOP_INITHIDDENPROP || |
| op == JSOP_SETALIASEDVAR || |
| op == JSOP_INITALIASEDLEXICAL || |
| op == JSOP_INITGLEXICAL); |
| |
| RootedPropertyName name(cx); |
| if (op == JSOP_SETALIASEDVAR || op == JSOP_INITALIASEDLEXICAL) |
| name = ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc); |
| else |
| name = script->getName(pc); |
| RootedId id(cx, NameToId(name)); |
| |
| RootedObject obj(cx, ToObjectFromStack(cx, lhs)); |
| if (!obj) |
| return false; |
| RootedShape oldShape(cx, obj->maybeShape()); |
| RootedObjectGroup oldGroup(cx, obj->getGroup(cx)); |
| if (!oldGroup) |
| return false; |
| RootedReceiverGuard oldGuard(cx, ReceiverGuard(obj)); |
| |
| if (obj->is<UnboxedPlainObject>()) { |
| MOZ_ASSERT(!oldShape); |
| if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) |
| oldShape = expando->lastProperty(); |
| } |
| |
| bool attached = false; |
| // There are some reasons we can fail to attach a stub that are temporary. |
| // We want to avoid calling noteUnoptimizableAccess() if the reason we |
| // failed to attach a stub is one of those temporary reasons, since we might |
| // end up attaching a stub for the exact same access later. |
| bool isTemporarilyUnoptimizable = false; |
| if (stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS && |
| lhs.isObject() && |
| !TryAttachSetAccessorPropStub(cx, script, pc, stub, obj, oldGuard, name, id, |
| rhs, &attached, &isTemporarilyUnoptimizable)) |
| { |
| return false; |
| } |
| |
| if (op == JSOP_INITPROP || |
| op == JSOP_INITLOCKEDPROP || |
| op == JSOP_INITHIDDENPROP) |
| { |
| if (!InitPropertyOperation(cx, op, obj, id, rhs)) |
| return false; |
| } else if (op == JSOP_SETNAME || |
| op == JSOP_STRICTSETNAME || |
| op == JSOP_SETGNAME || |
| op == JSOP_STRICTSETGNAME) |
| { |
| if (!SetNameOperation(cx, script, pc, obj, rhs)) |
| return false; |
| } else if (op == JSOP_SETALIASEDVAR || op == JSOP_INITALIASEDLEXICAL) { |
| obj->as<ScopeObject>().setAliasedVar(cx, ScopeCoordinate(pc), name, rhs); |
| } else if (op == JSOP_INITGLEXICAL) { |
| RootedValue v(cx, rhs); |
| ClonedBlockObject* lexicalScope; |
| if (script->hasNonSyntacticScope()) |
| lexicalScope = &NearestEnclosingExtensibleLexicalScope(frame->scopeChain()); |
| else |
| lexicalScope = &cx->global()->lexicalScope(); |
| InitGlobalLexicalOperation(cx, lexicalScope, script, pc, v); |
| } else { |
| MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP); |
| |
| RootedValue v(cx, rhs); |
| if (!PutProperty(cx, obj, id, v, op == JSOP_STRICTSETPROP)) |
| return false; |
| } |
| |
| // Leave the RHS on the stack. |
| res.set(rhs); |
| |
| // Check if debug mode toggling made the stub invalid. |
| if (stub.invalid()) |
| return true; |
| |
| if (stub->numOptimizedStubs() >= ICSetProp_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with generic setprop stub. |
| return true; |
| } |
| |
| if (!attached && |
| lhs.isObject() && |
| !TryAttachSetValuePropStub(cx, script, pc, stub, obj, oldShape, oldGroup, |
| name, id, rhs, &attached)) |
| { |
| return false; |
| } |
| if (attached) |
| return true; |
| |
| if (!attached && |
| lhs.isObject() && |
| !TryAttachUnboxedSetPropStub(cx, script, stub, id, obj, rhs, &attached)) |
| { |
| return false; |
| } |
| if (attached) |
| return true; |
| |
| if (!attached && |
| lhs.isObject() && |
| !TryAttachTypedObjectSetPropStub(cx, script, stub, id, obj, rhs, &attached)) |
| { |
| return false; |
| } |
| if (attached) |
| return true; |
| |
| MOZ_ASSERT(!attached); |
| if (!isTemporarilyUnoptimizable) |
| stub->noteUnoptimizableAccess(); |
| |
| return true; |
| } |
| |
| typedef bool (*DoSetPropFallbackFn)(JSContext*, BaselineFrame*, ICSetProp_Fallback*, |
| HandleValue, HandleValue, MutableHandleValue); |
| static const VMFunction DoSetPropFallbackInfo = |
| FunctionInfo<DoSetPropFallbackFn>(DoSetPropFallback, TailCall, PopValues(2)); |
| |
| bool |
| ICSetProp_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| // Ensure stack is fully synced for the expression decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| // Push arguments. |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| if (!tailCallVM(DoSetPropFallbackInfo, masm)) |
| return false; |
| |
| // Even though the fallback frame doesn't enter a stub frame, the CallScripted |
| // frame that we are emulating does. Again, we lie. |
| #ifdef DEBUG |
| EmitRepushTailCallReg(masm); |
| EmitStowICValues(masm, 1); |
| enterStubFrame(masm, R1.scratchReg()); |
| #else |
| inStubFrame_ = true; |
| #endif |
| |
| // What follows is bailout-only code for inlined script getters. |
| // The return address pointed to by the baseline stack points here. |
| returnOffset_ = masm.currentOffset(); |
| |
| leaveStubFrame(masm, true); |
| |
| // Retrieve the stashed initial argument from the caller's frame before returning |
| EmitUnstowICValues(masm, 1); |
| EmitReturnFromIC(masm); |
| |
| return true; |
| } |
| |
| void |
| ICSetProp_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code) |
| { |
| cx->compartment()->jitCompartment()->initBaselineSetPropReturnAddr(code->raw() + returnOffset_); |
| } |
| |
| static void |
| GuardGroupAndShapeMaybeUnboxedExpando(MacroAssembler& masm, JSObject* obj, |
| Register object, Register scratch, |
| size_t offsetOfGroup, size_t offsetOfShape, Label* failure) |
| { |
| // Guard against object group. |
| masm.loadPtr(Address(ICStubReg, offsetOfGroup), scratch); |
| masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), scratch, |
| failure); |
| |
| // Guard against shape or expando shape. |
| masm.loadPtr(Address(ICStubReg, offsetOfShape), scratch); |
| if (obj->is<UnboxedPlainObject>()) { |
| Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando()); |
| masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure); |
| Label done; |
| masm.push(object); |
| masm.loadPtr(expandoAddress, object); |
| masm.branchTestObjShape(Assembler::Equal, object, scratch, &done); |
| masm.pop(object); |
| masm.jump(failure); |
| masm.bind(&done); |
| masm.pop(object); |
| } else { |
| masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure); |
| } |
| } |
| |
| bool |
| ICSetProp_Native::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| GuardGroupAndShapeMaybeUnboxedExpando(masm, obj_, objReg, scratch, |
| ICSetProp_Native::offsetOfGroup(), |
| ICSetProp_Native::offsetOfShape(), |
| &failure); |
| |
| // Stow both R0 and R1 (object and value). |
| EmitStowICValues(masm, 2); |
| |
| // Type update stub expects the value to check in R0. |
| masm.moveValue(R1, R0); |
| |
| // Call the type-update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| |
| regs.add(R0); |
| regs.takeUnchecked(objReg); |
| |
| Register holderReg; |
| if (obj_->is<UnboxedPlainObject>()) { |
| // We are loading off the expando object, so use that for the holder. |
| holderReg = regs.takeAny(); |
| masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); |
| if (!isFixedSlot_) |
| masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg); |
| } else if (isFixedSlot_) { |
| holderReg = objReg; |
| } else { |
| holderReg = regs.takeAny(); |
| masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg); |
| } |
| |
| // Perform the store. |
| masm.load32(Address(ICStubReg, ICSetProp_Native::offsetOfOffset()), scratch); |
| EmitPreBarrier(masm, BaseIndex(holderReg, scratch, TimesOne), MIRType_Value); |
| masm.storeValue(R1, BaseIndex(holderReg, scratch, TimesOne)); |
| if (holderReg != objReg) |
| regs.add(holderReg); |
| if (cx->runtime()->gc.nursery.exists()) { |
| Register scr = regs.takeAny(); |
| LiveGeneralRegisterSet saveRegs; |
| saveRegs.add(R1); |
| emitPostWriteBarrierSlot(masm, objReg, R1, scr, saveRegs); |
| regs.add(scr); |
| } |
| |
| // The RHS has to be in R0. |
| masm.moveValue(R1, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| ICUpdatedStub* |
| ICSetPropNativeAddCompiler::getStub(ICStubSpace* space) |
| { |
| Rooted<ShapeVector> shapes(cx, ShapeVector(cx)); |
| if (!shapes.append(oldShape_)) |
| return nullptr; |
| |
| if (!GetProtoShapes(obj_, protoChainDepth_, &shapes)) |
| return nullptr; |
| |
| JS_STATIC_ASSERT(ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH == 4); |
| |
| ICUpdatedStub* stub = nullptr; |
| switch(protoChainDepth_) { |
| case 0: stub = getStubSpecific<0>(space, shapes); break; |
| case 1: stub = getStubSpecific<1>(space, shapes); break; |
| case 2: stub = getStubSpecific<2>(space, shapes); break; |
| case 3: stub = getStubSpecific<3>(space, shapes); break; |
| case 4: stub = getStubSpecific<4>(space, shapes); break; |
| default: MOZ_CRASH("ProtoChainDepth too high."); |
| } |
| if (!stub || !stub->initUpdatingChain(cx, space)) |
| return nullptr; |
| return stub; |
| } |
| |
| bool |
| ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| Label failureUnstow; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| GuardGroupAndShapeMaybeUnboxedExpando(masm, obj_, objReg, scratch, |
| ICSetProp_NativeAdd::offsetOfGroup(), |
| ICSetProp_NativeAddImpl<0>::offsetOfShape(0), |
| &failure); |
| |
| // Stow both R0 and R1 (object and value). |
| EmitStowICValues(masm, 2); |
| |
| regs = availableGeneralRegs(1); |
| scratch = regs.takeAny(); |
| Register protoReg = regs.takeAny(); |
| // Check the proto chain. |
| for (size_t i = 0; i < protoChainDepth_; i++) { |
| masm.loadObjProto(i == 0 ? objReg : protoReg, protoReg); |
| masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAddImpl<0>::offsetOfShape(i + 1)), |
| scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratch, &failureUnstow); |
| } |
| |
| // Shape and type checks succeeded, ok to proceed. |
| |
| // Load RHS into R0 for TypeUpdate check. |
| // Stack is currently: [..., ObjValue, RHSValue, MaybeReturnAddr? ] |
| masm.loadValue(Address(masm.getStackPointer(), ICStackValueOffset), R0); |
| |
| // Call the type-update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| regs = availableGeneralRegs(2); |
| scratch = regs.takeAny(); |
| |
| if (obj_->is<PlainObject>()) { |
| // Try to change the object's group. |
| Label noGroupChange; |
| |
| // Check if the cache has a new group to change to. |
| masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewGroup()), scratch); |
| masm.branchTestPtr(Assembler::Zero, scratch, scratch, &noGroupChange); |
| |
| // Check if the old group still has a newScript. |
| masm.loadPtr(Address(objReg, JSObject::offsetOfGroup()), scratch); |
| masm.branchPtr(Assembler::Equal, |
| Address(scratch, ObjectGroup::offsetOfAddendum()), |
| ImmWord(0), |
| &noGroupChange); |
| |
| // Reload the new group from the cache. |
| masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewGroup()), scratch); |
| |
| // Change the object's group. |
| Address groupAddr(objReg, JSObject::offsetOfGroup()); |
| EmitPreBarrier(masm, groupAddr, MIRType_ObjectGroup); |
| masm.storePtr(scratch, groupAddr); |
| |
| masm.bind(&noGroupChange); |
| } |
| |
| Register holderReg; |
| regs.add(R0); |
| regs.takeUnchecked(objReg); |
| |
| if (obj_->is<UnboxedPlainObject>()) { |
| holderReg = regs.takeAny(); |
| masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); |
| |
| // Write the expando object's new shape. |
| Address shapeAddr(holderReg, JSObject::offsetOfShape()); |
| EmitPreBarrier(masm, shapeAddr, MIRType_Shape); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch); |
| masm.storePtr(scratch, shapeAddr); |
| |
| if (!isFixedSlot_) |
| masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg); |
| } else { |
| // Write the object's new shape. |
| Address shapeAddr(objReg, JSObject::offsetOfShape()); |
| EmitPreBarrier(masm, shapeAddr, MIRType_Shape); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch); |
| masm.storePtr(scratch, shapeAddr); |
| |
| if (isFixedSlot_) { |
| holderReg = objReg; |
| } else { |
| holderReg = regs.takeAny(); |
| masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg); |
| } |
| } |
| |
| // Perform the store. No write barrier required since this is a new |
| // initialization. |
| masm.load32(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfOffset()), scratch); |
| masm.storeValue(R1, BaseIndex(holderReg, scratch, TimesOne)); |
| |
| if (holderReg != objReg) |
| regs.add(holderReg); |
| |
| if (cx->runtime()->gc.nursery.exists()) { |
| Register scr = regs.takeAny(); |
| LiveGeneralRegisterSet saveRegs; |
| saveRegs.add(R1); |
| emitPostWriteBarrierSlot(masm, objReg, R1, scr, saveRegs); |
| } |
| |
| // The RHS has to be in R0. |
| masm.moveValue(R1, R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failureUnstow); |
| EmitUnstowICValues(masm, 2); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICSetProp_Unboxed::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| // Unbox and group guard. |
| Register object = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_Unboxed::offsetOfGroup()), scratch); |
| masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), scratch, |
| &failure); |
| |
| if (needsUpdateStubs()) { |
| // Stow both R0 and R1 (object and value). |
| EmitStowICValues(masm, 2); |
| |
| // Move RHS into R0 for TypeUpdate check. |
| masm.moveValue(R1, R0); |
| |
| // Call the type update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| |
| // The TypeUpdate IC may have smashed object. Rederive it. |
| masm.unboxObject(R0, object); |
| |
| // Trigger post barriers here on the values being written. Fields which |
| // objects can be written to also need update stubs. |
| LiveGeneralRegisterSet saveRegs; |
| saveRegs.add(R0); |
| saveRegs.add(R1); |
| saveRegs.addUnchecked(object); |
| saveRegs.add(ICStubReg); |
| emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs); |
| } |
| |
| // Compute the address being written to. |
| masm.load32(Address(ICStubReg, ICSetProp_Unboxed::offsetOfFieldOffset()), scratch); |
| BaseIndex address(object, scratch, TimesOne); |
| |
| EmitUnboxedPreBarrierForBaseline(masm, address, fieldType_); |
| masm.storeUnboxedProperty(address, fieldType_, |
| ConstantOrRegister(TypedOrValueRegister(R1)), &failure); |
| |
| // The RHS has to be in R0. |
| masm.moveValue(R1, R0); |
| |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICSetProp_TypedObject::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| |
| CheckForNeuteredTypedObject(cx, masm, &failure); |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(2)); |
| Register scratch = regs.takeAny(); |
| |
| // Unbox and shape guard. |
| Register object = masm.extractObject(R0, ExtractTemp0); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_TypedObject::offsetOfShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, object, scratch, &failure); |
| |
| // Guard that the object group matches. |
| masm.loadPtr(Address(ICStubReg, ICSetProp_TypedObject::offsetOfGroup()), scratch); |
| masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), scratch, |
| &failure); |
| |
| if (needsUpdateStubs()) { |
| // Stow both R0 and R1 (object and value). |
| EmitStowICValues(masm, 2); |
| |
| // Move RHS into R0 for TypeUpdate check. |
| masm.moveValue(R1, R0); |
| |
| // Call the type update stub. |
| if (!callTypeUpdateIC(masm, sizeof(Value))) |
| return false; |
| |
| // Unstow R0 and R1 (object and key) |
| EmitUnstowICValues(masm, 2); |
| |
| // We may have clobbered object in the TypeUpdate IC. Rederive it. |
| masm.unboxObject(R0, object); |
| |
| // Trigger post barriers here on the values being written. Descriptors |
| // which can write objects also need update stubs. |
| LiveGeneralRegisterSet saveRegs; |
| saveRegs.add(R0); |
| saveRegs.add(R1); |
| saveRegs.addUnchecked(object); |
| saveRegs.add(ICStubReg); |
| emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs); |
| } |
| |
| // Save the rhs on the stack so we can get a second scratch register. |
| Label failurePopRHS; |
| masm.pushValue(R1); |
| regs = availableGeneralRegs(1); |
| regs.takeUnchecked(object); |
| regs.take(scratch); |
| Register secondScratch = regs.takeAny(); |
| |
| // Get the object's data pointer. |
| LoadTypedThingData(masm, layout_, object, scratch); |
| |
| // Compute the address being written to. |
| masm.load32(Address(ICStubReg, ICSetProp_TypedObject::offsetOfFieldOffset()), secondScratch); |
| masm.addPtr(secondScratch, scratch); |
| |
| Address dest(scratch, 0); |
| Address value(masm.getStackPointer(), 0); |
| |
| if (fieldDescr_->is<ScalarTypeDescr>()) { |
| Scalar::Type type = fieldDescr_->as<ScalarTypeDescr>().type(); |
| StoreToTypedArray(cx, masm, type, value, dest, |
| secondScratch, &failurePopRHS, &failurePopRHS); |
| masm.popValue(R1); |
| EmitReturnFromIC(masm); |
| } else { |
| ReferenceTypeDescr::Type type = fieldDescr_->as<ReferenceTypeDescr>().type(); |
| |
| masm.popValue(R1); |
| |
| switch (type) { |
| case ReferenceTypeDescr::TYPE_ANY: |
| EmitPreBarrier(masm, dest, MIRType_Value); |
| masm.storeValue(R1, dest); |
| break; |
| |
| case ReferenceTypeDescr::TYPE_OBJECT: { |
| EmitPreBarrier(masm, dest, MIRType_Object); |
| Label notObject; |
| masm.branchTestObject(Assembler::NotEqual, R1, ¬Object); |
| Register rhsObject = masm.extractObject(R1, ExtractTemp0); |
| masm.storePtr(rhsObject, dest); |
| EmitReturnFromIC(masm); |
| masm.bind(¬Object); |
| masm.branchTestNull(Assembler::NotEqual, R1, &failure); |
| masm.storePtr(ImmWord(0), dest); |
| break; |
| } |
| |
| case ReferenceTypeDescr::TYPE_STRING: { |
| EmitPreBarrier(masm, dest, MIRType_String); |
| masm.branchTestString(Assembler::NotEqual, R1, &failure); |
| Register rhsString = masm.extractString(R1, ExtractTemp0); |
| masm.storePtr(rhsString, dest); |
| break; |
| } |
| |
| default: |
| MOZ_CRASH(); |
| } |
| |
| EmitReturnFromIC(masm); |
| } |
| |
| masm.bind(&failurePopRHS); |
| masm.popValue(R1); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICSetProp_CallScripted::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| Label failureUnstow; |
| Label failureLeaveStubFrame; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Stow R0 and R1 to free up registers. |
| EmitStowICValues(masm, 2); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register scratch = regs.takeAnyExcluding(ICTailCallReg); |
| |
| // Unbox and shape guard. |
| uint32_t framePushed = masm.framePushed(); |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch, |
| ICSetProp_CallScripted::offsetOfReceiverGuard(), &failureUnstow); |
| |
| if (receiver_ != holder_) { |
| Register holderReg = regs.takeAny(); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_CallScripted::offsetOfHolder()), holderReg); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_CallScripted::offsetOfHolderShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failureUnstow); |
| regs.add(holderReg); |
| } |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, scratch); |
| |
| // Load callee function and code. To ensure that |code| doesn't end up being |
| // ArgumentsRectifierReg, if it's available we assign it to |callee| instead. |
| Register callee; |
| if (regs.has(ArgumentsRectifierReg)) { |
| callee = ArgumentsRectifierReg; |
| regs.take(callee); |
| } else { |
| callee = regs.takeAny(); |
| } |
| Register code = regs.takeAny(); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_CallScripted::offsetOfSetter()), callee); |
| masm.branchIfFunctionHasNoScript(callee, &failureLeaveStubFrame); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code); |
| masm.loadBaselineOrIonRaw(code, code, &failureLeaveStubFrame); |
| |
| // Align the stack such that the JitFrameLayout is aligned on |
| // JitStackAlignment. |
| masm.alignJitStackBasedOnNArgs(1); |
| |
| // Setter is called with the new value as the only argument, and |obj| as thisv. |
| // Note that we use Push, not push, so that callJit will align the stack |
| // properly on ARM. |
| |
| // To Push R1, read it off of the stowed values on stack. |
| // Stack: [ ..., R0, R1, ..STUBFRAME-HEADER.., padding? ] |
| masm.PushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE)); |
| masm.Push(R0); |
| EmitBaselineCreateStubFrameDescriptor(masm, scratch); |
| masm.Push(Imm32(1)); // ActualArgc is 1 |
| masm.Push(callee); |
| masm.Push(scratch); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), scratch); |
| masm.branch32(Assembler::BelowOrEqual, scratch, Imm32(1), &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| MOZ_ASSERT(ArgumentsRectifierReg != code); |
| |
| JitCode* argumentsRectifier = |
| cx->runtime()->jitRuntime()->getArgumentsRectifier(); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), code); |
| masm.loadPtr(Address(code, JitCode::offsetOfCode()), code); |
| masm.movePtr(ImmWord(1), ArgumentsRectifierReg); |
| } |
| |
| masm.bind(&noUnderflow); |
| masm.callJit(code); |
| |
| uint32_t framePushedAfterCall = masm.framePushed(); |
| |
| leaveStubFrame(masm, true); |
| // Do not care about return value from function. The original RHS should be returned |
| // as the result of this operation. |
| EmitUnstowICValues(masm, 2); |
| masm.moveValue(R1, R0); |
| EmitReturnFromIC(masm); |
| |
| // Leave stub frame and go to next stub. |
| masm.bind(&failureLeaveStubFrame); |
| masm.setFramePushed(framePushedAfterCall); |
| inStubFrame_ = true; |
| leaveStubFrame(masm, false); |
| |
| // Unstow R0 and R1 |
| masm.bind(&failureUnstow); |
| masm.setFramePushed(framePushed); |
| EmitUnstowICValues(masm, 2); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| static bool |
| DoCallNativeSetter(JSContext* cx, HandleFunction callee, HandleObject obj, HandleValue val) |
| { |
| MOZ_ASSERT(callee->isNative()); |
| JSNative natfun = callee->native(); |
| |
| JS::AutoValueArray<3> vp(cx); |
| vp[0].setObject(*callee.get()); |
| vp[1].setObject(*obj.get()); |
| vp[2].set(val); |
| |
| return natfun(cx, 1, vp.begin()); |
| } |
| |
| typedef bool (*DoCallNativeSetterFn)(JSContext*, HandleFunction, HandleObject, HandleValue); |
| static const VMFunction DoCallNativeSetterInfo = |
| FunctionInfo<DoCallNativeSetterFn>(DoCallNativeSetter); |
| |
| bool |
| ICSetProp_CallNative::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| Label failureUnstow; |
| |
| // Guard input is an object. |
| masm.branchTestObject(Assembler::NotEqual, R0, &failure); |
| |
| // Stow R0 and R1 to free up registers. |
| EmitStowICValues(masm, 2); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register scratch = regs.takeAnyExcluding(ICTailCallReg); |
| |
| // Unbox and shape guard. |
| uint32_t framePushed = masm.framePushed(); |
| Register objReg = masm.extractObject(R0, ExtractTemp0); |
| GuardReceiverObject(masm, ReceiverGuard(receiver_), objReg, scratch, |
| ICSetProp_CallNative::offsetOfReceiverGuard(), &failureUnstow); |
| |
| if (receiver_ != holder_) { |
| Register holderReg = regs.takeAny(); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_CallNative::offsetOfHolder()), holderReg); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_CallNative::offsetOfHolderShape()), scratch); |
| masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failureUnstow); |
| regs.add(holderReg); |
| } |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, scratch); |
| |
| // Load callee function and code. To ensure that |code| doesn't end up being |
| // ArgumentsRectifierReg, if it's available we assign it to |callee| instead. |
| Register callee = regs.takeAny(); |
| masm.loadPtr(Address(ICStubReg, ICSetProp_CallNative::offsetOfSetter()), callee); |
| |
| // To Push R1, read it off of the stowed values on stack. |
| // Stack: [ ..., R0, R1, ..STUBFRAME-HEADER.. ] |
| masm.moveStackPtrTo(scratch); |
| masm.pushValue(Address(scratch, STUB_FRAME_SIZE)); |
| masm.push(objReg); |
| masm.push(callee); |
| |
| // Don't need to preserve R0 anymore. |
| regs.add(R0); |
| |
| if (!callVM(DoCallNativeSetterInfo, masm)) |
| return false; |
| leaveStubFrame(masm); |
| |
| // Do not care about return value from function. The original RHS should be returned |
| // as the result of this operation. |
| EmitUnstowICValues(masm, 2); |
| masm.moveValue(R1, R0); |
| EmitReturnFromIC(masm); |
| |
| // Unstow R0 and R1 |
| masm.bind(&failureUnstow); |
| masm.setFramePushed(framePushed); |
| EmitUnstowICValues(masm, 2); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // Call_Fallback |
| // |
| |
| static bool |
| TryAttachFunApplyStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsbytecode* pc, |
| HandleValue thisv, uint32_t argc, Value* argv, bool* attached) |
| { |
| if (argc != 2) |
| return true; |
| |
| if (!thisv.isObject() || !thisv.toObject().is<JSFunction>()) |
| return true; |
| RootedFunction target(cx, &thisv.toObject().as<JSFunction>()); |
| |
| bool isScripted = target->hasJITCode(); |
| |
| // right now, only handle situation where second argument is |arguments| |
| if (argv[1].isMagic(JS_OPTIMIZED_ARGUMENTS) && !script->needsArgsObj()) { |
| if (isScripted && !stub->hasStub(ICStub::Call_ScriptedApplyArguments)) { |
| JitSpew(JitSpew_BaselineIC, " Generating Call_ScriptedApplyArguments stub"); |
| |
| ICCall_ScriptedApplyArguments::Compiler compiler( |
| cx, stub->fallbackMonitorStub()->firstMonitorStub(), script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| // TODO: handle FUNAPPLY for native targets. |
| } |
| |
| if (argv[1].isObject() && argv[1].toObject().is<ArrayObject>()) { |
| if (isScripted && !stub->hasStub(ICStub::Call_ScriptedApplyArray)) { |
| JitSpew(JitSpew_BaselineIC, " Generating Call_ScriptedApplyArray stub"); |
| |
| ICCall_ScriptedApplyArray::Compiler compiler( |
| cx, stub->fallbackMonitorStub()->firstMonitorStub(), script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| static bool |
| TryAttachFunCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsbytecode* pc, |
| HandleValue thisv, bool* attached) |
| { |
| // Try to attach a stub for Function.prototype.call with scripted |this|. |
| |
| *attached = false; |
| if (!thisv.isObject() || !thisv.toObject().is<JSFunction>()) |
| return true; |
| RootedFunction target(cx, &thisv.toObject().as<JSFunction>()); |
| |
| // Attach a stub if the script can be Baseline-compiled. We do this also |
| // if the script is not yet compiled to avoid attaching a CallNative stub |
| // that handles everything, even after the callee becomes hot. |
| if (target->hasScript() && target->nonLazyScript()->canBaselineCompile() && |
| !stub->hasStub(ICStub::Call_ScriptedFunCall)) |
| { |
| JitSpew(JitSpew_BaselineIC, " Generating Call_ScriptedFunCall stub"); |
| |
| ICCall_ScriptedFunCall::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| *attached = true; |
| stub->addNewStub(newStub); |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| GetTemplateObjectForNative(JSContext* cx, Native native, const CallArgs& args, |
| MutableHandleObject res, bool* skipAttach) |
| { |
| // Check for natives to which template objects can be attached. This is |
| // done to provide templates to Ion for inlining these natives later on. |
| |
| if (native == ArrayConstructor) { |
| // Note: the template array won't be used if its length is inaccurately |
| // computed here. (We allocate here because compilation may occur on a |
| // separate thread where allocation is impossible.) |
| size_t count = 0; |
| if (args.length() != 1) |
| count = args.length(); |
| else if (args.length() == 1 && args[0].isInt32() && args[0].toInt32() >= 0) |
| count = args[0].toInt32(); |
| |
| if (count <= ArrayObject::EagerAllocationMaxLength) { |
| ObjectGroup* group = ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array); |
| if (!group) |
| return false; |
| if (group->maybePreliminaryObjects()) { |
| *skipAttach = true; |
| return true; |
| } |
| |
| // With this and other array templates, set forceAnalyze so that we |
| // don't end up with a template whose structure might change later. |
| res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, count, TenuredObject)); |
| if (!res) |
| return false; |
| return true; |
| } |
| } |
| |
| if (native == js::array_concat || native == js::array_slice) { |
| if (args.thisv().isObject()) { |
| JSObject* obj = &args.thisv().toObject(); |
| if (!obj->isSingleton()) { |
| if (obj->group()->maybePreliminaryObjects()) { |
| *skipAttach = true; |
| return true; |
| } |
| res.set(NewFullyAllocatedArrayTryReuseGroup(cx, &args.thisv().toObject(), 0, |
| TenuredObject)); |
| return !!res; |
| } |
| } |
| } |
| |
| if (native == js::str_split && args.length() == 1 && args[0].isString()) { |
| ObjectGroup* group = ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array); |
| if (!group) |
| return false; |
| if (group->maybePreliminaryObjects()) { |
| *skipAttach = true; |
| return true; |
| } |
| |
| res.set(NewFullyAllocatedArrayForCallingAllocationSite(cx, 0, TenuredObject)); |
| if (!res) |
| return false; |
| return true; |
| } |
| |
| if (native == StringConstructor) { |
| RootedString emptyString(cx, cx->runtime()->emptyString); |
| res.set(StringObject::create(cx, emptyString, /* proto = */ nullptr, TenuredObject)); |
| return !!res; |
| } |
| |
| if (native == obj_create && args.length() == 1 && args[0].isObjectOrNull()) { |
| RootedObject proto(cx, args[0].toObjectOrNull()); |
| res.set(ObjectCreateImpl(cx, proto, TenuredObject)); |
| return !!res; |
| } |
| |
| if (JitSupportsSimd()) { |
| #define ADD_INT32X4_SIMD_OP_NAME_(OP) || native == js::simd_int32x4_##OP |
| #define ADD_FLOAT32X4_SIMD_OP_NAME_(OP) || native == js::simd_float32x4_##OP |
| if (false |
| ION_COMMONX4_SIMD_OP(ADD_INT32X4_SIMD_OP_NAME_) |
| COMP_COMMONX4_TO_INT32X4_SIMD_OP(ADD_INT32X4_SIMD_OP_NAME_) |
| COMP_COMMONX4_TO_INT32X4_SIMD_OP(ADD_FLOAT32X4_SIMD_OP_NAME_) |
| FOREACH_INT32X4_SIMD_OP(ADD_INT32X4_SIMD_OP_NAME_)) |
| { |
| Rooted<SimdTypeDescr*> descr(cx, cx->global()->getOrCreateSimdTypeDescr<Int32x4>(cx)); |
| res.set(cx->compartment()->jitCompartment()->getSimdTemplateObjectFor(cx, descr)); |
| return !!res; |
| } |
| if (false |
| FOREACH_FLOAT32X4_SIMD_OP(ADD_FLOAT32X4_SIMD_OP_NAME_) |
| ION_COMMONX4_SIMD_OP(ADD_FLOAT32X4_SIMD_OP_NAME_)) |
| { |
| Rooted<SimdTypeDescr*> descr(cx, cx->global()->getOrCreateSimdTypeDescr<Float32x4>(cx)); |
| res.set(cx->compartment()->jitCompartment()->getSimdTemplateObjectFor(cx, descr)); |
| return !!res; |
| } |
| #undef ADD_INT32X4_SIMD_OP_NAME_ |
| #undef ADD_FLOAT32X4_SIMD_OP_NAME_ |
| } |
| |
| return true; |
| } |
| |
| static bool |
| GetTemplateObjectForClassHook(JSContext* cx, JSNative hook, CallArgs& args, |
| MutableHandleObject templateObject) |
| { |
| if (hook == TypedObject::construct) { |
| Rooted<TypeDescr*> descr(cx, &args.callee().as<TypeDescr>()); |
| templateObject.set(TypedObject::createZeroed(cx, descr, 1, gc::TenuredHeap)); |
| return !!templateObject; |
| } |
| |
| if (hook == SimdTypeDescr::call && JitSupportsSimd()) { |
| Rooted<SimdTypeDescr*> descr(cx, &args.callee().as<SimdTypeDescr>()); |
| templateObject.set(cx->compartment()->jitCompartment()->getSimdTemplateObjectFor(cx, descr)); |
| return !!templateObject; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| IsOptimizableCallStringSplit(Value callee, Value thisv, int argc, Value* args) |
| { |
| if (argc != 1 || !thisv.isString() || !args[0].isString()) |
| return false; |
| |
| if (!thisv.toString()->isAtom() || !args[0].toString()->isAtom()) |
| return false; |
| |
| if (!callee.isObject() || !callee.toObject().is<JSFunction>()) |
| return false; |
| |
| JSFunction& calleeFun = callee.toObject().as<JSFunction>(); |
| if (!calleeFun.isNative() || calleeFun.native() != js::str_split) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsbytecode* pc, |
| JSOp op, uint32_t argc, Value* vp, bool constructing, bool isSpread, |
| bool createSingleton, bool* handled) |
| { |
| bool isSuper = op == JSOP_SUPERCALL || op == JSOP_SPREADSUPERCALL; |
| |
| if (createSingleton || op == JSOP_EVAL || op == JSOP_STRICTEVAL) |
| return true; |
| |
| if (stub->numOptimizedStubs() >= ICCall_Fallback::MAX_OPTIMIZED_STUBS) { |
| // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. |
| // But for now we just bail. |
| return true; |
| } |
| |
| RootedValue callee(cx, vp[0]); |
| RootedValue thisv(cx, vp[1]); |
| |
| // Don't attach an optimized call stub if we could potentially attach an |
| // optimized StringSplit stub. |
| if (stub->numOptimizedStubs() == 0 && IsOptimizableCallStringSplit(callee, thisv, argc, vp + 2)) |
| return true; |
| |
| MOZ_ASSERT_IF(stub->hasStub(ICStub::Call_StringSplit), stub->numOptimizedStubs() == 1); |
| |
| stub->unlinkStubsWithKind(cx, ICStub::Call_StringSplit); |
| |
| if (!callee.isObject()) |
| return true; |
| |
| RootedObject obj(cx, &callee.toObject()); |
| if (!obj->is<JSFunction>()) { |
| // Try to attach a stub for a call/construct hook on the object. |
| // Ignore proxies, which are special cased by callHook/constructHook. |
| if (obj->is<ProxyObject>()) |
| return true; |
| if (JSNative hook = constructing ? obj->constructHook() : obj->callHook()) { |
| if (op != JSOP_FUNAPPLY && !isSpread && !createSingleton) { |
| RootedObject templateObject(cx); |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!GetTemplateObjectForClassHook(cx, hook, args, &templateObject)) |
| return false; |
| |
| JitSpew(JitSpew_BaselineIC, " Generating Call_ClassHook stub"); |
| ICCall_ClassHook::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| obj->getClass(), hook, templateObject, |
| script->pcToOffset(pc), constructing); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *handled = true; |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| RootedFunction fun(cx, &obj->as<JSFunction>()); |
| |
| if (fun->hasScript()) { |
| // Never attach optimized scripted call stubs for JSOP_FUNAPPLY. |
| // MagicArguments may escape the frame through them. |
| if (op == JSOP_FUNAPPLY) |
| return true; |
| |
| // If callee is not an interpreted constructor, we have to throw. |
| if (constructing && !fun->isConstructor()) |
| return true; |
| |
| // Likewise, if the callee is a class constructor, we have to throw. |
| if (!constructing && fun->isClassConstructor()) |
| return true; |
| |
| if (!fun->hasJITCode()) { |
| // Don't treat this as an unoptimizable case, as we'll add a stub |
| // when the callee becomes hot. |
| *handled = true; |
| return true; |
| } |
| |
| // Check if this stub chain has already generalized scripted calls. |
| if (stub->scriptedStubsAreGeneralized()) { |
| JitSpew(JitSpew_BaselineIC, " Chain already has generalized scripted call stub!"); |
| return true; |
| } |
| |
| if (stub->scriptedStubCount() >= ICCall_Fallback::MAX_SCRIPTED_STUBS) { |
| // Create a Call_AnyScripted stub. |
| JitSpew(JitSpew_BaselineIC, " Generating Call_AnyScripted stub (cons=%s, spread=%s)", |
| constructing ? "yes" : "no", isSpread ? "yes" : "no"); |
| ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| constructing, isSpread, script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| // Before adding new stub, unlink all previous Call_Scripted. |
| stub->unlinkStubsWithKind(cx, ICStub::Call_Scripted); |
| |
| // Add new generalized stub. |
| stub->addNewStub(newStub); |
| *handled = true; |
| return true; |
| } |
| |
| // Keep track of the function's |prototype| property in type |
| // information, for use during Ion compilation. |
| if (IsIonEnabled(cx)) |
| EnsureTrackPropertyTypes(cx, fun, NameToId(cx->names().prototype)); |
| |
| // Remember the template object associated with any script being called |
| // as a constructor, for later use during Ion compilation. This is unsound |
| // for super(), as a single callsite can have multiple possible prototype object |
| // created (via different newTargets) |
| RootedObject templateObject(cx); |
| if (constructing && !isSuper) { |
| // If we are calling a constructor for which the new script |
| // properties analysis has not been performed yet, don't attach a |
| // stub. After the analysis is performed, CreateThisForFunction may |
| // start returning objects with a different type, and the Ion |
| // compiler will get confused. |
| |
| // Only attach a stub if the function already has a prototype and |
| // we can look it up without causing side effects. |
| RootedObject newTarget(cx, &vp[2 + argc].toObject()); |
| RootedValue protov(cx); |
| if (!GetPropertyPure(cx, newTarget, NameToId(cx->names().prototype), protov.address())) { |
| JitSpew(JitSpew_BaselineIC, " Can't purely lookup function prototype"); |
| return true; |
| } |
| |
| if (protov.isObject()) { |
| TaggedProto proto(&protov.toObject()); |
| ObjectGroup* group = ObjectGroup::defaultNewGroup(cx, nullptr, proto, newTarget); |
| if (!group) |
| return false; |
| |
| if (group->newScript() && !group->newScript()->analyzed()) { |
| JitSpew(JitSpew_BaselineIC, " Function newScript has not been analyzed"); |
| |
| // This is temporary until the analysis is perfomed, so |
| // don't treat this as unoptimizable. |
| *handled = true; |
| return true; |
| } |
| } |
| |
| JSObject* thisObject = CreateThisForFunction(cx, fun, newTarget, TenuredObject); |
| if (!thisObject) |
| return false; |
| |
| if (thisObject->is<PlainObject>() || thisObject->is<UnboxedPlainObject>()) |
| templateObject = thisObject; |
| } |
| |
| JitSpew(JitSpew_BaselineIC, |
| " Generating Call_Scripted stub (fun=%p, %s:%" PRIuSIZE ", cons=%s, spread=%s)", |
| fun.get(), fun->nonLazyScript()->filename(), fun->nonLazyScript()->lineno(), |
| constructing ? "yes" : "no", isSpread ? "yes" : "no"); |
| ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| fun, templateObject, |
| constructing, isSpread, script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *handled = true; |
| return true; |
| } |
| |
| if (fun->isNative() && (!constructing || (constructing && fun->isConstructor()))) { |
| // Generalized native call stubs are not here yet! |
| MOZ_ASSERT(!stub->nativeStubsAreGeneralized()); |
| |
| // Check for JSOP_FUNAPPLY |
| if (op == JSOP_FUNAPPLY) { |
| if (fun->native() == fun_apply) |
| return TryAttachFunApplyStub(cx, stub, script, pc, thisv, argc, vp + 2, handled); |
| |
| // Don't try to attach a "regular" optimized call stubs for FUNAPPLY ops, |
| // since MagicArguments may escape through them. |
| return true; |
| } |
| |
| if (op == JSOP_FUNCALL && fun->native() == fun_call) { |
| if (!TryAttachFunCallStub(cx, stub, script, pc, thisv, handled)) |
| return false; |
| if (*handled) |
| return true; |
| } |
| |
| if (stub->nativeStubCount() >= ICCall_Fallback::MAX_NATIVE_STUBS) { |
| JitSpew(JitSpew_BaselineIC, |
| " Too many Call_Native stubs. TODO: add Call_AnyNative!"); |
| return true; |
| } |
| |
| if (fun->native() == intrinsic_IsSuspendedStarGenerator) { |
| // This intrinsic only appears in self-hosted code. |
| MOZ_ASSERT(op != JSOP_NEW); |
| MOZ_ASSERT(argc == 1); |
| JitSpew(JitSpew_BaselineIC, " Generating Call_IsSuspendedStarGenerator stub"); |
| |
| ICCall_IsSuspendedStarGenerator::Compiler compiler(cx); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *handled = true; |
| return true; |
| } |
| |
| RootedObject templateObject(cx); |
| if (MOZ_LIKELY(!isSpread && !isSuper)) { |
| bool skipAttach = false; |
| CallArgs args = CallArgsFromVp(argc, vp); |
| if (!GetTemplateObjectForNative(cx, fun->native(), args, &templateObject, &skipAttach)) |
| return false; |
| if (skipAttach) { |
| *handled = true; |
| return true; |
| } |
| MOZ_ASSERT_IF(templateObject, !templateObject->group()->maybePreliminaryObjects()); |
| } |
| |
| JitSpew(JitSpew_BaselineIC, " Generating Call_Native stub (fun=%p, cons=%s, spread=%s)", |
| fun.get(), constructing ? "yes" : "no", isSpread ? "yes" : "no"); |
| ICCall_Native::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| fun, templateObject, constructing, isSpread, |
| script->pcToOffset(pc)); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *handled = true; |
| return true; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| CopyArray(JSContext* cx, HandleObject obj, MutableHandleValue result) |
| { |
| uint32_t length = GetAnyBoxedOrUnboxedArrayLength(obj); |
| JSObject* nobj = NewFullyAllocatedArrayTryReuseGroup(cx, obj, length, TenuredObject, |
| /* forceAnalyze = */ true); |
| if (!nobj) |
| return false; |
| CopyAnyBoxedOrUnboxedDenseElements(cx, nobj, obj, 0, 0, length); |
| |
| result.setObject(*nobj); |
| return true; |
| } |
| |
| static bool |
| TryAttachStringSplit(JSContext* cx, ICCall_Fallback* stub, HandleScript script, |
| uint32_t argc, Value* vp, jsbytecode* pc, HandleValue res, |
| bool* attached) |
| { |
| if (stub->numOptimizedStubs() != 0) |
| return true; |
| |
| RootedValue callee(cx, vp[0]); |
| RootedValue thisv(cx, vp[1]); |
| Value* args = vp + 2; |
| |
| // String.prototype.split will not yield a constructable. |
| if (JSOp(*pc) == JSOP_NEW) |
| return true; |
| |
| if (!IsOptimizableCallStringSplit(callee, thisv, argc, args)) |
| return true; |
| |
| MOZ_ASSERT(callee.isObject()); |
| MOZ_ASSERT(callee.toObject().is<JSFunction>()); |
| |
| RootedString thisString(cx, thisv.toString()); |
| RootedString argString(cx, args[0].toString()); |
| RootedObject obj(cx, &res.toObject()); |
| RootedValue arr(cx); |
| |
| // Copy the array before storing in stub. |
| if (!CopyArray(cx, obj, &arr)) |
| return false; |
| |
| // Atomize all elements of the array. |
| RootedObject arrObj(cx, &arr.toObject()); |
| uint32_t initLength = GetAnyBoxedOrUnboxedArrayLength(arrObj); |
| for (uint32_t i = 0; i < initLength; i++) { |
| JSAtom* str = js::AtomizeString(cx, GetAnyBoxedOrUnboxedDenseElement(arrObj, i).toString()); |
| if (!str) |
| return false; |
| |
| if (!SetAnyBoxedOrUnboxedDenseElement(cx, arrObj, i, StringValue(str))) { |
| // The value could not be stored to an unboxed dense element. |
| return true; |
| } |
| } |
| |
| ICCall_StringSplit::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), |
| script->pcToOffset(pc), thisString, argString, |
| arr); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| static bool |
| DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, uint32_t argc, |
| Value* vp, MutableHandleValue res) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICCall_Fallback*> stub(frame, stub_); |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| FallbackICSpew(cx, stub, "Call(%s)", CodeName[op]); |
| |
| MOZ_ASSERT(argc == GET_ARGC(pc)); |
| bool constructing = (op == JSOP_NEW); |
| |
| // Ensure vp array is rooted - we may GC in here. |
| AutoArrayRooter vpRoot(cx, argc + 2 + constructing, vp); |
| |
| RootedValue callee(cx, vp[0]); |
| RootedValue thisv(cx, vp[1]); |
| |
| Value* args = vp + 2; |
| |
| // Handle funapply with JSOP_ARGUMENTS |
| if (op == JSOP_FUNAPPLY && argc == 2 && args[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) { |
| CallArgs callArgs = CallArgsFromVp(argc, vp); |
| if (!GuardFunApplyArgumentsOptimization(cx, frame, callArgs)) |
| return false; |
| } |
| |
| bool createSingleton = ObjectGroup::useSingletonForNewObject(cx, script, pc); |
| |
| // Try attaching a call stub. |
| bool handled = false; |
| if (!TryAttachCallStub(cx, stub, script, pc, op, argc, vp, constructing, false, |
| createSingleton, &handled)) |
| { |
| return false; |
| } |
| |
| if (op == JSOP_NEW) { |
| // Callees from the stack could have any old non-constructor callee. |
| if (!IsConstructor(callee)) { |
| ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, callee, nullptr); |
| return false; |
| } |
| |
| ConstructArgs cargs(cx); |
| if (!cargs.init(argc)) |
| return false; |
| |
| for (uint32_t i = 0; i < argc; i++) |
| cargs[i].set(args[i]); |
| |
| RootedValue newTarget(cx, args[argc]); |
| MOZ_ASSERT(IsConstructor(newTarget), |
| "either callee == newTarget, or the initial |new| checked " |
| "that IsConstructor(newTarget)"); |
| |
| if (!Construct(cx, callee, cargs, newTarget, res)) |
| return false; |
| } else if ((op == JSOP_EVAL || op == JSOP_STRICTEVAL) && |
| frame->scopeChain()->global().valueIsEval(callee)) |
| { |
| if (!DirectEval(cx, CallArgsFromVp(argc, vp))) |
| return false; |
| res.set(vp[0]); |
| } else { |
| MOZ_ASSERT(op == JSOP_CALL || |
| op == JSOP_CALLITER || |
| op == JSOP_FUNCALL || |
| op == JSOP_FUNAPPLY || |
| op == JSOP_EVAL || |
| op == JSOP_STRICTEVAL); |
| if (op == JSOP_CALLITER && callee.isPrimitive()) { |
| MOZ_ASSERT(argc == 0, "thisv must be on top of the stack"); |
| ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, thisv, nullptr); |
| return false; |
| } |
| if (!Invoke(cx, thisv, callee, argc, args, res)) |
| return false; |
| } |
| |
| TypeScript::Monitor(cx, script, pc, res); |
| |
| // Check if debug mode toggling made the stub invalid. |
| if (stub.invalid()) |
| return true; |
| |
| // Attach a new TypeMonitor stub for this value. |
| ICTypeMonitor_Fallback* typeMonFbStub = stub->fallbackMonitorStub(); |
| if (!typeMonFbStub->addMonitorStubForValue(cx, script, res, |
| ICStubCompiler::Engine::Baseline)) |
| { |
| return false; |
| } |
| |
| // Add a type monitor stub for the resulting value. |
| if (!stub->addMonitorStubForValue(cx, script, res, ICStubCompiler::Engine::Baseline)) |
| return false; |
| |
| // If 'callee' is a potential Call_StringSplit, try to attach an |
| // optimized StringSplit stub. |
| if (!TryAttachStringSplit(cx, stub, script, argc, vp, pc, res, &handled)) |
| return false; |
| |
| if (!handled) |
| stub->noteUnoptimizableCall(); |
| return true; |
| } |
| |
| static bool |
| DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, Value* vp, |
| MutableHandleValue res) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICCall_Fallback*> stub(frame, stub_); |
| |
| RootedScript script(cx, frame->script()); |
| jsbytecode* pc = stub->icEntry()->pc(script); |
| JSOp op = JSOp(*pc); |
| bool constructing = (op == JSOP_SPREADNEW); |
| FallbackICSpew(cx, stub, "SpreadCall(%s)", CodeName[op]); |
| |
| // Ensure vp array is rooted - we may GC in here. |
| AutoArrayRooter vpRoot(cx, 3 + constructing, vp); |
| |
| RootedValue callee(cx, vp[0]); |
| RootedValue thisv(cx, vp[1]); |
| RootedValue arr(cx, vp[2]); |
| RootedValue newTarget(cx, constructing ? vp[3] : NullValue()); |
| |
| // Try attaching a call stub. |
| bool handled = false; |
| if (op != JSOP_SPREADEVAL && op != JSOP_STRICTSPREADEVAL && |
| !TryAttachCallStub(cx, stub, script, pc, op, 1, vp, constructing, true, false, |
| &handled)) |
| { |
| return false; |
| } |
| |
| if (!SpreadCallOperation(cx, script, pc, thisv, callee, arr, newTarget, res)) |
| return false; |
| |
| // Check if debug mode toggling made the stub invalid. |
| if (stub.invalid()) |
| return true; |
| |
| // Attach a new TypeMonitor stub for this value. |
| ICTypeMonitor_Fallback* typeMonFbStub = stub->fallbackMonitorStub(); |
| if (!typeMonFbStub->addMonitorStubForValue(cx, script, res, |
| ICStubCompiler::Engine::Baseline)) |
| { |
| return false; |
| } |
| // Add a type monitor stub for the resulting value. |
| if (!stub->addMonitorStubForValue(cx, script, res, ICStubCompiler::Engine::Baseline)) |
| return false; |
| |
| if (!handled) |
| stub->noteUnoptimizableCall(); |
| return true; |
| } |
| |
| void |
| ICCallStubCompiler::pushCallArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs, |
| Register argcReg, bool isJitCall, bool isConstructing) |
| { |
| MOZ_ASSERT(!regs.has(argcReg)); |
| |
| // Account for new.target |
| Register count = regs.takeAny(); |
| |
| masm.move32(argcReg, count); |
| |
| // If we are setting up for a jitcall, we have to align the stack taking |
| // into account the args and newTarget. We could also count callee and |this|, |
| // but it's a waste of stack space. Because we want to keep argcReg unchanged, |
| // just account for newTarget initially, and add the other 2 after assuring |
| // allignment. |
| if (isJitCall) { |
| if (isConstructing) |
| masm.add32(Imm32(1), count); |
| } else { |
| masm.add32(Imm32(2 + isConstructing), count); |
| } |
| |
| // argPtr initially points to the last argument. |
| Register argPtr = regs.takeAny(); |
| masm.moveStackPtrTo(argPtr); |
| |
| // Skip 4 pointers pushed on top of the arguments: the frame descriptor, |
| // return address, old frame pointer and stub reg. |
| masm.addPtr(Imm32(STUB_FRAME_SIZE), argPtr); |
| |
| // Align the stack such that the JitFrameLayout is aligned on the |
| // JitStackAlignment. |
| if (isJitCall) { |
| masm.alignJitStackBasedOnNArgs(count); |
| |
| // Account for callee and |this|, skipped earlier |
| masm.add32(Imm32(2), count); |
| } |
| |
| // Push all values, starting at the last one. |
| Label loop, done; |
| masm.bind(&loop); |
| masm.branchTest32(Assembler::Zero, count, count, &done); |
| { |
| masm.pushValue(Address(argPtr, 0)); |
| masm.addPtr(Imm32(sizeof(Value)), argPtr); |
| |
| masm.sub32(Imm32(1), count); |
| masm.jump(&loop); |
| } |
| masm.bind(&done); |
| } |
| |
| void |
| ICCallStubCompiler::guardSpreadCall(MacroAssembler& masm, Register argcReg, Label* failure, |
| bool isConstructing) |
| { |
| masm.unboxObject(Address(masm.getStackPointer(), |
| isConstructing * sizeof(Value) + ICStackValueOffset), argcReg); |
| masm.loadPtr(Address(argcReg, NativeObject::offsetOfElements()), argcReg); |
| masm.load32(Address(argcReg, ObjectElements::offsetOfLength()), argcReg); |
| |
| // Limit actual argc to something reasonable (huge number of arguments can |
| // blow the stack limit). |
| static_assert(ICCall_Scripted::MAX_ARGS_SPREAD_LENGTH <= ARGS_LENGTH_MAX, |
| "maximum arguments length for optimized stub should be <= ARGS_LENGTH_MAX"); |
| masm.branch32(Assembler::Above, argcReg, Imm32(ICCall_Scripted::MAX_ARGS_SPREAD_LENGTH), |
| failure); |
| } |
| |
| void |
| ICCallStubCompiler::pushSpreadCallArguments(MacroAssembler& masm, |
| AllocatableGeneralRegisterSet regs, |
| Register argcReg, bool isJitCall, |
| bool isConstructing) |
| { |
| // Pull the array off the stack before aligning. |
| Register startReg = regs.takeAny(); |
| masm.unboxObject(Address(masm.getStackPointer(), |
| (isConstructing * sizeof(Value)) + STUB_FRAME_SIZE), startReg); |
| masm.loadPtr(Address(startReg, NativeObject::offsetOfElements()), startReg); |
| |
| // Align the stack such that the JitFrameLayout is aligned on the |
| // JitStackAlignment. |
| if (isJitCall) { |
| Register alignReg = argcReg; |
| if (isConstructing) { |
| alignReg = regs.takeAny(); |
| masm.movePtr(argcReg, alignReg); |
| masm.addPtr(Imm32(1), alignReg); |
| } |
| masm.alignJitStackBasedOnNArgs(alignReg); |
| if (isConstructing) { |
| MOZ_ASSERT(alignReg != argcReg); |
| regs.add(alignReg); |
| } |
| } |
| |
| // Push newTarget, if necessary |
| if (isConstructing) |
| masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE)); |
| |
| // Push arguments: set up endReg to point to &array[argc] |
| Register endReg = regs.takeAny(); |
| masm.movePtr(argcReg, endReg); |
| static_assert(sizeof(Value) == 8, "Value must be 8 bytes"); |
| masm.lshiftPtr(Imm32(3), endReg); |
| masm.addPtr(startReg, endReg); |
| |
| // Copying pre-decrements endReg by 8 until startReg is reached |
| Label copyDone; |
| Label copyStart; |
| masm.bind(©Start); |
| masm.branchPtr(Assembler::Equal, endReg, startReg, ©Done); |
| masm.subPtr(Imm32(sizeof(Value)), endReg); |
| masm.pushValue(Address(endReg, 0)); |
| masm.jump(©Start); |
| masm.bind(©Done); |
| |
| regs.add(startReg); |
| regs.add(endReg); |
| |
| // Push the callee and |this|. |
| masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + (1 + isConstructing) * sizeof(Value))); |
| masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + (2 + isConstructing) * sizeof(Value))); |
| } |
| |
| // (see Bug 1149377 comment 31) MSVC 2013 PGO miss-compiles branchTestObjClass |
| // calls from this function. |
| #if defined(_MSC_VER) && _MSC_VER == 1800 |
| # pragma optimize("g", off) |
| #endif |
| Register |
| ICCallStubCompiler::guardFunApply(MacroAssembler& masm, AllocatableGeneralRegisterSet regs, |
| Register argcReg, bool checkNative, FunApplyThing applyThing, |
| Label* failure) |
| { |
| // Ensure argc == 2 |
| masm.branch32(Assembler::NotEqual, argcReg, Imm32(2), failure); |
| |
| // Stack looks like: |
| // [..., CalleeV, ThisV, Arg0V, Arg1V <MaybeReturnReg>] |
| |
| Address secondArgSlot(masm.getStackPointer(), ICStackValueOffset); |
| if (applyThing == FunApply_MagicArgs) { |
| // Ensure that the second arg is magic arguments. |
| masm.branchTestMagic(Assembler::NotEqual, secondArgSlot, failure); |
| |
| // Ensure that this frame doesn't have an arguments object. |
| masm.branchTest32(Assembler::NonZero, |
| Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()), |
| Imm32(BaselineFrame::HAS_ARGS_OBJ), |
| failure); |
| } else { |
| MOZ_ASSERT(applyThing == FunApply_Array); |
| |
| AllocatableGeneralRegisterSet regsx = regs; |
| |
| // Ensure that the second arg is an array. |
| ValueOperand secondArgVal = regsx.takeAnyValue(); |
| masm.loadValue(secondArgSlot, secondArgVal); |
| |
| masm.branchTestObject(Assembler::NotEqual, secondArgVal, failure); |
| Register secondArgObj = masm.extractObject(secondArgVal, ExtractTemp1); |
| |
| regsx.add(secondArgVal); |
| regsx.takeUnchecked(secondArgObj); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, secondArgObj, regsx.getAny(), |
| &ArrayObject::class_, failure); |
| |
| // Get the array elements and ensure that initializedLength == length |
| masm.loadPtr(Address(secondArgObj, NativeObject::offsetOfElements()), secondArgObj); |
| |
| Register lenReg = regsx.takeAny(); |
| masm.load32(Address(secondArgObj, ObjectElements::offsetOfLength()), lenReg); |
| |
| masm.branch32(Assembler::NotEqual, |
| Address(secondArgObj, ObjectElements::offsetOfInitializedLength()), |
| lenReg, failure); |
| |
| // Limit the length to something reasonable (huge number of arguments can |
| // blow the stack limit). |
| masm.branch32(Assembler::Above, lenReg, |
| Imm32(ICCall_ScriptedApplyArray::MAX_ARGS_ARRAY_LENGTH), |
| failure); |
| |
| // Ensure no holes. Loop through values in array and make sure none are magic. |
| // Start address is secondArgObj, end address is secondArgObj + (lenReg * sizeof(Value)) |
| JS_STATIC_ASSERT(sizeof(Value) == 8); |
| masm.lshiftPtr(Imm32(3), lenReg); |
| masm.addPtr(secondArgObj, lenReg); |
| |
| Register start = secondArgObj; |
| Register end = lenReg; |
| Label loop; |
| Label endLoop; |
| masm.bind(&loop); |
| masm.branchPtr(Assembler::AboveOrEqual, start, end, &endLoop); |
| masm.branchTestMagic(Assembler::Equal, Address(start, 0), failure); |
| masm.addPtr(Imm32(sizeof(Value)), start); |
| masm.jump(&loop); |
| masm.bind(&endLoop); |
| } |
| |
| // Stack now confirmed to be like: |
| // [..., CalleeV, ThisV, Arg0V, MagicValue(Arguments), <MaybeReturnAddr>] |
| |
| // Load the callee, ensure that it's fun_apply |
| ValueOperand val = regs.takeAnyValue(); |
| Address calleeSlot(masm.getStackPointer(), ICStackValueOffset + (3 * sizeof(Value))); |
| masm.loadValue(calleeSlot, val); |
| |
| masm.branchTestObject(Assembler::NotEqual, val, failure); |
| Register callee = masm.extractObject(val, ExtractTemp1); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, |
| failure); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| |
| masm.branchPtr(Assembler::NotEqual, callee, ImmPtr(fun_apply), failure); |
| |
| // Load the |thisv|, ensure that it's a scripted function with a valid baseline or ion |
| // script, or a native function. |
| Address thisSlot(masm.getStackPointer(), ICStackValueOffset + (2 * sizeof(Value))); |
| masm.loadValue(thisSlot, val); |
| |
| masm.branchTestObject(Assembler::NotEqual, val, failure); |
| Register target = masm.extractObject(val, ExtractTemp1); |
| regs.add(val); |
| regs.takeUnchecked(target); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, target, regs.getAny(), &JSFunction::class_, |
| failure); |
| |
| if (checkNative) { |
| masm.branchIfInterpreted(target, failure); |
| } else { |
| masm.branchIfFunctionHasNoScript(target, failure); |
| Register temp = regs.takeAny(); |
| masm.loadPtr(Address(target, JSFunction::offsetOfNativeOrScript()), temp); |
| masm.loadBaselineOrIonRaw(temp, temp, failure); |
| regs.add(temp); |
| } |
| return target; |
| } |
| #if defined(_MSC_VER) && _MSC_VER == 1800 |
| # pragma optimize("", on) |
| #endif |
| |
| void |
| ICCallStubCompiler::pushCallerArguments(MacroAssembler& masm, AllocatableGeneralRegisterSet regs) |
| { |
| // Initialize copyReg to point to start caller arguments vector. |
| // Initialize argcReg to poitn to the end of it. |
| Register startReg = regs.takeAny(); |
| Register endReg = regs.takeAny(); |
| masm.loadPtr(Address(BaselineFrameReg, 0), startReg); |
| masm.loadPtr(Address(startReg, BaselineFrame::offsetOfNumActualArgs()), endReg); |
| masm.addPtr(Imm32(BaselineFrame::offsetOfArg(0)), startReg); |
| masm.alignJitStackBasedOnNArgs(endReg); |
| masm.lshiftPtr(Imm32(ValueShift), endReg); |
| masm.addPtr(startReg, endReg); |
| |
| // Copying pre-decrements endReg by 8 until startReg is reached |
| Label copyDone; |
| Label copyStart; |
| masm.bind(©Start); |
| masm.branchPtr(Assembler::Equal, endReg, startReg, ©Done); |
| masm.subPtr(Imm32(sizeof(Value)), endReg); |
| masm.pushValue(Address(endReg, 0)); |
| masm.jump(©Start); |
| masm.bind(©Done); |
| } |
| |
| void |
| ICCallStubCompiler::pushArrayArguments(MacroAssembler& masm, Address arrayVal, |
| AllocatableGeneralRegisterSet regs) |
| { |
| // Load start and end address of values to copy. |
| // guardFunApply has already gauranteed that the array is packed and contains |
| // no holes. |
| Register startReg = regs.takeAny(); |
| Register endReg = regs.takeAny(); |
| masm.extractObject(arrayVal, startReg); |
| masm.loadPtr(Address(startReg, NativeObject::offsetOfElements()), startReg); |
| masm.load32(Address(startReg, ObjectElements::offsetOfInitializedLength()), endReg); |
| masm.alignJitStackBasedOnNArgs(endReg); |
| masm.lshiftPtr(Imm32(ValueShift), endReg); |
| masm.addPtr(startReg, endReg); |
| |
| // Copying pre-decrements endReg by 8 until startReg is reached |
| Label copyDone; |
| Label copyStart; |
| masm.bind(©Start); |
| masm.branchPtr(Assembler::Equal, endReg, startReg, ©Done); |
| masm.subPtr(Imm32(sizeof(Value)), endReg); |
| masm.pushValue(Address(endReg, 0)); |
| masm.jump(©Start); |
| masm.bind(©Done); |
| } |
| |
| typedef bool (*DoCallFallbackFn)(JSContext*, BaselineFrame*, ICCall_Fallback*, |
| uint32_t, Value*, MutableHandleValue); |
| static const VMFunction DoCallFallbackInfo = FunctionInfo<DoCallFallbackFn>(DoCallFallback); |
| |
| typedef bool (*DoSpreadCallFallbackFn)(JSContext*, BaselineFrame*, ICCall_Fallback*, |
| Value*, MutableHandleValue); |
| static const VMFunction DoSpreadCallFallbackInfo = |
| FunctionInfo<DoSpreadCallFallbackFn>(DoSpreadCallFallback); |
| |
| bool |
| ICCall_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| MOZ_ASSERT(R0 == JSReturnOperand); |
| |
| // Values are on the stack left-to-right. Calling convention wants them |
| // right-to-left so duplicate them on the stack in reverse order. |
| // |this| and callee are pushed last. |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| |
| if (MOZ_UNLIKELY(isSpread_)) { |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, R1.scratchReg()); |
| |
| // Use BaselineFrameReg instead of BaselineStackReg, because |
| // BaselineFrameReg and BaselineStackReg hold the same value just after |
| // calling enterStubFrame. |
| |
| // newTarget |
| if (isConstructing_) |
| masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE)); |
| |
| // array |
| uint32_t valueOffset = isConstructing_; |
| masm.pushValue(Address(BaselineFrameReg, valueOffset++ * sizeof(Value) + STUB_FRAME_SIZE)); |
| |
| // this |
| masm.pushValue(Address(BaselineFrameReg, valueOffset++ * sizeof(Value) + STUB_FRAME_SIZE)); |
| |
| // callee |
| masm.pushValue(Address(BaselineFrameReg, valueOffset++ * sizeof(Value) + STUB_FRAME_SIZE)); |
| |
| masm.push(masm.getStackPointer()); |
| masm.push(ICStubReg); |
| |
| PushFramePtr(masm, R0.scratchReg()); |
| |
| if (!callVM(DoSpreadCallFallbackInfo, masm)) |
| return false; |
| |
| leaveStubFrame(masm); |
| EmitReturnFromIC(masm); |
| |
| // SPREADCALL is not yet supported in Ion, so do not generate asmcode for |
| // bailout. |
| return true; |
| } |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, R1.scratchReg()); |
| |
| regs.take(R0.scratchReg()); // argc. |
| |
| pushCallArguments(masm, regs, R0.scratchReg(), /* isJitCall = */ false, isConstructing_); |
| |
| masm.push(masm.getStackPointer()); |
| masm.push(R0.scratchReg()); |
| masm.push(ICStubReg); |
| |
| PushFramePtr(masm, R0.scratchReg()); |
| |
| if (!callVM(DoCallFallbackInfo, masm)) |
| return false; |
| |
| uint32_t framePushed = masm.framePushed(); |
| leaveStubFrame(masm); |
| EmitReturnFromIC(masm); |
| |
| // The following asmcode is only used when an Ion inlined frame bails out |
| // into into baseline jitcode. The return address pushed onto the |
| // reconstructed baseline stack points here. |
| returnOffset_ = masm.currentOffset(); |
| |
| // Here we are again in a stub frame. Marking as so. |
| inStubFrame_ = true; |
| masm.setFramePushed(framePushed); |
| |
| // Load passed-in ThisV into R1 just in case it's needed. Need to do this before |
| // we leave the stub frame since that info will be lost. |
| // Current stack: [...., ThisV, ActualArgc, CalleeToken, Descriptor ] |
| masm.loadValue(Address(masm.getStackPointer(), 3 * sizeof(size_t)), R1); |
| |
| leaveStubFrame(masm, true); |
| |
| // If this is a |constructing| call, if the callee returns a non-object, we replace it with |
| // the |this| object passed in. |
| if (isConstructing_) { |
| MOZ_ASSERT(JSReturnOperand == R0); |
| Label skipThisReplace; |
| |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); |
| masm.moveValue(R1, R0); |
| #ifdef DEBUG |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); |
| masm.assumeUnreachable("Failed to return object in constructing call."); |
| #endif |
| masm.bind(&skipThisReplace); |
| } |
| |
| // At this point, ICStubReg points to the ICCall_Fallback stub, which is NOT |
| // a MonitoredStub, but rather a MonitoredFallbackStub. To use EmitEnterTypeMonitorIC, |
| // first load the ICTypeMonitor_Fallback stub into ICStubReg. Then, use |
| // EmitEnterTypeMonitorIC with a custom struct offset. |
| masm.loadPtr(Address(ICStubReg, ICMonitoredFallbackStub::offsetOfFallbackMonitorStub()), |
| ICStubReg); |
| EmitEnterTypeMonitorIC(masm, ICTypeMonitor_Fallback::offsetOfFirstMonitorStub()); |
| |
| return true; |
| } |
| |
| void |
| ICCall_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm, Handle<JitCode*> code) |
| { |
| if (MOZ_UNLIKELY(isSpread_)) |
| return; |
| |
| cx->compartment()->jitCompartment()->initBaselineCallReturnAddr(code->raw() + returnOffset_, |
| isConstructing_); |
| } |
| |
| typedef bool (*CreateThisFn)(JSContext* cx, HandleObject callee, HandleObject newTarget, |
| MutableHandleValue rval); |
| static const VMFunction CreateThisInfoBaseline = FunctionInfo<CreateThisFn>(CreateThis); |
| |
| bool |
| ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| bool canUseTailCallReg = regs.has(ICTailCallReg); |
| |
| Register argcReg = R0.scratchReg(); |
| MOZ_ASSERT(argcReg != ArgumentsRectifierReg); |
| |
| regs.take(argcReg); |
| regs.take(ArgumentsRectifierReg); |
| regs.takeUnchecked(ICTailCallReg); |
| |
| if (isSpread_) |
| guardSpreadCall(masm, argcReg, &failure, isConstructing_); |
| |
| // Load the callee in R1, accounting for newTarget, if necessary |
| // Stack Layout: [ ..., CalleeVal, ThisVal, Arg0Val, ..., ArgNVal, [newTarget] +ICStackValueOffset+ ] |
| if (isSpread_) { |
| unsigned skipToCallee = (2 + isConstructing_) * sizeof(Value); |
| masm.loadValue(Address(masm.getStackPointer(), skipToCallee + ICStackValueOffset), R1); |
| } else { |
| // Account for newTarget, if necessary |
| unsigned nonArgsSkip = (1 + isConstructing_) * sizeof(Value); |
| BaseValueIndex calleeSlot(masm.getStackPointer(), argcReg, ICStackValueOffset + nonArgsSkip); |
| masm.loadValue(calleeSlot, R1); |
| } |
| regs.take(R1); |
| |
| // Ensure callee is an object. |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| // Ensure callee is a function. |
| Register callee = masm.extractObject(R1, ExtractTemp0); |
| |
| // If calling a specific script, check if the script matches. Otherwise, ensure that |
| // callee function is scripted. Leave calleeScript in |callee| reg. |
| if (callee_) { |
| MOZ_ASSERT(kind == ICStub::Call_Scripted); |
| |
| // Check if the object matches this callee. |
| Address expectedCallee(ICStubReg, ICCall_Scripted::offsetOfCallee()); |
| masm.branchPtr(Assembler::NotEqual, expectedCallee, callee, &failure); |
| |
| // Guard against relazification. |
| masm.branchIfFunctionHasNoScript(callee, &failure); |
| } else { |
| // Ensure the object is a function. |
| masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, |
| &failure); |
| if (isConstructing_) { |
| masm.branchIfNotInterpretedConstructor(callee, regs.getAny(), &failure); |
| } else { |
| masm.branchIfFunctionHasNoScript(callee, &failure); |
| masm.branchFunctionKind(Assembler::Equal, JSFunction::ClassConstructor, callee, |
| regs.getAny(), &failure); |
| } |
| } |
| |
| // Load the JSScript. |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| |
| // Load the start of the target JitCode. |
| Register code; |
| if (!isConstructing_) { |
| code = regs.takeAny(); |
| masm.loadBaselineOrIonRaw(callee, code, &failure); |
| } else { |
| Address scriptCode(callee, JSScript::offsetOfBaselineOrIonRaw()); |
| masm.branchPtr(Assembler::Equal, scriptCode, ImmPtr(nullptr), &failure); |
| } |
| |
| // We no longer need R1. |
| regs.add(R1); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, regs.getAny()); |
| if (canUseTailCallReg) |
| regs.add(ICTailCallReg); |
| |
| Label failureLeaveStubFrame; |
| |
| if (isConstructing_) { |
| // Save argc before call. |
| masm.push(argcReg); |
| |
| // Stack now looks like: |
| // [..., Callee, ThisV, Arg0V, ..., ArgNV, NewTarget, StubFrameHeader, ArgC ] |
| masm.loadValue(Address(masm.getStackPointer(), STUB_FRAME_SIZE + sizeof(size_t)), R1); |
| masm.push(masm.extractObject(R1, ExtractTemp0)); |
| |
| if (isSpread_) { |
| masm.loadValue(Address(masm.getStackPointer(), |
| 3 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t) + |
| sizeof(JSObject*)), |
| R1); |
| } else { |
| BaseValueIndex calleeSlot2(masm.getStackPointer(), argcReg, |
| 2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t) + |
| sizeof(JSObject*)); |
| masm.loadValue(calleeSlot2, R1); |
| } |
| masm.push(masm.extractObject(R1, ExtractTemp0)); |
| if (!callVM(CreateThisInfoBaseline, masm)) |
| return false; |
| |
| // Return of CreateThis must be an object or uninitialized. |
| #ifdef DEBUG |
| Label createdThisOK; |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisOK); |
| masm.branchTestMagic(Assembler::Equal, JSReturnOperand, &createdThisOK); |
| masm.assumeUnreachable("The return of CreateThis must be an object or uninitialized."); |
| masm.bind(&createdThisOK); |
| #endif |
| |
| // Reset the register set from here on in. |
| MOZ_ASSERT(JSReturnOperand == R0); |
| regs = availableGeneralRegs(0); |
| regs.take(R0); |
| regs.take(ArgumentsRectifierReg); |
| argcReg = regs.takeAny(); |
| |
| // Restore saved argc so we can use it to calculate the address to save |
| // the resulting this object to. |
| masm.pop(argcReg); |
| |
| // Save "this" value back into pushed arguments on stack. R0 can be clobbered after that. |
| // Stack now looks like: |
| // [..., Callee, ThisV, Arg0V, ..., ArgNV, [NewTarget], StubFrameHeader ] |
| if (isSpread_) { |
| masm.storeValue(R0, Address(masm.getStackPointer(), |
| (1 + isConstructing_) * sizeof(Value) + STUB_FRAME_SIZE)); |
| } else { |
| BaseValueIndex thisSlot(masm.getStackPointer(), argcReg, |
| STUB_FRAME_SIZE + isConstructing_ * sizeof(Value)); |
| masm.storeValue(R0, thisSlot); |
| } |
| |
| // Restore the stub register from the baseline stub frame. |
| masm.loadPtr(Address(masm.getStackPointer(), STUB_FRAME_SAVED_STUB_OFFSET), ICStubReg); |
| |
| // Reload callee script. Note that a GC triggered by CreateThis may |
| // have destroyed the callee BaselineScript and IonScript. CreateThis is |
| // safely repeatable though, so in this case we just leave the stub frame |
| // and jump to the next stub. |
| |
| // Just need to load the script now. |
| if (isSpread_) { |
| unsigned skipForCallee = (2 + isConstructing_) * sizeof(Value); |
| masm.loadValue(Address(masm.getStackPointer(), skipForCallee + STUB_FRAME_SIZE), R0); |
| } else { |
| // Account for newTarget, if necessary |
| unsigned nonArgsSkip = (1 + isConstructing_) * sizeof(Value); |
| BaseValueIndex calleeSlot3(masm.getStackPointer(), argcReg, nonArgsSkip + STUB_FRAME_SIZE); |
| masm.loadValue(calleeSlot3, R0); |
| } |
| callee = masm.extractObject(R0, ExtractTemp0); |
| regs.add(R0); |
| regs.takeUnchecked(callee); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| |
| code = regs.takeAny(); |
| masm.loadBaselineOrIonRaw(callee, code, &failureLeaveStubFrame); |
| |
| // Release callee register, but don't add ExtractTemp0 back into the pool |
| // ExtractTemp0 is used later, and if it's allocated to some other register at that |
| // point, it will get clobbered when used. |
| if (callee != ExtractTemp0) |
| regs.add(callee); |
| |
| if (canUseTailCallReg) |
| regs.addUnchecked(ICTailCallReg); |
| } |
| Register scratch = regs.takeAny(); |
| |
| // Values are on the stack left-to-right. Calling convention wants them |
| // right-to-left so duplicate them on the stack in reverse order. |
| // |this| and callee are pushed last. |
| if (isSpread_) |
| pushSpreadCallArguments(masm, regs, argcReg, /* isJitCall = */ true, isConstructing_); |
| else |
| pushCallArguments(masm, regs, argcReg, /* isJitCall = */ true, isConstructing_); |
| |
| // The callee is on top of the stack. Pop and unbox it. |
| ValueOperand val = regs.takeAnyValue(); |
| masm.popValue(val); |
| callee = masm.extractObject(val, ExtractTemp0); |
| |
| EmitBaselineCreateStubFrameDescriptor(masm, scratch); |
| |
| // Note that we use Push, not push, so that callJit will align the stack |
| // properly on ARM. |
| masm.Push(argcReg); |
| masm.PushCalleeToken(callee, isConstructing_); |
| masm.Push(scratch); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), callee); |
| masm.branch32(Assembler::AboveOrEqual, argcReg, callee, &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| MOZ_ASSERT(ArgumentsRectifierReg != code); |
| MOZ_ASSERT(ArgumentsRectifierReg != argcReg); |
| |
| JitCode* argumentsRectifier = |
| cx->runtime()->jitRuntime()->getArgumentsRectifier(); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), code); |
| masm.loadPtr(Address(code, JitCode::offsetOfCode()), code); |
| masm.movePtr(argcReg, ArgumentsRectifierReg); |
| } |
| |
| masm.bind(&noUnderflow); |
| masm.callJit(code); |
| |
| // If this is a constructing call, and the callee returns a non-object, replace it with |
| // the |this| object passed in. |
| if (isConstructing_) { |
| Label skipThisReplace; |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); |
| |
| // Current stack: [ Padding?, ARGVALS..., ThisVal, ActualArgc, Callee, Descriptor ] |
| // However, we can't use this ThisVal, because it hasn't been traced. We need to use |
| // The ThisVal higher up the stack: |
| // Current stack: [ ThisVal, ARGVALS..., ...STUB FRAME..., |
| // Padding?, ARGVALS..., ThisVal, ActualArgc, Callee, Descriptor ] |
| |
| // Restore the BaselineFrameReg based on the frame descriptor. |
| // |
| // BaselineFrameReg = BaselineStackReg |
| // + sizeof(Descriptor) + sizeof(Callee) + sizeof(ActualArgc) |
| // + stubFrameSize(Descriptor) |
| // - sizeof(ICStubReg) - sizeof(BaselineFrameReg) |
| Address descriptorAddr(masm.getStackPointer(), 0); |
| masm.loadPtr(descriptorAddr, BaselineFrameReg); |
| masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), BaselineFrameReg); |
| masm.addPtr(Imm32((3 - 2) * sizeof(size_t)), BaselineFrameReg); |
| masm.addStackPtrTo(BaselineFrameReg); |
| |
| // Load the number of arguments present before the stub frame. |
| Register argcReg = JSReturnOperand.scratchReg(); |
| if (isSpread_) { |
| // Account for the Array object. |
| masm.move32(Imm32(1), argcReg); |
| } else { |
| Address argcAddr(masm.getStackPointer(), 2 * sizeof(size_t)); |
| masm.loadPtr(argcAddr, argcReg); |
| } |
| |
| // Current stack: [ ThisVal, ARGVALS..., ...STUB FRAME..., <-- BaselineFrameReg |
| // Padding?, ARGVALS..., ThisVal, ActualArgc, Callee, Descriptor ] |
| // |
| // &ThisVal = BaselineFrameReg + argc * sizeof(Value) + STUB_FRAME_SIZE + sizeof(Value) |
| // This last sizeof(Value) accounts for the newTarget on the end of the arguments vector |
| // which is not reflected in actualArgc |
| BaseValueIndex thisSlotAddr(BaselineFrameReg, argcReg, STUB_FRAME_SIZE + sizeof(Value)); |
| masm.loadValue(thisSlotAddr, JSReturnOperand); |
| #ifdef DEBUG |
| masm.branchTestObject(Assembler::Equal, JSReturnOperand, &skipThisReplace); |
| masm.assumeUnreachable("Return of constructing call should be an object."); |
| #endif |
| masm.bind(&skipThisReplace); |
| } |
| |
| leaveStubFrame(masm, true); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Leave stub frame and restore argc for the next stub. |
| masm.bind(&failureLeaveStubFrame); |
| inStubFrame_ = true; |
| leaveStubFrame(masm, false); |
| if (argcReg != R0.scratchReg()) |
| masm.movePtr(argcReg, R0.scratchReg()); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| typedef bool (*CopyArrayFn)(JSContext*, HandleObject, MutableHandleValue); |
| static const VMFunction CopyArrayInfo = FunctionInfo<CopyArrayFn>(CopyArray); |
| |
| bool |
| ICCall_StringSplit::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| // Stack Layout: [ ..., CalleeVal, ThisVal, Arg0Val, +ICStackValueOffset+ ] |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| Label failureRestoreArgc; |
| #ifdef DEBUG |
| Label oneArg; |
| Register argcReg = R0.scratchReg(); |
| masm.branch32(Assembler::Equal, argcReg, Imm32(1), &oneArg); |
| masm.assumeUnreachable("Expected argc == 1"); |
| masm.bind(&oneArg); |
| #endif |
| Register scratchReg = regs.takeAny(); |
| |
| // Guard that callee is native function js::str_split. |
| { |
| Address calleeAddr(masm.getStackPointer(), ICStackValueOffset + (2 * sizeof(Value))); |
| ValueOperand calleeVal = regs.takeAnyValue(); |
| |
| // Ensure that callee is an object. |
| masm.loadValue(calleeAddr, calleeVal); |
| masm.branchTestObject(Assembler::NotEqual, calleeVal, &failureRestoreArgc); |
| |
| // Ensure that callee is a function. |
| Register calleeObj = masm.extractObject(calleeVal, ExtractTemp0); |
| masm.branchTestObjClass(Assembler::NotEqual, calleeObj, scratchReg, |
| &JSFunction::class_, &failureRestoreArgc); |
| |
| // Ensure that callee's function impl is the native str_split. |
| masm.loadPtr(Address(calleeObj, JSFunction::offsetOfNativeOrScript()), scratchReg); |
| masm.branchPtr(Assembler::NotEqual, scratchReg, ImmPtr(js::str_split), &failureRestoreArgc); |
| |
| regs.add(calleeVal); |
| } |
| |
| // Guard argument. |
| { |
| // Ensure that arg is a string. |
| Address argAddr(masm.getStackPointer(), ICStackValueOffset); |
| ValueOperand argVal = regs.takeAnyValue(); |
| |
| masm.loadValue(argAddr, argVal); |
| masm.branchTestString(Assembler::NotEqual, argVal, &failureRestoreArgc); |
| |
| Register argString = masm.extractString(argVal, ExtractTemp0); |
| masm.branchPtr(Assembler::NotEqual, Address(ICStubReg, offsetOfExpectedArg()), |
| argString, &failureRestoreArgc); |
| regs.add(argVal); |
| } |
| |
| // Guard this-value. |
| { |
| // Ensure that thisv is a string. |
| Address thisvAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value)); |
| ValueOperand thisvVal = regs.takeAnyValue(); |
| |
| masm.loadValue(thisvAddr, thisvVal); |
| masm.branchTestString(Assembler::NotEqual, thisvVal, &failureRestoreArgc); |
| |
| Register thisvString = masm.extractString(thisvVal, ExtractTemp0); |
| masm.branchPtr(Assembler::NotEqual, Address(ICStubReg, offsetOfExpectedThis()), |
| thisvString, &failureRestoreArgc); |
| regs.add(thisvVal); |
| } |
| |
| // Main stub body. |
| { |
| Register paramReg = regs.takeAny(); |
| |
| // Push arguments. |
| enterStubFrame(masm, scratchReg); |
| masm.loadPtr(Address(ICStubReg, offsetOfTemplateObject()), paramReg); |
| masm.push(paramReg); |
| |
| if (!callVM(CopyArrayInfo, masm)) |
| return false; |
| leaveStubFrame(masm); |
| regs.add(paramReg); |
| } |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| // Guard failure path. |
| masm.bind(&failureRestoreArgc); |
| masm.move32(Imm32(1), R0.scratchReg()); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICCall_IsSuspendedStarGenerator::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| // The IsSuspendedStarGenerator intrinsic is only called in self-hosted |
| // code, so it's safe to assume we have a single argument and the callee |
| // is our intrinsic. |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| |
| // Load the argument. |
| Address argAddr(masm.getStackPointer(), ICStackValueOffset); |
| ValueOperand argVal = regs.takeAnyValue(); |
| masm.loadValue(argAddr, argVal); |
| |
| // Check if it's an object. |
| Label returnFalse; |
| Register genObj = regs.takeAny(); |
| masm.branchTestObject(Assembler::NotEqual, argVal, &returnFalse); |
| masm.unboxObject(argVal, genObj); |
| |
| // Check if it's a StarGeneratorObject. |
| Register scratch = regs.takeAny(); |
| masm.branchTestObjClass(Assembler::NotEqual, genObj, scratch, &StarGeneratorObject::class_, |
| &returnFalse); |
| |
| // If the yield index slot holds an int32 value < YIELD_INDEX_CLOSING, |
| // the generator is suspended. |
| masm.loadValue(Address(genObj, GeneratorObject::offsetOfYieldIndexSlot()), argVal); |
| masm.branchTestInt32(Assembler::NotEqual, argVal, &returnFalse); |
| masm.unboxInt32(argVal, scratch); |
| masm.branch32(Assembler::AboveOrEqual, scratch, Imm32(StarGeneratorObject::YIELD_INDEX_CLOSING), |
| &returnFalse); |
| |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&returnFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| bool |
| ICCall_Native::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| |
| Register argcReg = R0.scratchReg(); |
| regs.take(argcReg); |
| regs.takeUnchecked(ICTailCallReg); |
| |
| if (isSpread_) |
| guardSpreadCall(masm, argcReg, &failure, isConstructing_); |
| |
| // Load the callee in R1. |
| if (isSpread_) { |
| masm.loadValue(Address(masm.getStackPointer(), ICStackValueOffset + 2 * sizeof(Value)), R1); |
| } else { |
| unsigned nonArgsSlots = (1 + isConstructing_) * sizeof(Value); |
| BaseValueIndex calleeSlot(masm.getStackPointer(), argcReg, ICStackValueOffset + nonArgsSlots); |
| masm.loadValue(calleeSlot, R1); |
| } |
| regs.take(R1); |
| |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| // Ensure callee matches this stub's callee. |
| Register callee = masm.extractObject(R1, ExtractTemp0); |
| Address expectedCallee(ICStubReg, ICCall_Native::offsetOfCallee()); |
| masm.branchPtr(Assembler::NotEqual, expectedCallee, callee, &failure); |
| |
| regs.add(R1); |
| regs.takeUnchecked(callee); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| // Note that this leaves the return address in TailCallReg. |
| enterStubFrame(masm, regs.getAny()); |
| |
| // Values are on the stack left-to-right. Calling convention wants them |
| // right-to-left so duplicate them on the stack in reverse order. |
| // |this| and callee are pushed last. |
| if (isSpread_) |
| pushSpreadCallArguments(masm, regs, argcReg, /* isJitCall = */ false, isConstructing_); |
| else |
| pushCallArguments(masm, regs, argcReg, /* isJitCall = */ false, isConstructing_); |
| |
| if (isConstructing_) { |
| // Stack looks like: [ ..., Arg0Val, ThisVal, CalleeVal ] |
| // Replace ThisVal with MagicValue(JS_IS_CONSTRUCTING) |
| masm.storeValue(MagicValue(JS_IS_CONSTRUCTING), Address(masm.getStackPointer(), sizeof(Value))); |
| } |
| |
| |
| // Native functions have the signature: |
| // |
| // bool (*)(JSContext*, unsigned, Value* vp) |
| // |
| // Where vp[0] is space for callee/return value, vp[1] is |this|, and vp[2] onward |
| // are the function arguments. |
| |
| // Initialize vp. |
| Register vpReg = regs.takeAny(); |
| masm.moveStackPtrTo(vpReg); |
| |
| // Construct a native exit frame. |
| masm.push(argcReg); |
| |
| Register scratch = regs.takeAny(); |
| EmitBaselineCreateStubFrameDescriptor(masm, scratch); |
| masm.push(scratch); |
| masm.push(ICTailCallReg); |
| masm.enterFakeExitFrameForNative(isConstructing_); |
| |
| // Execute call. |
| masm.setupUnalignedABICall(scratch); |
| masm.loadJSContext(scratch); |
| masm.passABIArg(scratch); |
| masm.passABIArg(argcReg); |
| masm.passABIArg(vpReg); |
| |
| #ifdef JS_SIMULATOR |
| // The simulator requires VM calls to be redirected to a special swi |
| // instruction to handle them, so we store the redirected pointer in the |
| // stub and use that instead of the original one. |
| masm.callWithABI(Address(ICStubReg, ICCall_Native::offsetOfNative())); |
| #else |
| masm.callWithABI(Address(callee, JSFunction::offsetOfNativeOrScript())); |
| #endif |
| |
| // Test for failure. |
| masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); |
| |
| // Load the return value into R0. |
| masm.loadValue(Address(masm.getStackPointer(), NativeExitFrameLayout::offsetOfResult()), R0); |
| |
| leaveStubFrame(masm); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICCall_ClassHook::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| |
| Register argcReg = R0.scratchReg(); |
| regs.take(argcReg); |
| regs.takeUnchecked(ICTailCallReg); |
| |
| // Load the callee in R1. |
| unsigned nonArgSlots = (1 + isConstructing_) * sizeof(Value); |
| BaseValueIndex calleeSlot(masm.getStackPointer(), argcReg, ICStackValueOffset + nonArgSlots); |
| masm.loadValue(calleeSlot, R1); |
| regs.take(R1); |
| |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| // Ensure the callee's class matches the one in this stub. |
| Register callee = masm.extractObject(R1, ExtractTemp0); |
| Register scratch = regs.takeAny(); |
| masm.loadObjClass(callee, scratch); |
| masm.branchPtr(Assembler::NotEqual, |
| Address(ICStubReg, ICCall_ClassHook::offsetOfClass()), |
| scratch, &failure); |
| |
| regs.add(R1); |
| regs.takeUnchecked(callee); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| // Note that this leaves the return address in TailCallReg. |
| enterStubFrame(masm, regs.getAny()); |
| |
| regs.add(scratch); |
| pushCallArguments(masm, regs, argcReg, /* isJitCall = */ false, isConstructing_); |
| regs.take(scratch); |
| |
| if (isConstructing_) { |
| // Stack looks like: [ ..., Arg0Val, ThisVal, CalleeVal ] |
| // Replace ThisVal with MagicValue(JS_IS_CONSTRUCTING) |
| masm.storeValue(MagicValue(JS_IS_CONSTRUCTING), Address(masm.getStackPointer(), sizeof(Value))); |
| } |
| |
| masm.checkStackAlignment(); |
| |
| // Native functions have the signature: |
| // |
| // bool (*)(JSContext*, unsigned, Value* vp) |
| // |
| // Where vp[0] is space for callee/return value, vp[1] is |this|, and vp[2] onward |
| // are the function arguments. |
| |
| // Initialize vp. |
| Register vpReg = regs.takeAny(); |
| masm.moveStackPtrTo(vpReg); |
| |
| // Construct a native exit frame. |
| masm.push(argcReg); |
| |
| EmitBaselineCreateStubFrameDescriptor(masm, scratch); |
| masm.push(scratch); |
| masm.push(ICTailCallReg); |
| masm.enterFakeExitFrameForNative(isConstructing_); |
| |
| // Execute call. |
| masm.setupUnalignedABICall(scratch); |
| masm.loadJSContext(scratch); |
| masm.passABIArg(scratch); |
| masm.passABIArg(argcReg); |
| masm.passABIArg(vpReg); |
| masm.callWithABI(Address(ICStubReg, ICCall_ClassHook::offsetOfNative())); |
| |
| // Test for failure. |
| masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); |
| |
| // Load the return value into R0. |
| masm.loadValue(Address(masm.getStackPointer(), NativeExitFrameLayout::offsetOfResult()), R0); |
| |
| leaveStubFrame(masm); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICCall_ScriptedApplyArray::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| |
| Register argcReg = R0.scratchReg(); |
| regs.take(argcReg); |
| regs.takeUnchecked(ICTailCallReg); |
| regs.takeUnchecked(ArgumentsRectifierReg); |
| |
| // |
| // Validate inputs |
| // |
| |
| Register target = guardFunApply(masm, regs, argcReg, /*checkNative=*/false, |
| FunApply_Array, &failure); |
| if (regs.has(target)) { |
| regs.take(target); |
| } else { |
| // If target is already a reserved reg, take another register for it, because it's |
| // probably currently an ExtractTemp, which might get clobbered later. |
| Register targetTemp = regs.takeAny(); |
| masm.movePtr(target, targetTemp); |
| target = targetTemp; |
| } |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, regs.getAny()); |
| |
| // |
| // Push arguments |
| // |
| |
| // Stack now looks like: |
| // BaselineFrameReg -------------------. |
| // v |
| // [..., fun_apply, TargetV, TargetThisV, ArgsArrayV, StubFrameHeader] |
| |
| // Push all array elements onto the stack: |
| Address arrayVal(BaselineFrameReg, STUB_FRAME_SIZE); |
| pushArrayArguments(masm, arrayVal, regs); |
| |
| // Stack now looks like: |
| // BaselineFrameReg -------------------. |
| // v |
| // [..., fun_apply, TargetV, TargetThisV, ArgsArrayV, StubFrameHeader, |
| // PushedArgN, ..., PushedArg0] |
| // Can't fail after this, so it's ok to clobber argcReg. |
| |
| // Push actual argument 0 as |thisv| for call. |
| masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + sizeof(Value))); |
| |
| // All pushes after this use Push instead of push to make sure ARM can align |
| // stack properly for call. |
| Register scratch = regs.takeAny(); |
| EmitBaselineCreateStubFrameDescriptor(masm, scratch); |
| |
| // Reload argc from length of array. |
| masm.extractObject(arrayVal, argcReg); |
| masm.loadPtr(Address(argcReg, NativeObject::offsetOfElements()), argcReg); |
| masm.load32(Address(argcReg, ObjectElements::offsetOfInitializedLength()), argcReg); |
| |
| masm.Push(argcReg); |
| masm.Push(target); |
| masm.Push(scratch); |
| |
| // Load nargs into scratch for underflow check, and then load jitcode pointer into target. |
| masm.load16ZeroExtend(Address(target, JSFunction::offsetOfNargs()), scratch); |
| masm.loadPtr(Address(target, JSFunction::offsetOfNativeOrScript()), target); |
| masm.loadBaselineOrIonRaw(target, target, nullptr); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.branch32(Assembler::AboveOrEqual, argcReg, scratch, &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| MOZ_ASSERT(ArgumentsRectifierReg != target); |
| MOZ_ASSERT(ArgumentsRectifierReg != argcReg); |
| |
| JitCode* argumentsRectifier = |
| cx->runtime()->jitRuntime()->getArgumentsRectifier(); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), target); |
| masm.loadPtr(Address(target, JitCode::offsetOfCode()), target); |
| masm.movePtr(argcReg, ArgumentsRectifierReg); |
| } |
| masm.bind(&noUnderflow); |
| regs.add(argcReg); |
| |
| // Do call |
| masm.callJit(target); |
| leaveStubFrame(masm, true); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICCall_ScriptedApplyArguments::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| |
| Register argcReg = R0.scratchReg(); |
| regs.take(argcReg); |
| regs.takeUnchecked(ICTailCallReg); |
| regs.takeUnchecked(ArgumentsRectifierReg); |
| |
| // |
| // Validate inputs |
| // |
| |
| Register target = guardFunApply(masm, regs, argcReg, /*checkNative=*/false, |
| FunApply_MagicArgs, &failure); |
| if (regs.has(target)) { |
| regs.take(target); |
| } else { |
| // If target is already a reserved reg, take another register for it, because it's |
| // probably currently an ExtractTemp, which might get clobbered later. |
| Register targetTemp = regs.takeAny(); |
| masm.movePtr(target, targetTemp); |
| target = targetTemp; |
| } |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, regs.getAny()); |
| |
| // |
| // Push arguments |
| // |
| |
| // Stack now looks like: |
| // [..., fun_apply, TargetV, TargetThisV, MagicArgsV, StubFrameHeader] |
| |
| // Push all arguments supplied to caller function onto the stack. |
| pushCallerArguments(masm, regs); |
| |
| // Stack now looks like: |
| // BaselineFrameReg -------------------. |
| // v |
| // [..., fun_apply, TargetV, TargetThisV, MagicArgsV, StubFrameHeader, |
| // PushedArgN, ..., PushedArg0] |
| // Can't fail after this, so it's ok to clobber argcReg. |
| |
| // Push actual argument 0 as |thisv| for call. |
| masm.pushValue(Address(BaselineFrameReg, STUB_FRAME_SIZE + sizeof(Value))); |
| |
| // All pushes after this use Push instead of push to make sure ARM can align |
| // stack properly for call. |
| Register scratch = regs.takeAny(); |
| EmitBaselineCreateStubFrameDescriptor(masm, scratch); |
| |
| masm.loadPtr(Address(BaselineFrameReg, 0), argcReg); |
| masm.loadPtr(Address(argcReg, BaselineFrame::offsetOfNumActualArgs()), argcReg); |
| masm.Push(argcReg); |
| masm.Push(target); |
| masm.Push(scratch); |
| |
| // Load nargs into scratch for underflow check, and then load jitcode pointer into target. |
| masm.load16ZeroExtend(Address(target, JSFunction::offsetOfNargs()), scratch); |
| masm.loadPtr(Address(target, JSFunction::offsetOfNativeOrScript()), target); |
| masm.loadBaselineOrIonRaw(target, target, nullptr); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.branch32(Assembler::AboveOrEqual, argcReg, scratch, &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| MOZ_ASSERT(ArgumentsRectifierReg != target); |
| MOZ_ASSERT(ArgumentsRectifierReg != argcReg); |
| |
| JitCode* argumentsRectifier = |
| cx->runtime()->jitRuntime()->getArgumentsRectifier(); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), target); |
| masm.loadPtr(Address(target, JitCode::offsetOfCode()), target); |
| masm.movePtr(argcReg, ArgumentsRectifierReg); |
| } |
| masm.bind(&noUnderflow); |
| regs.add(argcReg); |
| |
| // Do call |
| masm.callJit(target); |
| leaveStubFrame(masm, true); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| bool |
| ICCall_ScriptedFunCall::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| bool canUseTailCallReg = regs.has(ICTailCallReg); |
| |
| Register argcReg = R0.scratchReg(); |
| MOZ_ASSERT(argcReg != ArgumentsRectifierReg); |
| |
| regs.take(argcReg); |
| regs.take(ArgumentsRectifierReg); |
| regs.takeUnchecked(ICTailCallReg); |
| |
| // Load the callee in R1. |
| // Stack Layout: [ ..., CalleeVal, ThisVal, Arg0Val, ..., ArgNVal, +ICStackValueOffset+ ] |
| BaseValueIndex calleeSlot(masm.getStackPointer(), argcReg, ICStackValueOffset + sizeof(Value)); |
| masm.loadValue(calleeSlot, R1); |
| regs.take(R1); |
| |
| // Ensure callee is fun_call. |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| |
| Register callee = masm.extractObject(R1, ExtractTemp0); |
| masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, |
| &failure); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| masm.branchPtr(Assembler::NotEqual, callee, ImmPtr(fun_call), &failure); |
| |
| // Ensure |this| is a scripted function with JIT code. |
| BaseIndex thisSlot(masm.getStackPointer(), argcReg, TimesEight, ICStackValueOffset); |
| masm.loadValue(thisSlot, R1); |
| |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| callee = masm.extractObject(R1, ExtractTemp0); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, callee, regs.getAny(), &JSFunction::class_, |
| &failure); |
| masm.branchIfFunctionHasNoScript(callee, &failure); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), callee); |
| |
| // Load the start of the target JitCode. |
| Register code = regs.takeAny(); |
| masm.loadBaselineOrIonRaw(callee, code, &failure); |
| |
| // We no longer need R1. |
| regs.add(R1); |
| |
| // Push a stub frame so that we can perform a non-tail call. |
| enterStubFrame(masm, regs.getAny()); |
| if (canUseTailCallReg) |
| regs.add(ICTailCallReg); |
| |
| // Decrement argc if argc > 0. If argc == 0, push |undefined| as |this|. |
| Label zeroArgs, done; |
| masm.branchTest32(Assembler::Zero, argcReg, argcReg, &zeroArgs); |
| |
| // Avoid the copy of the callee (function.call). |
| masm.sub32(Imm32(1), argcReg); |
| |
| // Values are on the stack left-to-right. Calling convention wants them |
| // right-to-left so duplicate them on the stack in reverse order. |
| |
| pushCallArguments(masm, regs, argcReg, /* isJitCall = */ true); |
| |
| // Pop scripted callee (the original |this|). |
| ValueOperand val = regs.takeAnyValue(); |
| masm.popValue(val); |
| |
| masm.jump(&done); |
| masm.bind(&zeroArgs); |
| |
| // Copy scripted callee (the original |this|). |
| Address thisSlotFromStubFrame(BaselineFrameReg, STUB_FRAME_SIZE); |
| masm.loadValue(thisSlotFromStubFrame, val); |
| |
| // Align the stack. |
| masm.alignJitStackBasedOnNArgs(0); |
| |
| // Store the new |this|. |
| masm.pushValue(UndefinedValue()); |
| |
| masm.bind(&done); |
| |
| // Unbox scripted callee. |
| callee = masm.extractObject(val, ExtractTemp0); |
| |
| Register scratch = regs.takeAny(); |
| EmitBaselineCreateStubFrameDescriptor(masm, scratch); |
| |
| // Note that we use Push, not push, so that callJit will align the stack |
| // properly on ARM. |
| masm.Push(argcReg); |
| masm.Push(callee); |
| masm.Push(scratch); |
| |
| // Handle arguments underflow. |
| Label noUnderflow; |
| masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), callee); |
| masm.branch32(Assembler::AboveOrEqual, argcReg, callee, &noUnderflow); |
| { |
| // Call the arguments rectifier. |
| MOZ_ASSERT(ArgumentsRectifierReg != code); |
| MOZ_ASSERT(ArgumentsRectifierReg != argcReg); |
| |
| JitCode* argumentsRectifier = |
| cx->runtime()->jitRuntime()->getArgumentsRectifier(); |
| |
| masm.movePtr(ImmGCPtr(argumentsRectifier), code); |
| masm.loadPtr(Address(code, JitCode::offsetOfCode()), code); |
| masm.movePtr(argcReg, ArgumentsRectifierReg); |
| } |
| |
| masm.bind(&noUnderflow); |
| masm.callJit(code); |
| |
| leaveStubFrame(masm, true); |
| |
| // Enter type monitor IC to type-check result. |
| EmitEnterTypeMonitorIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| static bool |
| DoubleValueToInt32ForSwitch(Value* v) |
| { |
| double d = v->toDouble(); |
| int32_t truncated = int32_t(d); |
| if (d != double(truncated)) |
| return false; |
| |
| v->setInt32(truncated); |
| return true; |
| } |
| |
| bool |
| ICTableSwitch::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label isInt32, notInt32, outOfRange; |
| Register scratch = R1.scratchReg(); |
| |
| masm.branchTestInt32(Assembler::NotEqual, R0, ¬Int32); |
| |
| Register key = masm.extractInt32(R0, ExtractTemp0); |
| |
| masm.bind(&isInt32); |
| |
| masm.load32(Address(ICStubReg, offsetof(ICTableSwitch, min_)), scratch); |
| masm.sub32(scratch, key); |
| masm.branch32(Assembler::BelowOrEqual, |
| Address(ICStubReg, offsetof(ICTableSwitch, length_)), key, &outOfRange); |
| |
| masm.loadPtr(Address(ICStubReg, offsetof(ICTableSwitch, table_)), scratch); |
| masm.loadPtr(BaseIndex(scratch, key, ScalePointer), scratch); |
| |
| EmitChangeICReturnAddress(masm, scratch); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(¬Int32); |
| |
| masm.branchTestDouble(Assembler::NotEqual, R0, &outOfRange); |
| if (cx->runtime()->jitSupportsFloatingPoint) { |
| masm.unboxDouble(R0, FloatReg0); |
| |
| // N.B. -0 === 0, so convert -0 to a 0 int32. |
| masm.convertDoubleToInt32(FloatReg0, key, &outOfRange, /* negativeZeroCheck = */ false); |
| } else { |
| // Pass pointer to double value. |
| masm.pushValue(R0); |
| masm.moveStackPtrTo(R0.scratchReg()); |
| |
| masm.setupUnalignedABICall(scratch); |
| masm.passABIArg(R0.scratchReg()); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, DoubleValueToInt32ForSwitch)); |
| |
| // If the function returns |true|, the value has been converted to |
| // int32. |
| masm.movePtr(ReturnReg, scratch); |
| masm.popValue(R0); |
| masm.branchIfFalseBool(scratch, &outOfRange); |
| masm.unboxInt32(R0, key); |
| } |
| masm.jump(&isInt32); |
| |
| masm.bind(&outOfRange); |
| |
| masm.loadPtr(Address(ICStubReg, offsetof(ICTableSwitch, defaultTarget_)), scratch); |
| |
| EmitChangeICReturnAddress(masm, scratch); |
| EmitReturnFromIC(masm); |
| return true; |
| } |
| |
| ICStub* |
| ICTableSwitch::Compiler::getStub(ICStubSpace* space) |
| { |
| JitCode* code = getStubCode(); |
| if (!code) |
| return nullptr; |
| |
| jsbytecode* pc = pc_; |
| pc += JUMP_OFFSET_LEN; |
| int32_t low = GET_JUMP_OFFSET(pc); |
| pc += JUMP_OFFSET_LEN; |
| int32_t high = GET_JUMP_OFFSET(pc); |
| int32_t length = high - low + 1; |
| pc += JUMP_OFFSET_LEN; |
| |
| void** table = (void**) space->alloc(sizeof(void*) * length); |
| if (!table) |
| return nullptr; |
| |
| jsbytecode* defaultpc = pc_ + GET_JUMP_OFFSET(pc_); |
| |
| for (int32_t i = 0; i < length; i++) { |
| int32_t off = GET_JUMP_OFFSET(pc); |
| if (off) |
| table[i] = pc_ + off; |
| else |
| table[i] = defaultpc; |
| pc += JUMP_OFFSET_LEN; |
| } |
| |
| return newStub<ICTableSwitch>(space, code, table, low, length, defaultpc); |
| } |
| |
| void |
| ICTableSwitch::fixupJumpTable(JSScript* script, BaselineScript* baseline) |
| { |
| defaultTarget_ = baseline->nativeCodeForPC(script, (jsbytecode*) defaultTarget_); |
| |
| for (int32_t i = 0; i < length_; i++) |
| table_[i] = baseline->nativeCodeForPC(script, (jsbytecode*) table_[i]); |
| } |
| |
| // |
| // IteratorNew_Fallback |
| // |
| |
| static bool |
| DoIteratorNewFallback(JSContext* cx, BaselineFrame* frame, ICIteratorNew_Fallback* stub, |
| HandleValue value, MutableHandleValue res) |
| { |
| jsbytecode* pc = stub->icEntry()->pc(frame->script()); |
| FallbackICSpew(cx, stub, "IteratorNew"); |
| |
| uint8_t flags = GET_UINT8(pc); |
| res.set(value); |
| return ValueToIterator(cx, flags, res); |
| } |
| |
| typedef bool (*DoIteratorNewFallbackFn)(JSContext*, BaselineFrame*, ICIteratorNew_Fallback*, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoIteratorNewFallbackInfo = |
| FunctionInfo<DoIteratorNewFallbackFn>(DoIteratorNewFallback, TailCall, PopValues(1)); |
| |
| bool |
| ICIteratorNew_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| // Sync stack for the decompiler. |
| masm.pushValue(R0); |
| |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoIteratorNewFallbackInfo, masm); |
| } |
| |
| // |
| // IteratorMore_Fallback |
| // |
| |
| static bool |
| DoIteratorMoreFallback(JSContext* cx, BaselineFrame* frame, ICIteratorMore_Fallback* stub_, |
| HandleObject iterObj, MutableHandleValue res) |
| { |
| // This fallback stub may trigger debug mode toggling. |
| DebugModeOSRVolatileStub<ICIteratorMore_Fallback*> stub(frame, stub_); |
| |
| FallbackICSpew(cx, stub, "IteratorMore"); |
| |
| if (!IteratorMore(cx, iterObj, res)) |
| return false; |
| |
| // Check if debug mode toggling made the stub invalid. |
| if (stub.invalid()) |
| return true; |
| |
| if (!res.isMagic(JS_NO_ITER_VALUE) && !res.isString()) |
| stub->setHasNonStringResult(); |
| |
| if (iterObj->is<PropertyIteratorObject>() && |
| !stub->hasStub(ICStub::IteratorMore_Native)) |
| { |
| ICIteratorMore_Native::Compiler compiler(cx); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(frame->script())); |
| if (!newStub) |
| return false; |
| stub->addNewStub(newStub); |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoIteratorMoreFallbackFn)(JSContext*, BaselineFrame*, ICIteratorMore_Fallback*, |
| HandleObject, MutableHandleValue); |
| static const VMFunction DoIteratorMoreFallbackInfo = |
| FunctionInfo<DoIteratorMoreFallbackFn>(DoIteratorMoreFallback, TailCall); |
| |
| bool |
| ICIteratorMore_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.unboxObject(R0, R0.scratchReg()); |
| masm.push(R0.scratchReg()); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoIteratorMoreFallbackInfo, masm); |
| } |
| |
| // |
| // IteratorMore_Native |
| // |
| |
| bool |
| ICIteratorMore_Native::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| |
| Register obj = masm.extractObject(R0, ExtractTemp0); |
| |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); |
| Register nativeIterator = regs.takeAny(); |
| Register scratch = regs.takeAny(); |
| |
| masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, |
| &PropertyIteratorObject::class_, &failure); |
| masm.loadObjPrivate(obj, JSObject::ITER_CLASS_NFIXED_SLOTS, nativeIterator); |
| |
| masm.branchTest32(Assembler::NonZero, Address(nativeIterator, offsetof(NativeIterator, flags)), |
| Imm32(JSITER_FOREACH), &failure); |
| |
| // If props_cursor < props_end, load the next string and advance the cursor. |
| // Else, return MagicValue(JS_NO_ITER_VALUE). |
| Label iterDone; |
| Address cursorAddr(nativeIterator, offsetof(NativeIterator, props_cursor)); |
| Address cursorEndAddr(nativeIterator, offsetof(NativeIterator, props_end)); |
| masm.loadPtr(cursorAddr, scratch); |
| masm.branchPtr(Assembler::BelowOrEqual, cursorEndAddr, scratch, &iterDone); |
| |
| // Get next string. |
| masm.loadPtr(Address(scratch, 0), scratch); |
| |
| // Increase the cursor. |
| masm.addPtr(Imm32(sizeof(JSString*)), cursorAddr); |
| |
| masm.tagValue(JSVAL_TYPE_STRING, scratch, R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&iterDone); |
| masm.moveValue(MagicValue(JS_NO_ITER_VALUE), R0); |
| EmitReturnFromIC(masm); |
| |
| // Failure case - jump to next stub |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // IteratorClose_Fallback |
| // |
| |
| static bool |
| DoIteratorCloseFallback(JSContext* cx, ICIteratorClose_Fallback* stub, HandleValue iterValue) |
| { |
| FallbackICSpew(cx, stub, "IteratorClose"); |
| |
| RootedObject iteratorObject(cx, &iterValue.toObject()); |
| return CloseIterator(cx, iteratorObject); |
| } |
| |
| typedef bool (*DoIteratorCloseFallbackFn)(JSContext*, ICIteratorClose_Fallback*, HandleValue); |
| static const VMFunction DoIteratorCloseFallbackInfo = |
| FunctionInfo<DoIteratorCloseFallbackFn>(DoIteratorCloseFallback, TailCall); |
| |
| bool |
| ICIteratorClose_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| |
| return tailCallVM(DoIteratorCloseFallbackInfo, masm); |
| } |
| |
| // |
| // InstanceOf_Fallback |
| // |
| |
| static bool |
| TryAttachInstanceOfStub(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub, |
| HandleFunction fun, bool* attached) |
| { |
| MOZ_ASSERT(!*attached); |
| if (fun->isBoundFunction()) |
| return true; |
| |
| Shape* shape = fun->lookupPure(cx->names().prototype); |
| if (!shape || !shape->hasSlot() || !shape->hasDefaultGetter()) |
| return true; |
| |
| uint32_t slot = shape->slot(); |
| MOZ_ASSERT(fun->numFixedSlots() == 0, "Stub code relies on this"); |
| |
| if (!fun->getSlot(slot).isObject()) |
| return true; |
| |
| JSObject* protoObject = &fun->getSlot(slot).toObject(); |
| |
| JitSpew(JitSpew_BaselineIC, " Generating InstanceOf(Function) stub"); |
| ICInstanceOf_Function::Compiler compiler(cx, fun->lastProperty(), protoObject, slot); |
| ICStub* newStub = compiler.getStub(compiler.getStubSpace(frame->script())); |
| if (!newStub) |
| return false; |
| |
| stub->addNewStub(newStub); |
| *attached = true; |
| return true; |
| } |
| |
| static bool |
| DoInstanceOfFallback(JSContext* cx, BaselineFrame* frame, ICInstanceOf_Fallback* stub, |
| HandleValue lhs, HandleValue rhs, MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "InstanceOf"); |
| |
| if (!rhs.isObject()) { |
| ReportValueError(cx, JSMSG_BAD_INSTANCEOF_RHS, -1, rhs, nullptr); |
| return false; |
| } |
| |
| RootedObject obj(cx, &rhs.toObject()); |
| bool cond = false; |
| if (!HasInstance(cx, obj, lhs, &cond)) |
| return false; |
| |
| res.setBoolean(cond); |
| |
| if (!obj->is<JSFunction>()) { |
| stub->noteUnoptimizableAccess(); |
| return true; |
| } |
| |
| // For functions, keep track of the |prototype| property in type information, |
| // for use during Ion compilation. |
| EnsureTrackPropertyTypes(cx, obj, NameToId(cx->names().prototype)); |
| |
| if (stub->numOptimizedStubs() >= ICInstanceOf_Fallback::MAX_OPTIMIZED_STUBS) |
| return true; |
| |
| RootedFunction fun(cx, &obj->as<JSFunction>()); |
| bool attached = false; |
| if (!TryAttachInstanceOfStub(cx, frame, stub, fun, &attached)) |
| return false; |
| if (!attached) |
| stub->noteUnoptimizableAccess(); |
| return true; |
| } |
| |
| typedef bool (*DoInstanceOfFallbackFn)(JSContext*, BaselineFrame*, ICInstanceOf_Fallback*, |
| HandleValue, HandleValue, MutableHandleValue); |
| static const VMFunction DoInstanceOfFallbackInfo = |
| FunctionInfo<DoInstanceOfFallbackFn>(DoInstanceOfFallback, TailCall, PopValues(2)); |
| |
| bool |
| ICInstanceOf_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| // Sync stack for the decompiler. |
| masm.pushValue(R0); |
| masm.pushValue(R1); |
| |
| masm.pushValue(R1); |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoInstanceOfFallbackInfo, masm); |
| } |
| |
| bool |
| ICInstanceOf_Function::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| Label failure; |
| |
| // Ensure RHS is an object. |
| masm.branchTestObject(Assembler::NotEqual, R1, &failure); |
| Register rhsObj = masm.extractObject(R1, ExtractTemp0); |
| |
| // Allow using R1's type register as scratch. We have to restore it when |
| // we want to jump to the next stub. |
| Label failureRestoreR1; |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(1)); |
| regs.takeUnchecked(rhsObj); |
| |
| Register scratch1 = regs.takeAny(); |
| Register scratch2 = regs.takeAny(); |
| |
| // Shape guard. |
| masm.loadPtr(Address(ICStubReg, ICInstanceOf_Function::offsetOfShape()), scratch1); |
| masm.branchTestObjShape(Assembler::NotEqual, rhsObj, scratch1, &failureRestoreR1); |
| |
| // Guard on the .prototype object. |
| masm.loadPtr(Address(rhsObj, NativeObject::offsetOfSlots()), scratch1); |
| masm.load32(Address(ICStubReg, ICInstanceOf_Function::offsetOfSlot()), scratch2); |
| BaseValueIndex prototypeSlot(scratch1, scratch2); |
| masm.branchTestObject(Assembler::NotEqual, prototypeSlot, &failureRestoreR1); |
| masm.unboxObject(prototypeSlot, scratch1); |
| masm.branchPtr(Assembler::NotEqual, |
| Address(ICStubReg, ICInstanceOf_Function::offsetOfPrototypeObject()), |
| scratch1, &failureRestoreR1); |
| |
| // If LHS is a primitive, return false. |
| Label returnFalse, returnTrue; |
| masm.branchTestObject(Assembler::NotEqual, R0, &returnFalse); |
| |
| // LHS is an object. Load its proto. |
| masm.unboxObject(R0, scratch2); |
| masm.loadObjProto(scratch2, scratch2); |
| |
| { |
| // Walk the proto chain until we either reach the target object, |
| // nullptr or LazyProto. |
| Label loop; |
| masm.bind(&loop); |
| |
| masm.branchPtr(Assembler::Equal, scratch2, scratch1, &returnTrue); |
| masm.branchTestPtr(Assembler::Zero, scratch2, scratch2, &returnFalse); |
| |
| MOZ_ASSERT(uintptr_t(TaggedProto::LazyProto) == 1); |
| masm.branchPtr(Assembler::Equal, scratch2, ImmWord(1), &failureRestoreR1); |
| |
| masm.loadObjProto(scratch2, scratch2); |
| masm.jump(&loop); |
| } |
| |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&returnFalse); |
| masm.moveValue(BooleanValue(false), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&returnTrue); |
| masm.moveValue(BooleanValue(true), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failureRestoreR1); |
| masm.tagValue(JSVAL_TYPE_OBJECT, rhsObj, R1); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| // |
| // TypeOf_Fallback |
| // |
| |
| static bool |
| DoTypeOfFallback(JSContext* cx, BaselineFrame* frame, ICTypeOf_Fallback* stub, HandleValue val, |
| MutableHandleValue res) |
| { |
| FallbackICSpew(cx, stub, "TypeOf"); |
| JSType type = js::TypeOfValue(val); |
| RootedString string(cx, TypeName(type, cx->names())); |
| |
| res.setString(string); |
| |
| MOZ_ASSERT(type != JSTYPE_NULL); |
| if (type != JSTYPE_OBJECT && type != JSTYPE_FUNCTION) { |
| // Create a new TypeOf stub. |
| JitSpew(JitSpew_BaselineIC, " Generating TypeOf stub for JSType (%d)", (int) type); |
| ICTypeOf_Typed::Compiler compiler(cx, type, string); |
| ICStub* typeOfStub = compiler.getStub(compiler.getStubSpace(frame->script())); |
| if (!typeOfStub) |
| return false; |
| stub->addNewStub(typeOfStub); |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*DoTypeOfFallbackFn)(JSContext*, BaselineFrame* frame, ICTypeOf_Fallback*, |
| HandleValue, MutableHandleValue); |
| static const VMFunction DoTypeOfFallbackInfo = |
| FunctionInfo<DoTypeOfFallbackFn>(DoTypeOfFallback, TailCall); |
| |
| bool |
| ICTypeOf_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.pushValue(R0); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoTypeOfFallbackInfo, masm); |
| } |
| |
| bool |
| ICTypeOf_Typed::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| MOZ_ASSERT(type_ != JSTYPE_NULL); |
| MOZ_ASSERT(type_ != JSTYPE_FUNCTION); |
| MOZ_ASSERT(type_ != JSTYPE_OBJECT); |
| |
| Label failure; |
| switch(type_) { |
| case JSTYPE_VOID: |
| masm.branchTestUndefined(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| case JSTYPE_STRING: |
| masm.branchTestString(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| case JSTYPE_NUMBER: |
| masm.branchTestNumber(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| case JSTYPE_BOOLEAN: |
| masm.branchTestBoolean(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| case JSTYPE_SYMBOL: |
| masm.branchTestSymbol(Assembler::NotEqual, R0, &failure); |
| break; |
| |
| default: |
| MOZ_CRASH("Unexpected type"); |
| } |
| |
| masm.movePtr(ImmGCPtr(typeString_), R0.scratchReg()); |
| masm.tagValue(JSVAL_TYPE_STRING, R0.scratchReg(), R0); |
| EmitReturnFromIC(masm); |
| |
| masm.bind(&failure); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| static bool |
| DoRetSubFallback(JSContext* cx, BaselineFrame* frame, ICRetSub_Fallback* stub, |
| HandleValue val, uint8_t** resumeAddr) |
| { |
| FallbackICSpew(cx, stub, "RetSub"); |
| |
| // |val| is the bytecode offset where we should resume. |
| |
| MOZ_ASSERT(val.isInt32()); |
| MOZ_ASSERT(val.toInt32() >= 0); |
| |
| JSScript* script = frame->script(); |
| uint32_t offset = uint32_t(val.toInt32()); |
| |
| *resumeAddr = script->baselineScript()->nativeCodeForPC(script, script->offsetToPC(offset)); |
| |
| if (stub->numOptimizedStubs() >= ICRetSub_Fallback::MAX_OPTIMIZED_STUBS) |
| return true; |
| |
| // Attach an optimized stub for this pc offset. |
| JitSpew(JitSpew_BaselineIC, " Generating RetSub stub for pc offset %u", offset); |
| ICRetSub_Resume::Compiler compiler(cx, offset, *resumeAddr); |
| ICStub* optStub = compiler.getStub(compiler.getStubSpace(script)); |
| if (!optStub) |
| return false; |
| |
| stub->addNewStub(optStub); |
| return true; |
| } |
| |
| typedef bool(*DoRetSubFallbackFn)(JSContext* cx, BaselineFrame*, ICRetSub_Fallback*, |
| HandleValue, uint8_t**); |
| static const VMFunction DoRetSubFallbackInfo = FunctionInfo<DoRetSubFallbackFn>(DoRetSubFallback); |
| |
| typedef bool (*ThrowFn)(JSContext*, HandleValue); |
| static const VMFunction ThrowInfoBaseline = FunctionInfo<ThrowFn>(js::Throw, TailCall); |
| |
| bool |
| ICRetSub_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| // If R0 is BooleanValue(true), rethrow R1. |
| Label rethrow; |
| masm.branchTestBooleanTruthy(true, R0, &rethrow); |
| { |
| // Call a stub to get the native code address for the pc offset in R1. |
| AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); |
| regs.take(R1); |
| regs.takeUnchecked(ICTailCallReg); |
| Register scratch = regs.getAny(); |
| |
| enterStubFrame(masm, scratch); |
| |
| masm.pushValue(R1); |
| masm.push(ICStubReg); |
| pushFramePtr(masm, scratch); |
| |
| if (!callVM(DoRetSubFallbackInfo, masm)) |
| return false; |
| |
| leaveStubFrame(masm); |
| |
| EmitChangeICReturnAddress(masm, ReturnReg); |
| EmitReturnFromIC(masm); |
| } |
| |
| masm.bind(&rethrow); |
| EmitRestoreTailCallReg(masm); |
| masm.pushValue(R1); |
| return tailCallVM(ThrowInfoBaseline, masm); |
| } |
| |
| bool |
| ICRetSub_Resume::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| // If R0 is BooleanValue(true), rethrow R1. |
| Label fail, rethrow; |
| masm.branchTestBooleanTruthy(true, R0, &rethrow); |
| |
| // R1 is the pc offset. Ensure it matches this stub's offset. |
| Register offset = masm.extractInt32(R1, ExtractTemp0); |
| masm.branch32(Assembler::NotEqual, |
| Address(ICStubReg, ICRetSub_Resume::offsetOfPCOffset()), |
| offset, |
| &fail); |
| |
| // pc offset matches, resume at the target pc. |
| masm.loadPtr(Address(ICStubReg, ICRetSub_Resume::offsetOfAddr()), R0.scratchReg()); |
| EmitChangeICReturnAddress(masm, R0.scratchReg()); |
| EmitReturnFromIC(masm); |
| |
| // Rethrow the Value stored in R1. |
| masm.bind(&rethrow); |
| EmitRestoreTailCallReg(masm); |
| masm.pushValue(R1); |
| if (!tailCallVM(ThrowInfoBaseline, masm)) |
| return false; |
| |
| masm.bind(&fail); |
| EmitStubGuardFailure(masm); |
| return true; |
| } |
| |
| ICTypeMonitor_SingleObject::ICTypeMonitor_SingleObject(JitCode* stubCode, JSObject* obj) |
| : ICStub(TypeMonitor_SingleObject, stubCode), |
| obj_(obj) |
| { } |
| |
| ICTypeMonitor_ObjectGroup::ICTypeMonitor_ObjectGroup(JitCode* stubCode, ObjectGroup* group) |
| : ICStub(TypeMonitor_ObjectGroup, stubCode), |
| group_(group) |
| { } |
| |
| ICTypeUpdate_SingleObject::ICTypeUpdate_SingleObject(JitCode* stubCode, JSObject* obj) |
| : ICStub(TypeUpdate_SingleObject, stubCode), |
| obj_(obj) |
| { } |
| |
| ICTypeUpdate_ObjectGroup::ICTypeUpdate_ObjectGroup(JitCode* stubCode, ObjectGroup* group) |
| : ICStub(TypeUpdate_ObjectGroup, stubCode), |
| group_(group) |
| { } |
| |
| ICGetElemNativeStub::ICGetElemNativeStub(ICStub::Kind kind, JitCode* stubCode, |
| ICStub* firstMonitorStub, |
| ReceiverGuard guard, AccessType acctype, |
| bool needsAtomize, bool isSymbol) |
| : ICMonitoredStub(kind, stubCode, firstMonitorStub), |
| receiverGuard_(guard) |
| { |
| extra_ = (static_cast<uint16_t>(acctype) << ACCESSTYPE_SHIFT) | |
| (static_cast<uint16_t>(needsAtomize) << NEEDS_ATOMIZE_SHIFT) | |
| (static_cast<uint16_t>(isSymbol) << ISSYMBOL_SHIFT); |
| } |
| |
| ICGetElemNativeStub::~ICGetElemNativeStub() |
| { } |
| |
| template <class T> |
| ICGetElemNativeGetterStub<T>::ICGetElemNativeGetterStub( |
| ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub, |
| ReceiverGuard guard, const T* key, AccType acctype, bool needsAtomize, |
| JSFunction* getter, uint32_t pcOffset) |
| : ICGetElemNativeStubImpl<T>(kind, stubCode, firstMonitorStub, guard, key, acctype, needsAtomize), |
| getter_(getter), |
| pcOffset_(pcOffset) |
| { |
| MOZ_ASSERT(kind == ICStub::GetElem_NativePrototypeCallNativeName || |
| kind == ICStub::GetElem_NativePrototypeCallNativeSymbol || |
| kind == ICStub::GetElem_NativePrototypeCallScriptedName || |
| kind == ICStub::GetElem_NativePrototypeCallScriptedSymbol); |
| MOZ_ASSERT(acctype == ICGetElemNativeStub::NativeGetter || |
| acctype == ICGetElemNativeStub::ScriptedGetter); |
| } |
| |
| template <class T> |
| ICGetElem_NativePrototypeSlot<T>::ICGetElem_NativePrototypeSlot( |
| JitCode* stubCode, ICStub* firstMonitorStub, ReceiverGuard guard, |
| const T* key, AccType acctype, bool needsAtomize, uint32_t offset, |
| JSObject* holder, Shape* holderShape) |
| : ICGetElemNativeSlotStub<T>(getGetElemStubKind<T>(ICStub::GetElem_NativePrototypeSlotName), |
| stubCode, firstMonitorStub, guard, key, acctype, needsAtomize, offset), |
| holder_(holder), |
| holderShape_(holderShape) |
| { } |
| |
| template <class T> |
| ICGetElemNativePrototypeCallStub<T>::ICGetElemNativePrototypeCallStub( |
| ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub, |
| ReceiverGuard guard, const T* key, AccType acctype, |
| bool needsAtomize, JSFunction* getter, uint32_t pcOffset, |
| JSObject* holder, Shape* holderShape) |
| : ICGetElemNativeGetterStub<T>(kind, stubCode, firstMonitorStub, guard, key, acctype, needsAtomize, |
| getter, pcOffset), |
| holder_(holder), |
| holderShape_(holderShape) |
| {} |
| |
| template <class T> |
| /* static */ ICGetElem_NativePrototypeCallNative<T>* |
| ICGetElem_NativePrototypeCallNative<T>::Clone(JSContext* cx, |
| ICStubSpace* space, |
| ICStub* firstMonitorStub, |
| ICGetElem_NativePrototypeCallNative<T>& other) |
| { |
| return ICStub::New<ICGetElem_NativePrototypeCallNative<T>>(cx, space, other.jitCode(), |
| firstMonitorStub, other.receiverGuard(), &other.key().get(), other.accessType(), |
| other.needsAtomize(), other.getter(), other.pcOffset_, other.holder(), |
| other.holderShape()); |
| } |
| |
| template ICGetElem_NativePrototypeCallNative<JS::Symbol*>* |
| ICGetElem_NativePrototypeCallNative<JS::Symbol*>::Clone(JSContext*, ICStubSpace*, ICStub*, |
| ICGetElem_NativePrototypeCallNative<JS::Symbol*>&); |
| template ICGetElem_NativePrototypeCallNative<js::PropertyName*>* |
| ICGetElem_NativePrototypeCallNative<js::PropertyName*>::Clone(JSContext*, ICStubSpace*, ICStub*, |
| ICGetElem_NativePrototypeCallNative<js::PropertyName*>&); |
| |
| template <class T> |
| /* static */ ICGetElem_NativePrototypeCallScripted<T>* |
| ICGetElem_NativePrototypeCallScripted<T>::Clone(JSContext* cx, |
| ICStubSpace* space, |
| ICStub* firstMonitorStub, |
| ICGetElem_NativePrototypeCallScripted<T>& other) |
| { |
| return ICStub::New<ICGetElem_NativePrototypeCallScripted<T>>(cx, space, other.jitCode(), |
| firstMonitorStub, other.receiverGuard(), &other.key().get(), other.accessType(), |
| other.needsAtomize(), other.getter(), other.pcOffset_, other.holder(), |
| other.holderShape()); |
| } |
| |
| template ICGetElem_NativePrototypeCallScripted<JS::Symbol*>* |
| ICGetElem_NativePrototypeCallScripted<JS::Symbol*>::Clone(JSContext*, ICStubSpace*, ICStub*, |
| ICGetElem_NativePrototypeCallScripted<JS::Symbol*>&); |
| template ICGetElem_NativePrototypeCallScripted<js::PropertyName*>* |
| ICGetElem_NativePrototypeCallScripted<js::PropertyName*>::Clone(JSContext*, ICStubSpace*, ICStub*, |
| ICGetElem_NativePrototypeCallScripted<js::PropertyName*>&); |
| |
| ICGetElem_Dense::ICGetElem_Dense(JitCode* stubCode, ICStub* firstMonitorStub, Shape* shape) |
| : ICMonitoredStub(GetElem_Dense, stubCode, firstMonitorStub), |
| shape_(shape) |
| { } |
| |
| /* static */ ICGetElem_Dense* |
| ICGetElem_Dense::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICGetElem_Dense& other) |
| { |
| return New<ICGetElem_Dense>(cx, space, other.jitCode(), firstMonitorStub, other.shape_); |
| } |
| |
| ICGetElem_UnboxedArray::ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstMonitorStub, |
| ObjectGroup *group) |
| : ICMonitoredStub(GetElem_UnboxedArray, stubCode, firstMonitorStub), |
| group_(group) |
| { } |
| |
| /* static */ ICGetElem_UnboxedArray* |
| ICGetElem_UnboxedArray::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICGetElem_UnboxedArray& other) |
| { |
| return New<ICGetElem_UnboxedArray>(cx, space, other.jitCode(), firstMonitorStub, other.group_); |
| } |
| |
| ICGetElem_TypedArray::ICGetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type) |
| : ICStub(GetElem_TypedArray, stubCode), |
| shape_(shape) |
| { |
| extra_ = uint16_t(type); |
| MOZ_ASSERT(extra_ == type); |
| } |
| |
| /* static */ ICGetElem_Arguments* |
| ICGetElem_Arguments::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICGetElem_Arguments& other) |
| { |
| return New<ICGetElem_Arguments>(cx, space, other.jitCode(), firstMonitorStub, other.which()); |
| } |
| |
| ICSetElem_DenseOrUnboxedArray::ICSetElem_DenseOrUnboxedArray(JitCode* stubCode, Shape* shape, ObjectGroup* group) |
| : ICUpdatedStub(SetElem_DenseOrUnboxedArray, stubCode), |
| shape_(shape), |
| group_(group) |
| { } |
| |
| ICSetElem_DenseOrUnboxedArrayAdd::ICSetElem_DenseOrUnboxedArrayAdd(JitCode* stubCode, ObjectGroup* group, |
| size_t protoChainDepth) |
| : ICUpdatedStub(SetElem_DenseOrUnboxedArrayAdd, stubCode), |
| group_(group) |
| { |
| MOZ_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH); |
| extra_ = protoChainDepth; |
| } |
| |
| template <size_t ProtoChainDepth> |
| ICUpdatedStub* |
| ICSetElemDenseOrUnboxedArrayAddCompiler::getStubSpecific(ICStubSpace* space, |
| Handle<ShapeVector> shapes) |
| { |
| RootedObjectGroup group(cx, obj_->getGroup(cx)); |
| if (!group) |
| return nullptr; |
| Rooted<JitCode*> stubCode(cx, getStubCode()); |
| return newStub<ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>>(space, stubCode, group, shapes); |
| } |
| |
| ICSetElem_TypedArray::ICSetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type, |
| bool expectOutOfBounds) |
| : ICStub(SetElem_TypedArray, stubCode), |
| shape_(shape) |
| { |
| extra_ = uint8_t(type); |
| MOZ_ASSERT(extra_ == type); |
| extra_ |= (static_cast<uint16_t>(expectOutOfBounds) << 8); |
| } |
| |
| ICInNativeStub::ICInNativeStub(ICStub::Kind kind, JitCode* stubCode, HandleShape shape, |
| HandlePropertyName name) |
| : ICStub(kind, stubCode), |
| shape_(shape), |
| name_(name) |
| { } |
| |
| ICIn_NativePrototype::ICIn_NativePrototype(JitCode* stubCode, HandleShape shape, |
| HandlePropertyName name, HandleObject holder, |
| HandleShape holderShape) |
| : ICInNativeStub(In_NativePrototype, stubCode, shape, name), |
| holder_(holder), |
| holderShape_(holderShape) |
| { } |
| |
| ICIn_NativeDoesNotExist::ICIn_NativeDoesNotExist(JitCode* stubCode, size_t protoChainDepth, |
| HandlePropertyName name) |
| : ICStub(In_NativeDoesNotExist, stubCode), |
| name_(name) |
| { |
| MOZ_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH); |
| extra_ = protoChainDepth; |
| } |
| |
| /* static */ size_t |
| ICIn_NativeDoesNotExist::offsetOfShape(size_t idx) |
| { |
| MOZ_ASSERT(ICIn_NativeDoesNotExistImpl<0>::offsetOfShape(idx) == |
| ICIn_NativeDoesNotExistImpl< |
| ICIn_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH>::offsetOfShape(idx)); |
| return ICIn_NativeDoesNotExistImpl<0>::offsetOfShape(idx); |
| } |
| |
| template <size_t ProtoChainDepth> |
| ICIn_NativeDoesNotExistImpl<ProtoChainDepth>::ICIn_NativeDoesNotExistImpl( |
| JitCode* stubCode, Handle<ShapeVector> shapes, HandlePropertyName name) |
| : ICIn_NativeDoesNotExist(stubCode, ProtoChainDepth, name) |
| { |
| MOZ_ASSERT(shapes.length() == NumShapes); |
| for (size_t i = 0; i < NumShapes; i++) |
| shapes_[i].init(shapes[i]); |
| } |
| |
| ICInNativeDoesNotExistCompiler::ICInNativeDoesNotExistCompiler( |
| JSContext* cx, HandleObject obj, HandlePropertyName name, size_t protoChainDepth) |
| : ICStubCompiler(cx, ICStub::In_NativeDoesNotExist, Engine::Baseline), |
| obj_(cx, obj), |
| name_(cx, name), |
| protoChainDepth_(protoChainDepth) |
| { |
| MOZ_ASSERT(protoChainDepth_ <= ICIn_NativeDoesNotExist::MAX_PROTO_CHAIN_DEPTH); |
| } |
| |
| ICIn_Dense::ICIn_Dense(JitCode* stubCode, HandleShape shape) |
| : ICStub(In_Dense, stubCode), |
| shape_(shape) |
| { } |
| |
| ICGetName_GlobalLexical::ICGetName_GlobalLexical(JitCode* stubCode, ICStub* firstMonitorStub, |
| uint32_t slot) |
| : ICMonitoredStub(GetName_GlobalLexical, stubCode, firstMonitorStub), |
| slot_(slot) |
| { } |
| |
| template <size_t NumHops> |
| ICGetName_Scope<NumHops>::ICGetName_Scope(JitCode* stubCode, ICStub* firstMonitorStub, |
| Handle<ShapeVector> shapes, uint32_t offset) |
| : ICMonitoredStub(GetStubKind(), stubCode, firstMonitorStub), |
| offset_(offset) |
| { |
| JS_STATIC_ASSERT(NumHops <= MAX_HOPS); |
| MOZ_ASSERT(shapes.length() == NumHops + 1); |
| for (size_t i = 0; i < NumHops + 1; i++) |
| shapes_[i].init(shapes[i]); |
| } |
| |
| ICGetIntrinsic_Constant::ICGetIntrinsic_Constant(JitCode* stubCode, const Value& value) |
| : ICStub(GetIntrinsic_Constant, stubCode), |
| value_(value) |
| { } |
| |
| ICGetIntrinsic_Constant::~ICGetIntrinsic_Constant() |
| { } |
| |
| ICGetName_Global::ICGetName_Global(JitCode* stubCode, ICStub* firstMonitorStub, |
| ReceiverGuard guard, uint32_t offset, |
| JSObject* holder, Shape* holderShape, Shape* globalShape) |
| : ICGetPropNativePrototypeStub(GetName_Global, stubCode, firstMonitorStub, guard, offset, |
| holder, holderShape), |
| globalShape_(globalShape) |
| { } |
| |
| /* static */ ICGetName_Global* |
| ICGetName_Global::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICGetName_Global& other) |
| { |
| return New<ICGetName_Global>(cx, space, other.jitCode(), firstMonitorStub, |
| other.receiverGuard(), other.offset(), |
| other.holder(), other.holderShape(), other.globalShape()); |
| } |
| |
| ICInstanceOf_Function::ICInstanceOf_Function(JitCode* stubCode, Shape* shape, |
| JSObject* prototypeObj, uint32_t slot) |
| : ICStub(InstanceOf_Function, stubCode), |
| shape_(shape), |
| prototypeObj_(prototypeObj), |
| slot_(slot) |
| { } |
| |
| ICSetProp_Native::ICSetProp_Native(JitCode* stubCode, ObjectGroup* group, Shape* shape, |
| uint32_t offset) |
| : ICUpdatedStub(SetProp_Native, stubCode), |
| group_(group), |
| shape_(shape), |
| offset_(offset) |
| { } |
| |
| ICSetProp_Native* |
| ICSetProp_Native::Compiler::getStub(ICStubSpace* space) |
| { |
| RootedObjectGroup group(cx, obj_->getGroup(cx)); |
| if (!group) |
| return nullptr; |
| |
| RootedShape shape(cx, LastPropertyForSetProp(obj_)); |
| ICSetProp_Native* stub = newStub<ICSetProp_Native>(space, getStubCode(), group, shape, offset_); |
| if (!stub || !stub->initUpdatingChain(cx, space)) |
| return nullptr; |
| return stub; |
| } |
| |
| ICSetProp_NativeAdd::ICSetProp_NativeAdd(JitCode* stubCode, ObjectGroup* group, |
| size_t protoChainDepth, |
| Shape* newShape, |
| ObjectGroup* newGroup, |
| uint32_t offset) |
| : ICUpdatedStub(SetProp_NativeAdd, stubCode), |
| group_(group), |
| newShape_(newShape), |
| newGroup_(newGroup), |
| offset_(offset) |
| { |
| MOZ_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH); |
| extra_ = protoChainDepth; |
| } |
| |
| template <size_t ProtoChainDepth> |
| ICSetProp_NativeAddImpl<ProtoChainDepth>::ICSetProp_NativeAddImpl(JitCode* stubCode, |
| ObjectGroup* group, |
| Handle<ShapeVector> shapes, |
| Shape* newShape, |
| ObjectGroup* newGroup, |
| uint32_t offset) |
| : ICSetProp_NativeAdd(stubCode, group, ProtoChainDepth, newShape, newGroup, offset) |
| { |
| MOZ_ASSERT(shapes.length() == NumShapes); |
| for (size_t i = 0; i < NumShapes; i++) |
| shapes_[i].init(shapes[i]); |
| } |
| |
| ICSetPropNativeAddCompiler::ICSetPropNativeAddCompiler(JSContext* cx, HandleObject obj, |
| HandleShape oldShape, |
| HandleObjectGroup oldGroup, |
| size_t protoChainDepth, |
| bool isFixedSlot, |
| uint32_t offset) |
| : ICStubCompiler(cx, ICStub::SetProp_NativeAdd, Engine::Baseline), |
| obj_(cx, obj), |
| oldShape_(cx, oldShape), |
| oldGroup_(cx, oldGroup), |
| protoChainDepth_(protoChainDepth), |
| isFixedSlot_(isFixedSlot), |
| offset_(offset) |
| { |
| MOZ_ASSERT(protoChainDepth_ <= ICSetProp_NativeAdd::MAX_PROTO_CHAIN_DEPTH); |
| } |
| |
| ICSetPropCallSetter::ICSetPropCallSetter(Kind kind, JitCode* stubCode, ReceiverGuard receiverGuard, |
| JSObject* holder, Shape* holderShape, |
| JSFunction* setter, uint32_t pcOffset) |
| : ICStub(kind, stubCode), |
| receiverGuard_(receiverGuard), |
| holder_(holder), |
| holderShape_(holderShape), |
| setter_(setter), |
| pcOffset_(pcOffset) |
| { |
| MOZ_ASSERT(kind == ICStub::SetProp_CallScripted || kind == ICStub::SetProp_CallNative); |
| } |
| |
| /* static */ ICSetProp_CallScripted* |
| ICSetProp_CallScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub*, |
| ICSetProp_CallScripted& other) |
| { |
| return New<ICSetProp_CallScripted>(cx, space, other.jitCode(), other.receiverGuard(), |
| other.holder_, other.holderShape_, other.setter_, |
| other.pcOffset_); |
| } |
| |
| /* static */ ICSetProp_CallNative* |
| ICSetProp_CallNative::Clone(JSContext* cx, ICStubSpace* space, ICStub*, ICSetProp_CallNative& other) |
| { |
| return New<ICSetProp_CallNative>(cx, space, other.jitCode(), other.receiverGuard(), |
| other.holder_, other.holderShape_, other.setter_, |
| other.pcOffset_); |
| } |
| |
| ICCall_Scripted::ICCall_Scripted(JitCode* stubCode, ICStub* firstMonitorStub, |
| JSFunction* callee, JSObject* templateObject, |
| uint32_t pcOffset) |
| : ICMonitoredStub(ICStub::Call_Scripted, stubCode, firstMonitorStub), |
| callee_(callee), |
| templateObject_(templateObject), |
| pcOffset_(pcOffset) |
| { } |
| |
| /* static */ ICCall_Scripted* |
| ICCall_Scripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICCall_Scripted& other) |
| { |
| return New<ICCall_Scripted>(cx, space, other.jitCode(), firstMonitorStub, other.callee_, |
| other.templateObject_, other.pcOffset_); |
| } |
| |
| /* static */ ICCall_AnyScripted* |
| ICCall_AnyScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICCall_AnyScripted& other) |
| { |
| return New<ICCall_AnyScripted>(cx, space, other.jitCode(), firstMonitorStub, other.pcOffset_); |
| } |
| |
| ICCall_Native::ICCall_Native(JitCode* stubCode, ICStub* firstMonitorStub, |
| JSFunction* callee, JSObject* templateObject, |
| uint32_t pcOffset) |
| : ICMonitoredStub(ICStub::Call_Native, stubCode, firstMonitorStub), |
| callee_(callee), |
| templateObject_(templateObject), |
| pcOffset_(pcOffset) |
| { |
| #ifdef JS_SIMULATOR |
| // The simulator requires VM calls to be redirected to a special swi |
| // instruction to handle them. To make this work, we store the redirected |
| // pointer in the stub. |
| native_ = Simulator::RedirectNativeFunction(JS_FUNC_TO_DATA_PTR(void*, callee->native()), |
| Args_General3); |
| #endif |
| } |
| |
| /* static */ ICCall_Native* |
| ICCall_Native::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICCall_Native& other) |
| { |
| return New<ICCall_Native>(cx, space, other.jitCode(), firstMonitorStub, other.callee_, |
| other.templateObject_, other.pcOffset_); |
| } |
| |
| ICCall_ClassHook::ICCall_ClassHook(JitCode* stubCode, ICStub* firstMonitorStub, |
| const Class* clasp, Native native, |
| JSObject* templateObject, uint32_t pcOffset) |
| : ICMonitoredStub(ICStub::Call_ClassHook, stubCode, firstMonitorStub), |
| clasp_(clasp), |
| native_(JS_FUNC_TO_DATA_PTR(void*, native)), |
| templateObject_(templateObject), |
| pcOffset_(pcOffset) |
| { |
| #ifdef JS_SIMULATOR |
| // The simulator requires VM calls to be redirected to a special swi |
| // instruction to handle them. To make this work, we store the redirected |
| // pointer in the stub. |
| native_ = Simulator::RedirectNativeFunction(native_, Args_General3); |
| #endif |
| } |
| |
| /* static */ ICCall_ClassHook* |
| ICCall_ClassHook::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICCall_ClassHook& other) |
| { |
| ICCall_ClassHook* res = New<ICCall_ClassHook>(cx, space, other.jitCode(), firstMonitorStub, |
| other.clasp(), nullptr, other.templateObject_, |
| other.pcOffset_); |
| if (res) |
| res->native_ = other.native(); |
| return res; |
| } |
| |
| /* static */ ICCall_ScriptedApplyArray* |
| ICCall_ScriptedApplyArray::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICCall_ScriptedApplyArray& other) |
| { |
| return New<ICCall_ScriptedApplyArray>(cx, space, other.jitCode(), firstMonitorStub, |
| other.pcOffset_); |
| } |
| |
| /* static */ ICCall_ScriptedApplyArguments* |
| ICCall_ScriptedApplyArguments::Clone(JSContext* cx, |
| ICStubSpace* space, |
| ICStub* firstMonitorStub, |
| ICCall_ScriptedApplyArguments& other) |
| { |
| return New<ICCall_ScriptedApplyArguments>(cx, space, other.jitCode(), firstMonitorStub, |
| other.pcOffset_); |
| } |
| |
| /* static */ ICCall_ScriptedFunCall* |
| ICCall_ScriptedFunCall::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, |
| ICCall_ScriptedFunCall& other) |
| { |
| return New<ICCall_ScriptedFunCall>(cx, space, other.jitCode(), firstMonitorStub, |
| other.pcOffset_); |
| } |
| |
| // |
| // Rest_Fallback |
| // |
| |
| static bool DoRestFallback(JSContext* cx, BaselineFrame* frame, ICRest_Fallback* stub, |
| MutableHandleValue res) |
| { |
| unsigned numFormals = frame->numFormalArgs() - 1; |
| unsigned numActuals = frame->numActualArgs(); |
| unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0; |
| Value* rest = frame->argv() + numFormals; |
| |
| JSObject* obj = ObjectGroup::newArrayObject(cx, rest, numRest, GenericObject, |
| ObjectGroup::NewArrayKind::UnknownIndex); |
| if (!obj) |
| return false; |
| res.setObject(*obj); |
| return true; |
| } |
| |
| typedef bool (*DoRestFallbackFn)(JSContext*, BaselineFrame*, ICRest_Fallback*, |
| MutableHandleValue); |
| static const VMFunction DoRestFallbackInfo = |
| FunctionInfo<DoRestFallbackFn>(DoRestFallback, TailCall); |
| |
| bool |
| ICRest_Fallback::Compiler::generateStubCode(MacroAssembler& masm) |
| { |
| MOZ_ASSERT(engine_ == Engine::Baseline); |
| |
| EmitRestoreTailCallReg(masm); |
| |
| masm.push(ICStubReg); |
| pushFramePtr(masm, R0.scratchReg()); |
| |
| return tailCallVM(DoRestFallbackInfo, masm); |
| } |
| |
| } // namespace jit |
| } // namespace js |