Bug 856627 - Add stubs to handle ListBase GetProp invoking native getters.

This commit is contained in:
Kannan Vijayan 2013-04-11 23:45:16 -04:00
parent d33503213b
commit 39f78382b4
5 changed files with 417 additions and 33 deletions

View File

@ -302,6 +302,18 @@ ICStub::trace(JSTracer *trc)
MarkShape(trc, &propStub->holderShape(), "baseline-getpropnativeproto-stub-holdershape");
break;
}
case ICStub::GetProp_CallListBaseNative: {
ICGetProp_CallListBaseNative *propStub = toGetProp_CallListBaseNative();
MarkShape(trc, &propStub->shape(), "baseline-getproplistbasenative-stub-shape");
if (propStub->expandoShape()) {
MarkShape(trc, &propStub->expandoShape(),
"baseline-getproplistbasenative-stub-expandoshape");
}
MarkObject(trc, &propStub->holder(), "baseline-getproplistbasenative-stub-holder");
MarkShape(trc, &propStub->holderShape(), "baseline-getproplistbasenative-stub-holdershape");
MarkObject(trc, &propStub->getter(), "baseline-getproplistbasenative-stub-getter");
break;
}
case ICStub::GetProp_CallScripted: {
ICGetProp_CallScripted *callStub = toGetProp_CallScripted();
MarkShape(trc, &callStub->shape(), "baseline-getpropcallscripted-stub-shape");
@ -595,8 +607,8 @@ ICStubCompiler::guardProfilingEnabled(MacroAssembler &masm, Register scratch, La
// This should only be called from the following stubs.
JS_ASSERT(kind == ICStub::Call_Scripted || kind == ICStub::Call_AnyScripted ||
kind == ICStub::Call_Native || kind == ICStub::GetProp_CallScripted ||
kind == ICStub::GetProp_CallNative || kind == ICStub::SetProp_CallScripted ||
kind == ICStub::SetProp_CallNative);
kind == ICStub::GetProp_CallNative || kind == ICStub::GetProp_CallListBaseNative ||
kind == ICStub::SetProp_CallScripted || kind == ICStub::SetProp_CallNative);
// Guard on bit in frame that indicates if the SPS frame was pushed in the first
// place. This code is expected to be called from within a stub that has already
@ -2996,57 +3008,171 @@ static void GetFixedOrDynamicSlotOffset(HandleObject obj, uint32_t slot,
: obj->dynamicSlotIndex(slot) * sizeof(Value);
}
static bool
IsCacheableListBase(JSObject *obj)
{
if (!obj->isProxy())
return false;
BaseProxyHandler *handler = GetProxyHandler(obj);
if (handler->family() != GetListBaseHandlerFamily())
return false;
if (obj->numFixedSlots() <= GetListBaseExpandoSlot())
return false;
return true;
}
static JSObject *
GetListBaseProto(JSObject *obj)
{
JS_ASSERT(IsCacheableListBase(obj));
return obj->getTaggedProto().toObjectOrNull();
}
static void
GenerateListBaseChecks(JSContext *cx, MacroAssembler &masm, Register object,
Address checkProxyHandlerAddr,
Address checkExpandoShapeAddr,
Register scratch,
GeneralRegisterSet &listBaseRegSet,
Label *checkFailed)
{
// Guard the following:
// 1. The object is a ListBase.
// 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 expandoAddr(object, JSObject::getFixedSlotOffset(GetListBaseExpandoSlot()));
// Check that object is a ListBase.
masm.loadPtr(checkProxyHandlerAddr, scratch);
masm.branchPrivatePtr(Assembler::NotEqual, handlerAddr, scratch, checkFailed);
// For the remaining code, we need to reserve some registers to load a value.
// This is ugly, but unvaoidable.
ValueOperand tempVal = listBaseRegSet.takeAnyValue();
masm.pushValue(tempVal);
Label failListBaseCheck;
Label listBaseOk;
masm.loadValue(expandoAddr, tempVal);
// If the incoming object does not have an expando object then we're sure we're not
// shadowing.
masm.branchTestUndefined(Assembler::Equal, tempVal, &listBaseOk);
// The reference object used to generate this check may not have had an
// expando object at all, in which case the presence of a non-undefined
// expando value in the incoming object is automatically a failure.
masm.loadPtr(checkExpandoShapeAddr, scratch);
masm.branchPtr(Assembler::Equal, scratch, ImmWord((void*)NULL), &failListBaseCheck);
// Otherwise, ensure that the incoming object has an object for its expando value and that
// the shape matches.
masm.branchTestObject(Assembler::NotEqual, tempVal, &failListBaseCheck);
Register objReg = masm.extractObject(tempVal, tempVal.scratchReg());
masm.branchTestObjShape(Assembler::Equal, objReg, scratch, &listBaseOk);
// Failure case: restore the tempVal registers and jump to failures.
masm.bind(&failListBaseCheck);
masm.popValue(tempVal);
masm.jump(checkFailed);
// Success case: restore the tempval and proceed.
masm.bind(&listBaseOk);
masm.popValue(tempVal);
}
// Look up a property's shape on an object, being careful never to do any effectful
// operations. This procedure not yielding a shape should not be taken as a lack of
// existence of the property on the object.
static bool
EffectlesslyLookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name,
MutableHandleObject holder, MutableHandleShape shape)
MutableHandleObject holder, MutableHandleShape shape,
bool *checkListBase=NULL)
{
if (obj->hasIdempotentProtoChain()) {
if (!JSObject::lookupProperty(cx, obj, name, holder, shape))
shape.set(NULL);
holder.set(NULL);
bool isListBase = false;
if (checkListBase)
*checkListBase = false;
// Check for list base if asked to.
RootedObject checkObj(cx, obj);
if (checkListBase && IsCacheableListBase(obj)) {
*checkListBase = isListBase = true;
if (obj->hasUncacheableProto())
return true;
// Expando objects just hold any extra properties the object has been given by a script,
// and have no prototype or anything else that will complicate property lookups on them.
Value expandoVal = obj->getFixedSlot(GetListBaseExpandoSlot());
JS_ASSERT_IF(expandoVal.isObject(),
expandoVal.toObject().isNative() && !expandoVal.toObject().getProto());
if (expandoVal.isObject() && expandoVal.toObject().nativeContains(cx, name))
return true;
checkObj = GetListBaseProto(obj);
}
if (!isListBase && !obj->isNative())
return true;
if (checkObj->hasIdempotentProtoChain()) {
if (!JSObject::lookupProperty(cx, checkObj, name, holder, shape))
return false;
} else if (obj->isNative()) {
shape.set(obj->nativeLookup(cx, NameToId(name)));
} else if (checkObj->isNative()) {
shape.set(checkObj->nativeLookup(cx, NameToId(name)));
if (shape)
holder.set(obj);
} else {
shape.set(NULL);
holder.set(NULL);
holder.set(checkObj);
}
return true;
}
static bool
IsCacheableProtoChain(JSObject *obj, JSObject *holder)
IsCacheableProtoChain(JSObject *obj, JSObject *holder, bool isListBase=false)
{
JS_ASSERT(obj->isNative());
JS_ASSERT_IF(isListBase, IsCacheableListBase(obj));
JS_ASSERT_IF(!isListBase, obj->isNative());
// Don't handle objects which require a prototype guard. This should
// be uncommon so handling it is likely not worth the complexity.
if (obj->hasUncacheableProto())
return false;
while (obj != holder) {
JSObject *cur = obj;
while (cur != 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 = obj->getProto();
JSObject *proto;
if (isListBase && cur == obj)
proto = cur->getTaggedProto().toObjectOrNull();
else
proto = cur->getProto();
if (!proto || !proto->isNative())
return false;
if (proto->hasUncacheableProto())
return false;
obj = proto;
cur = proto;
}
return true;
}
static bool
IsCacheableGetPropReadSlot(JSObject *obj, JSObject *holder, Shape *shape)
IsCacheableGetPropReadSlot(JSObject *obj, JSObject *holder, Shape *shape, bool isListBase=false)
{
if (!shape || !IsCacheableProtoChain(obj, holder))
if (!shape || !IsCacheableProtoChain(obj, holder, isListBase))
return false;
if (!shape->hasSlot() || !shape->hasDefaultGetter())
@ -3056,7 +3182,8 @@ IsCacheableGetPropReadSlot(JSObject *obj, JSObject *holder, Shape *shape)
}
static bool
IsCacheableGetPropCall(JSObject *obj, JSObject *holder, Shape *shape, bool *isScripted)
IsCacheableGetPropCall(JSObject *obj, JSObject *holder, Shape *shape, bool *isScripted,
bool isListBase=false)
{
JS_ASSERT(isScripted);
@ -3064,7 +3191,7 @@ IsCacheableGetPropCall(JSObject *obj, JSObject *holder, Shape *shape, bool *isSc
if (obj == holder)
return false;
if (!shape || !IsCacheableProtoChain(obj, holder))
if (!shape || !IsCacheableProtoChain(obj, holder, isListBase))
return false;
if (shape->hasSlot() || shape->hasDefaultGetter())
@ -4766,16 +4893,18 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc,
return true;
RootedObject obj(cx, &val.toObject());
if (!obj->isNative())
return true;
bool isListBase;
RootedShape shape(cx);
RootedObject holder(cx);
if (!EffectlesslyLookupProperty(cx, obj, name, &holder, &shape))
if (!EffectlesslyLookupProperty(cx, obj, name, &holder, &shape, &isListBase))
return false;
if (!isListBase && !obj->isNative())
return true;
ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
if (IsCacheableGetPropReadSlot(obj, holder, shape)) {
if (!isListBase && IsCacheableGetPropReadSlot(obj, holder, shape)) {
bool isFixedSlot;
uint32_t offset;
GetFixedOrDynamicSlotOffset(holder, shape->slot(), &isFixedSlot, &offset);
@ -4783,7 +4912,8 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc,
ICStub::Kind kind = (obj == holder) ? ICStub::GetProp_Native
: ICStub::GetProp_NativePrototype;
IonSpew(IonSpew_BaselineIC, " Generating GetProp(Native %s) stub",
IonSpew(IonSpew_BaselineIC, " Generating GetProp(%s %s%s) stub",
isListBase ? "ListBase" : "Native",
(obj == holder) ? "direct" : "prototype");
ICGetPropNativeCompiler compiler(cx, kind, monitorStub, obj, holder, isFixedSlot, offset);
ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
@ -4796,10 +4926,10 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc,
}
bool isScripted = false;
bool cacheableCall = IsCacheableGetPropCall(obj, holder, shape, &isScripted);
bool cacheableCall = IsCacheableGetPropCall(obj, holder, shape, &isScripted, isListBase);
// Try handling scripted getters.
if (cacheableCall && isScripted) {
if (cacheableCall && isScripted && !isListBase) {
RootedFunction callee(cx, shape->getterObject()->toFunction());
JS_ASSERT(obj != holder);
JS_ASSERT(callee->hasScript());
@ -4824,15 +4954,25 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc,
JS_ASSERT(obj != holder);
JS_ASSERT(callee->isNative());
IonSpew(IonSpew_BaselineIC, " Generating GetProp(NativeObj/NativeGetter %p) stub",
callee->native());
ICStub *newStub = NULL;
if (isListBase) {
IonSpew(IonSpew_BaselineIC, " Generating GetProp(ListBaseObj/NativeGetter %p) stub",
callee->native());
ICGetProp_CallNative::Compiler compiler(cx, monitorStub, obj, holder, callee,
pc - script->code);
ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
ICGetProp_CallListBaseNative::Compiler compiler(cx, monitorStub, obj, holder, callee,
pc - script->code);
newStub = compiler.getStub(compiler.getStubSpace(script));
} else {
IonSpew(IonSpew_BaselineIC, " Generating GetProp(NativeObj/NativeGetter %p) stub",
callee->native());
ICGetProp_CallNative::Compiler compiler(cx, monitorStub, obj, holder, callee,
pc - script->code);
newStub = compiler.getStub(compiler.getStubSpace(script));
}
if (!newStub)
return false;
stub->addNewStub(newStub);
*attached = true;
return true;
@ -5336,6 +5476,98 @@ ICGetProp_CallNative::Compiler::generateStubCode(MacroAssembler &masm)
return true;
}
bool
ICGetProp_CallListBaseNative::Compiler::generateStubCode(MacroAssembler &masm)
{
Label failure;
GeneralRegisterSet regs(availableGeneralRegs(1));
Register scratch;
if (regs.has(BaselineTailCallReg)) {
regs.take(BaselineTailCallReg);
scratch = regs.takeAny();
regs.add(BaselineTailCallReg);
} else {
scratch = regs.takeAny();
}
// Guard input is an object.
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
// Unbox.
Register objReg = masm.extractObject(R0, ExtractTemp0);
// Shape guard.
masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallListBaseNative::offsetOfShape()), scratch);
masm.branchTestObjShape(Assembler::NotEqual, objReg, scratch, &failure);
// Guard for ListObject.
{
GeneralRegisterSet listBaseRegSet(GeneralRegisterSet::All());
listBaseRegSet.take(BaselineStubReg);
listBaseRegSet.take(objReg);
listBaseRegSet.take(scratch);
GenerateListBaseChecks(
cx, masm, objReg,
Address(BaselineStubReg, ICGetProp_CallListBaseNative::offsetOfProxyHandler()),
Address(BaselineStubReg, ICGetProp_CallListBaseNative::offsetOfExpandoShape()),
scratch,
listBaseRegSet,
&failure);
}
Register holderReg = regs.takeAny();
masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallListBaseNative::offsetOfHolder()),
holderReg);
masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallListBaseNative::offsetOfHolderShape()),
scratch);
masm.branchTestObjShape(Assembler::NotEqual, holderReg, scratch, &failure);
regs.add(holderReg);
// Push a stub frame so that we can perform a non-tail call.
enterStubFrame(masm, scratch);
// Load callee function.
Register callee = regs.takeAny();
masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallListBaseNative::offsetOfGetter()), callee);
// Push args for vm call.
masm.push(objReg);
masm.push(callee);
// Don't have to preserve R0 anymore.
regs.add(R0);
// If needed, update SPS Profiler frame entry.
{
Label skipProfilerUpdate;
Register scratch = regs.takeAny();
Register pcIdx = regs.takeAny();
// Check if profiling is enabled.
guardProfilingEnabled(masm, scratch, &skipProfilerUpdate);
// Update profiling entry before leaving function.
masm.load32(Address(BaselineStubReg, ICGetProp_CallListBaseNative::offsetOfPCOffset()),
pcIdx);
masm.spsUpdatePCIdx(&cx->runtime->spsProfiler, pcIdx, scratch);
masm.bind(&skipProfilerUpdate);
regs.add(scratch);
regs.add(pcIdx);
}
if (!callVM(DoCallNativeGetterInfo, masm))
return false;
leaveStubFrame(masm);
// Enter type monitor IC to type-check result.
EmitEnterTypeMonitorIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
//
// SetProp_Fallback
//

