blob: f833d0f696a63e836138eba07e6e3f3b6898d1a3 [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 "mozilla/DebugOnly.h"
#include "PerfSpewer.h"
#include "CodeGenerator.h"
#include "Ion.h"
#include "IonCaches.h"
#include "IonLinker.h"
#include "IonSpewer.h"
#include "VMFunctions.h"
#include "vm/Shape.h"
#include "IonFrames-inl.h"
#include "vm/Interpreter-inl.h"
using namespace js;
using namespace js::jit;
using mozilla::DebugOnly;
void
CodeLocationJump::repoint(IonCode *code, MacroAssembler *masm)
{
JS_ASSERT(!absolute_);
size_t new_off = (size_t)raw_;
#ifdef JS_SMALL_BRANCH
size_t jumpTableEntryOffset = reinterpret_cast<size_t>(jumpTableEntry_);
#endif
if (masm != NULL) {
#ifdef JS_CPU_X64
JS_ASSERT((uint64_t)raw_ <= UINT32_MAX);
#endif
new_off = masm->actualOffset((uintptr_t)raw_);
#ifdef JS_SMALL_BRANCH
jumpTableEntryOffset = masm->actualIndex(jumpTableEntryOffset);
#endif
}
raw_ = code->raw() + new_off;
#ifdef JS_SMALL_BRANCH
jumpTableEntry_ = Assembler::PatchableJumpAddress(code, (size_t) jumpTableEntryOffset);
#endif
setAbsolute();
}
void
CodeLocationLabel::repoint(IonCode *code, MacroAssembler *masm)
{
JS_ASSERT(!absolute_);
size_t new_off = (size_t)raw_;
if (masm != NULL) {
#ifdef JS_CPU_X64
JS_ASSERT((uint64_t)raw_ <= UINT32_MAX);
#endif
new_off = masm->actualOffset((uintptr_t)raw_);
}
JS_ASSERT(new_off < code->instructionsSize());
raw_ = code->raw() + new_off;
setAbsolute();
}
void
CodeOffsetLabel::fixup(MacroAssembler *masm)
{
offset_ = masm->actualOffset(offset_);
}
void
CodeOffsetJump::fixup(MacroAssembler *masm)
{
offset_ = masm->actualOffset(offset_);
#ifdef JS_SMALL_BRANCH
jumpTableIndex_ = masm->actualIndex(jumpTableIndex_);
#endif
}
const char *
IonCache::CacheName(IonCache::Kind kind)
{
static const char * const names[] =
{
#define NAME(x) #x,
IONCACHE_KIND_LIST(NAME)
#undef NAME
};
return names[kind];
}
IonCache::LinkStatus
IonCache::linkCode(JSContext *cx, MacroAssembler &masm, IonScript *ion, IonCode **code)
{
Linker linker(masm);
*code = linker.newCode(cx, JSC::ION_CODE);
if (!*code)
return LINK_ERROR;
if (ion->invalidated())
return CACHE_FLUSHED;
return LINK_GOOD;
}
const size_t IonCache::MAX_STUBS = 16;
// Helper class which encapsulates logic to attach a stub to an IC by hooking
// up rejoins and next stub jumps.
//
// The simplest stubs have a single jump to the next stub and look like the
// following:
//
// branch guard NEXTSTUB
// ... IC-specific code ...
// jump REJOIN
//
// This corresponds to:
//
// attacher.branchNextStub(masm, ...);
// ... emit IC-specific code ...
// attacher.jumpRejoin(masm);
//
// Whether the stub needs multiple next stub jumps look like:
//
// branch guard FAILURES
// ... IC-specific code ...
// branch another-guard FAILURES
// ... IC-specific code ...
// jump REJOIN
// FAILURES:
// jump NEXTSTUB
//
// This corresponds to:
//
// Label failures;
// masm.branchX(..., &failures);
// ... emit IC-specific code ...
// masm.branchY(..., failures);
// ... emit more IC-specific code ...
// attacher.jumpRejoin(masm);
// masm.bind(&failures);
// attacher.jumpNextStub(masm);
//
// A convenience function |branchNextStubOrLabel| is provided in the case that
// the stub sometimes has multiple next stub jumps and sometimes a single
// one. If a non-NULL label is passed in, a |branchPtr| will be made to that
// label instead of a |branchPtrWithPatch| to the next stub.
class IonCache::StubAttacher
{
protected:
bool hasNextStubOffset_ : 1;
bool hasStubCodePatchOffset_ : 1;
CodeLocationLabel rejoinLabel_;
CodeOffsetJump nextStubOffset_;
CodeOffsetJump rejoinOffset_;
CodeOffsetLabel stubCodePatchOffset_;
public:
StubAttacher(CodeLocationLabel rejoinLabel)
: hasNextStubOffset_(false),
hasStubCodePatchOffset_(false),
rejoinLabel_(rejoinLabel),
nextStubOffset_(),
rejoinOffset_(),
stubCodePatchOffset_()
{ }
// Value used instead of the IonCode self-reference of generated
// stubs. This value is needed for marking calls made inside stubs. This
// value would be replaced by the attachStub function after the allocation
// of the IonCode. The self-reference is used to keep the stub path alive
// even if the IonScript is invalidated or if the IC is flushed.
static const ImmWord STUB_ADDR;
template <class T1, class T2>
void branchNextStub(MacroAssembler &masm, Assembler::Condition cond, T1 op1, T2 op2) {
JS_ASSERT(!hasNextStubOffset_);
RepatchLabel nextStub;
nextStubOffset_ = masm.branchPtrWithPatch(cond, op1, op2, &nextStub);
hasNextStubOffset_ = true;
masm.bind(&nextStub);
}
template <class T1, class T2>
void branchNextStubOrLabel(MacroAssembler &masm, Assembler::Condition cond, T1 op1, T2 op2,
Label *label)
{
if (label != NULL)
masm.branchPtr(cond, op1, op2, label);
else
branchNextStub(masm, cond, op1, op2);
}
void jumpRejoin(MacroAssembler &masm) {
RepatchLabel rejoin;
rejoinOffset_ = masm.jumpWithPatch(&rejoin);
masm.bind(&rejoin);
}
void jumpNextStub(MacroAssembler &masm) {
JS_ASSERT(!hasNextStubOffset_);
RepatchLabel nextStub;
nextStubOffset_ = masm.jumpWithPatch(&nextStub);
hasNextStubOffset_ = true;
masm.bind(&nextStub);
}
void pushStubCodePointer(MacroAssembler &masm) {
// Push the IonCode pointer for the stub we're generating.
// WARNING:
// WARNING: If IonCode ever becomes relocatable, the following code is incorrect.
// WARNING: Note that we're not marking the pointer being pushed as an ImmGCPtr.
// WARNING: This location will be patched with the pointer of the generated stub,
// WARNING: such as it can be marked when a call is made with this stub. Be aware
// WARNING: that ICs are not marked and so this stub will only be kept alive iff
// WARNING: it is on the stack at the time of the GC. No ImmGCPtr is needed as the
// WARNING: stubs are flushed on GC.
// WARNING:
JS_ASSERT(!hasStubCodePatchOffset_);
stubCodePatchOffset_ = masm.PushWithPatch(STUB_ADDR);
hasStubCodePatchOffset_ = true;
}
void patchRejoinJump(MacroAssembler &masm, IonCode *code) {
rejoinOffset_.fixup(&masm);
CodeLocationJump rejoinJump(code, rejoinOffset_);
PatchJump(rejoinJump, rejoinLabel_);
}
void patchStubCodePointer(MacroAssembler &masm, IonCode *code) {
if (hasStubCodePatchOffset_) {
stubCodePatchOffset_.fixup(&masm);
Assembler::patchDataWithValueCheck(CodeLocationLabel(code, stubCodePatchOffset_),
ImmWord(uintptr_t(code)), STUB_ADDR);
}
}
virtual void patchNextStubJump(MacroAssembler &masm, IonCode *code) = 0;
};
const ImmWord IonCache::StubAttacher::STUB_ADDR = ImmWord(uintptr_t(0xdeadc0de));
class RepatchIonCache::RepatchStubAppender : public IonCache::StubAttacher
{
RepatchIonCache &cache_;
public:
RepatchStubAppender(RepatchIonCache &cache)
: StubAttacher(cache.rejoinLabel()),
cache_(cache)
{
}
void patchNextStubJump(MacroAssembler &masm, IonCode *code) {
// Patch the previous nextStubJump of the last stub, or the jump from the
// codeGen, to jump into the newly allocated code.
PatchJump(cache_.lastJump_, CodeLocationLabel(code));
// If this path is not taken, we are producing an entry which can no
// longer go back into the update function.
if (hasNextStubOffset_) {
nextStubOffset_.fixup(&masm);
CodeLocationJump nextStubJump(code, nextStubOffset_);
PatchJump(nextStubJump, cache_.fallbackLabel_);
// When the last stub fails, it fallback to the ool call which can
// produce a stub. Next time we generate a stub, we will patch the
// nextStub jump to try the new stub.
cache_.lastJump_ = nextStubJump;
}
}
};
void
RepatchIonCache::reset()
{
IonCache::reset();
PatchJump(initialJump_, fallbackLabel_);
lastJump_ = initialJump_;
}
void
RepatchIonCache::emitInitialJump(MacroAssembler &masm, AddCacheState &addState)
{
initialJump_ = masm.jumpWithPatch(&addState.repatchEntry);
lastJump_ = initialJump_;
}
void
RepatchIonCache::bindInitialJump(MacroAssembler &masm, AddCacheState &addState)
{
masm.bind(&addState.repatchEntry);
}
void
RepatchIonCache::updateBaseAddress(IonCode *code, MacroAssembler &masm)
{
IonCache::updateBaseAddress(code, masm);
initialJump_.repoint(code, &masm);
lastJump_.repoint(code, &masm);
}
class DispatchIonCache::DispatchStubPrepender : public IonCache::StubAttacher
{
DispatchIonCache &cache_;
public:
DispatchStubPrepender(DispatchIonCache &cache)
: StubAttacher(cache.rejoinLabel_),
cache_(cache)
{
}
void patchNextStubJump(MacroAssembler &masm, IonCode *code) {
JS_ASSERT(hasNextStubOffset_);
// Jump to the previous entry in the stub dispatch table. We
// have not yet executed the code we're patching the jump in.
nextStubOffset_.fixup(&masm);
CodeLocationJump nextStubJump(code, nextStubOffset_);
PatchJump(nextStubJump, CodeLocationLabel(cache_.firstStub_));
// Update the dispatch table. Modification to jumps after the dispatch
// table is updated is disallowed, lest we race on entry into an
// unfinalized stub.
cache_.firstStub_ = code->raw();
}
};
void
DispatchIonCache::reset()
{
IonCache::reset();
firstStub_ = fallbackLabel_.raw();
}
void
DispatchIonCache::emitInitialJump(MacroAssembler &masm, AddCacheState &addState)
{
Register scratch = addState.dispatchScratch;
dispatchLabel_ = masm.movWithPatch(ImmWord(uintptr_t(-1)), scratch);
masm.loadPtr(Address(scratch, 0), scratch);
masm.jump(scratch);
rejoinLabel_ = masm.labelForPatch();
}
void
DispatchIonCache::bindInitialJump(MacroAssembler &masm, AddCacheState &addState)
{
// Do nothing.
}
void
DispatchIonCache::updateBaseAddress(IonCode *code, MacroAssembler &masm)
{
// The address of firstStub_ should be pointer aligned.
JS_ASSERT(uintptr_t(&firstStub_) % sizeof(uintptr_t) == 0);
IonCache::updateBaseAddress(code, masm);
dispatchLabel_.fixup(&masm);
Assembler::patchDataWithValueCheck(CodeLocationLabel(code, dispatchLabel_),
ImmWord(uintptr_t(&firstStub_)),
ImmWord(uintptr_t(-1)));
firstStub_ = fallbackLabel_.raw();
rejoinLabel_.repoint(code, &masm);
}
void
IonCache::attachStub(MacroAssembler &masm, StubAttacher &attacher, Handle<IonCode *> code)
{
JS_ASSERT(canAttachStub());
incrementStubCount();
// Update the success path to continue after the IC initial jump.
attacher.patchRejoinJump(masm, code);
// Update the failure path.
attacher.patchNextStubJump(masm, code);
// Replace the STUB_ADDR constant by the address of the generated stub, such
// as it can be kept alive even if the cache is flushed (see
// MarkIonExitFrame).
attacher.patchStubCodePointer(masm, code);
}
bool
IonCache::linkAndAttachStub(JSContext *cx, MacroAssembler &masm, StubAttacher &attacher,
IonScript *ion, const char *attachKind)
{
Rooted<IonCode *> code(cx);
LinkStatus status = linkCode(cx, masm, ion, code.address());
if (status != LINK_GOOD)
return status != LINK_ERROR;
attachStub(masm, attacher, code);
if (pc) {
IonSpew(IonSpew_InlineCaches, "Cache %p(%s:%d/%d) generated %s %s stub at %p",
this, script->filename(), script->lineno, pc - script->code,
attachKind, CacheName(kind()), code->raw());
} else {
IonSpew(IonSpew_InlineCaches, "Cache %p generated %s %s stub at %p",
this, attachKind, CacheName(kind()), code->raw());
}
return true;
}
void
IonCache::updateBaseAddress(IonCode *code, MacroAssembler &masm)
{
fallbackLabel_.repoint(code, &masm);
}
void
IonCache::initializeAddCacheState(LInstruction *ins, AddCacheState *addState)
{
}
static bool
IsCacheableDOMProxy(JSObject *obj)
{
if (!obj->isProxy())
return false;
BaseProxyHandler *handler = GetProxyHandler(obj);
if (handler->family() != GetDOMProxyHandlerFamily())
return false;
if (obj->numFixedSlots() <= GetDOMProxyExpandoSlot())
return false;
return true;
}
static void
GeneratePrototypeGuards(JSContext *cx, IonScript *ion, MacroAssembler &masm, JSObject *obj,
JSObject *holder, Register objectReg, Register scratchReg,
Label *failures)
{
JS_ASSERT(obj != holder);
if (obj->hasUncacheableProto()) {
// Note: objectReg and scratchReg may be the same register, so we cannot
// use objectReg in the rest of this function.
masm.loadPtr(Address(objectReg, JSObject::offsetOfType()), scratchReg);
Address proto(scratchReg, offsetof(types::TypeObject, proto));
masm.branchNurseryPtr(Assembler::NotEqual, proto,
ImmMaybeNurseryPtr(obj->getProto()), failures);
}
JSObject *pobj = IsCacheableDOMProxy(obj)
? obj->getTaggedProto().toObjectOrNull()
: obj->getProto();
if (!pobj)
return;
while (pobj != holder) {
if (pobj->hasUncacheableProto()) {
JS_ASSERT(!pobj->hasSingletonType());
masm.moveNurseryPtr(ImmMaybeNurseryPtr(pobj), scratchReg);
Address objType(scratchReg, JSObject::offsetOfType());
masm.branchPtr(Assembler::NotEqual, objType, ImmGCPtr(pobj->type()), failures);
}
pobj = pobj->getProto();
}
}
static bool
IsCacheableProtoChain(JSObject *obj, JSObject *holder)
{
while (obj != holder) {
/*
* We cannot assume that we find the holder object on the prototype
* chain and must check for null proto. The prototype chain can be
* altered during the lookupProperty call.
*/
JSObject *proto = IsCacheableDOMProxy(obj)
? obj->getTaggedProto().toObjectOrNull()
: obj->getProto();
if (!proto || !proto->isNative())
return false;
obj = proto;
}
return true;
}
static bool
IsCacheableGetPropReadSlot(JSObject *obj, JSObject *holder, Shape *shape)
{
if (!shape || !IsCacheableProtoChain(obj, holder))
return false;
if (!shape->hasSlot() || !shape->hasDefaultGetter())
return false;
return true;
}
static bool
IsCacheableNoProperty(JSObject *obj, JSObject *holder, Shape *shape, jsbytecode *pc,
const TypedOrValueRegister &output)
{
if (shape)
return false;
JS_ASSERT(!holder);
// Just because we didn't find the property on the object doesn't mean it
// won't magically appear through various engine hacks:
if (obj->getClass()->getProperty && obj->getClass()->getProperty != JS_PropertyStub)
return false;
// Don't generate missing property ICs if we skipped a non-native object, as
// lookups may extend beyond the prototype chain (e.g. for DOMProxy
// proxies).
JSObject *obj2 = obj;
while (obj2) {
if (!obj2->isNative())
return false;
obj2 = obj2->getProto();
}
// The pc is NULL if the cache is idempotent. We cannot share missing
// properties between caches because TI can only try to prove that a type is
// contained, but does not attempts to check if something does not exists.
// So the infered type of getprop would be missing and would not contain
// undefined, as expected for missing properties.
if (!pc)
return false;
#if JS_HAS_NO_SUCH_METHOD
// The __noSuchMethod__ hook may substitute in a valid method. Since,
// if o.m is missing, o.m() will probably be an error, just mark all
// missing callprops as uncacheable.
if (JSOp(*pc) == JSOP_CALLPROP ||
JSOp(*pc) == JSOP_CALLELEM)
{
return false;
}
#endif
// TI has not yet monitored an Undefined value. The fallback path will
// monitor and invalidate the script.
if (!output.hasValue())
return false;
return true;
}
static bool
IsOptimizableArgumentsObjectForLength(JSObject *obj)
{
if (!obj->is<ArgumentsObject>())
return false;
if (obj->as<ArgumentsObject>().hasOverriddenLength())
return false;
return true;
}
static bool
IsOptimizableArgumentsObjectForGetElem(JSObject *obj, Value idval)
{
if (!IsOptimizableArgumentsObjectForLength(obj))
return false;
ArgumentsObject &argsObj = obj->as<ArgumentsObject>();
if (argsObj.isAnyElementDeleted())
return false;
if (!idval.isInt32())
return false;
int32_t idint = idval.toInt32();
if (idint < 0 || static_cast<uint32_t>(idint) >= argsObj.initialLength())
return false;
return true;
}
static bool
IsCacheableGetPropCallNative(JSObject *obj, JSObject *holder, Shape *shape)
{
if (!shape || !IsCacheableProtoChain(obj, holder))
return false;
if (!shape->hasGetterValue() || !shape->getterValue().isObject())
return false;
return shape->getterValue().toObject().is<JSFunction>() &&
shape->getterValue().toObject().as<JSFunction>().isNative();
}
static bool
IsCacheableGetPropCallPropertyOp(JSObject *obj, JSObject *holder, Shape *shape)
{
if (!shape || !IsCacheableProtoChain(obj, holder))
return false;
if (shape->hasSlot() || shape->hasGetterValue() || shape->hasDefaultGetter())
return false;
return true;
}
static inline void
EmitLoadSlot(MacroAssembler &masm, JSObject *holder, Shape *shape, Register holderReg,
TypedOrValueRegister output, Register scratchReg)
{
JS_ASSERT(holder);
if (holder->isFixedSlot(shape->slot())) {
Address addr(holderReg, JSObject::getFixedSlotOffset(shape->slot()));
masm.loadTypedOrValue(addr, output);
} else {
masm.loadPtr(Address(holderReg, JSObject::offsetOfSlots()), scratchReg);
Address addr(scratchReg, holder->dynamicSlotIndex(shape->slot()) * sizeof(Value));
masm.loadTypedOrValue(addr, output);
}
}
static void
GenerateDOMProxyChecks(JSContext *cx, MacroAssembler &masm, JSObject *obj,
PropertyName *name, Register object, Label *stubFailure,
bool skipExpandoCheck = false)
{
MOZ_ASSERT(IsCacheableDOMProxy(obj));
// Guard the following:
// 1. The object is a DOMProxy.
// 2. The object does not have expando properties, or has an expando
// which is known to not have the desired property.
Address handlerAddr(object, JSObject::getFixedSlotOffset(JSSLOT_PROXY_HANDLER));
Address expandoSlotAddr(object, JSObject::getFixedSlotOffset(GetDOMProxyExpandoSlot()));
// Check that object is a DOMProxy.
masm.branchPrivatePtr(Assembler::NotEqual, handlerAddr, ImmWord(GetProxyHandler(obj)), stubFailure);
if (skipExpandoCheck)
return;
// For the remaining code, we need to reserve some registers to load a value.
// This is ugly, but unvaoidable.
RegisterSet domProxyRegSet(RegisterSet::All());
domProxyRegSet.take(AnyRegister(object));
ValueOperand tempVal = domProxyRegSet.takeValueOperand();
masm.pushValue(tempVal);
Label failDOMProxyCheck;
Label domProxyOk;
Value expandoVal = obj->getFixedSlot(GetDOMProxyExpandoSlot());
masm.loadValue(expandoSlotAddr, tempVal);
if (!expandoVal.isObject() && !expandoVal.isUndefined()) {
masm.branchTestValue(Assembler::NotEqual, tempVal, expandoVal, &failDOMProxyCheck);
ExpandoAndGeneration *expandoAndGeneration = (ExpandoAndGeneration*)expandoVal.toPrivate();
masm.movePtr(ImmWord(expandoAndGeneration), tempVal.scratchReg());
masm.branch32(Assembler::NotEqual, Address(tempVal.scratchReg(), sizeof(Value)),
Imm32(expandoAndGeneration->generation),
&failDOMProxyCheck);
expandoVal = expandoAndGeneration->expando;
masm.loadValue(Address(tempVal.scratchReg(), 0), tempVal);
}
// If the incoming object does not have an expando object then we're sure we're not
// shadowing.
masm.branchTestUndefined(Assembler::Equal, tempVal, &domProxyOk);
if (expandoVal.isObject()) {
JS_ASSERT(!expandoVal.toObject().nativeContains(cx, name));
// Reference object has an expando object that doesn't define the name. Check that
// the incoming object has an expando object with the same shape.
masm.branchTestObject(Assembler::NotEqual, tempVal, &failDOMProxyCheck);
masm.extractObject(tempVal, tempVal.scratchReg());
masm.branchPtr(Assembler::Equal,
Address(tempVal.scratchReg(), JSObject::offsetOfShape()),
ImmGCPtr(expandoVal.toObject().lastProperty()),
&domProxyOk);
}
// Failure case: restore the tempVal registers and jump to failures.
masm.bind(&failDOMProxyCheck);
masm.popValue(tempVal);
masm.jump(stubFailure);
// Success case: restore the tempval and proceed.
masm.bind(&domProxyOk);
masm.popValue(tempVal);
}
static void
GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm,
IonCache::StubAttacher &attacher, JSObject *obj, PropertyName *name,
JSObject *holder, Shape *shape, Register object, TypedOrValueRegister output,
Label *failures = NULL)
{
// If there's a single jump to |failures|, we can patch the shape guard
// jump directly. Otherwise, jump to the end of the stub, so there's a
// common point to patch.
bool multipleFailureJumps = (obj != holder) || (failures != NULL && failures->used());
// If we have multiple failure jumps but didn't get a label from the
// outside, make one ourselves.
Label failures_;
if (multipleFailureJumps && !failures)
failures = &failures_;
// Guard on the shape of the object.
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfShape()),
ImmGCPtr(obj->lastProperty()),
failures);
bool isCacheableDOMProxy = IsCacheableDOMProxy(obj);
Label domProxyFailures;
if (isCacheableDOMProxy) {
JS_ASSERT(multipleFailureJumps);
GenerateDOMProxyChecks(cx, masm, obj, name, object, &domProxyFailures);
}
// If we need a scratch register, use either an output register or the
// object register. After this point, we cannot jump directly to
// |failures| since we may still have to pop the object register.
bool restoreScratch = false;
Register scratchReg = Register::FromCode(0); // Quell compiler warning.
if (obj != holder || !holder->isFixedSlot(shape->slot())) {
if (output.hasValue()) {
scratchReg = output.valueReg().scratchReg();
} else if (output.type() == MIRType_Double) {
scratchReg = object;
masm.push(scratchReg);
restoreScratch = true;
} else {
scratchReg = output.typedReg().gpr();
}
}
// Fast path: single failure jump, no prototype guards.
if (!multipleFailureJumps) {
EmitLoadSlot(masm, holder, shape, object, output, scratchReg);
if (restoreScratch)
masm.pop(scratchReg);
attacher.jumpRejoin(masm);
return;
}
// Slow path: multiple jumps; generate prototype guards.
Label prototypeFailures;
Register holderReg;
if (obj != holder) {
// Note: this may clobber the object register if it's used as scratch.
GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg,
failures);
if (holder) {
// Guard on the holder's shape.
holderReg = scratchReg;
masm.moveNurseryPtr(ImmMaybeNurseryPtr(holder), holderReg);
masm.branchPtr(Assembler::NotEqual,
Address(holderReg, JSObject::offsetOfShape()),
ImmGCPtr(holder->lastProperty()),
&prototypeFailures);
} else {
// The property does not exist. Guard on everything in the
// prototype chain.
JSObject *proto = obj->getTaggedProto().toObjectOrNull();
Register lastReg = object;
JS_ASSERT(scratchReg != object);
while (proto) {
Address addrType(lastReg, JSObject::offsetOfType());
masm.loadPtr(addrType, scratchReg);
Address addrProto(scratchReg, offsetof(types::TypeObject, proto));
masm.loadPtr(addrProto, scratchReg);
Address addrShape(scratchReg, JSObject::offsetOfShape());
// Guard the shape of the current prototype.
masm.branchPtr(Assembler::NotEqual,
Address(scratchReg, JSObject::offsetOfShape()),
ImmGCPtr(proto->lastProperty()),
&prototypeFailures);
proto = proto->getProto();
lastReg = scratchReg;
}
holderReg = InvalidReg;
}
} else {
holderReg = object;
}
// Slot access.
if (holder)
EmitLoadSlot(masm, holder, shape, holderReg, output, scratchReg);
else
masm.moveValue(UndefinedValue(), output.valueReg());
// Restore scratch on success.
if (restoreScratch)
masm.pop(scratchReg);
attacher.jumpRejoin(masm);
if (multipleFailureJumps) {
masm.bind(&prototypeFailures);
if (restoreScratch)
masm.pop(scratchReg);
if (isCacheableDOMProxy)
masm.bind(&domProxyFailures);
masm.bind(failures);
}
attacher.jumpNextStub(masm);
if (restoreScratch)
masm.pop(scratchReg);
}
static bool
GenerateCallGetter(JSContext *cx, IonScript *ion, MacroAssembler &masm,
IonCache::StubAttacher &attacher, JSObject *obj, PropertyName *name,
JSObject *holder, HandleShape shape, RegisterSet &liveRegs, Register object,
TypedOrValueRegister output, void *returnAddr, jsbytecode *pc)
{
// Initial shape check.
Label stubFailure;
masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfShape()),
ImmGCPtr(obj->lastProperty()), &stubFailure);
if (IsCacheableDOMProxy(obj))
GenerateDOMProxyChecks(cx, masm, obj, name, object, &stubFailure);
JS_ASSERT(output.hasValue());
Register scratchReg = output.valueReg().scratchReg();
// Note: this may clobber the object register if it's used as scratch.
if (obj != holder)
GeneratePrototypeGuards(cx, ion, masm, obj, holder, object, scratchReg, &stubFailure);
// Guard on the holder's shape.
Register holderReg = scratchReg;
masm.moveNurseryPtr(ImmMaybeNurseryPtr(holder), holderReg);
masm.branchPtr(Assembler::NotEqual,
Address(holderReg, JSObject::offsetOfShape()),
ImmGCPtr(holder->lastProperty()),
&stubFailure);
// Now we're good to go to invoke the native call.
// saveLive()
masm.PushRegsInMask(liveRegs);
// Remaining registers should basically be free, but we need to use |object| still
// so leave it alone.
RegisterSet regSet(RegisterSet::All());
regSet.take(AnyRegister(object));
// This is a slower stub path, and we're going to be doing a call anyway. Don't need
// to try so hard to not use the stack. Scratch regs are just taken from the register
// set not including the input, current value saved on the stack, and restored when
// we're done with it.
scratchReg = regSet.takeGeneral();
Register argJSContextReg = regSet.takeGeneral();
Register argUintNReg = regSet.takeGeneral();
Register argVpReg = regSet.takeGeneral();
// Shape has a getter function.
bool callNative = IsCacheableGetPropCallNative(obj, holder, shape);
JS_ASSERT_IF(!callNative, IsCacheableGetPropCallPropertyOp(obj, holder, shape));
// TODO: ensure stack is aligned?
DebugOnly<uint32_t> initialStack = masm.framePushed();
Label success, exception;
attacher.pushStubCodePointer(masm);
if (callNative) {
JS_ASSERT(shape->hasGetterValue() && shape->getterValue().isObject() &&
shape->getterValue().toObject().is<JSFunction>());
JSFunction *target = &shape->getterValue().toObject().as<JSFunction>();
JS_ASSERT(target);
JS_ASSERT(target->isNative());
// Native functions have the signature:
// bool (*)(JSContext *, unsigned, Value *vp)
// Where vp[0] is space for an outparam, vp[1] is |this|, and vp[2] onward
// are the function arguments.
// Construct vp array:
// Push object value for |this|
masm.Push(TypedOrValueRegister(MIRType_Object, AnyRegister(object)));
// Push callee/outparam.
masm.Push(ObjectValue(*target));
// Preload arguments into registers.
masm.loadJSContext(argJSContextReg);
masm.move32(Imm32(0), argUintNReg);
masm.movePtr(StackPointer, argVpReg);
if (!masm.buildOOLFakeExitFrame(returnAddr))
return false;
masm.enterFakeExitFrame(ION_FRAME_OOL_NATIVE_GETTER);
// Construct and execute call.
masm.setupUnalignedABICall(3, scratchReg);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argUintNReg);
masm.passABIArg(argVpReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->native()));
// Test for failure.
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &exception);
// Load the outparam vp[0] into output register(s).
masm.loadValue(
Address(StackPointer, IonOOLNativeGetterExitFrameLayout::offsetOfResult()),
JSReturnOperand);
} else {
Register argObjReg = argUintNReg;
Register argIdReg = regSet.takeGeneral();
PropertyOp target = shape->getterOp();
JS_ASSERT(target);
// JSPropertyOp: JSBool fn(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp)
// Push args on stack first so we can take pointers to make handles.
masm.Push(UndefinedValue());
masm.movePtr(StackPointer, argVpReg);
// push canonical jsid from shape instead of propertyname.
RootedId propId(cx);
if (!shape->getUserId(cx, &propId))
return false;
masm.Push(propId, scratchReg);
masm.movePtr(StackPointer, argIdReg);
masm.Push(object);
masm.movePtr(StackPointer, argObjReg);
masm.loadJSContext(argJSContextReg);
if (!masm.buildOOLFakeExitFrame(returnAddr))
return false;
masm.enterFakeExitFrame(ION_FRAME_OOL_PROPERTY_OP);
// Make the call.
masm.setupUnalignedABICall(4, scratchReg);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argObjReg);
masm.passABIArg(argIdReg);
masm.passABIArg(argVpReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target));
// Test for failure.
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &exception);
// Load the outparam vp[0] into output register(s).
masm.loadValue(
Address(StackPointer, IonOOLPropertyOpExitFrameLayout::offsetOfResult()),
JSReturnOperand);
}
// If generating getter call stubs, then return type MUST have been generalized
// to MIRType_Value.
masm.jump(&success);
// Handle exception case.
masm.bind(&exception);
masm.handleException();
// Handle success case.
masm.bind(&success);
masm.storeCallResultValue(output);
// The next instruction is removing the footer of the exit frame, so there
// is no need for leaveFakeExitFrame.
// Move the StackPointer back to its original location, unwinding the native exit frame.
if (callNative)
masm.adjustStack(IonOOLNativeGetterExitFrameLayout::Size());
else
masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size());
JS_ASSERT(masm.framePushed() == initialStack);
// restoreLive()
masm.PopRegsInMask(liveRegs);
// Rejoin jump.
attacher.jumpRejoin(masm);
// Jump to next stub.
masm.bind(&stubFailure);
attacher.jumpNextStub(masm);
return true;
}
bool
GetPropertyIC::attachReadSlot(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
HandleShape shape)
{
RepatchStubAppender attacher(*this);
MacroAssembler masm(cx);
GenerateReadSlot(cx, ion, masm, attacher, obj, name(), holder, shape, object(), output());
const char *attachKind = "non idempotent reading";
if (idempotent())
attachKind = "idempotent reading";
return linkAndAttachStub(cx, masm, attacher, ion, attachKind);
}
bool
GetPropertyIC::attachDOMProxyShadowed(JSContext *cx, IonScript *ion, JSObject *obj,
void *returnAddr)
{
JS_ASSERT(!idempotent());
JS_ASSERT(IsCacheableDOMProxy(obj));
JS_ASSERT(output().hasValue());
Label failures;
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
masm.setFramePushed(ion->frameSize());
// Guard on the shape of the object.
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object(), JSObject::offsetOfShape()),
ImmGCPtr(obj->lastProperty()),
&failures);
// Make sure object is a DOMProxy proxy
GenerateDOMProxyChecks(cx, masm, obj, name(), object(), &failures,
/*skipExpandoCheck=*/true);
// saveLive()
masm.PushRegsInMask(liveRegs_);
DebugOnly<uint32_t> initialStack = masm.framePushed();
// Remaining registers should be free, but we need to use |object| still
// so leave it alone.
RegisterSet regSet(RegisterSet::All());
regSet.take(AnyRegister(object()));
// Proxy::get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
// MutableHandleValue vp)
Register argJSContextReg = regSet.takeGeneral();
Register argProxyReg = regSet.takeGeneral();
Register argIdReg = regSet.takeGeneral();
Register argVpReg = regSet.takeGeneral();
Register scratch = regSet.takeGeneral();
// Push args on stack first so we can take pointers to make handles.
masm.Push(UndefinedValue());
masm.movePtr(StackPointer, argVpReg);
RootedId propId(cx, AtomToId(name()));
masm.Push(propId, scratch);
masm.movePtr(StackPointer, argIdReg);
// Pushing object and receiver. Both are same, so Handle to one is equivalent to
// handle to other.
masm.Push(object());
masm.Push(object());
masm.movePtr(StackPointer, argProxyReg);
masm.loadJSContext(argJSContextReg);
if (!masm.buildOOLFakeExitFrame(returnAddr))
return false;
masm.enterFakeExitFrame(ION_FRAME_OOL_PROXY_GET);
// Make the call.
masm.setupUnalignedABICall(5, scratch);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argProxyReg);
masm.passABIArg(argProxyReg);
masm.passABIArg(argIdReg);
masm.passABIArg(argVpReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, Proxy::get));
// Test for failure.
Label exception;
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &exception);
// Load the outparam vp[0] into output register(s).
masm.loadValue(
Address(StackPointer, IonOOLProxyGetExitFrameLayout::offsetOfResult()),
JSReturnOperand);
Label success;
masm.jump(&success);
// Handle exception case.
masm.bind(&exception);
masm.handleException();
// Handle success case.
masm.bind(&success);
masm.storeCallResultValue(output());
// The next instruction is removing the footer of the exit frame, so there
// is no need for leaveFakeExitFrame.
// Move the StackPointer back to its original location, unwinding the exit frame.
masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size());
JS_ASSERT(masm.framePushed() == initialStack);
// restoreLive()
masm.PopRegsInMask(liveRegs_);
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(&failures);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "list base shadowed get");
}
bool
GetPropertyIC::attachCallGetter(JSContext *cx, IonScript *ion, JSObject *obj,
JSObject *holder, HandleShape shape,
const SafepointIndex *safepointIndex, void *returnAddr)
{
MacroAssembler masm(cx);
JS_ASSERT(!idempotent());
JS_ASSERT(allowGetters());
// Need to set correct framePushed on the masm so that exit frame descriptors are
// properly constructed.
masm.setFramePushed(ion->frameSize());
RepatchStubAppender attacher(*this);
if (!GenerateCallGetter(cx, ion, masm, attacher, obj, name(), holder, shape, liveRegs_,
object(), output(), returnAddr, pc))
{
return false;
}
const char *attachKind = "non idempotent calling";
if (idempotent())
attachKind = "idempotent calling";
return linkAndAttachStub(cx, masm, attacher, ion, attachKind);
}
bool
GetPropertyIC::attachArrayLength(JSContext *cx, IonScript *ion, JSObject *obj)
{
JS_ASSERT(obj->isArray());
JS_ASSERT(!idempotent());
Label failures;
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
// Guard object is a dense array.
RootedObject globalObj(cx, &script->global());
RootedShape shape(cx, obj->lastProperty());
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object(), shape, &failures);
// Load length.
Register outReg;
if (output().hasValue()) {
outReg = output().valueReg().scratchReg();
} else {
JS_ASSERT(output().type() == MIRType_Int32);
outReg = output().typedReg().gpr();
}
masm.loadPtr(Address(object(), JSObject::offsetOfElements()), outReg);
masm.load32(Address(outReg, ObjectElements::offsetOfLength()), outReg);
// The length is an unsigned int, but the value encodes a signed int.
JS_ASSERT(object() != outReg);
masm.branchTest32(Assembler::Signed, outReg, outReg, &failures);
if (output().hasValue())
masm.tagValue(JSVAL_TYPE_INT32, outReg, output().valueReg());
/* Success. */
attacher.jumpRejoin(masm);
/* Failure. */
masm.bind(&failures);
attacher.jumpNextStub(masm);
JS_ASSERT(!hasArrayLengthStub_);
hasArrayLengthStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "array length");
}
bool
GetPropertyIC::attachTypedArrayLength(JSContext *cx, IonScript *ion, JSObject *obj)
{
JS_ASSERT(obj->isTypedArray());
JS_ASSERT(!idempotent());
Label failures;
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
Register tmpReg;
if (output().hasValue()) {
tmpReg = output().valueReg().scratchReg();
} else {
JS_ASSERT(output().type() == MIRType_Int32);
tmpReg = output().typedReg().gpr();
}
JS_ASSERT(object() != tmpReg);
// Implement the negated version of JSObject::isTypedArray predicate.
masm.loadObjClass(object(), tmpReg);
masm.branchPtr(Assembler::Below, tmpReg, ImmWord(&TypedArray::classes[0]), &failures);
masm.branchPtr(Assembler::AboveOrEqual, tmpReg, ImmWord(&TypedArray::classes[TypedArray::TYPE_MAX]), &failures);
// Load length.
masm.loadTypedOrValue(Address(object(), TypedArray::lengthOffset()), output());
/* Success. */
attacher.jumpRejoin(masm);
/* Failure. */
masm.bind(&failures);
attacher.jumpNextStub(masm);
JS_ASSERT(!hasTypedArrayLengthStub_);
hasTypedArrayLengthStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "typed array length");
}
bool
GetPropertyIC::attachArgumentsLength(JSContext *cx, IonScript *ion, JSObject *obj)
{
JS_ASSERT(obj->is<ArgumentsObject>());
JS_ASSERT(!idempotent());
Label failures;
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
Register tmpReg;
if (output().hasValue()) {
tmpReg = output().valueReg().scratchReg();
} else {
JS_ASSERT(output().type() == MIRType_Int32);
tmpReg = output().typedReg().gpr();
}
JS_ASSERT(object() != tmpReg);
Class *clasp = obj->is<StrictArgumentsObject>() ? &StrictArgumentsObject::class_
: &NormalArgumentsObject::class_;
Label fail;
Label pass;
masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures);
// Get initial ArgsObj length value, test if length has been overridden.
masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
&failures);
masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
// If output is Int32, result is already in right place, otherwise box it into output.
if (output().hasValue())
masm.tagValue(JSVAL_TYPE_INT32, tmpReg, output().valueReg());
// Success.
attacher.jumpRejoin(masm);
// Failure.
masm.bind(&failures);
attacher.jumpNextStub(masm);
if (obj->is<StrictArgumentsObject>()) {
JS_ASSERT(!hasStrictArgumentsLengthStub_);
hasStrictArgumentsLengthStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (strict)");
}
JS_ASSERT(!hasNormalArgumentsLengthStub_);
hasNormalArgumentsLengthStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (normal)");
}
static bool
IsIdempotentAndMaybeHasHooks(IonCache &cache, JSObject *obj)
{
// If the cache is idempotent, watch out for resolve hooks or non-native
// objects on the proto chain. We check this before calling lookupProperty,
// to make sure no effectful lookup hooks or resolve hooks are called.
return cache.idempotent() && !obj->hasIdempotentProtoChain();
}
/*
* The receiver is the object the get is actually happening on, and what should
* be used for missing-property checks. The checkObj is the object that we want
* to do the get on if the property is present on it.
*/
static bool
DetermineGetPropKind(JSContext *cx, IonCache &cache, JSObject *receiver,
JSObject *checkObj, JSObject *holder, HandleShape shape,
TypedOrValueRegister output, bool allowGetters,
bool *readSlot, bool *callGetter)
{
// Check what kind of cache stub we can emit: either a slot read,
// or a getter call.
*readSlot = false;
*callGetter = false;
RootedScript script(cx);
jsbytecode *pc;
cache.getScriptedLocation(&script, &pc);
if (IsCacheableGetPropReadSlot(checkObj, holder, shape) ||
IsCacheableNoProperty(receiver, holder, shape, pc, output))
{
// With Proxies, we cannot garantee any property access as the proxy can
// mask any property from the prototype chain.
JS_ASSERT(!checkObj->isProxy());
*readSlot = true;
} else if (IsCacheableGetPropCallNative(checkObj, holder, shape) ||
IsCacheableGetPropCallPropertyOp(checkObj, holder, shape))
{
// Don't enable getter call if cache is idempotent, since
// they can be effectful.
if (!cache.idempotent() && allowGetters)
*callGetter = true;
}
// readSlot and callGetter are mutually exclusive
JS_ASSERT_IF(*readSlot, !*callGetter);
JS_ASSERT_IF(*callGetter, !*readSlot);
// Return true only if one strategy is viable.
return *readSlot || *callGetter;
}
static bool
IsIdempotentAndHasSingletonHolder(IonCache &cache, HandleObject holder, HandleShape shape)
{
// TI infers the possible types of native object properties. There's one
// edge case though: for singleton objects it does not add the initial
// "undefined" type, see the propertySet comment in jsinfer.h. We can't
// monitor the return type inside an idempotent cache though, so we don't
// handle this case.
return (cache.idempotent() &&
holder &&
holder->hasSingletonType() &&
holder->getSlot(shape->slot()).isUndefined());
}
static bool
TryAttachNativeGetPropStub(JSContext *cx, IonScript *ion,
GetPropertyIC &cache, HandleObject obj,
HandlePropertyName name,
const SafepointIndex *safepointIndex,
void *returnAddr, bool *isCacheable)
{
JS_ASSERT(!*isCacheable);
JS_ASSERT(cache.canAttachStub());
RootedObject checkObj(cx, obj);
if (IsCacheableDOMProxy(obj)) {
RootedId id(cx, NameToId(name));
DOMProxyShadowsResult shadows = GetDOMProxyShadowsCheck()(cx, obj, id);
if (shadows == ShadowCheckFailed)
return false;
if (shadows == Shadows) {
if (cache.idempotent() || !cache.output().hasValue())
return true;
*isCacheable = true;
return cache.attachDOMProxyShadowed(cx, ion, obj, returnAddr);
}
if (shadows == DoesntShadowUnique)
// We reset the cache to clear out an existing IC for this object
// (if there is one). The generation is a constant in the generated
// code and we will not have the same generation again for this
// object, so the generation check in the existing IC would always
// fail anyway.
cache.reset();
checkObj = obj->getTaggedProto().toObjectOrNull();
}
if (!checkObj || !checkObj->isNative())
return true;
if (IsIdempotentAndMaybeHasHooks(cache, checkObj))
return true;
RootedShape shape(cx);
RootedObject holder(cx);
if (!JSObject::lookupProperty(cx, checkObj, name, &holder, &shape))
return false;
bool readSlot;
bool callGetter;
if (!DetermineGetPropKind(cx, cache, obj, checkObj, holder, shape, cache.output(),
cache.allowGetters(), &readSlot, &callGetter))
{
return true;
}
if (IsIdempotentAndHasSingletonHolder(cache, holder, shape))
return true;
*isCacheable = true;
if (readSlot)
return cache.attachReadSlot(cx, ion, obj, holder, shape);
else if (obj->isArray() && !cache.hasArrayLengthStub() && cx->names().length == name)
return cache.attachArrayLength(cx, ion, obj);
return cache.attachCallGetter(cx, ion, obj, holder, shape, safepointIndex, returnAddr);
}
bool
GetPropertyIC::update(JSContext *cx, size_t cacheIndex,
HandleObject obj, MutableHandleValue vp)
{
AutoFlushCache afc ("GetPropertyCache");
const SafepointIndex *safepointIndex;
void *returnAddr;
RootedScript topScript(cx, GetTopIonJSScript(cx, &safepointIndex, &returnAddr));
IonScript *ion = topScript->ionScript();
GetPropertyIC &cache = ion->getCache(cacheIndex).toGetProperty();
RootedPropertyName name(cx, cache.name());
// Override the return value if we are invalidated (bug 728188).
AutoDetectInvalidation adi(cx, vp.address(), ion);
// If the cache is idempotent, we will redo the op in the interpreter.
if (cache.idempotent())
adi.disable();
// For now, just stop generating new stubs once we hit the stub count
// limit. Once we can make calls from within generated stubs, a new call
// stub will be generated instead and the previous stubs unlinked.
bool isCacheable = false;
if (cache.canAttachStub()) {
if (name == cx->names().length &&
IsOptimizableArgumentsObjectForLength(obj) &&
(cache.output().type() == MIRType_Value || cache.output().type() == MIRType_Int32) &&
!cache.hasArgumentsLengthStub(obj->is<StrictArgumentsObject>()))
{
isCacheable = true;
if (!cache.attachArgumentsLength(cx, ion, obj))
return false;
}
if (!isCacheable && !TryAttachNativeGetPropStub(cx, ion, cache, obj, name,
safepointIndex, returnAddr,
&isCacheable))
{
return false;
}
if (!isCacheable && !cache.idempotent() && cx->names().length == name) {
if (cache.output().type() != MIRType_Value && cache.output().type() != MIRType_Int32) {
// The next execution should cause an invalidation because the type
// does not fit.
isCacheable = false;
} else if (obj->isTypedArray() && !cache.hasTypedArrayLengthStub()) {
isCacheable = true;
if (!cache.attachTypedArrayLength(cx, ion, obj))
return false;
}
}
}
if (cache.idempotent() && !isCacheable) {
// Invalidate the cache if the property was not found, or was found on
// a non-native object. This ensures:
// 1) The property read has no observable side-effects.
// 2) There's no need to dynamically monitor the return type. This would
// be complicated since (due to GVN) there can be multiple pc's
// associated with a single idempotent cache.
IonSpew(IonSpew_InlineCaches, "Invalidating from idempotent cache %s:%d",
topScript->filename(), topScript->lineno);
topScript->invalidatedIdempotentCache = true;
// Do not re-invalidate if the lookup already caused invalidation.
if (!topScript->hasIonScript())
return true;
return Invalidate(cx, topScript);
}
RootedId id(cx, NameToId(name));
if (obj->getOps()->getProperty) {
if (!JSObject::getGeneric(cx, obj, obj, id, vp))
return false;
} else {
if (!GetPropertyHelper(cx, obj, id, 0, vp))
return false;
}
if (!cache.idempotent()) {
RootedScript script(cx);
jsbytecode *pc;
cache.getScriptedLocation(&script, &pc);
// If the cache is idempotent, the property exists so we don't have to
// call __noSuchMethod__.
#if JS_HAS_NO_SUCH_METHOD
// Handle objects with __noSuchMethod__.
if (JSOp(*pc) == JSOP_CALLPROP && JS_UNLIKELY(vp.isPrimitive())) {
if (!OnUnknownMethod(cx, obj, IdToValue(id), vp))
return false;
}
#endif
// Monitor changes to cache entry.
types::TypeScript::Monitor(cx, script, pc, vp);
}
return true;
}
void
GetPropertyIC::reset()
{
RepatchIonCache::reset();
hasArrayLengthStub_ = false;
hasTypedArrayLengthStub_ = false;
hasStrictArgumentsLengthStub_ = false;
hasNormalArgumentsLengthStub_ = false;
}
void
ParallelGetPropertyIC::reset()
{
DispatchIonCache::reset();
if (stubbedShapes_)
stubbedShapes_->clear();
}
void
ParallelGetPropertyIC::destroy()
{
if (stubbedShapes_)
js_delete(stubbedShapes_);
}
bool
ParallelGetPropertyIC::initStubbedShapes(JSContext *cx)
{
JS_ASSERT(isAllocated());
if (!stubbedShapes_) {
stubbedShapes_ = cx->new_<ShapeSet>(cx);
return stubbedShapes_ && stubbedShapes_->init();
}
return true;
}
bool
ParallelGetPropertyIC::canAttachReadSlot(LockedJSContext &cx, JSObject *obj,
MutableHandleObject holder, MutableHandleShape shape)
{
// Parallel execution should only cache native objects.
if (!obj->isNative())
return false;
if (IsIdempotentAndMaybeHasHooks(*this, obj))
return false;
// Bail if we have hooks.
if (obj->getOps()->lookupProperty || obj->getOps()->lookupGeneric)
return false;
if (!js::LookupPropertyPure(obj, NameToId(name()), holder.address(), shape.address()))
return false;
// In parallel execution we can't cache getters due to possible
// side-effects, so only check if we can cache slot reads.
bool readSlot;
bool callGetter;
if (!DetermineGetPropKind(cx, *this, obj, obj, holder, shape, output(), false,
&readSlot, &callGetter) || !readSlot)
{
return false;
}
if (IsIdempotentAndHasSingletonHolder(*this, holder, shape))
return false;
return true;
}
bool
ParallelGetPropertyIC::attachReadSlot(LockedJSContext &cx, IonScript *ion,
JSObject *obj, bool *attachedStub)
{
*attachedStub = false;
RootedShape shape(cx);
RootedObject holder(cx);
if (!canAttachReadSlot(cx, obj, &holder, &shape))
return true;
// Ready to generate the read slot stub.
DispatchStubPrepender attacher(*this);
MacroAssembler masm(cx);
GenerateReadSlot(cx, ion, masm, attacher, obj, name(), holder, shape, object(), output());
const char *attachKind = "parallel non-idempotent reading";
if (idempotent())
attachKind = "parallel idempotent reading";
if (!linkAndAttachStub(cx, masm, attacher, ion, attachKind))
return false;
*attachedStub = true;
return true;
}
ParallelResult
ParallelGetPropertyIC::update(ForkJoinSlice *slice, size_t cacheIndex,
HandleObject obj, MutableHandleValue vp)
{
AutoFlushCache afc("ParallelGetPropertyCache");
PerThreadData *pt = slice->perThreadData;
const SafepointIndex *safepointIndex;
void *returnAddr;
RootedScript topScript(pt, GetTopIonJSScript(pt, &safepointIndex, &returnAddr));
IonScript *ion = topScript->parallelIonScript();
ParallelGetPropertyIC &cache = ion->getCache(cacheIndex).toParallelGetProperty();
RootedScript script(pt);
jsbytecode *pc;
cache.getScriptedLocation(&script, &pc);
// Grab the property early, as the pure path is fast anyways and doesn't
// need a lock. If we can't do it purely, bail out of parallel execution.
if (!GetPropertyPure(obj, NameToId(cache.name()), vp.address()))
return TP_RETRY_SEQUENTIALLY;
// Avoid unnecessary locking if cannot attach stubs and idempotent.
if (cache.idempotent() && !cache.canAttachStub())
return TP_SUCCESS;
{
// Lock the context before mutating the cache. Ideally we'd like to do
// finer-grained locking, with one lock per cache. However, generating
// new jitcode uses a global ExecutableAllocator tied to the runtime.
LockedJSContext cx(slice);
if (cache.canAttachStub()) {
// Check if we have already stubbed the current object to avoid
// attaching a duplicate stub.
if (!cache.initStubbedShapes(cx))
return TP_FATAL;
ShapeSet::AddPtr p = cache.stubbedShapes()->lookupForAdd(obj->lastProperty());
if (p)
return TP_SUCCESS;
if (!cache.stubbedShapes()->add(p, obj->lastProperty()))
return TP_FATAL;
// See note about the stub limit in GetPropertyCache.
bool attachedStub;
if (!cache.attachReadSlot(cx, ion, obj, &attachedStub))
return TP_FATAL;
if (!attachedStub) {
if (cache.idempotent())
topScript->invalidatedIdempotentCache = true;
// ParallelDo will take care of invalidating all bailed out
// scripts, so just bail out now.
return TP_RETRY_SEQUENTIALLY;
}
}
if (!cache.idempotent()) {
#if JS_HAS_NO_SUCH_METHOD
// Straight up bail if there's this __noSuchMethod__ hook.
if (JSOp(*pc) == JSOP_CALLPROP && JS_UNLIKELY(vp.isPrimitive()))
return TP_RETRY_SEQUENTIALLY;
#endif
// Monitor changes to cache entry.
types::TypeScript::Monitor(cx, script, pc, vp);
}
}
return TP_SUCCESS;
}
void
IonCache::disable()
{
reset();
this->disabled_ = 1;
}
void
IonCache::reset()
{
this->stubCount_ = 0;
}
void
IonCache::destroy()
{
}
bool
SetPropertyIC::attachNativeExisting(JSContext *cx, IonScript *ion,
HandleObject obj, HandleShape shape)
{
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
attacher.branchNextStub(masm, Assembler::NotEqual,
Address(object(), JSObject::offsetOfShape()),
ImmGCPtr(obj->lastProperty()));
if (obj->isFixedSlot(shape->slot())) {
Address addr(object(), JSObject::getFixedSlotOffset(shape->slot()));
if (cx->zone()->needsBarrier())
masm.callPreBarrier(addr, MIRType_Value);
masm.storeConstantOrRegister(value(), addr);
} else {
Register slotsReg = object();
masm.loadPtr(Address(object(), JSObject::offsetOfSlots()), slotsReg);
Address addr(slotsReg, obj->dynamicSlotIndex(shape->slot()) * sizeof(Value));
if (cx->zone()->needsBarrier())
masm.callPreBarrier(addr, MIRType_Value);
masm.storeConstantOrRegister(value(), addr);
}
attacher.jumpRejoin(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "setting");
}
bool
SetPropertyIC::attachSetterCall(JSContext *cx, IonScript *ion,
HandleObject obj, HandleObject holder, HandleShape shape,
void *returnAddr)
{
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
// Need to set correct framePushed on the masm so that exit frame descriptors are
// properly constructed.
masm.setFramePushed(ion->frameSize());
Label failure;
masm.branchPtr(Assembler::NotEqual,
Address(object(), JSObject::offsetOfShape()),
ImmGCPtr(obj->lastProperty()),
&failure);
// Generate prototype guards if needed.
// Take a scratch register for use, save on stack.
{
RegisterSet regSet(RegisterSet::All());
regSet.take(AnyRegister(object()));
if (!value().constant())
regSet.maybeTake(value().reg());
Register scratchReg = regSet.takeGeneral();
masm.push(scratchReg);
Label protoFailure;
Label protoSuccess;
// Generate prototype/shape guards.
if (obj != holder)
GeneratePrototypeGuards(cx, ion, masm, obj, holder, object(), scratchReg, &protoFailure);
masm.moveNurseryPtr(ImmMaybeNurseryPtr(holder), scratchReg);
masm.branchPtr(Assembler::NotEqual,
Address(scratchReg, JSObject::offsetOfShape()),
ImmGCPtr(holder->lastProperty()),
&protoFailure);
masm.jump(&protoSuccess);
masm.bind(&protoFailure);
masm.pop(scratchReg);
masm.jump(&failure);
masm.bind(&protoSuccess);
masm.pop(scratchReg);
}
// Good to go for invoking setter.
// saveLive()
masm.PushRegsInMask(liveRegs_);
// Remaining registers should basically be free, but we need to use |object| still
// so leave it alone.
RegisterSet regSet(RegisterSet::All());
regSet.take(AnyRegister(object()));
// This is a slower stub path, and we're going to be doing a call anyway. Don't need
// to try so hard to not use the stack. Scratch regs are just taken from the register
// set not including the input, current value saved on the stack, and restored when
// we're done with it.
Register scratchReg = regSet.takeGeneral();
Register argJSContextReg = regSet.takeGeneral();
Register argObjReg = regSet.takeGeneral();
Register argIdReg = regSet.takeGeneral();
Register argStrictReg = regSet.takeGeneral();
Register argVpReg = regSet.takeGeneral();
// Ensure stack is aligned.
DebugOnly<uint32_t> initialStack = masm.framePushed();
Label success, exception;
attacher.pushStubCodePointer(masm);
StrictPropertyOp target = shape->setterOp();
JS_ASSERT(target);
// JSStrictPropertyOp: JSBool fn(JSContext *cx, HandleObject obj,
// HandleId id, JSBool strict, MutableHandleValue vp);
// Push args on stack first so we can take pointers to make handles.
if (value().constant())
masm.Push(value().value());
else
masm.Push(value().reg());
masm.movePtr(StackPointer, argVpReg);
masm.move32(Imm32(strict() ? 1 : 0), argStrictReg);
// push canonical jsid from shape instead of propertyname.
RootedId propId(cx);
if (!shape->getUserId(cx, &propId))
return false;
masm.Push(propId, argIdReg);
masm.movePtr(StackPointer, argIdReg);
masm.Push(object());
masm.movePtr(StackPointer, argObjReg);
masm.loadJSContext(argJSContextReg);
if (!masm.buildOOLFakeExitFrame(returnAddr))
return false;
masm.enterFakeExitFrame(ION_FRAME_OOL_PROPERTY_OP);
// Make the call.
masm.setupUnalignedABICall(5, scratchReg);
masm.passABIArg(argJSContextReg);
masm.passABIArg(argObjReg);
masm.passABIArg(argIdReg);
masm.passABIArg(argStrictReg);
masm.passABIArg(argVpReg);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target));
// Test for failure.
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &exception);
masm.jump(&success);
// Handle exception case.
masm.bind(&exception);
masm.handleException();
// Handle success case.
masm.bind(&success);
// The next instruction is removing the footer of the exit frame, so there
// is no need for leaveFakeExitFrame.
// Move the StackPointer back to its original location, unwinding the exit frame.
masm.adjustStack(IonOOLPropertyOpExitFrameLayout::Size());
JS_ASSERT(masm.framePushed() == initialStack);
// restoreLive()
masm.PopRegsInMask(liveRegs_);
// Rejoin jump.
attacher.jumpRejoin(masm);
// Jump to next stub.
masm.bind(&failure);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "calling");
}
bool
SetPropertyIC::attachNativeAdding(JSContext *cx, IonScript *ion, JSObject *obj,
HandleShape oldShape, HandleShape newShape,
HandleShape propShape)
{
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
Label failures;
/* Guard the type of the object */
masm.branchPtr(Assembler::NotEqual, Address(object(), JSObject::offsetOfType()),
ImmGCPtr(obj->type()), &failures);
/* Guard shapes along prototype chain. */
masm.branchTestObjShape(Assembler::NotEqual, object(), oldShape, &failures);
Label protoFailures;
masm.push(object()); // save object reg because we clobber it
JSObject *proto = obj->getProto();
Register protoReg = object();
while (proto) {
Shape *protoShape = proto->lastProperty();
// load next prototype
masm.loadPtr(Address(protoReg, JSObject::offsetOfType()), protoReg);
masm.loadPtr(Address(protoReg, offsetof(types::TypeObject, proto)), protoReg);
// Ensure that its shape matches.
masm.branchTestObjShape(Assembler::NotEqual, protoReg, protoShape, &protoFailures);
proto = proto->getProto();
}
masm.pop(object()); // restore object reg
/* Changing object shape. Write the object's new shape. */
Address shapeAddr(object(), JSObject::offsetOfShape());
if (cx->zone()->needsBarrier())
masm.callPreBarrier(shapeAddr, MIRType_Shape);
masm.storePtr(ImmGCPtr(newShape), shapeAddr);
/* Set the value on the object. */
if (obj->isFixedSlot(propShape->slot())) {
Address addr(object(), JSObject::getFixedSlotOffset(propShape->slot()));
masm.storeConstantOrRegister(value(), addr);
} else {
Register slotsReg = object();
masm.loadPtr(Address(object(), JSObject::offsetOfSlots()), slotsReg);
Address addr(slotsReg, obj->dynamicSlotIndex(propShape->slot()) * sizeof(Value));
masm.storeConstantOrRegister(value(), addr);
}
/* Success. */
attacher.jumpRejoin(masm);
/* Failure. */
masm.bind(&protoFailures);
masm.pop(object());
masm.bind(&failures);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "adding");
}
static bool
IsPropertyInlineable(JSObject *obj)
{
if (!obj->isNative())
return false;
if (obj->watched())
return false;
return true;
}
static bool
IsPropertySetInlineable(JSContext *cx, HandleObject obj, HandleId id, MutableHandleShape pshape)
{
Shape *shape = obj->nativeLookup(cx, id);
if (!shape)
return false;
if (!shape->hasSlot())
return false;
if (!shape->hasDefaultSetter())
return false;
if (!shape->writable())
return false;
pshape.set(shape);
return true;
}
static bool
IsPropertySetterCallInlineable(JSContext *cx, HandleObject obj, HandleObject holder,
HandleShape shape)
{
if (!shape)
return false;
if (!holder->isNative())
return false;
if (shape->hasSlot())
return false;
if (shape->hasDefaultSetter())
return false;
if (!shape->writable())
return false;
// We only handle propertyOps for now, so fail if we have SetterValue
// (which implies JSNative setter).
if (shape->hasSetterValue())
return false;
return true;
}
static bool
IsPropertyAddInlineable(JSContext *cx, HandleObject obj, HandleId id, uint32_t oldSlots,
MutableHandleShape pShape)
{
// This is not a Add, the property exists.
if (pShape.get())
return false;
RootedShape shape(cx, obj->nativeLookup(cx, id));
if (!shape || shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter())
return false;
// If object has a non-default resolve hook, don't inline
if (obj->getClass()->resolve != JS_ResolveStub)
return false;
// Likewise for a non-default addProperty hook, since we'll need
// to invoke it.
if (obj->getClass()->addProperty != JS_PropertyStub)
return false;
if (!obj->isExtensible() || !shape->writable())
return false;
// walk up the object prototype chain and ensure that all prototypes
// are native, and that all prototypes have no getter or setter
// defined on the property
for (JSObject *proto = obj->getProto(); proto; proto = proto->getProto()) {
// 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->nativeLookup(cx, id);
if (protoShape && !protoShape->hasDefaultSetter())
return false;
// Otherise, 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 (proto->getClass()->resolve != JS_ResolveStub)
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 (obj->numDynamicSlots() != oldSlots)
return false;
pShape.set(shape);
return true;
}
bool
SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
HandleValue value)
{
AutoFlushCache afc ("SetPropertyCache");
void *returnAddr;
const SafepointIndex *safepointIndex;
RootedScript script(cx, GetTopIonJSScript(cx, &safepointIndex, &returnAddr));
IonScript *ion = script->ionScript();
SetPropertyIC &cache = ion->getCache(cacheIndex).toSetProperty();
RootedPropertyName name(cx, cache.name());
RootedId id(cx, AtomToId(name));
RootedShape shape(cx);
RootedObject holder(cx);
// Stop generating new stubs once we hit the stub count limit, see
// GetPropertyCache.
bool inlinable = cache.canAttachStub() && IsPropertyInlineable(obj);
bool addedSetterStub = false;
if (inlinable) {
RootedShape shape(cx);
if (IsPropertySetInlineable(cx, obj, id, &shape)) {
if (!cache.attachNativeExisting(cx, ion, obj, shape))
return false;
addedSetterStub = true;
} else {
RootedObject holder(cx);
if (!JSObject::lookupProperty(cx, obj, name, &holder, &shape))
return false;
if (IsPropertySetterCallInlineable(cx, obj, holder, shape)) {
if (!cache.attachSetterCall(cx, ion, obj, holder, shape, returnAddr))
return false;
addedSetterStub = true;
}
}
}
uint32_t oldSlots = obj->numDynamicSlots();
RootedShape oldShape(cx, obj->lastProperty());
// Set/Add the property on the object, the inlined cache are setup for the next execution.
if (!SetProperty(cx, obj, name, value, cache.strict(), cache.isSetName()))
return false;
// The property did not exist before, now we can try to inline the property add.
if (inlinable && !addedSetterStub && obj->lastProperty() != oldShape &&
IsPropertyAddInlineable(cx, obj, id, oldSlots, &shape))
{
RootedShape newShape(cx, obj->lastProperty());
if (!cache.attachNativeAdding(cx, ion, obj, oldShape, newShape, shape))
return false;
}
return true;
}
const size_t GetElementIC::MAX_FAILED_UPDATES = 16;
bool
GetElementIC::attachGetProp(JSContext *cx, IonScript *ion, HandleObject obj,
const Value &idval, HandlePropertyName name)
{
JS_ASSERT(index().reg().hasValue());
RootedObject holder(cx);
RootedShape shape(cx);
if (!JSObject::lookupProperty(cx, obj, name, &holder, &shape))
return false;
RootedScript script(cx);
jsbytecode *pc;
getScriptedLocation(&script, &pc);
if (!IsCacheableGetPropReadSlot(obj, holder, shape) &&
!IsCacheableNoProperty(obj, holder, shape, pc, output())) {
IonSpew(IonSpew_InlineCaches, "GETELEM uncacheable property");
return true;
}
JS_ASSERT(idval.isString());
Label failures;
MacroAssembler masm(cx);
// Guard on the index value.
ValueOperand val = index().reg().valueReg();
masm.branchTestValue(Assembler::NotEqual, val, idval, &failures);
RepatchStubAppender attacher(*this);
GenerateReadSlot(cx, ion, masm, attacher, obj, name, holder, shape, object(), output(),
&failures);
return linkAndAttachStub(cx, masm, attacher, ion, "property");
}
bool
GetElementIC::attachDenseElement(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval)
{
JS_ASSERT(obj->isNative());
JS_ASSERT(idval.isInt32());
Label failures;
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
// Guard object's shape.
RootedObject globalObj(cx, &script->global());
RootedShape shape(cx, obj->lastProperty());
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object(), shape, &failures);
// Ensure the index is an int32 value.
Register indexReg = InvalidReg;
if (index().reg().hasValue()) {
indexReg = output().scratchReg().gpr();
JS_ASSERT(indexReg != InvalidReg);
ValueOperand val = index().reg().valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
// Unbox the index.
masm.unboxInt32(val, indexReg);
} else {
JS_ASSERT(!index().reg().typedReg().isFloat());
indexReg = index().reg().typedReg().gpr();
}
// Load elements vector.
masm.push(object());
masm.loadPtr(Address(object(), JSObject::offsetOfElements()), object());
Label hole;
// Guard on the initialized length.
Address initLength(object(), ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);
// Check for holes & load the value.
masm.loadElementTypedOrValue(BaseIndex(object(), indexReg, TimesEight),
output(), true, &hole);
masm.pop(object());
attacher.jumpRejoin(masm);
// All failures flow to here.
masm.bind(&hole);
masm.pop(object());
masm.bind(&failures);
attacher.jumpNextStub(masm);
setHasDenseStub();
return linkAndAttachStub(cx, masm, attacher, ion, "dense array");
}
bool
GetElementIC::attachTypedArrayElement(JSContext *cx, IonScript *ion, JSObject *obj,
const Value &idval)
{
JS_ASSERT(obj->isTypedArray());
Label failures;
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
// The array type is the object within the table of typed array classes.
int arrayType = TypedArray::type(obj);
// The output register is not yet specialized as a float register, the only
// way to accept float typed arrays for now is to return a Value type.
DebugOnly<bool> floatOutput = arrayType == TypedArray::TYPE_FLOAT32 ||
arrayType == TypedArray::TYPE_FLOAT64;
JS_ASSERT_IF(!output().hasValue(), !floatOutput);
Register tmpReg = output().scratchReg().gpr();
JS_ASSERT(tmpReg != InvalidReg);
// Check that the typed array is of the same type as the current object
// because load size differ in function of the typed array data width.
masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, obj->getClass(), &failures);
// Decide to what type index the stub should be optimized
Register indexReg = tmpReg;
JS_ASSERT(!index().constant());
if (idval.isString()) {
JS_ASSERT(GetIndexFromString(idval.toString()) != UINT32_MAX);
// Part 1: Get the string into a register
Register str;
if (index().reg().hasValue()) {
ValueOperand val = index().reg().valueReg();
masm.branchTestString(Assembler::NotEqual, val, &failures);
str = masm.extractString(val, indexReg);
} else {
JS_ASSERT(!index().reg().typedReg().isFloat());
str = index().reg().typedReg().gpr();
}
// Part 2: Call to translate the str into index
RegisterSet regs = RegisterSet::Volatile();
masm.PushRegsInMask(regs);
regs.maybeTake(str);
Register temp = regs.takeGeneral();
masm.setupUnalignedABICall(1, temp);
masm.passABIArg(str);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, GetIndexFromString));
masm.mov(ReturnReg, indexReg);
RegisterSet ignore = RegisterSet();
ignore.add(indexReg);
masm.PopRegsInMaskIgnore(RegisterSet::Volatile(), ignore);
masm.branch32(Assembler::Equal, indexReg, Imm32(UINT32_MAX), &failures);
} else {
JS_ASSERT(idval.isInt32());
if (index().reg().hasValue()) {
ValueOperand val = index().reg().valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
// Unbox the index.
masm.unboxInt32(val, indexReg);
} else {
JS_ASSERT(!index().reg().typedReg().isFloat());
indexReg = index().reg().typedReg().gpr();
}
}
// Guard on the initialized length.
Address length(object(), TypedArray::lengthOffset());
masm.branch32(Assembler::BelowOrEqual, length, indexReg, &failures);
// Save the object register on the stack in case of failure.
Label popAndFail;
Register elementReg = object();
masm.push(object());
// Load elements vector.
masm.loadPtr(Address(object(), TypedArray::dataOffset()), elementReg);
// Load the value. We use an invalid register because the destination
// register is necessary a non double register.
int width = TypedArray::slotWidth(arrayType);
BaseIndex source(elementReg, indexReg, ScaleFromElemWidth(width));
if (output().hasValue())
masm.loadFromTypedArray(arrayType, source, output().valueReg(), true,
elementReg, &popAndFail);
else
masm.loadFromTypedArray(arrayType, source, output().typedReg(),
elementReg, &popAndFail);
masm.pop(object());
attacher.jumpRejoin(masm);
// Restore the object before continuing to the next stub.
masm.bind(&popAndFail);
masm.pop(object());
masm.bind(&failures);
attacher.jumpNextStub(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "typed array");
}
bool
GetElementIC::attachArgumentsElement(JSContext *cx, IonScript *ion, JSObject *obj)
{
JS_ASSERT(obj->is<ArgumentsObject>());
Label failures;
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
Register tmpReg = output().scratchReg().gpr();
JS_ASSERT(tmpReg != InvalidReg);
Class *clasp = obj->is<StrictArgumentsObject>() ? &StrictArgumentsObject::class_
: &NormalArgumentsObject::class_;
Label fail;
Label pass;
masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures);
// Get initial ArgsObj length value, test if length has been overridden.
masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
&failures);
masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
// Decide to what type index the stub should be optimized
Register indexReg;
JS_ASSERT(!index().constant());
// Check index against length.
Label failureRestoreIndex;
if (index().reg().hasValue()) {
ValueOperand val = index().reg().valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
indexReg = val.scratchReg();
masm.unboxInt32(val, indexReg);
masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failureRestoreIndex);
} else {
JS_ASSERT(index().reg().type() == MIRType_Int32);
indexReg = index().reg().typedReg().gpr();
masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failures);
}
// Save indexReg because it needs to be clobbered to check deleted bit.
Label failurePopIndex;
masm.push(indexReg);
// Check if property was deleted on arguments object.
masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
masm.loadPtr(Address(tmpReg, offsetof(ArgumentsData, deletedBits)), tmpReg);
// In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord)
masm.rshiftPtr(Imm32(JS_BITS_PER_WORD_LOG2), indexReg);
masm.loadPtr(BaseIndex(tmpReg, indexReg, ScaleFromElemWidth(sizeof(size_t))), tmpReg);
// Don't bother testing specific bit, if any bit is set in the word, fail.
masm.branchPtr(Assembler::NotEqual, tmpReg, ImmWord((size_t)0), &failurePopIndex);
// Get the address to load from into tmpReg
masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg);
// Restore original index register value, to use for indexing element.
masm.pop(indexReg);
BaseIndex elemIdx(tmpReg, indexReg, ScaleFromElemWidth(sizeof(Value)));
// Ensure result is not magic value, and type-check result.
masm.branchTestMagic(Assembler::Equal, elemIdx, &failureRestoreIndex);
if (output().hasTyped()) {
JS_ASSERT(!output().typedReg().isFloat());
JS_ASSERT(index().reg().type() == MIRType_Boolean ||
index().reg().type() == MIRType_Int32 ||
index().reg().type() == MIRType_String ||
index().reg().type() == MIRType_Object);
masm.branchTestMIRType(Assembler::NotEqual, elemIdx, index().reg().type(),
&failureRestoreIndex);
}
masm.loadTypedOrValue(elemIdx, output());
// indexReg may need to be reconstructed if it was originally a value.
if (index().reg().hasValue())
masm.tagValue(JSVAL_TYPE_INT32, indexReg, index().reg().valueReg());
// Success.
attacher.jumpRejoin(masm);
// Restore the object before continuing to the next stub.
masm.bind(&failurePopIndex);
masm.pop(indexReg);
masm.bind(&failureRestoreIndex);
if (index().reg().hasValue())
masm.tagValue(JSVAL_TYPE_INT32, indexReg, index().reg().valueReg());
masm.bind(&failures);
attacher.jumpNextStub(masm);
if (obj->is<StrictArgumentsObject>()) {
JS_ASSERT(!hasStrictArgumentsStub_);
hasStrictArgumentsStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (strict)");
}
JS_ASSERT(!hasNormalArgumentsStub_);
hasNormalArgumentsStub_ = true;
return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (normal)");
}
static bool
ShouldAttachGetElemTypedArray(JSObject *obj, const Value &idval,
TypedOrValueRegister output)
{
if (!obj->isTypedArray())
return false;
if (!idval.isInt32() && !idval.isString())
return false;
// Don't emit a stub if the access is out of bounds. We make to make
// certain that we monitor the type coming out of the typed array when
// we generate the stub. Out of bounds accesses will hit the fallback
// path.
uint32_t index;
if (idval.isInt32()) {
index = idval.toInt32();
} else {
index = GetIndexFromString(idval.toString());
if (index == UINT32_MAX)
return false;
}
if (index >= TypedArray::length(obj))
return false;
// The output register is not yet specialized as a float register, the only
// way to accept float typed arrays for now is to return a Value type.
int arrayType = TypedArray::type(obj);
bool floatOutput = arrayType == TypedArray::TYPE_FLOAT32 ||
arrayType == TypedArray::TYPE_FLOAT64;
return !floatOutput || output.hasValue();
}
bool
GetElementIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
HandleValue idval, MutableHandleValue res)
{
IonScript *ion = GetTopIonJSScript(cx)->ionScript();
GetElementIC &cache = ion->getCache(cacheIndex).toGetElement();
RootedScript script(cx);
jsbytecode *pc;
cache.getScriptedLocation(&script, &pc);
RootedValue lval(cx, ObjectValue(*obj));
// Override the return value if the script is invalidated (bug 728188).
AutoDetectInvalidation adi(cx, res.address(), ion);
if (cache.isDisabled()) {
if (!GetElementOperation(cx, JSOp(*pc), &lval, idval, res))
return false;
types::TypeScript::Monitor(cx, script, pc, res);
return true;
}
AutoFlushCache afc("GetElementCache");
RootedId id(cx);
if (!ValueToId<CanGC>(cx, idval, &id))
return false;
bool attachedStub = false;
if (cache.canAttachStub()) {
if (IsOptimizableArgumentsObjectForGetElem(obj, idval) &&
!cache.hasArgumentsStub(obj->is<StrictArgumentsObject>()) &&
!cache.index().constant() &&
(cache.index().reg().hasValue() ||
cache.index().reg().type() == MIRType_Int32) &&
(cache.output().hasValue() || !cache.output().typedReg().isFloat()))
{
if (!cache.attachArgumentsElement(cx, ion, obj))
return false;
attachedStub = true;
}
if (!attachedStub && obj->isNative() && cache.monitoredResult()) {
uint32_t dummy;
if (idval.isString() && JSID_IS_ATOM(id) && !JSID_TO_ATOM(id)->isIndex(&dummy)) {
RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
if (!cache.attachGetProp(cx, ion, obj, idval, name))
return false;
attachedStub = true;
}
}
if (!attachedStub && !cache.hasDenseStub() && obj->isNative() && idval.isInt32()) {
if (!cache.attachDenseElement(cx, ion, obj, idval))
return false;
attachedStub = true;
}
if (!attachedStub && ShouldAttachGetElemTypedArray(obj, idval, cache.output())) {
if (!cache.attachTypedArrayElement(cx, ion, obj, idval))
return false;
attachedStub = true;
}
}
if (!GetElementOperation(cx, JSOp(*pc), &lval, idval, res))
return false;
// Disable cache when we reach max stubs or update failed too much.
if (!attachedStub) {
cache.incFailedUpdates();
if (cache.shouldDisable()) {
IonSpew(IonSpew_InlineCaches, "Disable inline cache");
cache.disable();
}
} else {
cache.resetFailedUpdates();
}
types::TypeScript::Monitor(cx, script, pc, res);
return true;
}
void
GetElementIC::reset()
{
RepatchIonCache::reset();
hasDenseStub_ = false;
hasStrictArgumentsStub_ = false;
hasNormalArgumentsStub_ = false;
}
static bool
IsElementSetInlineable(HandleObject obj, HandleValue index)
{
if (!obj->isArray())
return false;
if (obj->watched())
return false;
if (!index.isInt32())
return false;
return true;
}
bool
SetElementIC::attachDenseElement(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval)
{
JS_ASSERT(obj->isNative());
JS_ASSERT(idval.isInt32());
Label failures;
Label outOfBounds; // index >= capacity || index > initialized length
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
// Guard object is a dense array.
RootedObject globalObj(cx, &script->global());
RootedShape shape(cx, obj->lastProperty());
if (!shape)
return false;
masm.branchTestObjShape(Assembler::NotEqual, object(), shape, &failures);
// Ensure the index is an int32 value.
ValueOperand indexVal = index();
masm.branchTestInt32(Assembler::NotEqual, indexVal, &failures);
// Unbox the index.
Register index = masm.extractInt32(indexVal, tempToUnboxIndex());
{
// Load obj->elements.
Register elements = temp();
masm.loadPtr(Address(object(), JSObject::offsetOfElements()), elements);
// Compute the location of the element.
BaseIndex target(elements, index, TimesEight);
// Guard that we can increase the initialized length.
Address capacity(elements, ObjectElements::offsetOfCapacity());
masm.branch32(Assembler::BelowOrEqual, capacity, index, &outOfBounds);
// Guard on the initialized length.
Address initLength(elements, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::Below, initLength, index, &outOfBounds);
// if (initLength == index)
Label markElem, storeElem;
masm.branch32(Assembler::NotEqual, initLength, index, &markElem);
{
// Increase initialize length.
Int32Key newLength(index);
masm.bumpKey(&newLength, 1);
masm.storeKey(newLength, initLength);
// Increase length if needed.
Label bumpedLength;
Address length(elements, ObjectElements::offsetOfLength());
masm.branch32(Assembler::AboveOrEqual, length, index, &bumpedLength);
masm.storeKey(newLength, length);
masm.bind(&bumpedLength);
// Restore the index.
masm.bumpKey(&newLength, -1);
masm.jump(&storeElem);
}
// else
{
// Mark old element.
masm.bind(&markElem);
if (cx->zone()->needsBarrier())
masm.callPreBarrier(target, MIRType_Value);
}
// Store the value.
masm.bind(&storeElem);
masm.storeConstantOrRegister(value(), target);
}
attacher.jumpRejoin(masm);
// All failures flow to here.
masm.bind(&outOfBounds);
masm.bind(&failures);
attacher.jumpNextStub(masm);
setHasDenseStub();
return linkAndAttachStub(cx, masm, attacher, ion, "dense array");
}
bool
SetElementIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
HandleValue idval, HandleValue value)
{
IonScript *ion = GetTopIonJSScript(cx)->ionScript();
SetElementIC &cache = ion->getCache(cacheIndex).toSetElement();
if (cache.canAttachStub() && !cache.hasDenseStub() && IsElementSetInlineable(obj, idval)) {
if (!cache.attachDenseElement(cx, ion, obj, idval))
return false;
}
if (!SetObjectElement(cx, obj, idval, value, cache.strict()))
return false;
return true;
}
void
SetElementIC::reset()
{
RepatchIonCache::reset();
hasDenseStub_ = false;
}
bool
BindNameIC::attachGlobal(JSContext *cx, IonScript *ion, JSObject *scopeChain)
{
JS_ASSERT(scopeChain->is<GlobalObject>());
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
// Guard on the scope chain.
attacher.branchNextStub(masm, Assembler::NotEqual, scopeChainReg(),
ImmGCPtr(scopeChain));
masm.movePtr(ImmGCPtr(scopeChain), outputReg());
attacher.jumpRejoin(masm);
return linkAndAttachStub(cx, masm, attacher, ion, "global");
}
static inline void
GenerateScopeChainGuard(MacroAssembler &masm, JSObject *scopeObj,
Register scopeObjReg, Shape *shape, Label *failures)
{
if (scopeObj->is<CallObject>()) {
// We can skip a guard on the call object if the script's bindings are
// guaranteed to be immutable (and thus cannot introduce shadowing
// variables).
CallObject *callObj = &scopeObj->as<CallObject>();
if (!callObj->isForEval()) {
JSFunction *fun = &callObj->callee();
JSScript *script = fun->nonLazyScript();
if (!script->funHasExtensibleScope)
return;
}
} else if (scopeObj->is<GlobalObject>()) {
// If this is the last object on the scope walk, and the property we've
// found is not configurable, then we don't need a shape guard because
// the shape cannot be removed.
if (shape && !shape->configurable())
return;
}
Address shapeAddr(scopeObjReg, JSObject::offsetOfShape());
masm.branchPtr(Assembler::NotEqual, shapeAddr, ImmGCPtr(scopeObj->lastProperty()), failures);
}
static void
GenerateScopeChainGuards(MacroAssembler &masm, JSObject *scopeChain, JSObject *holder,
Register outputReg, Label *failures)
{
JSObject *tobj = scopeChain;
// Walk up the scope chain. Note that IsCacheableScopeChain guarantees the
// |tobj == holder| condition terminates the loop.
while (true) {
JS_ASSERT(IsCacheableNonGlobalScope(tobj) || tobj->is<GlobalObject>());
GenerateScopeChainGuard(masm, tobj, outputReg, NULL, failures);
if (tobj == holder)
break;
// Load the next link.
tobj = &tobj->as<ScopeObject>().enclosingScope();
masm.extractObject(Address(outputReg, ScopeObject::offsetOfEnclosingScope()), outputReg);
}
}
bool
BindNameIC::attachNonGlobal(JSContext *cx, IonScript *ion, JSObject *scopeChain, JSObject *holder)
{
JS_ASSERT(IsCacheableNonGlobalScope(scopeChain));
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
// Guard on the shape of the scope chain.
Label failures;
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(scopeChainReg(), JSObject::offsetOfShape()),
ImmGCPtr(scopeChain->lastProperty()),
holder != scopeChain ? &failures : NULL);
if (holder != scopeChain) {
JSObject *parent = &scopeChain->as<ScopeObject>().enclosingScope();
masm.extractObject(Address(scopeChainReg(), ScopeObject::offsetOfEnclosingScope()), outputReg());
GenerateScopeChainGuards(masm, parent, holder, outputReg(), &failures);
} else {
masm.movePtr(scopeChainReg(), outputReg());
}
// At this point outputReg holds the object on which the property
// was found, so we're done.
attacher.jumpRejoin(masm);
// All failures flow to here, so there is a common point to patch.
if (holder != scopeChain) {
masm.bind(&failures);
attacher.jumpNextStub(masm);
}
return linkAndAttachStub(cx, masm, attacher, ion, "non-global");
}
static bool
IsCacheableScopeChain(JSObject *scopeChain, JSObject *holder)
{
while (true) {
if (!IsCacheableNonGlobalScope(scopeChain)) {
IonSpew(IonSpew_InlineCaches, "Non-cacheable object on scope chain");
return false;
}
if (scopeChain == holder)
return true;
scopeChain = &scopeChain->as<ScopeObject>().enclosingScope();
if (!scopeChain) {
IonSpew(IonSpew_InlineCaches, "Scope chain indirect hit");
return false;
}
}
JS_NOT_REACHED("Shouldn't get here");
return false;
}
JSObject *
BindNameIC::update(JSContext *cx, size_t cacheIndex, HandleObject scopeChain)
{
AutoFlushCache afc ("BindNameCache");
IonScript *ion = GetTopIonJSScript(cx)->ionScript();
BindNameIC &cache = ion->getCache(cacheIndex).toBindName();
HandlePropertyName name = cache.name();
RootedObject holder(cx);
if (scopeChain->is<GlobalObject>()) {
holder = scopeChain;
} else {
if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &holder))
return NULL;
}
// Stop generating new stubs once we hit the stub count limit, see
// GetPropertyCache.
if (cache.canAttachStub()) {
if (scopeChain->is<GlobalObject>()) {
if (!cache.attachGlobal(cx, ion, scopeChain))
return NULL;
} else if (IsCacheableScopeChain(scopeChain, holder)) {
if (!cache.attachNonGlobal(cx, ion, scopeChain, holder))
return NULL;
} else {
IonSpew(IonSpew_InlineCaches, "BINDNAME uncacheable scope chain");
}
}
return holder;
}
bool
NameIC::attachReadSlot(JSContext *cx, IonScript *ion, HandleObject scopeChain, HandleObject holder,
HandleShape shape)
{
MacroAssembler masm(cx);
Label failures;
RepatchStubAppender attacher(*this);
Register scratchReg = outputReg().valueReg().scratchReg();
masm.mov(scopeChainReg(), scratchReg);
GenerateScopeChainGuards(masm, scopeChain, holder, scratchReg, &failures);
unsigned slot = shape->slot();
if (holder->isFixedSlot(slot)) {
Address addr(scratchReg, JSObject::getFixedSlotOffset(slot));
masm.loadTypedOrValue(addr, outputReg());
} else {
masm.loadPtr(Address(scratchReg, JSObject::offsetOfSlots()), scratchReg);
Address addr(scratchReg, holder->dynamicSlotIndex(slot) * sizeof(Value));
masm.loadTypedOrValue(addr, outputReg());
}
attacher.jumpRejoin(masm);
if (failures.used()) {
masm.bind(&failures);
attacher.jumpNextStub(masm);
}
return linkAndAttachStub(cx, masm, attacher, ion, "generic");
}
static bool
IsCacheableNameReadSlot(JSContext *cx, HandleObject scopeChain, HandleObject obj,
HandleObject holder, HandleShape shape, jsbytecode *pc,
const TypedOrValueRegister &output)
{
if (!shape)
return false;
if (!obj->isNative())
return false;
if (obj != holder)
return false;
if (obj->is<GlobalObject>())