blob: ab11826b3bb5028091c617adc2e883bab9d6e4e9 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "jit/BaselineIC.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/SizePrintfMacros.h"
#include "mozilla/TemplateLib.h"
#include "jslibmath.h"
#include "jstypes.h"
#include "builtin/Eval.h"
#include "builtin/SIMD.h"
#include "jit/BaselineDebugModeOSR.h"
#include "jit/BaselineJIT.h"
#include "jit/JitSpewer.h"
#include "jit/Linker.h"
#include "jit/Lowering.h"
#ifdef JS_ION_PERF
# include "jit/PerfSpewer.h"
#endif
#include "jit/SharedICHelpers.h"
#include "jit/VMFunctions.h"
#include "js/Conversions.h"
#include "js/TraceableVector.h"
#include "vm/Opcodes.h"
#include "vm/TypedArrayCommon.h"
#include "jsboolinlines.h"
#include "jsscriptinlines.h"
#include "jit/JitFrames-inl.h"
#include "jit/MacroAssembler-inl.h"
#include "jit/shared/Lowering-shared-inl.h"
#include "vm/Interpreter-inl.h"
#include "vm/ScopeObject-inl.h"
#include "vm/StringObject-inl.h"
#include "vm/UnboxedObject-inl.h"
using mozilla::DebugOnly;
namespace js {
namespace jit {
//
// WarmUpCounter_Fallback
//
static bool
EnsureCanEnterIon(JSContext* cx, ICWarmUpCounter_Fallback* stub, BaselineFrame* frame,
HandleScript script, jsbytecode* pc, void** jitcodePtr)
{
MOZ_ASSERT(jitcodePtr);
MOZ_ASSERT(!*jitcodePtr);
bool isLoopEntry = (JSOp(*pc) == JSOP_LOOPENTRY);
MethodStatus stat;
if (isLoopEntry) {
MOZ_ASSERT(LoopEntryCanIonOsr(pc));
JitSpew(JitSpew_BaselineOSR, " Compile at loop entry!");
stat = CanEnterAtBranch(cx, script, frame, pc);
} else if (frame->isFunctionFrame()) {
JitSpew(JitSpew_BaselineOSR, " Compile function from top for later entry!");
stat = CompileFunctionForBaseline(cx, script, frame);
} else {
return true;
}
if (stat == Method_Error) {
JitSpew(JitSpew_BaselineOSR, " Compile with Ion errored!");
return false;
}
if (stat == Method_CantCompile)
JitSpew(JitSpew_BaselineOSR, " Can't compile with Ion!");
else if (stat == Method_Skipped)
JitSpew(JitSpew_BaselineOSR, " Skipped compile with Ion!");
else if (stat == Method_Compiled)
JitSpew(JitSpew_BaselineOSR, " Compiled with Ion!");
else
MOZ_CRASH("Invalid MethodStatus!");
// Failed to compile. Reset warm-up counter and return.
if (stat != Method_Compiled) {
// TODO: If stat == Method_CantCompile, insert stub that just skips the
// warm-up counter entirely, instead of resetting it.
bool bailoutExpected = script->hasIonScript() && script->ionScript()->bailoutExpected();
if (stat == Method_CantCompile || bailoutExpected) {
JitSpew(JitSpew_BaselineOSR, " Reset WarmUpCounter cantCompile=%s bailoutExpected=%s!",
stat == Method_CantCompile ? "yes" : "no",
bailoutExpected ? "yes" : "no");
script->resetWarmUpCounter();
}
return true;
}
if (isLoopEntry) {
IonScript* ion = script->ionScript();
MOZ_ASSERT(cx->runtime()->spsProfiler.enabled() == ion->hasProfilingInstrumentation());
MOZ_ASSERT(ion->osrPc() == pc);
JitSpew(JitSpew_BaselineOSR, " OSR possible!");
*jitcodePtr = ion->method()->raw() + ion->osrEntryOffset();
}
return true;
}
//
// The following data is kept in a temporary heap-allocated buffer, stored in
// JitRuntime (high memory addresses at top, low at bottom):
//
// +----->+=================================+ -- <---- High Address
// | | | |
// | | ...BaselineFrame... | |-- Copy of BaselineFrame + stack values
// | | | |
// | +---------------------------------+ |
// | | | |
// | | ...Locals/Stack... | |
// | | | |
// | +=================================+ --
// | | Padding(Maybe Empty) |
// | +=================================+ --
// +------|-- baselineFrame | |-- IonOsrTempData
// | jitcode | |
// +=================================+ -- <---- Low Address
//
// A pointer to the IonOsrTempData is returned.
struct IonOsrTempData
{
void* jitcode;
uint8_t* baselineFrame;
};
static IonOsrTempData*
PrepareOsrTempData(JSContext* cx, ICWarmUpCounter_Fallback* stub, BaselineFrame* frame,
HandleScript script, jsbytecode* pc, void* jitcode)
{
size_t numLocalsAndStackVals = frame->numValueSlots();
// Calculate the amount of space to allocate:
// BaselineFrame space:
// (sizeof(Value) * (numLocals + numStackVals))
// + sizeof(BaselineFrame)
//
// IonOsrTempData space:
// sizeof(IonOsrTempData)
size_t frameSpace = sizeof(BaselineFrame) + sizeof(Value) * numLocalsAndStackVals;
size_t ionOsrTempDataSpace = sizeof(IonOsrTempData);
size_t totalSpace = AlignBytes(frameSpace, sizeof(Value)) +
AlignBytes(ionOsrTempDataSpace, sizeof(Value));
IonOsrTempData* info = (IonOsrTempData*)cx->runtime()->getJitRuntime(cx)->allocateOsrTempData(totalSpace);
if (!info)
return nullptr;
memset(info, 0, totalSpace);
info->jitcode = jitcode;
// Copy the BaselineFrame + local/stack Values to the buffer. Arguments and
// |this| are not copied but left on the stack: the Baseline and Ion frame
// share the same frame prefix and Ion won't clobber these values. Note
// that info->baselineFrame will point to the *end* of the frame data, like
// the frame pointer register in baseline frames.
uint8_t* frameStart = (uint8_t*)info + AlignBytes(ionOsrTempDataSpace, sizeof(Value));
info->baselineFrame = frameStart + frameSpace;
memcpy(frameStart, (uint8_t*)frame - numLocalsAndStackVals * sizeof(Value), frameSpace);
JitSpew(JitSpew_BaselineOSR, "Allocated IonOsrTempData at %p", (void*) info);
JitSpew(JitSpew_BaselineOSR, "Jitcode is %p", info->jitcode);
// All done.
return info;
}
static bool
DoWarmUpCounterFallback(JSContext* cx, BaselineFrame* frame, ICWarmUpCounter_Fallback* stub,
IonOsrTempData** infoPtr)
{
MOZ_ASSERT(infoPtr);
*infoPtr = nullptr;
// A TI OOM will disable TI and Ion.
if (!jit::IsIonEnabled(cx))
return true;
RootedScript script(cx, frame->script());
jsbytecode* pc = stub->icEntry()->pc(script);
bool isLoopEntry = JSOp(*pc) == JSOP_LOOPENTRY;
MOZ_ASSERT(!isLoopEntry || LoopEntryCanIonOsr(pc));
FallbackICSpew(cx, stub, "WarmUpCounter(%d)", isLoopEntry ? int(script->pcToOffset(pc)) : int(-1));
if (!script->canIonCompile()) {
// TODO: ASSERT that ion-compilation-disabled checker stub doesn't exist.
// TODO: Clear all optimized stubs.
// TODO: Add a ion-compilation-disabled checker IC stub
script->resetWarmUpCounter();
return true;
}
MOZ_ASSERT(!script->isIonCompilingOffThread());
// If Ion script exists, but PC is not at a loop entry, then Ion will be entered for
// this script at an appropriate LOOPENTRY or the next time this function is called.
if (script->hasIonScript() && !isLoopEntry) {
JitSpew(JitSpew_BaselineOSR, "IonScript exists, but not at loop entry!");
// TODO: ASSERT that a ion-script-already-exists checker stub doesn't exist.
// TODO: Clear all optimized stubs.
// TODO: Add a ion-script-already-exists checker stub.
return true;
}
// Ensure that Ion-compiled code is available.
JitSpew(JitSpew_BaselineOSR,
"WarmUpCounter for %s:%" PRIuSIZE " reached %d at pc %p, trying to switch to Ion!",
script->filename(), script->lineno(), (int) script->getWarmUpCount(), (void*) pc);
void* jitcode = nullptr;
if (!EnsureCanEnterIon(cx, stub, frame, script, pc, &jitcode))
return false;
// Jitcode should only be set here if not at loop entry.
MOZ_ASSERT_IF(!isLoopEntry, !jitcode);
if (!jitcode)
return true;
// Prepare the temporary heap copy of the fake InterpreterFrame and actual args list.
JitSpew(JitSpew_BaselineOSR, "Got jitcode. Preparing for OSR into ion.");
IonOsrTempData* info = PrepareOsrTempData(cx, stub, frame, script, pc, jitcode);
if (!info)
return false;
*infoPtr = info;
return true;
}
typedef bool (*DoWarmUpCounterFallbackFn)(JSContext*, BaselineFrame*,
ICWarmUpCounter_Fallback*, IonOsrTempData** infoPtr);
static const VMFunction DoWarmUpCounterFallbackInfo =
FunctionInfo<DoWarmUpCounterFallbackFn>(DoWarmUpCounterFallback);
bool
ICWarmUpCounter_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
// Push a stub frame so that we can perform a non-tail call.
enterStubFrame(masm, R1.scratchReg());
Label noCompiledCode;
// Call DoWarmUpCounterFallback to compile/check-for Ion-compiled function
{
// Push IonOsrTempData pointer storage
masm.subFromStackPtr(Imm32(sizeof(void*)));
masm.push(masm.getStackPointer());
// Push stub pointer.
masm.push(ICStubReg);
pushFramePtr(masm, R0.scratchReg());
if (!callVM(DoWarmUpCounterFallbackInfo, masm))
return false;
// Pop IonOsrTempData pointer.
masm.pop(R0.scratchReg());
leaveStubFrame(masm);
// If no JitCode was found, then skip just exit the IC.
masm.branchPtr(Assembler::Equal, R0.scratchReg(), ImmPtr(nullptr), &noCompiledCode);
}
// Get a scratch register.
AllocatableGeneralRegisterSet regs(availableGeneralRegs(0));
Register osrDataReg = R0.scratchReg();
regs.take(osrDataReg);
regs.takeUnchecked(OsrFrameReg);
Register scratchReg = regs.takeAny();
// At this point, stack looks like:
// +-> [...Calling-Frame...]
// | [...Actual-Args/ThisV/ArgCount/Callee...]
// | [Descriptor]
// | [Return-Addr]
// +---[Saved-FramePtr] <-- BaselineFrameReg points here.
// [...Baseline-Frame...]
// Restore the stack pointer to point to the saved frame pointer.
masm.moveToStackPtr(BaselineFrameReg);
// Discard saved frame pointer, so that the return address is on top of
// the stack.
masm.pop(scratchReg);
#ifdef DEBUG
// If profiler instrumentation is on, ensure that lastProfilingFrame is
// the frame currently being OSR-ed
{
Label checkOk;
AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled());
masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &checkOk);
masm.loadPtr(AbsoluteAddress((void*)&cx->runtime()->jitActivation), scratchReg);
masm.loadPtr(Address(scratchReg, JitActivation::offsetOfLastProfilingFrame()), scratchReg);
// It may be the case that we entered the baseline frame with
// profiling turned off on, then in a call within a loop (i.e. a
// callee frame), turn on profiling, then return to this frame,
// and then OSR with profiling turned on. In this case, allow for
// lastProfilingFrame to be null.
masm.branchPtr(Assembler::Equal, scratchReg, ImmWord(0), &checkOk);
masm.branchStackPtr(Assembler::Equal, scratchReg, &checkOk);
masm.assumeUnreachable("Baseline OSR lastProfilingFrame mismatch.");
masm.bind(&checkOk);
}
#endif
// Jump into Ion.
masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, jitcode)), scratchReg);
masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, baselineFrame)), OsrFrameReg);
masm.jump(scratchReg);
// No jitcode available, do nothing.
masm.bind(&noCompiledCode);
EmitReturnFromIC(masm);
return true;
}
//
// TypeUpdate_Fallback
//
static bool
DoTypeUpdateFallback(JSContext* cx, BaselineFrame* frame, ICUpdatedStub* stub, HandleValue objval,
HandleValue value)
{
FallbackICSpew(cx, stub->getChainFallback(), "TypeUpdate(%s)",
ICStub::KindString(stub->kind()));
RootedScript script(cx, frame->script());
RootedObject obj(cx, &objval.toObject());
RootedId id(cx);
switch(stub->kind()) {
case ICStub::SetElem_DenseOrUnboxedArray:
case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
id = JSID_VOID;
AddTypePropertyId(cx, obj, id, value);
break;
}
case ICStub::SetProp_Native:
case ICStub::SetProp_NativeAdd:
case ICStub::SetProp_Unboxed: {
MOZ_ASSERT(obj->isNative() || obj->is<UnboxedPlainObject>());
jsbytecode* pc = stub->getChainFallback()->icEntry()->pc(script);
if (*pc == JSOP_SETALIASEDVAR || *pc == JSOP_INITALIASEDLEXICAL)
id = NameToId(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc));
else
id = NameToId(script->getName(pc));
AddTypePropertyId(cx, obj, id, value);
break;
}
case ICStub::SetProp_TypedObject: {
MOZ_ASSERT(obj->is<TypedObject>());
jsbytecode* pc = stub->getChainFallback()->icEntry()->pc(script);
id = NameToId(script->getName(pc));
if (stub->toSetProp_TypedObject()->isObjectReference()) {
// Ignore all values being written except plain objects. Null
// is included implicitly in type information for this property,
// and non-object non-null values will cause the stub to fail to
// match shortly and we will end up doing the assignment in the VM.
if (value.isObject())
AddTypePropertyId(cx, obj, id, value);
} else {
// Ignore undefined values, which are included implicitly in type
// information for this property.
if (!value.isUndefined())
AddTypePropertyId(cx, obj, id, value);
}
break;
}
default:
MOZ_CRASH("Invalid stub");
}
return stub->addUpdateStubForValue(cx, script, obj, id, value);
}
typedef bool (*DoTypeUpdateFallbackFn)(JSContext*, BaselineFrame*, ICUpdatedStub*, HandleValue,
HandleValue);
const VMFunction DoTypeUpdateFallbackInfo =
FunctionInfo<DoTypeUpdateFallbackFn>(DoTypeUpdateFallback, NonTailCall);
bool
ICTypeUpdate_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
// Just store false into R1.scratchReg() and return.
masm.move32(Imm32(0), R1.scratchReg());
EmitReturnFromIC(masm);
return true;
}
bool
ICTypeUpdate_PrimitiveSet::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label success;
if ((flags_ & TypeToFlag(JSVAL_TYPE_INT32)) && !(flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE)))
masm.branchTestInt32(Assembler::Equal, R0, &success);
if (flags_ & TypeToFlag(JSVAL_TYPE_DOUBLE))
masm.branchTestNumber(Assembler::Equal, R0, &success);
if (flags_ & TypeToFlag(JSVAL_TYPE_UNDEFINED))
masm.branchTestUndefined(Assembler::Equal, R0, &success);
if (flags_ & TypeToFlag(JSVAL_TYPE_BOOLEAN))
masm.branchTestBoolean(Assembler::Equal, R0, &success);
if (flags_ & TypeToFlag(JSVAL_TYPE_STRING))
masm.branchTestString(Assembler::Equal, R0, &success);
if (flags_ & TypeToFlag(JSVAL_TYPE_SYMBOL))
masm.branchTestSymbol(Assembler::Equal, R0, &success);
// Currently, we will never generate primitive stub checks for object. However,
// when we do get to the point where we want to collapse our monitor chains of
// objects and singletons down (when they get too long) to a generic "any object"
// in coordination with the typeset doing the same thing, this will need to
// be re-enabled.
/*
if (flags_ & TypeToFlag(JSVAL_TYPE_OBJECT))
masm.branchTestObject(Assembler::Equal, R0, &success);
*/
MOZ_ASSERT(!(flags_ & TypeToFlag(JSVAL_TYPE_OBJECT)));
if (flags_ & TypeToFlag(JSVAL_TYPE_NULL))
masm.branchTestNull(Assembler::Equal, R0, &success);
EmitStubGuardFailure(masm);
// Type matches, load true into R1.scratchReg() and return.
masm.bind(&success);
masm.mov(ImmWord(1), R1.scratchReg());
EmitReturnFromIC(masm);
return true;
}
bool
ICTypeUpdate_SingleObject::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
// Guard on the object's identity.
Register obj = masm.extractObject(R0, R1.scratchReg());
Address expectedObject(ICStubReg, ICTypeUpdate_SingleObject::offsetOfObject());
masm.branchPtr(Assembler::NotEqual, expectedObject, obj, &failure);
// Identity matches, load true into R1.scratchReg() and return.
masm.mov(ImmWord(1), R1.scratchReg());
EmitReturnFromIC(masm);
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
bool
ICTypeUpdate_ObjectGroup::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
// Guard on the object's ObjectGroup.
Register obj = masm.extractObject(R0, R1.scratchReg());
masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), R1.scratchReg());
Address expectedGroup(ICStubReg, ICTypeUpdate_ObjectGroup::offsetOfGroup());
masm.branchPtr(Assembler::NotEqual, expectedGroup, R1.scratchReg(), &failure);
// Group matches, load true into R1.scratchReg() and return.
masm.mov(ImmWord(1), R1.scratchReg());
EmitReturnFromIC(masm);
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
typedef bool (*DoCallNativeGetterFn)(JSContext*, HandleFunction, HandleObject, MutableHandleValue);
static const VMFunction DoCallNativeGetterInfo =
FunctionInfo<DoCallNativeGetterFn>(DoCallNativeGetter);
//
// NewArray_Fallback
//
static bool
DoNewArray(JSContext* cx, BaselineFrame* frame, ICNewArray_Fallback* stub, uint32_t length,
MutableHandleValue res)
{
FallbackICSpew(cx, stub, "NewArray");
RootedObject obj(cx);
if (stub->templateObject()) {
RootedObject templateObject(cx, stub->templateObject());
obj = NewArrayOperationWithTemplate(cx, templateObject);
if (!obj)
return false;
} else {
RootedScript script(cx, frame->script());
jsbytecode* pc = stub->icEntry()->pc(script);
obj = NewArrayOperation(cx, script, pc, length);
if (!obj)
return false;
if (obj && !obj->isSingleton() && !obj->group()->maybePreliminaryObjects()) {
JSObject* templateObject = NewArrayOperation(cx, script, pc, length, TenuredObject);
if (!templateObject)
return false;
stub->setTemplateObject(templateObject);
}
}
res.setObject(*obj);
return true;
}
typedef bool(*DoNewArrayFn)(JSContext*, BaselineFrame*, ICNewArray_Fallback*, uint32_t,
MutableHandleValue);
static const VMFunction DoNewArrayInfo = FunctionInfo<DoNewArrayFn>(DoNewArray, TailCall);
bool
ICNewArray_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
EmitRestoreTailCallReg(masm);
masm.push(R0.scratchReg()); // length
masm.push(ICStubReg); // stub.
pushFramePtr(masm, R0.scratchReg());
return tailCallVM(DoNewArrayInfo, masm);
}
//
// NewObject_Fallback
//
// Unlike typical baseline IC stubs, the code for NewObject_WithTemplate is
// specialized for the template object being allocated.
static JitCode*
GenerateNewObjectWithTemplateCode(JSContext* cx, JSObject* templateObject)
{
JitContext jctx(cx, nullptr);
MacroAssembler masm;
#ifdef JS_CODEGEN_ARM
masm.setSecondScratchReg(BaselineSecondScratchReg);
#endif
Label failure;
Register objReg = R0.scratchReg();
Register tempReg = R1.scratchReg();
masm.movePtr(ImmGCPtr(templateObject->group()), tempReg);
masm.branchTest32(Assembler::NonZero, Address(tempReg, ObjectGroup::offsetOfFlags()),
Imm32(OBJECT_FLAG_PRE_TENURE), &failure);
masm.branchPtr(Assembler::NotEqual, AbsoluteAddress(cx->compartment()->addressOfMetadataCallback()),
ImmWord(0), &failure);
masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, &failure);
masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0);
EmitReturnFromIC(masm);
masm.bind(&failure);
EmitStubGuardFailure(masm);
Linker linker(masm);
AutoFlushICache afc("GenerateNewObjectWithTemplateCode");
return linker.newCode<CanGC>(cx, BASELINE_CODE);
}
static bool
DoNewObject(JSContext* cx, BaselineFrame* frame, ICNewObject_Fallback* stub, MutableHandleValue res)
{
FallbackICSpew(cx, stub, "NewObject");
RootedObject obj(cx);
RootedObject templateObject(cx, stub->templateObject());
if (templateObject) {
MOZ_ASSERT(!templateObject->group()->maybePreliminaryObjects());
obj = NewObjectOperationWithTemplate(cx, templateObject);
} else {
RootedScript script(cx, frame->script());
jsbytecode* pc = stub->icEntry()->pc(script);
obj = NewObjectOperation(cx, script, pc);
if (obj && !obj->isSingleton() && !obj->group()->maybePreliminaryObjects()) {
JSObject* templateObject = NewObjectOperation(cx, script, pc, TenuredObject);
if (!templateObject)
return false;
if (templateObject->is<UnboxedPlainObject>() ||
!templateObject->as<PlainObject>().hasDynamicSlots())
{
JitCode* code = GenerateNewObjectWithTemplateCode(cx, templateObject);
if (!code)
return false;
ICStubSpace* space =
ICStubCompiler::StubSpaceForKind(ICStub::NewObject_WithTemplate, script);
ICStub* templateStub = ICStub::New<ICNewObject_WithTemplate>(cx, space, code);
if (!templateStub)
return false;
stub->addNewStub(templateStub);
}
stub->setTemplateObject(templateObject);
}
}
if (!obj)
return false;
res.setObject(*obj);
return true;
}
typedef bool(*DoNewObjectFn)(JSContext*, BaselineFrame*, ICNewObject_Fallback*, MutableHandleValue);
static const VMFunction DoNewObjectInfo = FunctionInfo<DoNewObjectFn>(DoNewObject, TailCall);
bool
ICNewObject_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
EmitRestoreTailCallReg(masm);
masm.push(ICStubReg); // stub.
pushFramePtr(masm, R0.scratchReg());
return tailCallVM(DoNewObjectInfo, masm);
}
//
// ToBool_Fallback
//
static bool
DoToBoolFallback(JSContext* cx, BaselineFrame* frame, ICToBool_Fallback* stub, HandleValue arg,
MutableHandleValue ret)
{
FallbackICSpew(cx, stub, "ToBool");
bool cond = ToBoolean(arg);
ret.setBoolean(cond);
// Check to see if a new stub should be generated.
if (stub->numOptimizedStubs() >= ICToBool_Fallback::MAX_OPTIMIZED_STUBS) {
// TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
// But for now we just bail.
return true;
}
MOZ_ASSERT(!arg.isBoolean());
JSScript* script = frame->script();
// Try to generate new stubs.
if (arg.isInt32()) {
JitSpew(JitSpew_BaselineIC, " Generating ToBool(Int32) stub.");
ICToBool_Int32::Compiler compiler(cx);
ICStub* int32Stub = compiler.getStub(compiler.getStubSpace(script));
if (!int32Stub)
return false;
stub->addNewStub(int32Stub);
return true;
}
if (arg.isDouble() && cx->runtime()->jitSupportsFloatingPoint) {
JitSpew(JitSpew_BaselineIC, " Generating ToBool(Double) stub.");
ICToBool_Double::Compiler compiler(cx);
ICStub* doubleStub = compiler.getStub(compiler.getStubSpace(script));
if (!doubleStub)
return false;
stub->addNewStub(doubleStub);
return true;
}
if (arg.isString()) {
JitSpew(JitSpew_BaselineIC, " Generating ToBool(String) stub");
ICToBool_String::Compiler compiler(cx);
ICStub* stringStub = compiler.getStub(compiler.getStubSpace(script));
if (!stringStub)
return false;
stub->addNewStub(stringStub);
return true;
}
if (arg.isNull() || arg.isUndefined()) {
ICToBool_NullUndefined::Compiler compiler(cx);
ICStub* nilStub = compiler.getStub(compiler.getStubSpace(script));
if (!nilStub)
return false;
stub->addNewStub(nilStub);
return true;
}
if (arg.isObject()) {
JitSpew(JitSpew_BaselineIC, " Generating ToBool(Object) stub.");
ICToBool_Object::Compiler compiler(cx);
ICStub* objStub = compiler.getStub(compiler.getStubSpace(script));
if (!objStub)
return false;
stub->addNewStub(objStub);
return true;
}
return true;
}
typedef bool (*pf)(JSContext*, BaselineFrame*, ICToBool_Fallback*, HandleValue,
MutableHandleValue);
static const VMFunction fun = FunctionInfo<pf>(DoToBoolFallback, TailCall);
bool
ICToBool_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
MOZ_ASSERT(R0 == JSReturnOperand);
// Restore the tail call register.
EmitRestoreTailCallReg(masm);
// Push arguments.
masm.pushValue(R0);
masm.push(ICStubReg);
pushFramePtr(masm, R0.scratchReg());
return tailCallVM(fun, masm);
}
//
// ToBool_Int32
//
bool
ICToBool_Int32::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
masm.branchTestInt32(Assembler::NotEqual, R0, &failure);
Label ifFalse;
masm.branchTestInt32Truthy(false, R0, &ifFalse);
masm.moveValue(BooleanValue(true), R0);
EmitReturnFromIC(masm);
masm.bind(&ifFalse);
masm.moveValue(BooleanValue(false), R0);
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// ToBool_String
//
bool
ICToBool_String::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
masm.branchTestString(Assembler::NotEqual, R0, &failure);
Label ifFalse;
masm.branchTestStringTruthy(false, R0, &ifFalse);
masm.moveValue(BooleanValue(true), R0);
EmitReturnFromIC(masm);
masm.bind(&ifFalse);
masm.moveValue(BooleanValue(false), R0);
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// ToBool_NullUndefined
//
bool
ICToBool_NullUndefined::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure, ifFalse;
masm.branchTestNull(Assembler::Equal, R0, &ifFalse);
masm.branchTestUndefined(Assembler::NotEqual, R0, &failure);
masm.bind(&ifFalse);
masm.moveValue(BooleanValue(false), R0);
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// ToBool_Double
//
bool
ICToBool_Double::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure, ifTrue;
masm.branchTestDouble(Assembler::NotEqual, R0, &failure);
masm.unboxDouble(R0, FloatReg0);
masm.branchTestDoubleTruthy(true, FloatReg0, &ifTrue);
masm.moveValue(BooleanValue(false), R0);
EmitReturnFromIC(masm);
masm.bind(&ifTrue);
masm.moveValue(BooleanValue(true), R0);
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// ToBool_Object
//
bool
ICToBool_Object::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure, ifFalse, slowPath;
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
Register objReg = masm.extractObject(R0, ExtractTemp0);
Register scratch = R1.scratchReg();
masm.branchTestObjectTruthy(false, objReg, scratch, &slowPath, &ifFalse);
// If object doesn't emulate undefined, it evaulates to true.
masm.moveValue(BooleanValue(true), R0);
EmitReturnFromIC(masm);
masm.bind(&ifFalse);
masm.moveValue(BooleanValue(false), R0);
EmitReturnFromIC(masm);
masm.bind(&slowPath);
masm.setupUnalignedABICall(scratch);
masm.passABIArg(objReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::EmulatesUndefined));
masm.convertBoolToInt32(ReturnReg, ReturnReg);
masm.xor32(Imm32(1), ReturnReg);
masm.tagValue(JSVAL_TYPE_BOOLEAN, ReturnReg, R0);
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// ToNumber_Fallback
//
static bool
DoToNumberFallback(JSContext* cx, ICToNumber_Fallback* stub, HandleValue arg, MutableHandleValue ret)
{
FallbackICSpew(cx, stub, "ToNumber");
ret.set(arg);
return ToNumber(cx, ret);
}
typedef bool (*DoToNumberFallbackFn)(JSContext*, ICToNumber_Fallback*, HandleValue, MutableHandleValue);
static const VMFunction DoToNumberFallbackInfo =
FunctionInfo<DoToNumberFallbackFn>(DoToNumberFallback, TailCall, PopValues(1));
bool
ICToNumber_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
MOZ_ASSERT(R0 == JSReturnOperand);
// Restore the tail call register.
EmitRestoreTailCallReg(masm);
// Ensure stack is fully synced for the expression decompiler.
masm.pushValue(R0);
// Push arguments.
masm.pushValue(R0);
masm.push(ICStubReg);
return tailCallVM(DoToNumberFallbackInfo, masm);
}
//
// GetElem_Fallback
//
static Shape*
LastPropertyForSetProp(JSObject* obj)
{
if (obj->isNative())
return obj->as<NativeObject>().lastProperty();
if (obj->is<UnboxedPlainObject>()) {
UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
return expando ? expando->lastProperty() : nullptr;
}
return nullptr;
}
static bool
IsCacheableSetPropWriteSlot(JSObject* obj, Shape* oldShape, Shape* propertyShape)
{
// Object shape must not have changed during the property set.
if (LastPropertyForSetProp(obj) != oldShape)
return false;
if (!propertyShape->hasSlot() ||
!propertyShape->hasDefaultSetter() ||
!propertyShape->writable())
{
return false;
}
return true;
}
static bool
IsCacheableSetPropAddSlot(JSContext* cx, JSObject* obj, Shape* oldShape,
jsid id, Shape* propertyShape, size_t* protoChainDepth)
{
// The property must be the last added property of the object.
if (LastPropertyForSetProp(obj) != propertyShape)
return false;
// Object must be extensible, oldShape must be immediate parent of current shape.
if (!obj->nonProxyIsExtensible() || propertyShape->previous() != oldShape)
return false;
// Basic shape checks.
if (propertyShape->inDictionary() ||
!propertyShape->hasSlot() ||
!propertyShape->hasDefaultSetter() ||
!propertyShape->writable())
{
return false;
}
// Watch out for resolve or addProperty hooks.
if (ClassMayResolveId(cx->names(), obj->getClass(), id, obj) ||
obj->getClass()->addProperty)
{
return false;
}
size_t chainDepth = 0;
// Walk up the object prototype chain and ensure that all prototypes are
// native, and that all prototypes have no setter defined on the property.
for (JSObject* proto = obj->getProto(); proto; proto = proto->getProto()) {
chainDepth++;
// if prototype is non-native, don't optimize
if (!proto->isNative())
return false;
// if prototype defines this property in a non-plain way, don't optimize
Shape* protoShape = proto->as<NativeObject>().lookup(cx, id);
if (protoShape && !protoShape->hasDefaultSetter())
return false;
// Otherwise, if there's no such property, watch out for a resolve hook
// that would need to be invoked and thus prevent inlining of property
// addition.
if (ClassMayResolveId(cx->names(), proto->getClass(), id, proto))
return false;
}
// Only add a IC entry if the dynamic slots didn't change when the shapes
// changed. Need to ensure that a shape change for a subsequent object
// won't involve reallocating the slot array.
if (NativeObject::dynamicSlotsCount(propertyShape) != NativeObject::dynamicSlotsCount(oldShape))
return false;
*protoChainDepth = chainDepth;
return true;
}
static bool
IsCacheableSetPropCall(JSContext* cx, JSObject* obj, JSObject* holder, Shape* shape,
bool* isScripted, bool* isTemporarilyUnoptimizable)
{
MOZ_ASSERT(isScripted);
if (!shape || !IsCacheableProtoChain(obj, holder))
return false;
if (shape->hasSlot() || shape->hasDefaultSetter())
return false;
if (!shape->hasSetterValue())
return false;
if (!shape->setterValue().isObject() || !shape->setterObject()->is<JSFunction>())
return false;
JSFunction* func = &shape->setterObject()->as<JSFunction>();
if (func->isNative()) {
*isScripted = false;
return true;
}
if (!func->hasJITCode()) {
*isTemporarilyUnoptimizable = true;
return false;
}
*isScripted = true;
return true;
}
template <class T>
static bool
GetElemNativeStubExists(ICGetElem_Fallback* stub, HandleObject obj, HandleObject holder,
Handle<T> key, bool needsAtomize)
{
bool indirect = (obj.get() != holder.get());
MOZ_ASSERT_IF(indirect, holder->isNative());
for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
if (iter->kind() != ICStub::GetElem_NativeSlotName &&
iter->kind() != ICStub::GetElem_NativeSlotSymbol &&
iter->kind() != ICStub::GetElem_NativePrototypeSlotName &&
iter->kind() != ICStub::GetElem_NativePrototypeSlotSymbol &&
iter->kind() != ICStub::GetElem_NativePrototypeCallNativeName &&
iter->kind() != ICStub::GetElem_NativePrototypeCallNativeSymbol &&
iter->kind() != ICStub::GetElem_NativePrototypeCallScriptedName &&
iter->kind() != ICStub::GetElem_NativePrototypeCallScriptedSymbol)
{
continue;
}
if (indirect && (iter->kind() != ICStub::GetElem_NativePrototypeSlotName &&
iter->kind() != ICStub::GetElem_NativePrototypeSlotSymbol &&
iter->kind() != ICStub::GetElem_NativePrototypeCallNativeName &&
iter->kind() != ICStub::GetElem_NativePrototypeCallNativeSymbol &&
iter->kind() != ICStub::GetElem_NativePrototypeCallScriptedName &&
iter->kind() != ICStub::GetElem_NativePrototypeCallScriptedSymbol))
{
continue;
}
if(mozilla::IsSame<T, JS::Symbol*>::value !=
static_cast<ICGetElemNativeStub*>(*iter)->isSymbol())
{
continue;
}
ICGetElemNativeStubImpl<T>* getElemNativeStub =
reinterpret_cast<ICGetElemNativeStubImpl<T>*>(*iter);
if (key != getElemNativeStub->key())
continue;
if (ReceiverGuard(obj) != getElemNativeStub->receiverGuard())
continue;
// If the new stub needs atomization, and the old stub doesn't atomize, then
// an appropriate stub doesn't exist.
if (needsAtomize && !getElemNativeStub->needsAtomize())
continue;
// For prototype gets, check the holder and holder shape.
if (indirect) {
if (iter->isGetElem_NativePrototypeSlotName() ||
iter->isGetElem_NativePrototypeSlotSymbol()) {
ICGetElem_NativePrototypeSlot<T>* protoStub =
reinterpret_cast<ICGetElem_NativePrototypeSlot<T>*>(*iter);
if (holder != protoStub->holder())
continue;
if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape())
continue;
} else {
MOZ_ASSERT(iter->isGetElem_NativePrototypeCallNativeName() ||
iter->isGetElem_NativePrototypeCallNativeSymbol() ||
iter->isGetElem_NativePrototypeCallScriptedName() ||
iter->isGetElem_NativePrototypeCallScriptedSymbol());
ICGetElemNativePrototypeCallStub<T>* protoStub =
reinterpret_cast<ICGetElemNativePrototypeCallStub<T>*>(*iter);
if (holder != protoStub->holder())
continue;
if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape())
continue;
}
}
return true;
}
return false;
}
template <class T>
static void
RemoveExistingGetElemNativeStubs(JSContext* cx, ICGetElem_Fallback* stub, HandleObject obj,
HandleObject holder, Handle<T> key, bool needsAtomize)
{
bool indirect = (obj.get() != holder.get());
MOZ_ASSERT_IF(indirect, holder->isNative());
for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
switch (iter->kind()) {
case ICStub::GetElem_NativeSlotName:
case ICStub::GetElem_NativeSlotSymbol:
if (indirect)
continue;
case ICStub::GetElem_NativePrototypeSlotName:
case ICStub::GetElem_NativePrototypeSlotSymbol:
case ICStub::GetElem_NativePrototypeCallNativeName:
case ICStub::GetElem_NativePrototypeCallNativeSymbol:
case ICStub::GetElem_NativePrototypeCallScriptedName:
case ICStub::GetElem_NativePrototypeCallScriptedSymbol:
break;
default:
continue;
}
if(mozilla::IsSame<T, JS::Symbol*>::value !=
static_cast<ICGetElemNativeStub*>(*iter)->isSymbol())
{
continue;
}
ICGetElemNativeStubImpl<T>* getElemNativeStub =
reinterpret_cast<ICGetElemNativeStubImpl<T>*>(*iter);
if (key != getElemNativeStub->key())
continue;
if (ReceiverGuard(obj) != getElemNativeStub->receiverGuard())
continue;
// For prototype gets, check the holder and holder shape.
if (indirect) {
if (iter->isGetElem_NativePrototypeSlotName() ||
iter->isGetElem_NativePrototypeSlotSymbol()) {
ICGetElem_NativePrototypeSlot<T>* protoStub =
reinterpret_cast<ICGetElem_NativePrototypeSlot<T>*>(*iter);
if (holder != protoStub->holder())
continue;
// If the holder matches, but the holder's lastProperty doesn't match, then
// this stub is invalid anyway. Unlink it.
if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape()) {
iter.unlink(cx);
continue;
}
} else {
MOZ_ASSERT(iter->isGetElem_NativePrototypeCallNativeName() ||
iter->isGetElem_NativePrototypeCallNativeSymbol() ||
iter->isGetElem_NativePrototypeCallScriptedName() ||
iter->isGetElem_NativePrototypeCallScriptedSymbol());
ICGetElemNativePrototypeCallStub<T>* protoStub =
reinterpret_cast<ICGetElemNativePrototypeCallStub<T>*>(*iter);
if (holder != protoStub->holder())
continue;
// If the holder matches, but the holder's lastProperty doesn't match, then
// this stub is invalid anyway. Unlink it.
if (holder->as<NativeObject>().lastProperty() != protoStub->holderShape()) {
iter.unlink(cx);
continue;
}
}
}
// If the new stub needs atomization, and the old stub doesn't atomize, then
// remove the old stub.
if (needsAtomize && !getElemNativeStub->needsAtomize()) {
iter.unlink(cx);
continue;
}
// Should never get here, because this means a matching stub exists, and if
// a matching stub exists, this procedure should never have been called.
MOZ_CRASH("Procedure should never have been called.");
}
}
static bool
TypedArrayGetElemStubExists(ICGetElem_Fallback* stub, HandleObject obj)
{
for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
if (!iter->isGetElem_TypedArray())
continue;
if (obj->maybeShape() == iter->toGetElem_TypedArray()->shape())
return true;
}
return false;
}
static bool
ArgumentsGetElemStubExists(ICGetElem_Fallback* stub, ICGetElem_Arguments::Which which)
{
for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
if (!iter->isGetElem_Arguments())
continue;
if (iter->toGetElem_Arguments()->which() == which)
return true;
}
return false;
}
template <class T>
static T
getKey(jsid id)
{
MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol");
return false;
}
template <>
JS::Symbol* getKey<JS::Symbol*>(jsid id)
{
if (!JSID_IS_SYMBOL(id))
return nullptr;
return JSID_TO_SYMBOL(id);
}
template <>
PropertyName* getKey<PropertyName*>(jsid id)
{
uint32_t dummy;
if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy))
return nullptr;
return JSID_TO_ATOM(id)->asPropertyName();
}
static bool
IsOptimizableElementPropertyName(JSContext* cx, HandleValue key, MutableHandleId idp)
{
if (!key.isString())
return false;
// Convert to interned property name.
if (!ValueToId<CanGC>(cx, key, idp))
return false;
uint32_t dummy;
if (!JSID_IS_ATOM(idp) || JSID_TO_ATOM(idp)->isIndex(&dummy))
return false;
return true;
}
template <class T>
static bool
checkAtomize(HandleValue key)
{
MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol");
return false;
}
template <>
bool checkAtomize<JS::Symbol*>(HandleValue key)
{
return false;
}
template <>
bool checkAtomize<PropertyName*>(HandleValue key)
{
return !key.toString()->isAtom();
}
template <class T>
static bool
TryAttachNativeOrUnboxedGetValueElemStub(JSContext* cx, HandleScript script, jsbytecode* pc,
ICGetElem_Fallback* stub, HandleObject obj,
HandleValue keyVal, bool* attached)
{
MOZ_ASSERT(keyVal.isString() || keyVal.isSymbol());
// Convert to id.
RootedId id(cx);
if (!ValueToId<CanGC>(cx, keyVal, &id))
return false;
Rooted<T> key(cx, getKey<T>(id));
if (!key)
return true;
bool needsAtomize = checkAtomize<T>(keyVal);
RootedShape shape(cx);
RootedObject holder(cx);
if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape))
return false;
if (!holder || (holder != obj && !holder->isNative()))
return true;
// If a suitable stub already exists, nothing else to do.
if (GetElemNativeStubExists<T>(stub, obj, holder, key, needsAtomize))
return true;
// Remove any existing stubs that may interfere with the new stub being added.
RemoveExistingGetElemNativeStubs<T>(cx, stub, obj, holder, key, needsAtomize);
ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
if (obj->is<UnboxedPlainObject>() && holder == obj) {
const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
// Once unboxed objects support symbol-keys, we need to change the following accordingly
MOZ_ASSERT_IF(!keyVal.isString(), !property);
if (property) {
if (!cx->runtime()->jitSupportsFloatingPoint)
return true;
RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
ICGetElemNativeCompiler<PropertyName*> compiler(cx, ICStub::GetElem_UnboxedPropertyName,
monitorStub, obj, holder,
name,
ICGetElemNativeStub::UnboxedProperty,
needsAtomize, property->offset +
UnboxedPlainObject::offsetOfData(),
property->type);
ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
stub->addNewStub(newStub);
*attached = true;
return true;
}
Shape* shape = obj->as<UnboxedPlainObject>().maybeExpando()->lookup(cx, id);
if (!shape->hasDefaultGetter() || !shape->hasSlot())
return true;
bool isFixedSlot;
uint32_t offset;
GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
ICGetElemNativeStub::AccessType acctype =
isFixedSlot ? ICGetElemNativeStub::FixedSlot
: ICGetElemNativeStub::DynamicSlot;
ICGetElemNativeCompiler<T> compiler(cx, getGetElemStubKind<T>(ICStub::GetElem_NativeSlotName),
monitorStub, obj, holder, key,
acctype, needsAtomize, offset);
ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
stub->addNewStub(newStub);
*attached = true;
return true;
}
if (!holder->isNative())
return true;
if (IsCacheableGetPropReadSlot(obj, holder, shape)) {
bool isFixedSlot;
uint32_t offset;
GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
ICStub::Kind kind = (obj == holder) ? ICStub::GetElem_NativeSlotName
: ICStub::GetElem_NativePrototypeSlotName;
kind = getGetElemStubKind<T>(kind);
JitSpew(JitSpew_BaselineIC, " Generating GetElem(Native %s%s slot) stub "
"(obj=%p, holder=%p, holderShape=%p)",
(obj == holder) ? "direct" : "prototype",
needsAtomize ? " atomizing" : "",
obj.get(), holder.get(), holder->as<NativeObject>().lastProperty());
AccType acctype = isFixedSlot ? ICGetElemNativeStub::FixedSlot
: ICGetElemNativeStub::DynamicSlot;
ICGetElemNativeCompiler<T> compiler(cx, kind, monitorStub, obj, holder, key,
acctype, needsAtomize, offset);
ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
stub->addNewStub(newStub);
*attached = true;
return true;
}
return true;
}
template <class T>
static bool
TryAttachNativeGetAccessorElemStub(JSContext* cx, HandleScript script, jsbytecode* pc,
ICGetElem_Fallback* stub, HandleNativeObject obj,
HandleValue keyVal, bool* attached,
bool* isTemporarilyUnoptimizable)
{
MOZ_ASSERT(!*attached);
MOZ_ASSERT(keyVal.isString() || keyVal.isSymbol());
RootedId id(cx);
if (!ValueToId<CanGC>(cx, keyVal, &id))
return false;
Rooted<T> key(cx, getKey<T>(id));
if (!key)
return true;
bool needsAtomize = checkAtomize<T>(keyVal);
RootedShape shape(cx);
RootedObject baseHolder(cx);
if (!EffectlesslyLookupProperty(cx, obj, id, &baseHolder, &shape))
return false;
if (!baseHolder || baseHolder->isNative())
return true;
HandleNativeObject holder = baseHolder.as<NativeObject>();
bool getterIsScripted = false;
if (IsCacheableGetPropCall(cx, obj, baseHolder, shape, &getterIsScripted,
isTemporarilyUnoptimizable, /*isDOMProxy=*/false))
{
RootedFunction getter(cx, &shape->getterObject()->as<JSFunction>());
// For now, we do not handle own property getters
if (obj == holder)
return true;
// If a suitable stub already exists, nothing else to do.
if (GetElemNativeStubExists<T>(stub, obj, holder, key, needsAtomize))
return true;
// Remove any existing stubs that may interfere with the new stub being added.
RemoveExistingGetElemNativeStubs<T>(cx, stub, obj, holder, key, needsAtomize);
ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
ICStub::Kind kind = getterIsScripted ? ICStub::GetElem_NativePrototypeCallScriptedName
: ICStub::GetElem_NativePrototypeCallNativeName;
kind = getGetElemStubKind<T>(kind);
if (getterIsScripted) {
JitSpew(JitSpew_BaselineIC,
" Generating GetElem(Native %s%s call scripted %s:%" PRIuSIZE ") stub "
"(obj=%p, shape=%p, holder=%p, holderShape=%p)",
(obj == holder) ? "direct" : "prototype",
needsAtomize ? " atomizing" : "",
getter->nonLazyScript()->filename(), getter->nonLazyScript()->lineno(),
obj.get(), obj->lastProperty(), holder.get(), holder->lastProperty());
} else {
JitSpew(JitSpew_BaselineIC,
" Generating GetElem(Native %s%s call native) stub "
"(obj=%p, shape=%p, holder=%p, holderShape=%p)",
(obj == holder) ? "direct" : "prototype",
needsAtomize ? " atomizing" : "",
obj.get(), obj->lastProperty(), holder.get(), holder->lastProperty());
}
AccType acctype = getterIsScripted ? ICGetElemNativeStub::ScriptedGetter
: ICGetElemNativeStub::NativeGetter;
ICGetElemNativeCompiler<T> compiler(cx, kind, monitorStub, obj, holder, key, acctype,
needsAtomize, getter, script->pcToOffset(pc));
ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
stub->addNewStub(newStub);
*attached = true;
return true;
}
return true;
}
static bool
IsPrimitiveArrayTypedObject(JSObject* obj)
{
if (!obj->is<TypedObject>())
return false;
TypeDescr& descr = obj->as<TypedObject>().typeDescr();
return descr.is<ArrayTypeDescr>() &&
descr.as<ArrayTypeDescr>().elementType().is<ScalarTypeDescr>();
}
static Scalar::Type
PrimitiveArrayTypedObjectType(JSObject* obj)
{
MOZ_ASSERT(IsPrimitiveArrayTypedObject(obj));
TypeDescr& descr = obj->as<TypedObject>().typeDescr();
return descr.as<ArrayTypeDescr>().elementType().as<ScalarTypeDescr>().type();
}
static Scalar::Type
TypedThingElementType(JSObject* obj)
{
return IsAnyTypedArray(obj)
? AnyTypedArrayType(obj)
: PrimitiveArrayTypedObjectType(obj);
}
static bool
TypedThingRequiresFloatingPoint(JSObject* obj)
{
Scalar::Type type = TypedThingElementType(obj);
return type == Scalar::Uint32 ||
type == Scalar::Float32 ||
type == Scalar::Float64;
}
static bool
IsNativeDenseElementAccess(HandleObject obj, HandleValue key)
{
if (obj->isNative() && key.isInt32() && key.toInt32() >= 0 && !IsAnyTypedArray(obj.get()))
return true;
return false;
}
static bool
IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key)
{
if (!obj->isNative() && !obj->is<UnboxedArrayObject>())
return false;
if (key.isInt32() && key.toInt32() >= 0 && !IsAnyTypedArray(obj.get()))
return true;
return false;
}
static bool
TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_Fallback* stub,
HandleValue lhs, HandleValue rhs, HandleValue res, bool* attached)
{
// Check for String[i] => Char accesses.
if (lhs.isString() && rhs.isInt32() && res.isString() &&
!stub->hasStub(ICStub::GetElem_String))
{
// NoSuchMethod handling doesn't apply to string targets.
JitSpew(JitSpew_BaselineIC, " Generating GetElem(String[Int32]) stub");
ICGetElem_String::Compiler compiler(cx);
ICStub* stringStub = compiler.getStub(compiler.getStubSpace(script));
if (!stringStub)
return false;
stub->addNewStub(stringStub);
*attached = true;
return true;
}
if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS) && rhs.isInt32() &&
!ArgumentsGetElemStubExists(stub, ICGetElem_Arguments::Magic))
{
JitSpew(JitSpew_BaselineIC, " Generating GetElem(MagicArgs[Int32]) stub");
ICGetElem_Arguments::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
ICGetElem_Arguments::Magic);
ICStub* argsStub = compiler.getStub(compiler.getStubSpace(script));
if (!argsStub)
return false;
stub->addNewStub(argsStub);
*attached = true;
return true;
}
// Otherwise, GetElem is only optimized on objects.
if (!lhs.isObject())
return true;
RootedObject obj(cx, &lhs.toObject());
// Check for ArgumentsObj[int] accesses
if (obj->is<ArgumentsObject>() && rhs.isInt32()) {
ICGetElem_Arguments::Which which = ICGetElem_Arguments::Mapped;
if (obj->is<UnmappedArgumentsObject>())
which = ICGetElem_Arguments::Unmapped;
if (!ArgumentsGetElemStubExists(stub, which)) {
JitSpew(JitSpew_BaselineIC, " Generating GetElem(ArgsObj[Int32]) stub");
ICGetElem_Arguments::Compiler compiler(
cx, stub->fallbackMonitorStub()->firstMonitorStub(), which);
ICStub* argsStub = compiler.getStub(compiler.getStubSpace(script));
if (!argsStub)
return false;
stub->addNewStub(argsStub);
*attached = true;
return true;
}
}
// Check for NativeObject[int] dense accesses.
if (IsNativeDenseElementAccess(obj, rhs)) {
JitSpew(JitSpew_BaselineIC, " Generating GetElem(Native[Int32] dense) stub");
ICGetElem_Dense::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
obj->as<NativeObject>().lastProperty());
ICStub* denseStub = compiler.getStub(compiler.getStubSpace(script));
if (!denseStub)
return false;
stub->addNewStub(denseStub);
*attached = true;
return true;
}
// Check for NativeObject[id] and UnboxedPlainObject[id] shape-optimizable accesses.
if (obj->isNative() || obj->is<UnboxedPlainObject>()) {
RootedScript rootedScript(cx, script);
if (rhs.isString()) {
if (!TryAttachNativeOrUnboxedGetValueElemStub<PropertyName*>(cx, rootedScript, pc, stub,
obj, rhs, attached))
{
return false;
}
} else if (rhs.isSymbol()) {
if (!TryAttachNativeOrUnboxedGetValueElemStub<JS::Symbol*>(cx, rootedScript, pc, stub,
obj, rhs, attached))
{
return false;
}
}
if (*attached)
return true;
script = rootedScript;
}
// Check for UnboxedArray[int] accesses.
if (obj->is<UnboxedArrayObject>() && rhs.isInt32() && rhs.toInt32() >= 0) {
JitSpew(JitSpew_BaselineIC, " Generating GetElem(UnboxedArray[Int32]) stub");
ICGetElem_UnboxedArray::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
obj->group());
ICStub* unboxedStub = compiler.getStub(compiler.getStubSpace(script));
if (!unboxedStub)
return false;
stub->addNewStub(unboxedStub);
*attached = true;
return true;
}
// Check for TypedArray[int] => Number and TypedObject[int] => Number accesses.
if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) &&
rhs.isNumber() &&
res.isNumber() &&
!TypedArrayGetElemStubExists(stub, obj))
{
if (!cx->runtime()->jitSupportsFloatingPoint &&
(TypedThingRequiresFloatingPoint(obj) || rhs.isDouble()))
{
return true;
}
// Don't attach typed object stubs if they might be neutered, as the
// stub will always bail out.
if (IsPrimitiveArrayTypedObject(obj) && cx->compartment()->neuteredTypedObjects)
return true;
JitSpew(JitSpew_BaselineIC, " Generating GetElem(TypedArray[Int32]) stub");
ICGetElem_TypedArray::Compiler compiler(cx, obj->maybeShape(), TypedThingElementType(obj));
ICStub* typedArrayStub = compiler.getStub(compiler.getStubSpace(script));
if (!typedArrayStub)
return false;
stub->addNewStub(typedArrayStub);
*attached = true;
return true;
}
// GetElem operations on non-native objects cannot be cached by either
// Baseline or Ion. Indicate this in the cache so that Ion does not
// generate a cache for this op.
if (!obj->isNative())
stub->noteNonNativeAccess();
// GetElem operations which could access negative indexes generally can't
// be optimized without the potential for bailouts, as we can't statically
// determine that an object has no properties on such indexes.
if (rhs.isNumber() && rhs.toNumber() < 0)
stub->noteNegativeIndex();
return true;
}
static bool
DoGetElemFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_, HandleValue lhs,
HandleValue rhs, MutableHandleValue res)
{
// This fallback stub may trigger debug mode toggling.
DebugModeOSRVolatileStub<ICGetElem_Fallback*> stub(frame, stub_);
RootedScript script(cx, frame->script());
jsbytecode* pc = stub->icEntry()->pc(frame->script());
JSOp op = JSOp(*pc);
FallbackICSpew(cx, stub, "GetElem(%s)", CodeName[op]);
MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM);
// Don't pass lhs directly, we need it when generating stubs.
RootedValue lhsCopy(cx, lhs);
bool isOptimizedArgs = false;
if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS)) {
// Handle optimized arguments[i] access.
if (!GetElemOptimizedArguments(cx, frame, &lhsCopy, rhs, res, &isOptimizedArgs))
return false;
if (isOptimizedArgs)
TypeScript::Monitor(cx, frame->script(), pc, res);
}
bool attached = false;
if (stub->numOptimizedStubs() >= ICGetElem_Fallback::MAX_OPTIMIZED_STUBS) {
// TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
// But for now we just bail.
stub->noteUnoptimizableAccess();
attached = true;
}
// Try to attach an optimized getter stub.
bool isTemporarilyUnoptimizable = false;
if (!attached && lhs.isObject() && lhs.toObject().isNative()){
if (rhs.isString()) {
RootedScript rootedScript(cx, frame->script());
RootedNativeObject obj(cx, &lhs.toObject().as<NativeObject>());
if (!TryAttachNativeGetAccessorElemStub<PropertyName*>(cx, rootedScript, pc, stub,
obj, rhs, &attached,
&isTemporarilyUnoptimizable))
{
return false;
}
script = rootedScript;
} else if (rhs.isSymbol()) {
RootedScript rootedScript(cx, frame->script());
RootedNativeObject obj(cx, &lhs.toObject().as<NativeObject>());
if (!TryAttachNativeGetAccessorElemStub<JS::Symbol*>(cx, rootedScript, pc, stub,
obj, rhs, &attached,
&isTemporarilyUnoptimizable))
{
return false;
}
script = rootedScript;
}
}
if (!isOptimizedArgs) {
if (!GetElementOperation(cx, op, &lhsCopy, rhs, res))
return false;
TypeScript::Monitor(cx, frame->script(), pc, res);
}
// Check if debug mode toggling made the stub invalid.
if (stub.invalid())
return true;
// Add a type monitor stub for the resulting value.
if (!stub->addMonitorStubForValue(cx, frame->script(), res,
ICStubCompiler::Engine::Baseline))
{
return false;
}
if (attached)
return true;
// Try to attach an optimized stub.
if (!TryAttachGetElemStub(cx, frame->script(), pc, stub, lhs, rhs, res, &attached))
return false;
if (!attached && !isTemporarilyUnoptimizable)
stub->noteUnoptimizableAccess();
return true;
}
typedef bool (*DoGetElemFallbackFn)(JSContext*, BaselineFrame*, ICGetElem_Fallback*,
HandleValue, HandleValue, MutableHandleValue);
static const VMFunction DoGetElemFallbackInfo =
FunctionInfo<DoGetElemFallbackFn>(DoGetElemFallback, TailCall, PopValues(2));
bool
ICGetElem_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
MOZ_ASSERT(R0 == JSReturnOperand);
// Restore the tail call register.
EmitRestoreTailCallReg(masm);
// Ensure stack is fully synced for the expression decompiler.
masm.pushValue(R0);
masm.pushValue(R1);
// Push arguments.
masm.pushValue(R1);
masm.pushValue(R0);
masm.push(ICStubReg);
pushFramePtr(masm, R0.scratchReg());
return tailCallVM(DoGetElemFallbackInfo, masm);
}
//
// GetElem_NativeSlot
//
static bool
DoAtomizeString(JSContext* cx, HandleString string, MutableHandleValue result)
{
JitSpew(JitSpew_BaselineIC, " AtomizeString called");
RootedValue key(cx, StringValue(string));
// Convert to interned property name.
RootedId id(cx);
if (!ValueToId<CanGC>(cx, key, &id))
return false;
if (!JSID_IS_ATOM(id)) {
result.set(key);
return true;
}
result.set(StringValue(JSID_TO_ATOM(id)));
return true;
}
typedef bool (*DoAtomizeStringFn)(JSContext*, HandleString, MutableHandleValue);
static const VMFunction DoAtomizeStringInfo = FunctionInfo<DoAtomizeStringFn>(DoAtomizeString);
template <class T>
bool
ICGetElemNativeCompiler<T>::emitCallNative(MacroAssembler& masm, Register objReg)
{
AllocatableGeneralRegisterSet regs(availableGeneralRegs(0));
regs.takeUnchecked(objReg);
regs.takeUnchecked(ICTailCallReg);
enterStubFrame(masm, regs.getAny());
// Push object.
masm.push(objReg);
// Push native callee.
masm.loadPtr(Address(ICStubReg, ICGetElemNativeGetterStub<T>::offsetOfGetter()), objReg);
masm.push(objReg);
regs.add(objReg);
// Call helper.
if (!callVM(DoCallNativeGetterInfo, masm))
return false;
leaveStubFrame(masm);
return true;
}
template <class T>
bool
ICGetElemNativeCompiler<T>::emitCallScripted(MacroAssembler& masm, Register objReg)
{
AllocatableGeneralRegisterSet regs(availableGeneralRegs(0));
regs.takeUnchecked(objReg);
regs.takeUnchecked(ICTailCallReg);
// Enter stub frame.
enterStubFrame(masm, regs.getAny());
// Align the stack such that the JitFrameLayout is aligned on
// JitStackAlignment.
masm.alignJitStackBasedOnNArgs(0);
// Push |this| for getter (target object).
{
ValueOperand val = regs.takeAnyValue();
masm.tagValue(JSVAL_TYPE_OBJECT, objReg, val);
masm.Push(val);
regs.add(val);
}
regs.add(objReg);
Register callee = regs.takeAny();
masm.loadPtr(Address(ICStubReg, ICGetElemNativeGetterStub<T>::offsetOfGetter()), callee);
// Push argc, callee, and descriptor.
{
Register callScratch = regs.takeAny();
EmitBaselineCreateStubFrameDescriptor(masm, callScratch);
masm.Push(Imm32(0)); // ActualArgc is 0
masm.Push(callee);
masm.Push(callScratch);
regs.add(callScratch);
}
Register code = regs.takeAnyExcluding(ArgumentsRectifierReg);
masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code);
masm.loadBaselineOrIonRaw(code, code, nullptr);
Register scratch = regs.takeAny();
// Handle arguments underflow.
Label noUnderflow;
masm.load16ZeroExtend(Address(callee, JSFunction::offsetOfNargs()), scratch);
masm.branch32(Assembler::Equal, scratch, Imm32(0), &noUnderflow);
{
// Call the arguments rectifier.
MOZ_ASSERT(ArgumentsRectifierReg != code);
JitCode* argumentsRectifier =
cx->runtime()->jitRuntime()->getArgumentsRectifier();
masm.movePtr(ImmGCPtr(argumentsRectifier), code);
masm.loadPtr(Address(code, JitCode::offsetOfCode()), code);
masm.movePtr(ImmWord(0), ArgumentsRectifierReg);
}
masm.bind(&noUnderflow);
masm.callJit(code);
leaveStubFrame(masm, true);
return true;
}
template <class T>
bool
ICGetElemNativeCompiler<T>::emitCheckKey(MacroAssembler& masm, Label& failure)
{
MOZ_ASSERT_UNREACHABLE("Key has to be PropertyName or Symbol");
return false;
}
template <>
bool
ICGetElemNativeCompiler<JS::Symbol*>::emitCheckKey(MacroAssembler& masm, Label& failure)
{
MOZ_ASSERT(!needsAtomize_);
masm.branchTestSymbol(Assembler::NotEqual, R1, &failure);
Address symbolAddr(ICStubReg, ICGetElemNativeStubImpl<JS::Symbol*>::offsetOfKey());
Register symExtract = masm.extractObject(R1, ExtractTemp1);
masm.branchPtr(Assembler::NotEqual, symbolAddr, symExtract, &failure);
return true;
}
template <>
bool
ICGetElemNativeCompiler<PropertyName*>::emitCheckKey(MacroAssembler& masm, Label& failure)
{
masm.branchTestString(Assembler::NotEqual, R1, &failure);
// Check key identity. Don't automatically fail if this fails, since the incoming
// key maybe a non-interned string. Switch to a slowpath vm-call based check.
Address nameAddr(ICStubReg, ICGetElemNativeStubImpl<PropertyName*>::offsetOfKey());
Register strExtract = masm.extractString(R1, ExtractTemp1);
// If needsAtomize_ is true, and the string is not already an atom, then atomize the
// string before proceeding.
if (needsAtomize_) {
Label skipAtomize;
// If string is already an atom, skip the atomize.
masm.branchTest32(Assembler::NonZero,
Address(strExtract, JSString::offsetOfFlags()),
Imm32(JSString::ATOM_BIT),
&skipAtomize);
// Stow R0.
EmitStowICValues(masm, 1);
enterStubFrame(masm, R0.scratchReg());
// Atomize the string into a new value.
masm.push(strExtract);
if (!callVM(DoAtomizeStringInfo, masm))
return false;
// Atomized string is now in JSReturnOperand (R0).
// Leave stub frame, move atomized string into R1.
MOZ_ASSERT(R0 == JSReturnOperand);
leaveStubFrame(masm);
masm.moveValue(JSReturnOperand, R1);
// Unstow R0
EmitUnstowICValues(masm, 1);
// Extract string from R1 again.
DebugOnly<Register> strExtract2 = masm.extractString(R1, ExtractTemp1);
MOZ_ASSERT(Register(strExtract2) == strExtract);
masm.bind(&skipAtomize);
}
// Key has been atomized if necessary. Do identity check on string pointer.
masm.branchPtr(Assembler::NotEqual, nameAddr, strExtract, &failure);
return true;
}
template <class T>
bool
ICGetElemNativeCompiler<T>::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
Label failurePopR1;
bool popR1 = false;
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
Register scratchReg = regs.takeAny();
// Unbox object.
Register objReg = masm.extractObject(R0, ExtractTemp0);
// Check object shape/group.
GuardReceiverObject(masm, ReceiverGuard(obj_), objReg, scratchReg,
ICGetElemNativeStub::offsetOfReceiverGuard(), &failure);
// Since this stub sometimes enters a stub frame, we manually set this to true (lie).
#ifdef DEBUG
entersStubFrame_ = true;
#endif
if (!emitCheckKey(masm, failure))
return false;
Register holderReg;
if (obj_ == holder_) {
holderReg = objReg;
if (obj_->is<UnboxedPlainObject>() && acctype_ != ICGetElemNativeStub::UnboxedProperty) {
// The property will be loaded off the unboxed expando.
masm.push(R1.scratchReg());
popR1 = true;
holderReg = R1.scratchReg();
masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
}
} else {
// Shape guard holder.
if (regs.empty()) {
masm.push(R1.scratchReg());
popR1 = true;
holderReg = R1.scratchReg();
} else {
holderReg = regs.takeAny();
}
if (kind == ICStub::GetElem_NativePrototypeCallNativeName ||
kind == ICStub::GetElem_NativePrototypeCallNativeSymbol ||
kind == ICStub::GetElem_NativePrototypeCallScriptedName ||
kind == ICStub::GetElem_NativePrototypeCallScriptedSymbol)
{
masm.loadPtr(Address(ICStubReg,
ICGetElemNativePrototypeCallStub<T>::offsetOfHolder()),
holderReg);
masm.loadPtr(Address(ICStubReg,
ICGetElemNativePrototypeCallStub<T>::offsetOfHolderShape()),
scratchReg);
} else {
masm.loadPtr(Address(ICStubReg,
ICGetElem_NativePrototypeSlot<T>::offsetOfHolder()),
holderReg);
masm.loadPtr(Address(ICStubReg,
ICGetElem_NativePrototypeSlot<T>::offsetOfHolderShape()),
scratchReg);
}
masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratchReg,
popR1 ? &failurePopR1 : &failure);
}
if (acctype_ == ICGetElemNativeStub::DynamicSlot ||
acctype_ == ICGetElemNativeStub::FixedSlot)
{
masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub<T>::offsetOfOffset()),
scratchReg);
// Load from object.
if (acctype_ == ICGetElemNativeStub::DynamicSlot)
masm.addPtr(Address(holderReg, NativeObject::offsetOfSlots()), scratchReg);
else
masm.addPtr(holderReg, scratchReg);
Address valAddr(scratchReg, 0);
masm.loadValue(valAddr, R0);
if (popR1)
masm.addToStackPtr(ImmWord(sizeof(size_t)));
} else if (acctype_ == ICGetElemNativeStub::UnboxedProperty) {
masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub<T>::offsetOfOffset()),
scratchReg);
masm.loadUnboxedProperty(BaseIndex(objReg, scratchReg, TimesOne), unboxedType_,
TypedOrValueRegister(R0));
if (popR1)
masm.addToStackPtr(ImmWord(sizeof(size_t)));
} else {
MOZ_ASSERT(acctype_ == ICGetElemNativeStub::NativeGetter ||
acctype_ == ICGetElemNativeStub::ScriptedGetter);
MOZ_ASSERT(kind == ICStub::GetElem_NativePrototypeCallNativeName ||
kind == ICStub::GetElem_NativePrototypeCallNativeSymbol ||
kind == ICStub::GetElem_NativePrototypeCallScriptedName ||
kind == ICStub::GetElem_NativePrototypeCallScriptedSymbol);
if (acctype_ == ICGetElemNativeStub::NativeGetter) {
// If calling a native getter, there is no chance of failure now.
// GetElem key (R1) is no longer needed.
if (popR1)
masm.addToStackPtr(ImmWord(sizeof(size_t)));
emitCallNative(masm, objReg);
} else {
MOZ_ASSERT(acctype_ == ICGetElemNativeStub::ScriptedGetter);
// Load function in scratchReg and ensure that it has a jit script.
masm.loadPtr(Address(ICStubReg, ICGetElemNativeGetterStub<T>::offsetOfGetter()),
scratchReg);
masm.branchIfFunctionHasNoScript(scratchReg, popR1 ? &failurePopR1 : &failure);
masm.loadPtr(Address(scratchReg, JSFunction::offsetOfNativeOrScript()), scratchReg);
masm.loadBaselineOrIonRaw(scratchReg, scratchReg, popR1 ? &failurePopR1 : &failure);
// At this point, we are guaranteed to successfully complete.
if (popR1)
masm.addToStackPtr(Imm32(sizeof(size_t)));
emitCallScripted(masm, objReg);
}
}
// Enter type monitor IC to type-check result.
EmitEnterTypeMonitorIC(masm);
// Failure case - jump to next stub
if (popR1) {
masm.bind(&failurePopR1);
masm.pop(R1.scratchReg());
}
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// GetElem_String
//
bool
ICGetElem_String::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
masm.branchTestString(Assembler::NotEqual, R0, &failure);
masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
Register scratchReg = regs.takeAny();
// Unbox string in R0.
Register str = masm.extractString(R0, ExtractTemp0);
// Check for non-linear strings.
masm.branchIfRope(str, &failure);
// Unbox key.
Register key = masm.extractInt32(R1, ExtractTemp1);
// Bounds check.
masm.branch32(Assembler::BelowOrEqual, Address(str, JSString::offsetOfLength()),
key, &failure);
// Get char code.
masm.loadStringChar(str, key, scratchReg);
// Check if char code >= UNIT_STATIC_LIMIT.
masm.branch32(Assembler::AboveOrEqual, scratchReg, Imm32(StaticStrings::UNIT_STATIC_LIMIT),
&failure);
// Load static string.
masm.movePtr(ImmPtr(&cx->staticStrings().unitStaticTable), str);
masm.loadPtr(BaseIndex(str, scratchReg, ScalePointer), str);
// Return.
masm.tagValue(JSVAL_TYPE_STRING, str, R0);
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// GetElem_Dense
//
bool
ICGetElem_Dense::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
Register scratchReg = regs.takeAny();
// Unbox R0 and shape guard.
Register obj = masm.extractObject(R0, ExtractTemp0);
masm.loadPtr(Address(ICStubReg, ICGetElem_Dense::offsetOfShape()), scratchReg);
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
// Load obj->elements.
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg);
// Unbox key.
Register key = masm.extractInt32(R1, ExtractTemp1);
// Bounds check.
Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure);
// Hole check and load value.
BaseObjectElementIndex element(scratchReg, key);
masm.branchTestMagic(Assembler::Equal, element, &failure);
// Load value from element location.
masm.loadValue(element, R0);
// Enter type monitor IC to type-check result.
EmitEnterTypeMonitorIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// GetElem_UnboxedArray
//
bool
ICGetElem_UnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
Register scratchReg = regs.takeAny();
// Unbox R0 and group guard.
Register obj = masm.extractObject(R0, ExtractTemp0);
masm.loadPtr(Address(ICStubReg, ICGetElem_UnboxedArray::offsetOfGroup()), scratchReg);
masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure);
// Unbox key.
Register key = masm.extractInt32(R1, ExtractTemp1);
// Bounds check.
masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()),
scratchReg);
masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
// Load obj->elements.
masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
// Load value.
size_t width = UnboxedTypeSize(elementType_);
BaseIndex addr(scratchReg, key, ScaleFromElemWidth(width));
masm.loadUnboxedProperty(addr, elementType_, R0);
// Only monitor the result if its type might change.
if (elementType_ == JSVAL_TYPE_OBJECT)
EmitEnterTypeMonitorIC(masm);
else
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// GetElem_TypedArray
//
static void
LoadTypedThingLength(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result)
{
switch (layout) {
case Layout_TypedArray:
masm.unboxInt32(Address(obj, TypedArrayObject::lengthOffset()), result);
break;
case Layout_OutlineTypedObject:
case Layout_InlineTypedObject:
masm.loadPtr(Address(obj, JSObject::offsetOfGroup()), result);
masm.loadPtr(Address(result, ObjectGroup::offsetOfAddendum()), result);
masm.unboxInt32(Address(result, ArrayTypeDescr::offsetOfLength()), result);
break;
default:
MOZ_CRASH();
}
}
bool
ICGetElem_TypedArray::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
if (layout_ != Layout_TypedArray)
CheckForNeuteredTypedObject(cx, masm, &failure);
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
Register scratchReg = regs.takeAny();
// Unbox R0 and shape guard.
Register obj = masm.extractObject(R0, ExtractTemp0);
masm.loadPtr(Address(ICStubReg, ICGetElem_TypedArray::offsetOfShape()), scratchReg);
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
// Ensure the index is an integer.
if (cx->runtime()->jitSupportsFloatingPoint) {
Label isInt32;
masm.branchTestInt32(Assembler::Equal, R1, &isInt32);
{
// If the index is a double, try to convert it to int32. It's okay
// to convert -0 to 0: the shape check ensures the object is a typed
// array so the difference is not observable.
masm.branchTestDouble(Assembler::NotEqual, R1, &failure);
masm.unboxDouble(R1, FloatReg0);
masm.convertDoubleToInt32(FloatReg0, scratchReg, &failure, /* negZeroCheck = */false);
masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R1);
}
masm.bind(&isInt32);
} else {
masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
}
// Unbox key.
Register key = masm.extractInt32(R1, ExtractTemp1);
// Bounds check.
LoadTypedThingLength(masm, layout_, obj, scratchReg);
masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
// Load the elements vector.
LoadTypedThingData(masm, layout_, obj, scratchReg);
// Load the value.
BaseIndex source(scratchReg, key, ScaleFromElemWidth(Scalar::byteSize(type_)));
masm.loadFromTypedArray(type_, source, R0, false, scratchReg, &failure);
// Todo: Allow loading doubles from uint32 arrays, but this requires monitoring.
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// GetElem_Arguments
//
bool
ICGetElem_Arguments::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
if (which_ == ICGetElem_Arguments::Magic) {
// Ensure that this is a magic arguments value.
masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure);
// Ensure that frame has not loaded different arguments object since.
masm.branchTest32(Assembler::NonZero,
Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()),
Imm32(BaselineFrame::HAS_ARGS_OBJ),
&failure);
// Ensure that index is an integer.
masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
Register idx = masm.extractInt32(R1, ExtractTemp1);
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
Register scratch = regs.takeAny();
// Load num actual arguments
Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs());
masm.loadPtr(actualArgs, scratch);
// Ensure idx < argc
masm.branch32(Assembler::AboveOrEqual, idx, scratch, &failure);
// Load argval
masm.movePtr(BaselineFrameReg, scratch);
masm.addPtr(Imm32(BaselineFrame::offsetOfArg(0)), scratch);
BaseValueIndex element(scratch, idx);
masm.loadValue(element, R0);
// Enter type monitor IC to type-check result.
EmitEnterTypeMonitorIC(masm);
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
MOZ_ASSERT(which_ == ICGetElem_Arguments::Mapped ||
which_ == ICGetElem_Arguments::Unmapped);
const Class* clasp = (which_ == ICGetElem_Arguments::Mapped)
? &MappedArgumentsObject::class_
: &UnmappedArgumentsObject::class_;
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
Register scratchReg = regs.takeAny();
// Guard on input being an arguments object.
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
Register objReg = masm.extractObject(R0, ExtractTemp0);
masm.branchTestObjClass(Assembler::NotEqual, objReg, scratchReg, clasp, &failure);
// Guard on index being int32
masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
Register idxReg = masm.extractInt32(R1, ExtractTemp1);
// Get initial ArgsObj length value.
masm.unboxInt32(Address(objReg, ArgumentsObject::getInitialLengthSlotOffset()), scratchReg);
// Test if length has been overridden.
masm.branchTest32(Assembler::NonZero,
scratchReg,
Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
&failure);
// Length has not been overridden, ensure that R1 is an integer and is <= length.
masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratchReg);
masm.branch32(Assembler::AboveOrEqual, idxReg, scratchReg, &failure);
// Length check succeeded, now check the correct bit. We clobber potential type regs
// now. Inputs will have to be reconstructed if we fail after this point, but that's
// unlikely.
Label failureReconstructInputs;
regs = availableGeneralRegs(0);
regs.takeUnchecked(objReg);
regs.takeUnchecked(idxReg);
regs.take(scratchReg);
Register argData = regs.takeAny();
Register tempReg = regs.takeAny();
// Load ArgumentsData
masm.loadPrivate(Address(objReg, ArgumentsObject::getDataSlotOffset()), argData);
// Load deletedBits bitArray pointer into scratchReg
masm.loadPtr(Address(argData, offsetof(ArgumentsData, deletedBits)), scratchReg);
// In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord)
masm.movePtr(idxReg, tempReg);
const uint32_t shift = mozilla::tl::FloorLog2<(sizeof(size_t) * JS_BITS_PER_BYTE)>::value;
MOZ_ASSERT(shift == 5 || shift == 6);
masm.rshiftPtr(Imm32(shift), tempReg);
masm.loadPtr(BaseIndex(scratchReg, tempReg, ScaleFromElemWidth(sizeof(size_t))), scratchReg);
// Don't bother testing specific bit, if any bit is set in the word, fail.
masm.branchPtr(Assembler::NotEqual, scratchReg, ImmPtr(nullptr), &failureReconstructInputs);
// Load the value. use scratchReg and tempReg to form a ValueOperand to load into.
masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), argData);
regs.add(scratchReg);
regs.add(tempReg);
ValueOperand tempVal = regs.takeAnyValue();
masm.loadValue(BaseValueIndex(argData, idxReg), tempVal);
// Makesure that this is not a FORWARD_TO_CALL_SLOT magic value.
masm.branchTestMagic(Assembler::Equal, tempVal, &failureReconstructInputs);
// Copy value from temp to R0.
masm.moveValue(tempVal, R0);
// Type-check result
EmitEnterTypeMonitorIC(masm);
// Failed, but inputs are deconstructed into object and int, and need to be
// reconstructed into values.
masm.bind(&failureReconstructInputs);
masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0);
masm.tagValue(JSVAL_TYPE_INT32, idxReg, R1);
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// SetElem_Fallback
//
static bool
SetElemAddHasSameShapes(ICSetElem_DenseOrUnboxedArrayAdd* stub, JSObject* obj)
{
static const size_t MAX_DEPTH = ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH;
ICSetElem_DenseOrUnboxedArrayAddImpl<MAX_DEPTH>* nstub = stub->toImplUnchecked<MAX_DEPTH>();
if (obj->maybeShape() != nstub->shape(0))
return false;
JSObject* proto = obj->getProto();
for (size_t i = 0; i < stub->protoChainDepth(); i++) {
if (!proto->isNative())
return false;
if (proto->as<NativeObject>().lastProperty() != nstub->shape(i + 1))
return false;
proto = obj->getProto();
if (!proto) {
if (i != stub->protoChainDepth() - 1)
return false;
break;
}
}
return true;
}
static bool
DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind,
ICSetElem_Fallback* stub, HandleObject obj)
{
MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArray ||
kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
if (kind == ICStub::SetElem_DenseOrUnboxedArray && iter->isSetElem_DenseOrUnboxedArray()) {
ICSetElem_DenseOrUnboxedArray* nstub = iter->toSetElem_DenseOrUnboxedArray();
if (obj->maybeShape() == nstub->shape() && obj->getGroup(cx) == nstub->group())
return true;
}
if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) {
ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
if (obj->getGroup(cx) == nstub->group() && SetElemAddHasSameShapes(nstub, obj))
return true;
}
}
return false;
}
static bool