View File

@ -12,6 +12,7 @@
#include "jscompartment.h"
#include "jsgcinlines.h"
#include "jsopcode.h"
#include "jsproxy.h"
#include "BaselineJIT.h"
#include "BaselineRegisters.h"
@ -363,6 +364,7 @@ class ICEntry
_(GetProp_NativePrototype) \
_(GetProp_CallScripted) \
_(GetProp_CallNative) \
_(GetProp_CallListBaseNative)\
\
_(SetProp_Fallback) \
_(SetProp_Native) \
@ -724,6 +726,7 @@ class ICStub
case UseCount_Fallback:
case GetProp_CallScripted:
case GetProp_CallNative:
case GetProp_CallListBaseNative:
case SetProp_CallScripted:
case SetProp_CallNative:
return true;
@ -3933,6 +3936,7 @@ class ICGetProp_NativePrototype : public ICGetPropNativeStub
{
friend class ICStubSpace;
protected:
// Holder and its shape.
HeapPtrObject holder_;
HeapPtrShape holderShape_;
@ -3971,6 +3975,7 @@ class ICGetProp_NativePrototype : public ICGetPropNativeStub
}
};
// Compiler for GetProp_Native and GetProp_NativePrototype stubs.
class ICGetPropNativeCompiler : public ICStubCompiler
{
@ -4191,6 +4196,138 @@ class ICGetProp_CallNative : public ICGetPropCallGetter
};
};
class ICGetProp_CallListBaseNative : public ICMonitoredStub
{
friend class ICStubSpace;
protected:
// Shape of the ListBase proxy
HeapPtrShape shape_;
// Proxy handler to check against.
BaseProxyHandler *proxyHandler_;
// Object shape of expected expando object. (NULL if no expando object should be there)
HeapPtrShape expandoShape_;
// Holder and its shape.
HeapPtrObject holder_;
HeapPtrShape holderShape_;
// Function to call.
HeapPtrFunction getter_;
// PC offset of call
uint32_t pcOffset_;
ICGetProp_CallListBaseNative(IonCode *stubCode, ICStub *firstMonitorStub,
HandleShape shape, BaseProxyHandler *proxyHandler,
HandleShape expandoShape, HandleObject holder, HandleShape holderShape,
HandleFunction getter, uint32_t pcOffset)
: ICMonitoredStub(GetProp_CallListBaseNative, stubCode, firstMonitorStub),
shape_(shape),
proxyHandler_(proxyHandler),
expandoShape_(expandoShape),
holder_(holder),
holderShape_(holderShape),
getter_(getter),
pcOffset_(pcOffset)
{ }
public:
static inline ICGetProp_CallListBaseNative *New(
ICStubSpace *space, IonCode *code, ICStub *firstMonitorStub,
HandleShape shape, BaseProxyHandler *proxyHandler,
HandleShape expandoShape, HandleObject holder, HandleShape holderShape,
HandleFunction getter, uint32_t pcOffset)
{
if (!code)
return NULL;
return space->allocate<ICGetProp_CallListBaseNative>(code, firstMonitorStub, shape,
proxyHandler, expandoShape, holder,
holderShape, getter, pcOffset);
}
HeapPtrShape &shape() {
return shape_;
}
HeapPtrShape &expandoShape() {
return expandoShape_;
}
HeapPtrObject &holder() {
return holder_;
}
HeapPtrShape &holderShape() {
return holderShape_;
}
HeapPtrFunction &getter() {
return getter_;
}
uint32_t pcOffset() const {
return pcOffset_;
}
static size_t offsetOfShape() {
return offsetof(ICGetProp_CallListBaseNative, shape_);
}
static size_t offsetOfProxyHandler() {
return offsetof(ICGetProp_CallListBaseNative, proxyHandler_);
}
static size_t offsetOfExpandoShape() {
return offsetof(ICGetProp_CallListBaseNative, expandoShape_);
}
static size_t offsetOfHolder() {
return offsetof(ICGetProp_CallListBaseNative, holder_);
}
static size_t offsetOfHolderShape() {
return offsetof(ICGetProp_CallListBaseNative, holderShape_);
}
static size_t offsetOfGetter() {
return offsetof(ICGetProp_CallListBaseNative, getter_);
}
static size_t offsetOfPCOffset() {
return offsetof(ICGetProp_CallListBaseNative, pcOffset_);
}
class Compiler : public ICStubCompiler {
protected:
ICStub *firstMonitorStub_;
RootedObject obj_;
RootedObject holder_;
RootedFunction getter_;
uint32_t pcOffset_;
bool generateStubCode(MacroAssembler &masm);
public:
Compiler(JSContext *cx, ICStub *firstMonitorStub, HandleObject obj,
HandleObject holder, HandleFunction getter, uint32_t pcOffset)
: ICStubCompiler(cx, ICStub::GetProp_CallListBaseNative),
firstMonitorStub_(firstMonitorStub),
obj_(cx, obj),
holder_(cx, holder),
getter_(cx, getter),
pcOffset_(pcOffset)
{
JS_ASSERT(obj_->isProxy());
JS_ASSERT(GetProxyHandler(obj_)->family() == GetListBaseHandlerFamily());
}
ICStub *getStub(ICStubSpace *space) {
RootedShape shape(cx, obj_->lastProperty());
RootedShape holderShape(cx, holder_->lastProperty());
Value expandoVal = obj_->getFixedSlot(GetListBaseExpandoSlot());
RootedShape expandoShape(cx, NULL);
if (expandoVal.isObject())
expandoShape = expandoVal.toObject().lastProperty();
return ICGetProp_CallListBaseNative::New(
space, getStubCode(), firstMonitorStub_, shape, GetProxyHandler(obj_),
expandoShape, holder_, holderShape, getter_, pcOffset_);
}
};
};
// SetProp
// JSOP_SETPROP
// JSOP_SETNAME

