/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * 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 "jsfriendapi.h" #include "jsfun.h" #include "jslibmath.h" #include "jstypes.h" #include "builtin/Eval.h" #include "builtin/SIMD.h" #include "gc/Policy.h" #include "jit/BaselineDebugModeOSR.h" #include "jit/BaselineJIT.h" #include "jit/InlinableNatives.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/GCVector.h" #include "vm/Opcodes.h" #include "vm/SelfHosting.h" #include "vm/TypedArrayCommon.h" #include "vm/TypedArrayObject.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/EnvironmentObject-inl.h" #include "vm/Interpreter-inl.h" #include "vm/StringObject-inl.h" #include "vm/UnboxedObject-inl.h" using mozilla::DebugOnly; namespace js { namespace jit { // // WarmUpCounter_Fallback // // // 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 DoWarmUpCounterFallbackOSR(JSContext* cx, BaselineFrame* frame, ICWarmUpCounter_Fallback* stub, IonOsrTempData** infoPtr) { MOZ_ASSERT(infoPtr); *infoPtr = nullptr; RootedScript script(cx, frame->script()); jsbytecode* pc = stub->icEntry()->pc(script); MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY); FallbackICSpew(cx, stub, "WarmUpCounter(%d)", int(script->pcToOffset(pc))); if (!IonCompileScriptForBaseline(cx, frame, pc)) return false; if (!script->hasIonScript() || script->ionScript()->osrPc() != pc || script->ionScript()->bailoutExpected() || frame->isDebuggee()) { return true; } IonScript* ion = script->ionScript(); MOZ_ASSERT(cx->runtime()->spsProfiler.enabled() == ion->hasProfilingInstrumentation()); MOZ_ASSERT(ion->osrPc() == pc); JitSpew(JitSpew_BaselineOSR, " OSR possible!"); void* jitcode = ion->method()->raw() + ion->osrEntryOffset(); // 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 (*DoWarmUpCounterFallbackOSRFn)(JSContext*, BaselineFrame*, ICWarmUpCounter_Fallback*, IonOsrTempData** infoPtr); static const VMFunction DoWarmUpCounterFallbackOSRInfo = FunctionInfo(DoWarmUpCounterFallbackOSR, "DoWarmUpCounterFallbackOSR"); 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 DoWarmUpCounterFallbackOSR 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); pushStubPayload(masm, R0.scratchReg()); if (!callVM(DoWarmUpCounterFallbackOSRInfo, 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) { // This can get called from optimized stubs. Therefore it is not allowed to gc. JS::AutoCheckCannotGC nogc; 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()); jsbytecode* pc = stub->getChainFallback()->icEntry()->pc(script); if (*pc == JSOP_SETALIASEDVAR || *pc == JSOP_INITALIASEDLEXICAL) id = NameToId(EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, script, pc)); else id = NameToId(script->getName(pc)); AddTypePropertyId(cx, obj, id, value); break; } case ICStub::SetProp_TypedObject: { MOZ_ASSERT(obj->is()); 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"); } if (!stub->addUpdateStubForValue(cx, script /* = outerScript */, obj, id, value)) { // The calling JIT code assumes this function is infallible (for // instance we may reallocate dynamic slots before calling this), // so ignore OOMs if we failed to attach a stub. cx->recoverFromOutOfMemory(); } return true; } typedef bool (*DoTypeUpdateFallbackFn)(JSContext*, BaselineFrame*, ICUpdatedStub*, HandleValue, HandleValue); const VMFunction DoTypeUpdateFallbackInfo = FunctionInfo(DoTypeUpdateFallback, "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(DoCallNativeGetter, "DoCallNativeGetter"); // // 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(DoToBoolFallback, "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); pushStubPayload(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(DoToNumberFallback, "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().lastProperty(); if (obj->is()) { UnboxedExpandoObject* expando = obj->as().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()->getAddProperty()) { 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->staticPrototype(); proto; proto = proto->staticPrototype()) { chainDepth++; // if prototype is non-native, don't optimize if (!proto->isNative()) return false; MOZ_ASSERT(proto->hasStaticPrototype()); // if prototype defines this property in a non-plain way, don't optimize Shape* protoShape = proto->as().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()) return false; JSFunction* func = &shape->setterObject()->as(); if (IsWindow(obj)) { if (!func->isNative()) return false; if (!func->jitInfo() || func->jitInfo()->needsOuterizedThisObject()) return false; } if (func->isNative()) { *isScripted = false; return true; } if (!func->hasJITCode()) { *isTemporarilyUnoptimizable = true; return false; } *isScripted = true; return true; } template static bool GetElemNativeStubExists(ICGetElem_Fallback* stub, HandleObject obj, HandleObject holder, Handle 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::value != static_cast(*iter)->isSymbol()) { continue; } ICGetElemNativeStubImpl* getElemNativeStub = reinterpret_cast*>(*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* protoStub = reinterpret_cast*>(*iter); if (holder != protoStub->holder()) continue; if (holder->as().lastProperty() != protoStub->holderShape()) continue; } else { MOZ_ASSERT(iter->isGetElem_NativePrototypeCallNativeName() || iter->isGetElem_NativePrototypeCallNativeSymbol() || iter->isGetElem_NativePrototypeCallScriptedName() || iter->isGetElem_NativePrototypeCallScriptedSymbol()); ICGetElemNativePrototypeCallStub* protoStub = reinterpret_cast*>(*iter); if (holder != protoStub->holder()) continue; if (holder->as().lastProperty() != protoStub->holderShape()) continue; } } return true; } return false; } template static void RemoveExistingGetElemNativeStubs(JSContext* cx, ICGetElem_Fallback* stub, HandleObject obj, HandleObject holder, Handle 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; MOZ_FALLTHROUGH; 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::value != static_cast(*iter)->isSymbol()) { continue; } ICGetElemNativeStubImpl* getElemNativeStub = reinterpret_cast*>(*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* protoStub = reinterpret_cast*>(*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().lastProperty() != protoStub->holderShape()) { iter.unlink(cx); continue; } } else { MOZ_ASSERT(iter->isGetElem_NativePrototypeCallNativeName() || iter->isGetElem_NativePrototypeCallNativeSymbol() || iter->isGetElem_NativePrototypeCallScriptedName() || iter->isGetElem_NativePrototypeCallScriptedSymbol()); ICGetElemNativePrototypeCallStub* protoStub = reinterpret_cast*>(*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().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 static T getKey(jsid id) { MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol"); return false; } template <> JS::Symbol* getKey(jsid id) { if (!JSID_IS_SYMBOL(id)) return nullptr; return JSID_TO_SYMBOL(id); } template <> PropertyName* getKey(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(cx, key, idp)) return false; uint32_t dummy; if (!JSID_IS_ATOM(idp) || JSID_TO_ATOM(idp)->isIndex(&dummy)) return false; return true; } template static bool checkAtomize(HandleValue key) { MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol"); return false; } template <> bool checkAtomize(HandleValue key) { return false; } template <> bool checkAtomize(HandleValue key) { return !key.toString()->isAtom(); } template 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(cx, keyVal, &id)) return false; Rooted key(cx, getKey(id)); if (!key) return true; bool needsAtomize = checkAtomize(keyVal); Rooted prop(cx); RootedObject holder(cx); if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &prop)) return false; if (!holder || (holder != obj && !holder->isNative())) return true; // If a suitable stub already exists, nothing else to do. if (GetElemNativeStubExists(stub, obj, holder, key, needsAtomize)) return true; // Remove any existing stubs that may interfere with the new stub being added. RemoveExistingGetElemNativeStubs(cx, stub, obj, holder, key, needsAtomize); ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); if (obj->is() && holder == obj) { const UnboxedLayout::Property* property = obj->as().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 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().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 compiler(cx, getGetElemStubKind(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; RootedShape shape(cx, prop.shape()); 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(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().lastProperty()); AccType acctype = isFixedSlot ? ICGetElemNativeStub::FixedSlot : ICGetElemNativeStub::DynamicSlot; ICGetElemNativeCompiler 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 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(cx, keyVal, &id)) return false; Rooted key(cx, getKey(id)); if (!key) return true; bool needsAtomize = checkAtomize(keyVal); Rooted prop(cx); RootedObject baseHolder(cx); if (!EffectlesslyLookupProperty(cx, obj, id, &baseHolder, &prop)) return false; if (!baseHolder || !baseHolder->isNative()) return true; RootedShape shape(cx, prop.shape()); HandleNativeObject holder = baseHolder.as(); bool getterIsScripted = false; if (IsCacheableGetPropCall(cx, obj, baseHolder, shape, &getterIsScripted, isTemporarilyUnoptimizable, /*isDOMProxy=*/false)) { RootedFunction getter(cx, &shape->getterObject()->as()); // 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(stub, obj, holder, key, needsAtomize)) return true; // Remove any existing stubs that may interfere with the new stub being added. RemoveExistingGetElemNativeStubs(cx, stub, obj, holder, key, needsAtomize); ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub(); ICStub::Kind kind = getterIsScripted ? ICStub::GetElem_NativePrototypeCallScriptedName : ICStub::GetElem_NativePrototypeCallNativeName; kind = getGetElemStubKind(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 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()) return false; TypeDescr& descr = obj->as().typeDescr(); return descr.is() && descr.as().elementType().is(); } static Scalar::Type PrimitiveArrayTypedObjectType(JSObject* obj) { MOZ_ASSERT(IsPrimitiveArrayTypedObject(obj)); TypeDescr& descr = obj->as().typeDescr(); return descr.as().elementType().as().type(); } static Scalar::Type TypedThingElementType(JSObject* obj) { return obj->is() ? obj->as().type() : 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 && !obj->is()) return true; return false; } static bool IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key) { if (!obj->isNative() && !obj->is()) return false; if (key.isInt32() && key.toInt32() >= 0 && !obj->is()) 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() && rhs.isInt32() && !obj->as().hasOverriddenElement()) { ICGetElem_Arguments::Which which = ICGetElem_Arguments::Mapped; if (obj->is()) 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().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()) { RootedScript rootedScript(cx, script); if (rhs.isString()) { if (!TryAttachNativeOrUnboxedGetValueElemStub(cx, rootedScript, pc, stub, obj, rhs, attached)) { return false; } } else if (rhs.isSymbol()) { if (!TryAttachNativeOrUnboxedGetValueElemStub(cx, rootedScript, pc, stub, obj, rhs, attached)) { return false; } } if (*attached) return true; script = rootedScript; } // Check for UnboxedArray[int] accesses. if (obj->is() && 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 ((obj->is() || 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 the underlying storage could be // detached, as the stub will always bail out. if (IsPrimitiveArrayTypedObject(obj) && cx->compartment()->detachedTypedObjects) 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) { SharedStubInfo info(cx, frame, stub_->icEntry()); // This fallback stub may trigger debug mode toggling. DebugModeOSRVolatileStub 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()); if (!TryAttachNativeGetAccessorElemStub(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()); if (!TryAttachNativeGetAccessorElemStub(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, &info, res)) { 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(DoGetElemFallback, "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); pushStubPayload(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(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(DoAtomizeString, "DoAtomizeString"); template bool ICGetElemNativeCompiler::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::offsetOfGetter()), objReg); masm.push(objReg); regs.add(objReg); // Call helper. if (!callVM(DoCallNativeGetterInfo, masm)) return false; leaveStubFrame(masm); return true; } template bool ICGetElemNativeCompiler::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::offsetOfGetter()), callee); // Push argc, callee, and descriptor. { Register callScratch = regs.takeAny(); EmitBaselineCreateStubFrameDescriptor(masm, callScratch, JitFrameLayout::Size()); 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 bool ICGetElemNativeCompiler::emitCheckKey(MacroAssembler& masm, Label& failure) { MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol"); return false; } template <> bool ICGetElemNativeCompiler::emitCheckKey(MacroAssembler& masm, Label& failure) { MOZ_ASSERT(!needsAtomize_); masm.branchTestSymbol(Assembler::NotEqual, R1, &failure); Address symbolAddr(ICStubReg, ICGetElemNativeStubImpl::offsetOfKey()); Register symExtract = masm.extractObject(R1, ExtractTemp1); masm.branchPtr(Assembler::NotEqual, symbolAddr, symExtract, &failure); return true; } template <> bool ICGetElemNativeCompiler::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::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 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 bool ICGetElemNativeCompiler::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() && 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::offsetOfHolder()), holderReg); masm.loadPtr(Address(ICStubReg, ICGetElemNativePrototypeCallStub::offsetOfHolderShape()), scratchReg); } else { masm.loadPtr(Address(ICStubReg, ICGetElem_NativePrototypeSlot::offsetOfHolder()), holderReg); masm.loadPtr(Address(ICStubReg, ICGetElem_NativePrototypeSlot::offsetOfHolderShape()), scratchReg); } masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratchReg, popR1 ? &failurePopR1 : &failure); } if (acctype_ == ICGetElemNativeStub::DynamicSlot || acctype_ == ICGetElemNativeStub::FixedSlot) { masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub::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::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))); if (!emitCallNative(masm, objReg)) return false; } else { MOZ_ASSERT(acctype_ == ICGetElemNativeStub::ScriptedGetter); // Load function in scratchReg and ensure that it has a jit script. masm.loadPtr(Address(ICStubReg, ICGetElemNativeGetterStub::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))); if (!emitCallScripted(masm, objReg)) return false; } } // 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) CheckForTypedObjectWithDetachedStorage(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 or any element have been overridden. masm.branchTest32(Assembler::NonZero, scratchReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT | ArgumentsObject::ELEMENT_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(); // Load ArgumentsData masm.loadPrivate(Address(objReg, ArgumentsObject::getDataSlotOffset()), argData); // Fail if we have a RareArgumentsData (elements were deleted). masm.branchPtr(Assembler::NotEqual, Address(argData, offsetof(ArgumentsData, rareData)), ImmWord(0), &failureReconstructInputs); // Load the value. Use scratchReg to form a ValueOperand to load into. masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), argData); regs.add(scratchReg); 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* nstub = stub->toImplUnchecked(); if (obj->maybeShape() != nstub->shape(0)) return false; JSObject* proto = obj->staticPrototype(); for (size_t i = 0; i < stub->protoChainDepth(); i++) { if (!proto->isNative()) return false; if (proto->as().lastProperty() != nstub->shape(i + 1)) return false; proto = obj->staticPrototype(); 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() && JSObject::getGroup(cx, obj) == nstub->group()) { return true; } } if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) { ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd(); if (JSObject::getGroup(cx, obj) == 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() && !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().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->staticPrototype(); while (curObj) { ++*protoDepthOut; if (!curObj->isNative() || curObj->isIndexed()) return false; curObj = curObj->staticPrototype(); } 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 stub(frame, stub_); RootedScript script(cx, frame->script()); RootedScript outerScript(cx, 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, objv, 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, JSObject::getGroup(cx, obj)); 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=%" PRIuSIZE ")", shape.get(), group.get(), protoDepth); ICSetElemDenseOrUnboxedArrayAddCompiler compiler(cx, obj, protoDepth); ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(outerScript)); if (!newStub) return false; if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, outerScript, 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(outerScript)); if (!newStub) return false; if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, outerScript, obj, JSID_VOIDHANDLE, rhs)) { return false; } stub->addNewStub(newStub); } } return true; } if ((obj->is() || IsPrimitiveArrayTypedObject(obj)) && index.isNumber() && rhs.isNumber()) { if (!cx->runtime()->jitSupportsFloatingPoint && (TypedThingRequiresFloatingPoint(obj) || index.isDouble())) { return true; } bool expectOutOfBounds; double idx = index.toNumber(); if (obj->is()) { expectOutOfBounds = (idx < 0 || idx >= double(obj->as().length())); } else { // Typed objects throw on out of bounds accesses. Don't attach // a stub in this case. if (idx < 0 || idx >= double(obj->as().length())) return true; expectOutOfBounds = false; // Don't attach stubs if the underlying storage for typed objects // in the compartment could be detached, as the stub will always // bail out. if (cx->compartment()->detachedTypedObjects) 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(outerScript)); 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(DoSetElemFallback, "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); pushStubPayload(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 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, ? } 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, clone the copy on write elements in the object or fail // due to a frozen element. Label noSpecialHandling; Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags()); masm.branchTest32(Assembler::Zero, elementsFlags, Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS | ObjectElements::COPY_ON_WRITE | ObjectElements::FROZEN), &noSpecialHandling); // Fail if we need to clone copy on write elements or to throw due // to a frozen element. masm.branchTest32(Assembler::NonZero, elementsFlags, Imm32(ObjectElements::COPY_ON_WRITE | ObjectElements::FROZEN), &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 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, ? } 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); uint32_t framePushedAfterStow = masm.framePushed(); // 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 | ObjectElements::FROZEN), &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, RegisterOrInt32Constant(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); masm.setFramePushed(framePushedAfterStow); 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 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.branchTruncateDoubleMaybeModUint32(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) CheckForTypedObjectWithDetachedStorage(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 outerScript, 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().lastProperty()); ICStub* denseStub = compiler.getStub(compiler.getStubSpace(outerScript)); if (!denseStub) return false; *attached = true; stub->addNewStub(denseStub); return true; } static bool TryAttachNativeInStub(JSContext* cx, HandleScript outerScript, 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()); Rooted prop(cx); RootedObject holder(cx); if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &prop)) return false; if (prop.isNonNativeProperty()) { MOZ_ASSERT(!IsCacheableProtoChain(obj, holder, false)); return true; } RootedShape shape(cx, prop.maybeShape()); 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(outerScript)); if (!newStub) return false; *attached = true; stub->addNewStub(newStub); return true; } return true; } static bool TryAttachNativeInDoesNotExistStub(JSContext* cx, HandleScript outerScript, 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.get(), name.get(), lastProto.address(), &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(outerScript)); 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 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(DoInFallback, "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); pushStubPayload(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 shapes(cx, ShapeVector(cx)); if (!shapes.append(obj_->as().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(*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 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->staticPrototype(); if (!proto || !proto->is()) return true; current = &proto->as(); } } // 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 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->staticPrototype(); if (!proto || !proto->is()) return true; current = &proto->as(); } // 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()); // 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 TryAttachEnvNameStub(JSContext* cx, HandleScript script, ICGetName_Fallback* stub, HandleObject initialEnvChain, HandlePropertyName name, bool* attached) { MOZ_ASSERT(!*attached); Rooted shapes(cx, ShapeVector(cx)); RootedId id(cx, NameToId(name)); RootedObject envChain(cx, initialEnvChain); Shape* shape = nullptr; while (envChain) { if (!shapes.append(envChain->maybeShape())) return false; if (envChain->is()) { shape = envChain->as().lookup(cx, id); if (shape) break; return true; } if (!envChain->is() || envChain->is()) return true; // Check for an 'own' property on the env. There is no need to // check the prototype as non-with scopes do not inherit properties // from any prototype. shape = envChain->as().lookup(cx, id); if (shape) break; envChain = envChain->enclosingEnvironment(); } // 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(envChain, envChain, 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_Env<0>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, offset); newStub = compiler.getStub(compiler.getStubSpace(script)); break; } case 2: { ICGetName_Env<1>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, offset); newStub = compiler.getStub(compiler.getStubSpace(script)); break; } case 3: { ICGetName_Env<2>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, offset); newStub = compiler.getStub(compiler.getStubSpace(script)); break; } case 4: { ICGetName_Env<3>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, offset); newStub = compiler.getStub(compiler.getStubSpace(script)); break; } case 5: { ICGetName_Env<4>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, offset); newStub = compiler.getStub(compiler.getStubSpace(script)); break; } case 6: { ICGetName_Env<5>::Compiler compiler(cx, monitorStub, Move(shapes.get()), isFixedSlot, offset); newStub = compiler.getStub(compiler.getStubSpace(script)); break; } case 7: { ICGetName_Env<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 envChain, MutableHandleValue res) { SharedStubInfo info(cx, frame, stub_->icEntry()); // This fallback stub may trigger debug mode toggling. DebugModeOSRVolatileStub stub(frame, stub_); RootedScript script(cx, frame->script()); jsbytecode* pc = stub->icEntry()->pc(script); mozilla::DebugOnly 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, envChain.as(), 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 (!GetEnvironmentNameForTypeOf(cx, envChain, name, res)) return false; } else { if (!GetEnvironmentName(cx, envChain, 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, &info, res)) return false; if (attached) return true; if (IsGlobalOp(JSOp(*pc)) && !script->hasNonSyntacticScope()) { Handle globalLexical = envChain.as(); if (!TryAttachGlobalNameValueStub(cx, script, pc, stub, globalLexical, name, &attached)) return false; } else { if (!TryAttachEnvNameStub(cx, script, stub, envChain, 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(DoGetNameFallback, "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); pushStubPayload(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 bool ICGetName_Env::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_Env::offsetOfShape(index)), scratch); masm.branchTestObjShape(Assembler::NotEqual, scope, scratch, &failure); if (index < numHops) { masm.extractObject(Address(scope, EnvironmentObject::offsetOfEnclosingEnvironment()), walker); } } Register scope = NumHops ? walker : obj; if (!isFixedSlot_) { masm.loadPtr(Address(scope, NativeObject::offsetOfSlots()), walker); scope = walker; } masm.load32(Address(ICStubReg, ICGetName_Env::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 envChain, MutableHandleValue res) { jsbytecode* pc = stub->icEntry()->pc(frame->script()); mozilla::DebugOnly 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, envChain, &scope)) return false; res.setObject(*scope); return true; } typedef bool (*DoBindNameFallbackFn)(JSContext*, BaselineFrame*, ICBindName_Fallback*, HandleObject, MutableHandleValue); static const VMFunction DoBindNameFallbackInfo = FunctionInfo(DoBindNameFallback, "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); pushStubPayload(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 stub(frame, stub_); RootedScript script(cx, frame->script()); jsbytecode* pc = stub->icEntry()->pc(script); mozilla::DebugOnly 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(DoGetIntrinsicFallback, "DoGetIntrinsicFallback", TailCall); bool ICGetIntrinsic_Fallback::Compiler::generateStubCode(MacroAssembler& masm) { MOZ_ASSERT(engine_ == Engine::Baseline); EmitRestoreTailCallReg(masm); masm.push(ICStubReg); pushStubPayload(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); Rooted prop(cx); RootedObject holder(cx); if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &prop)) return false; if (obj != holder) return true; RootedShape shape(cx); if (obj->isNative()) { shape = prop.shape(); } else { if (obj->is()) { UnboxedExpandoObject* expando = obj->as().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); Rooted prop(cx); RootedObject holder(cx); if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &prop)) return false; if (prop.isNonNativeProperty()) { MOZ_ASSERT(!IsCacheableProtoChain(obj, holder)); return true; } RootedShape shape(cx, prop.maybeShape()); 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()); MOZ_ASSERT(callee->hasScript()); if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallScripted, &holder->as(), 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()); MOZ_ASSERT(callee->isNative()); if (UpdateExistingSetPropCallStubs(stub, ICStub::SetProp_CallNative, &holder->as(), 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()) return true; const UnboxedLayout::Property* property = obj->as().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()) return true; if (!obj->as().typeDescr().is()) return true; Rooted structDescr(cx); structDescr = &obj->as().typeDescr().as(); size_t fieldIndex; if (!structDescr->fieldIndex(id, &fieldIndex)) return true; Rooted fieldDescr(cx, &structDescr->fieldDescr(fieldIndex)); if (!fieldDescr->is()) return true; uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex); ICSetProp_TypedObject::Compiler compiler(cx, obj->maybeShape(), obj->group(), fieldOffset, &fieldDescr->as()); 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 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 = EnvironmentCoordinateName(cx->caches.envCoordinateNameCache, 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, JSObject::getGroup(cx, obj)); if (!oldGroup) return false; RootedReceiverGuard oldGuard(cx, ReceiverGuard(obj)); if (obj->is()) { MOZ_ASSERT(!oldShape); if (UnboxedExpandoObject* expando = obj->as().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().setAliasedBinding(cx, EnvironmentCoordinate(pc), name, rhs); } else if (op == JSOP_INITGLEXICAL) { RootedValue v(cx, rhs); LexicalEnvironmentObject* lexicalEnv; if (script->hasNonSyntacticScope()) lexicalEnv = &NearestEnclosingExtensibleLexicalEnvironment(frame->environmentChain()); else lexicalEnv = &cx->global()->lexicalEnvironment(); InitGlobalLexicalOperation(cx, lexicalEnv, script, pc, v); } else { MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP); ObjectOpResult result; if (!SetProperty(cx, obj, id, rhs, lhs, result) || !result.checkStrictErrorOrWarning(cx, obj, id, 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(DoSetPropFallback, "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); pushStubPayload(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 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()) { 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()) { // 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 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()) { // 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()) { holderReg = regs.takeAny(); masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg); // Write the expando object's new shape. Address shapeAddr(holderReg, ShapedObject::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, ShapedObject::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; CheckForTypedObjectWithDetachedStorage(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()) { Scalar::Type type = fieldDescr_->as().type(); StoreToTypedArray(cx, masm, type, value, dest, secondScratch, &failurePopRHS, &failurePopRHS); masm.popValue(R1); } else { ReferenceTypeDescr::Type type = fieldDescr_->as().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(); } } // The RHS has to be in R0. masm.moveValue(R1, R0); 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, JitFrameLayout::Size()); 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(DoCallNativeSetter, "DoNativeCallSetter"); 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()) return true; RootedFunction target(cx, &thisv.toObject().as()); 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()) { 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()) return true; RootedFunction target(cx, &thisv.toObject().as()); // 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; } // Check if target is a native SIMD operation which returns a SIMD type. // If so, set res to a template object matching the SIMD type produced and return true. static bool GetTemplateObjectForSimd(JSContext* cx, JSFunction* target, MutableHandleObject res) { const JSJitInfo* jitInfo = target->jitInfo(); if (!jitInfo || jitInfo->type() != JSJitInfo::InlinableNative) return false; // Check if this is a native inlinable SIMD operation. SimdType ctrlType; switch (jitInfo->inlinableNative) { case InlinableNative::SimdInt8x16: ctrlType = SimdType::Int8x16; break; case InlinableNative::SimdUint8x16: ctrlType = SimdType::Uint8x16; break; case InlinableNative::SimdInt16x8: ctrlType = SimdType::Int16x8; break; case InlinableNative::SimdUint16x8: ctrlType = SimdType::Uint16x8; break; case InlinableNative::SimdInt32x4: ctrlType = SimdType::Int32x4; break; case InlinableNative::SimdUint32x4: ctrlType = SimdType::Uint32x4; break; case InlinableNative::SimdFloat32x4: ctrlType = SimdType::Float32x4; break; case InlinableNative::SimdBool8x16: ctrlType = SimdType::Bool8x16; break; case InlinableNative::SimdBool16x8: ctrlType = SimdType::Bool16x8; break; case InlinableNative::SimdBool32x4: ctrlType = SimdType::Bool32x4; break; // This is not an inlinable SIMD operation. default: return false; } // The controlling type is not necessarily the return type. // Check the actual operation. SimdOperation simdOp = SimdOperation(jitInfo->nativeOp); SimdType retType; switch(simdOp) { case SimdOperation::Fn_allTrue: case SimdOperation::Fn_anyTrue: case SimdOperation::Fn_extractLane: // These operations return a scalar. No template object needed. return false; case SimdOperation::Fn_lessThan: case SimdOperation::Fn_lessThanOrEqual: case SimdOperation::Fn_equal: case SimdOperation::Fn_notEqual: case SimdOperation::Fn_greaterThan: case SimdOperation::Fn_greaterThanOrEqual: // These operations return a boolean vector with the same shape as the // controlling type. retType = GetBooleanSimdType(ctrlType); break; default: // All other operations return the controlling type. retType = ctrlType; break; } // Create a template object based on retType. RootedGlobalObject global(cx, cx->global()); Rooted descr(cx, GlobalObject::getOrCreateSimdTypeDescr(cx, global, retType)); res.set(cx->compartment()->jitCompartment()->getSimdTemplateObjectFor(cx, descr)); return true; } static void EnsureArrayGroupAnalyzed(JSContext* cx, JSObject* obj) { if (PreliminaryObjectArrayWithTemplate* objects = obj->group()->maybePreliminaryObjects()) objects->maybeAnalyze(cx, obj->group(), /* forceAnalyze = */ true); } static bool GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs& args, MutableHandleObject res, bool* skipAttach) { Native native = target->native(); // 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 || native == array_construct) { // 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, analyze the group 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; EnsureArrayGroupAnalyzed(cx, res); return true; } } if (args.length() == 1) { size_t len = 0; if (args[0].isInt32() && args[0].toInt32() >= 0) len = args[0].toInt32(); if (!TypedArrayObject::GetTemplateObjectForNative(cx, native, len, res)) return false; if (res) return true; } if (native == js::array_slice) { if (args.thisv().isObject()) { RootedObject obj(cx, &args.thisv().toObject()); if (!obj->isSingleton()) { if (obj->group()->maybePreliminaryObjects()) { *skipAttach = true; return true; } res.set(NewFullyAllocatedArrayTryReuseGroup(cx, obj, 0, TenuredObject)); if (!res) return false; EnsureArrayGroupAnalyzed(cx, res); return true; } } } if (native == js::intrinsic_StringSplitString && args.length() == 2 && args[0].isString() && args[1].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; EnsureArrayGroupAnalyzed(cx, res); 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() && GetTemplateObjectForSimd(cx, target, res)) return !!res; return true; } static bool GetTemplateObjectForClassHook(JSContext* cx, JSNative hook, CallArgs& args, MutableHandleObject templateObject) { if (hook == TypedObject::construct) { Rooted descr(cx, &args.callee().as()); templateObject.set(TypedObject::createZeroed(cx, descr, 1, gc::TenuredHeap)); return !!templateObject; } if (hook == SimdTypeDescr::call && JitSupportsSimd()) { Rooted descr(cx, &args.callee().as()); templateObject.set(cx->compartment()->jitCompartment()->getSimdTemplateObjectFor(cx, descr)); return !!templateObject; } return true; } static bool IsOptimizableCallStringSplit(const Value& callee, int argc, Value* args) { if (argc != 2 || !args[0].isString() || !args[1].isString()) return false; if (!args[0].toString()->isAtom() || !args[1].toString()->isAtom()) return false; if (!callee.isObject() || !callee.toObject().is()) return false; JSFunction& calleeFun = callee.toObject().as(); if (!calleeFun.isNative() || calleeFun.native() != js::intrinsic_StringSplitString) 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, 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()) { // 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()) 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()); 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() || thisObject->is()) 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, args, &templateObject, &skipAttach)) return false; if (skipAttach) { *handled = true; return true; } MOZ_ASSERT_IF(templateObject, !templateObject->group()->maybePreliminaryObjects()); } bool ignoresReturnValue = false; if (op == JSOP_CALL_IGNORES_RV && fun->isNative()) { const JSJitInfo* jitInfo = fun->jitInfo(); ignoresReturnValue = jitInfo && jitInfo->type() == JSJitInfo::IgnoresReturnValueNative; } 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, ignoresReturnValue, 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); if (!nobj) return false; EnsureArrayGroupAnalyzed(cx, nobj); 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, HandleValue callee, Value* vp, jsbytecode* pc, HandleValue res, bool* attached) { if (stub->numOptimizedStubs() != 0) return true; Value* args = vp + 2; // String.prototype.split will not yield a constructable. if (JSOp(*pc) == JSOP_NEW) return true; if (!IsOptimizableCallStringSplit(callee, argc, args)) return true; MOZ_ASSERT(callee.isObject()); MOZ_ASSERT(callee.toObject().is()); RootedString str(cx, args[0].toString()); RootedString sep(cx, args[1].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), str, sep, 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) { SharedStubInfo info(cx, frame, stub_->icEntry()); // This fallback stub may trigger debug mode toggling. DebugModeOSRVolatileStub 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); bool ignoresReturnValue = (op == JSOP_CALL_IGNORES_RV); // Ensure vp array is rooted - we may GC in here. size_t numValues = argc + 2 + constructing; AutoArrayRooter vpRoot(cx, numValues, vp); CallArgs callArgs = CallArgsFromSp(argc + constructing, vp + numValues, constructing, ignoresReturnValue); RootedValue callee(cx, vp[0]); // Handle funapply with JSOP_ARGUMENTS if (op == JSOP_FUNAPPLY && argc == 2 && callArgs[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) { 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) { if (!ConstructFromStack(cx, callArgs)) return false; res.set(callArgs.rval()); } else if ((op == JSOP_EVAL || op == JSOP_STRICTEVAL) && frame->environmentChain()->global().valueIsEval(callee)) { if (!DirectEval(cx, callArgs.get(0), res)) return false; } else { MOZ_ASSERT(op == JSOP_CALL || op == JSOP_CALL_IGNORES_RV || 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, callArgs.thisv(), nullptr); return false; } if (!CallFromStack(cx, callArgs)) return false; res.set(callArgs.rval()); } 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, &info, res)) { return false; } // Add a type monitor stub for the resulting value. if (!stub->addMonitorStubForValue(cx, &info, res)) return false; // If 'callee' is a potential Call_StringSplit, try to attach an // optimized StringSplit stub. Note that vp[0] now holds the return value // instead of the callee, so we pass the callee as well. if (!TryAttachStringSplit(cx, stub, script, argc, callee, 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) { SharedStubInfo info(cx, frame, stub_->icEntry()); // This fallback stub may trigger debug mode toggling. DebugModeOSRVolatileStub 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, &info, res)) { return false; } // Add a type monitor stub for the resulting value. if (!stub->addMonitorStubForValue(cx, &info, res)) 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))); } 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 ] 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); // Limit the length to something reasonable. masm.branch32(Assembler::Above, Address(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs()), Imm32(ICCall_ScriptedApplyArray::MAX_ARGS_ARRAY_LENGTH), 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), ] // 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; } 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(DoCallFallback, "DoCallFallback"); typedef bool (*DoSpreadCallFallbackFn)(JSContext*, BaselineFrame*, ICCall_Fallback*, Value*, MutableHandleValue); static const VMFunction DoSpreadCallFallbackInfo = FunctionInfo(DoSpreadCallFallback, "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); PushStubPayload(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); PushStubPayload(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 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(CreateThis, "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, JitFrameLayout::Size()); // 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(CopyArray, "CopyArray"); bool ICCall_StringSplit::Compiler::generateStubCode(MacroAssembler& masm) { MOZ_ASSERT(engine_ == Engine::Baseline); // Stack Layout: [ ..., CalleeVal, ThisVal, strVal, sepVal, +ICStackValueOffset+ ] static const size_t SEP_DEPTH = 0; static const size_t STR_DEPTH = sizeof(Value); static const size_t CALLEE_DEPTH = 3 * sizeof(Value); AllocatableGeneralRegisterSet regs(availableGeneralRegs(0)); Label failureRestoreArgc; #ifdef DEBUG Label twoArg; Register argcReg = R0.scratchReg(); masm.branch32(Assembler::Equal, argcReg, Imm32(2), &twoArg); masm.assumeUnreachable("Expected argc == 2"); masm.bind(&twoArg); #endif Register scratchReg = regs.takeAny(); // Guard that callee is native function js::intrinsic_StringSplitString. { Address calleeAddr(masm.getStackPointer(), ICStackValueOffset + CALLEE_DEPTH); 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 intrinsic_StringSplitString. masm.loadPtr(Address(calleeObj, JSFunction::offsetOfNativeOrScript()), scratchReg); masm.branchPtr(Assembler::NotEqual, scratchReg, ImmPtr(js::intrinsic_StringSplitString), &failureRestoreArgc); regs.add(calleeVal); } // Guard sep. { // Ensure that sep is a string. Address sepAddr(masm.getStackPointer(), ICStackValueOffset + SEP_DEPTH); ValueOperand sepVal = regs.takeAnyValue(); masm.loadValue(sepAddr, sepVal); masm.branchTestString(Assembler::NotEqual, sepVal, &failureRestoreArgc); Register sep = masm.extractString(sepVal, ExtractTemp0); masm.branchPtr(Assembler::NotEqual, Address(ICStubReg, offsetOfExpectedSep()), sep, &failureRestoreArgc); regs.add(sepVal); } // Guard str. { // Ensure that str is a string. Address strAddr(masm.getStackPointer(), ICStackValueOffset + STR_DEPTH); ValueOperand strVal = regs.takeAnyValue(); masm.loadValue(strAddr, strVal); masm.branchTestString(Assembler::NotEqual, strVal, &failureRestoreArgc); Register str = masm.extractString(strVal, ExtractTemp0); masm.branchPtr(Assembler::NotEqual, Address(ICStubReg, offsetOfExpectedStr()), str, &failureRestoreArgc); regs.add(strVal); } // 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(2), 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_AND_AWAIT_INDEX_CLOSING, // the generator is suspended. masm.loadValue(Address(genObj, GeneratorObject::offsetOfYieldAndAwaitIndexSlot()), argVal); masm.branchTestInt32(Assembler::NotEqual, argVal, &returnFalse); masm.unboxInt32(argVal, scratch); masm.branch32(Assembler::AboveOrEqual, scratch, Imm32(StarGeneratorObject::YIELD_AND_AWAIT_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_); // 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, ExitFrameLayout::Size()); 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 if (ignoresReturnValue_) { masm.loadPtr(Address(callee, JSFunction::offsetOfJitInfo()), callee); masm.callWithABI(Address(callee, JSJitInfo::offsetOfIgnoresReturnValueNative())); } 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); 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, ExitFrameLayout::Size()); 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, JitFrameLayout::Size()); // 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, JitFrameLayout::Size()); 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, JitFrameLayout::Size()); // 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) { ReportOutOfMemory(cx); 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(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); RootedObject iterobj(cx, ValueToIterator(cx, flags, res)); if (!iterobj) return false; res.setObject(*iterobj); return true; } typedef bool (*DoIteratorNewFallbackFn)(JSContext*, BaselineFrame*, ICIteratorNew_Fallback*, HandleValue, MutableHandleValue); static const VMFunction DoIteratorNewFallbackInfo = FunctionInfo(DoIteratorNewFallback, "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); pushStubPayload(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 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() && !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(DoIteratorMoreFallback, "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); pushStubPayload(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(DoIteratorCloseFallback, "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; // If the user has supplied their own @@hasInstance method we shouldn't // clobber it. if (!js::FunctionHasDefaultHasInstance(fun, cx->wellKnownSymbols())) return true; // Refuse to optimize any function whose [[Prototype]] isn't // Function.prototype. if (!fun->hasStaticPrototype() || fun->hasUncacheableProto()) return true; Value funProto = cx->global()->getPrototype(JSProto_Function); if (funProto.isObject() && fun->staticPrototype() != &funProto.toObject()) 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()) { 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()); 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(DoInstanceOfFallback, "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); pushStubPayload(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(DoTypeOfFallback, "DoTypeOfFallback", TailCall); bool ICTypeOf_Fallback::Compiler::generateStubCode(MacroAssembler& masm) { MOZ_ASSERT(engine_ == Engine::Baseline); EmitRestoreTailCallReg(masm); masm.pushValue(R0); masm.push(ICStubReg); pushStubPayload(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(DoRetSubFallback, "DoRetSubFallback"); typedef bool (*ThrowFn)(JSContext*, HandleValue); static const VMFunction ThrowInfoBaseline = FunctionInfo(js::Throw, "ThrowInfoBaseline", 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); pushStubPayload(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(acctype) << ACCESSTYPE_SHIFT) | (static_cast(needsAtomize) << NEEDS_ATOMIZE_SHIFT) | (static_cast(isSymbol) << ISSYMBOL_SHIFT); } ICGetElemNativeStub::~ICGetElemNativeStub() { } template ICGetElemNativeGetterStub::ICGetElemNativeGetterStub( ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub, ReceiverGuard guard, const T* key, AccType acctype, bool needsAtomize, JSFunction* getter, uint32_t pcOffset) : ICGetElemNativeStubImpl(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 ICGetElem_NativePrototypeSlot::ICGetElem_NativePrototypeSlot( JitCode* stubCode, ICStub* firstMonitorStub, ReceiverGuard guard, const T* key, AccType acctype, bool needsAtomize, uint32_t offset, JSObject* holder, Shape* holderShape) : ICGetElemNativeSlotStub(getGetElemStubKind(ICStub::GetElem_NativePrototypeSlotName), stubCode, firstMonitorStub, guard, key, acctype, needsAtomize, offset), holder_(holder), holderShape_(holderShape) { } template ICGetElemNativePrototypeCallStub::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(kind, stubCode, firstMonitorStub, guard, key, acctype, needsAtomize, getter, pcOffset), holder_(holder), holderShape_(holderShape) {} template /* static */ ICGetElem_NativePrototypeCallNative* ICGetElem_NativePrototypeCallNative::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_NativePrototypeCallNative& other) { return ICStub::New>(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* ICGetElem_NativePrototypeCallNative::Clone(JSContext*, ICStubSpace*, ICStub*, ICGetElem_NativePrototypeCallNative&); template ICGetElem_NativePrototypeCallNative* ICGetElem_NativePrototypeCallNative::Clone(JSContext*, ICStubSpace*, ICStub*, ICGetElem_NativePrototypeCallNative&); template /* static */ ICGetElem_NativePrototypeCallScripted* ICGetElem_NativePrototypeCallScripted::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICGetElem_NativePrototypeCallScripted& other) { return ICStub::New>(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* ICGetElem_NativePrototypeCallScripted::Clone(JSContext*, ICStubSpace*, ICStub*, ICGetElem_NativePrototypeCallScripted&); template ICGetElem_NativePrototypeCallScripted* ICGetElem_NativePrototypeCallScripted::Clone(JSContext*, ICStubSpace*, ICStub*, ICGetElem_NativePrototypeCallScripted&); 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(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(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(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 ICUpdatedStub* ICSetElemDenseOrUnboxedArrayAddCompiler::getStubSpecific(ICStubSpace* space, Handle shapes) { RootedObjectGroup group(cx, JSObject::getGroup(cx, obj_)); if (!group) return nullptr; Rooted stubCode(cx, getStubCode()); return newStub>(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(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 ICIn_NativeDoesNotExistImpl::ICIn_NativeDoesNotExistImpl( JitCode* stubCode, Handle 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 ICGetName_Env::ICGetName_Env(JitCode* stubCode, ICStub* firstMonitorStub, Handle 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(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, JSObject::getGroup(cx, obj_)); if (!group) return nullptr; RootedShape shape(cx, LastPropertyForSetProp(obj_)); ICSetProp_Native* stub = newStub(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 ICSetProp_NativeAddImpl::ICSetProp_NativeAddImpl(JitCode* stubCode, ObjectGroup* group, Handle 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(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(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(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(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(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(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(cx, space, other.jitCode(), firstMonitorStub, other.pcOffset_); } /* static */ ICCall_ScriptedApplyArguments* ICCall_ScriptedApplyArguments::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ScriptedApplyArguments& other) { return New(cx, space, other.jitCode(), firstMonitorStub, other.pcOffset_); } /* static */ ICCall_ScriptedFunCall* ICCall_ScriptedFunCall::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub, ICCall_ScriptedFunCall& other) { return New(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(DoRestFallback, "DoRestFallback", TailCall); bool ICRest_Fallback::Compiler::generateStubCode(MacroAssembler& masm) { MOZ_ASSERT(engine_ == Engine::Baseline); EmitRestoreTailCallReg(masm); masm.push(ICStubReg); pushStubPayload(masm, R0.scratchReg()); return tailCallVM(DoRestFallbackInfo, masm); } } // namespace jit } // namespace js