View File

@ -762,6 +762,10 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM
branchPtr(cond, lhs, ptr, label);
}
void branchPrivatePtr(Condition cond, const Address &lhs, Register ptr, Label *label) {
branchPtr(cond, lhs, ptr, label);
}
void branchPrivatePtr(Condition cond, Register lhs, ImmWord ptr, Label *label) {
branchPtr(cond, lhs, ptr, label);
}

View File

@ -446,6 +446,13 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared
branchPtr(cond, lhs, ImmWord(ptr.value >> 1), label);
}
void branchPrivatePtr(Condition cond, Address lhs, Register ptr, Label *label) {
if (ptr != ScratchReg)
movePtr(ptr, ScratchReg);
rshiftPtr(Imm32(1), ScratchReg);
branchPtr(cond, lhs, ScratchReg, label);
}
template <typename T, typename S>
void branchPtr(Condition cond, T lhs, S ptr, Label *label) {
cmpPtr(Operand(lhs), ptr);

View File

@ -469,6 +469,10 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared
branchPtr(cond, lhs, ptr, label);
}
void branchPrivatePtr(Condition cond, const Address &lhs, Register ptr, Label *label) {
branchPtr(cond, lhs, ptr, label);
}
template <typename T, typename S>
void branchPtr(Condition cond, T lhs, S ptr, RepatchLabel *label) {
cmpl(Operand(lhs), ptr);