Bug 578761: PIC for JSOP_GETELEM. (r=dvander)

This commit is contained in:
Chris Leary 2010-07-20 21:16:47 -07:00
parent cdeede6c82
commit a887840fdf
20 changed files with 787 additions and 68 deletions

View File

@ -308,15 +308,12 @@ mjit::Compiler::finishThisUp()
}
for (size_t i = 0; i < pics.length(); i++) {
script->pics[i].kind = pics[i].kind;
pics[i].copySimpleMembersTo(script->pics[i]);
script->pics[i].fastPathStart = fullCode.locationOf(pics[i].hotPathBegin);
script->pics[i].storeBack = fullCode.locationOf(pics[i].storeBack);
script->pics[i].slowPathStart = stubCode.locationOf(pics[i].slowPathStart);
script->pics[i].callReturn = uint16((uint8*)stubCode.locationOf(pics[i].callReturn).executableAddress() -
(uint8*)script->pics[i].slowPathStart.executableAddress());
script->pics[i].shapeReg = pics[i].shapeReg;
script->pics[i].objReg = pics[i].objReg;
script->pics[i].atom = pics[i].atom;
script->pics[i].shapeGuard = masm.distanceOf(pics[i].shapeGuard) -
masm.distanceOf(pics[i].hotPathBegin);
script->pics[i].shapeRegHasBaseShape = true;
@ -324,15 +321,12 @@ mjit::Compiler::finishThisUp()
if (pics[i].kind == ic::PICInfo::SET) {
script->pics[i].u.vr = pics[i].vr;
} else if (pics[i].kind != ic::PICInfo::NAME) {
script->pics[i].u.get.typeReg = pics[i].typeReg;
if (pics[i].hasTypeCheck) {
int32 distance = stubcc.masm.distanceOf(pics[i].typeCheck) -
stubcc.masm.distanceOf(pics[i].slowPathStart);
script->pics[i].u.get.typeCheckOffset = uint16(-distance);
JS_ASSERT(script->pics[i].u.get.typeCheckOffset == -distance);
}
script->pics[i].u.get.hasTypeCheck = pics[i].hasTypeCheck;
script->pics[i].u.get.objRemat = pics[i].objRemat.offset;
}
new (&script->pics[i].execPools) ic::PICInfo::ExecPoolVector(SystemAllocPolicy());
}
@ -1961,6 +1955,56 @@ mjit::Compiler::jsop_getprop(JSAtom *atom, bool doTypeCheck)
pics.append(pic);
}
#ifdef JS_POLYIC
void
mjit::Compiler::jsop_getelem_pic(FrameEntry *obj, FrameEntry *id, RegisterID objReg,
RegisterID idReg, RegisterID shapeReg)
{
PICGenInfo pic(ic::PICInfo::GETELEM);
pic.objRemat = frame.dataRematInfo(obj);
pic.idRemat = frame.dataRematInfo(id);
pic.shapeReg = shapeReg;
pic.hasTypeCheck = false;
pic.hotPathBegin = masm.label();
/* Guard on shape. */
masm.loadPtr(Address(objReg, offsetof(JSObject, map)), shapeReg);
masm.load32(Address(shapeReg, offsetof(JSObjectMap, shape)), shapeReg);
pic.shapeGuard = masm.label();
Jump shapeGuard = masm.branch32(Assembler::NotEqual, shapeReg,
Imm32(int32(JSObjectMap::INVALID_SHAPE)));
/* Guard on id identity. */
static const int32 BOGUS_ATOM = 0xdeadbeef;
// :FIXME: x64
Jump idGuard = masm.branch32(Assembler::NotEqual, idReg, Imm32(BOGUS_ATOM));
pic.slowPathStart = stubcc.masm.label();
stubcc.linkExit(idGuard, Uses(2));
stubcc.linkExitDirect(shapeGuard, pic.slowPathStart);
stubcc.leave();
stubcc.masm.move(Imm32(pics.length()), Registers::ArgReg1);
pic.callReturn = stubcc.call(ic::GetElem);
/* Load dslots. */
masm.loadPtr(Address(objReg, offsetof(JSObject, dslots)), objReg);
/* Copy the slot value to the expression stack. */
Address slot(objReg, 1 << 24);
masm.loadTypeTag(slot, shapeReg);
masm.loadData32(slot, objReg);
pic.storeBack = masm.label();
pic.objReg = objReg;
pic.idReg = idReg;
JS_ASSERT(pic.idReg != pic.objReg);
JS_ASSERT(pic.idReg != pic.shapeReg);
JS_ASSERT(pic.objReg != pic.shapeReg);
pics.append(pic);
}
#endif
bool
mjit::Compiler::jsop_callprop_generic(JSAtom *atom)
{

View File

@ -110,13 +110,31 @@ class Compiler
Label slowPathStart;
RegisterID shapeReg;
RegisterID objReg;
RegisterID idReg;
RegisterID typeReg;
Label shapeGuard;
JSAtom *atom;
StateRemat objRemat;
StateRemat idRemat;
Call callReturn;
bool hasTypeCheck;
ValueRemat vr;
void copySimpleMembersTo(ic::PICInfo &pi) const {
pi.kind = kind;
pi.shapeReg = shapeReg;
pi.objReg = objReg;
pi.atom = atom;
if (kind == ic::PICInfo::SET) {
pi.u.vr = vr;
} else if (kind != ic::PICInfo::NAME) {
pi.u.get.idReg = idReg;
pi.u.get.typeReg = typeReg;
pi.u.get.hasTypeCheck = hasTypeCheck;
pi.u.get.objRemat = objRemat.offset;
}
}
};
#endif
@ -133,6 +151,10 @@ class Compiler
: reg(Registers::ReturnReg), set(false)
{ }
MaybeRegisterID(RegisterID reg)
: reg(reg), set(true)
{ }
inline RegisterID getReg() const { JS_ASSERT(set); return reg; }
inline void setReg(const RegisterID r) { reg = r; set = true; }
inline bool isSet() const { return set; }
@ -276,6 +298,13 @@ class Compiler
void jsop_localinc(JSOp op, uint32 slot, bool popped);
void jsop_setelem();
void jsop_getelem();
void jsop_getelem_known_type(FrameEntry *obj, FrameEntry *id, RegisterID tmpReg);
void jsop_getelem_with_pic(FrameEntry *obj, FrameEntry *id, RegisterID tmpReg);
void jsop_getelem_nopic(FrameEntry *obj, FrameEntry *id, RegisterID tmpReg);
void jsop_getelem_pic(FrameEntry *obj, FrameEntry *id, RegisterID objReg, RegisterID idReg,
RegisterID shapeReg);
void jsop_getelem_dense(FrameEntry *obj, FrameEntry *id, RegisterID objReg,
MaybeRegisterID &idReg, RegisterID shapeReg);
void jsop_stricteq(JSOp op);
void jsop_equality(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused);
void jsop_pos();

View File

@ -349,6 +349,25 @@ FrameState::predictRegForType(FrameEntry *fe)
return reg;
}
inline JSC::MacroAssembler::RegisterID
FrameState::tempRegForType(FrameEntry *fe, RegisterID fallback)
{
JS_ASSERT(regstate[fallback].fe == NULL);
if (fe->isCopy())
fe = fe->copyOf();
JS_ASSERT(!fe->type.isConstant());
if (fe->type.inRegister())
return fe->type.reg();
/* :XXX: X86 */
masm.loadTypeTag(addressOf(fe), fallback);
return fallback;
}
inline JSC::MacroAssembler::RegisterID
FrameState::tempRegForType(FrameEntry *fe)
{
@ -542,6 +561,15 @@ FrameState::testBoolean(Assembler::Condition cond, FrameEntry *fe)
return masm.testBoolean(cond, tempRegForType(fe));
}
inline JSC::MacroAssembler::Jump
FrameState::testString(Assembler::Condition cond, FrameEntry *fe)
{
JS_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
if (shouldAvoidTypeRemat(fe))
return masm.testString(cond, addressOf(fe));
return masm.testString(cond, tempRegForType(fe));
}
inline FrameEntry *
FrameState::getLocal(uint32 slot)
{

View File

@ -255,6 +255,15 @@ class FrameState
*/
inline RegisterID tempRegForType(FrameEntry *fe);
/*
* Try to use a register already allocated for fe's type, but if one
* is not already available, use fallback.
*
* Note: this does NOT change fe's type-register remat info. It's supposed
* to be a super lightweight/transparent operation.
*/
inline RegisterID tempRegForType(FrameEntry *fe, RegisterID fallback);
/*
* Returns a register that is guaranteed to contain the frame entry's
* data payload. The compiler may not modify the contents of the register.
@ -464,11 +473,17 @@ class FrameState
inline Jump testDouble(Assembler::Condition cond, FrameEntry *fe);
/*
* Helper function. Tests if a slot's type is an integer. Condition should
* Helper function. Tests if a slot's type is a boolean. Condition should
* be Equal or NotEqual.
*/
inline Jump testBoolean(Assembler::Condition cond, FrameEntry *fe);
/*
* Helper function. Tests if a slot's type is a string. Condition should
* be Equal or NotEqual.
*/
inline Jump testString(Assembler::Condition cond, FrameEntry *fe);
/*
* Helper function. Tests if a slot's type is a non-funobj. Condition should
* be Equal or NotEqual.

View File

@ -48,7 +48,8 @@ using namespace js;
using namespace js::mjit;
#ifdef JS_METHODJIT_PROFILE_STUBS
static uint32 StubCallsForOp[255];
static const size_t STUB_CALLS_FOR_OP_COUNT = 255;
static uint32 StubCallsForOp[STUB_CALLS_FOR_OP_COUNT];
#endif
extern "C" void JS_FASTCALL
@ -542,6 +543,11 @@ ThreadData::Initialize()
return false;
}
#ifdef JS_METHODJIT_PROFILE_STUBS
for (size_t i = 0; i < STUB_CALLS_FOR_OP_COUNT; ++i)
StubCallsForOp[i] = 0;
#endif
return true;
}

View File

@ -74,7 +74,7 @@ typedef JSC::MacroAssembler::Address Address;
typedef JSC::ReturnAddressPtr ReturnAddressPtr;
typedef JSC::MacroAssemblerCodePtr MacroAssemblerCodePtr;
struct AutoPropertyDropper
class AutoPropertyDropper
{
JSContext *cx;
JSObject *holder;
@ -827,7 +827,7 @@ class GetPropCompiler : public PICStubCompiler
JS_ASSERT(tempObj);
JS_ASSERT(tempObj->isNative());
masm.loadData32(proto, pic.objReg);
masm.loadPtr(proto, pic.objReg);
pic.shapeRegHasBaseShape = false;
pic.u.get.objNeedsRemat = true;
@ -939,10 +939,267 @@ class GetPropCompiler : public PICStubCompiler
return patchInline(holder, sprop);
else
return generateStub(holder, sprop);
}
bool disable(const char *reason)
{
return PICStubCompiler::disable(reason, stub);
}
};
class GetElemCompiler : public PICStubCompiler
{
JSObject *obj;
JSString *id;
void *stub;
int lastStubSecondShapeGuard;
#ifdef JS_CPU_X86
static const int32 DSLOTS_LOAD = -15;
static const int32 TYPE_LOAD = -6;
static const int32 DATA_LOAD = 0;
static const int32 INLINE_SHAPE_OFFSET = 0x6;
static const int32 INLINE_ATOM_OFFSET = 0x12;
static const int32 INLINE_ATOM_JUMP = 0x18;
static const int32 INLINE_SHAPE_JUMP = 12;
static const int32 STUB_SHAPE_JUMP = 12;
static const int32 STUB_ATOM_JUMP = 24;
#endif
public:
GetElemCompiler(VMFrame &f, JSScript *script, JSObject *obj, ic::PICInfo &pic, JSString *id,
VoidStub stub)
: PICStubCompiler("getelem", f, script, pic), obj(obj), id(id),
stub(JS_FUNC_TO_DATA_PTR(void *, stub)),
lastStubSecondShapeGuard(pic.u.get.secondShapeGuard)
{}
bool patchInline(JSObject *holder, JSScopeProperty *sprop)
{
spew("patch", "inline");
PICRepatchBuffer repatcher(pic, pic.fastPathStart);
int32 offset;
if (sprop->slot < JS_INITIAL_NSLOTS) {
JSC::CodeLocationInstruction istr = pic.storeBack.instructionAtOffset(DSLOTS_LOAD);
repatcher.repatchLoadPtrToLEA(istr);
//
// We've patched | mov dslots, [obj + DSLOTS_OFFSET]
// To: | lea fslots, [obj + DSLOTS_OFFSET]
//
// Because the offset is wrong, it's necessary to correct it
// below.
//
int32 diff = int32(offsetof(JSObject, fslots)) - int32(offsetof(JSObject, dslots));
JS_ASSERT(diff != 0);
offset = (int32(sprop->slot) * sizeof(Value)) + diff;
} else {
offset = (sprop->slot - JS_INITIAL_NSLOTS) * sizeof(Value);
}
uint32 shapeOffset = pic.shapeGuard + INLINE_SHAPE_OFFSET;
repatcher.repatch(pic.fastPathStart.dataLabel32AtOffset(shapeOffset), obj->shape());
uint32 idOffset = pic.shapeGuard + INLINE_ATOM_OFFSET;
repatcher.repatch(pic.fastPathStart.dataLabel32AtOffset(idOffset), int32(id));
repatcher.repatch(pic.storeBack.dataLabel32AtOffset(TYPE_LOAD), offset + 4);
repatcher.repatch(pic.storeBack.dataLabel32AtOffset(DATA_LOAD), offset);
pic.inlinePathPatched = true;
return true;
}
void patchPreviousToHere(PICRepatchBuffer &repatcher, CodeLocationLabel cs)
{
// Patch either the inline fast path or a generated stub. The stub
// omits the prefix of the inline fast path that loads the shape, so
// the offsets are different.
int shapeGuardJumpOffset;
int atomGuardJumpOffset;
if (pic.stubsGenerated) {
shapeGuardJumpOffset = STUB_SHAPE_JUMP;
atomGuardJumpOffset = STUB_ATOM_JUMP;
} else {
shapeGuardJumpOffset = pic.shapeGuard + INLINE_SHAPE_JUMP;
atomGuardJumpOffset = pic.shapeGuard + INLINE_ATOM_JUMP;
}
repatcher.relink(shapeGuardJumpOffset, cs);
repatcher.relink(atomGuardJumpOffset, cs);
if (lastStubSecondShapeGuard)
repatcher.relink(lastStubSecondShapeGuard, cs);
}
bool generateStub(JSObject *holder, JSScopeProperty *sprop)
{
JS_ASSERT(pic.u.get.idReg != pic.shapeReg);
Vector<Jump, 8> shapeMismatches(f.cx);
Assembler masm;
if (pic.objNeedsRemat()) {
if (pic.objRemat() >= sizeof(JSStackFrame))
masm.loadData32(Address(JSFrameReg, pic.objRemat()), pic.objReg);
else
masm.move(RegisterID(pic.objRemat()), pic.objReg);
pic.u.get.objNeedsRemat = false;
}
if (pic.idNeedsRemat()) {
if (pic.idRemat() >= sizeof(JSStackFrame))
masm.loadData32(Address(JSFrameReg, pic.idRemat()), pic.u.get.idReg);
else
masm.move(RegisterID(pic.idRemat()), pic.u.get.idReg);
pic.u.get.idNeedsRemat = false;
}
Label start;
Jump shapeGuard;
Jump atomGuard;
if (obj->isDenseArray()) {
start = masm.label();
atomGuard = masm.branchPtr(Assembler::NotEqual, pic.u.get.idReg, ImmPtr(id));
shapeGuard = masm.branchPtr(Assembler::NotEqual,
Address(pic.objReg, offsetof(JSObject, clasp)),
ImmPtr(obj->getClass()));
} else {
if (pic.shapeNeedsRemat()) {
masm.loadShape(pic.objReg, pic.shapeReg);
pic.shapeRegHasBaseShape = true;
}
start = masm.label();
atomGuard = masm.branchPtr(Assembler::NotEqual, pic.u.get.idReg, ImmPtr(id));
shapeGuard = masm.branch32_force32(Assembler::NotEqual, pic.shapeReg,
Imm32(obj->shape()));
}
if (!(shapeMismatches.append(shapeGuard) && shapeMismatches.append(atomGuard)))
return false;
if (obj != holder) {
// Emit code that walks the prototype chain.
JSObject *tempObj = obj;
Address proto(pic.objReg, offsetof(JSObject, proto));
do {
tempObj = tempObj->getProto();
// FIXME: we should find out why this condition occurs. It is probably
// related to PICs on globals.
if (!tempObj)
return false;
JS_ASSERT(tempObj);
/*
* If there is a native along the prototype chain the shape is technically
* invalid.
*/
if (!tempObj->isNative())
return false;
masm.loadPtr(proto, pic.objReg);
pic.shapeRegHasBaseShape = false;
pic.u.get.objNeedsRemat = true;
Jump j = masm.branchTestPtr(Assembler::Zero, pic.objReg, pic.objReg);
if (!shapeMismatches.append(j))
return false;
} while (tempObj != holder);
// Load the shape out of the holder and check it.
masm.loadShape(pic.objReg, pic.shapeReg);
Jump j = masm.branch32_force32(Assembler::NotEqual, pic.shapeReg,
Imm32(holder->shape()));
if (!shapeMismatches.append(j))
return false;
pic.u.get.secondShapeGuard = masm.distanceOf(masm.label()) - masm.distanceOf(start);
} else {
JS_ASSERT(holder->isNative()); /* Precondition: already checked. */
pic.u.get.secondShapeGuard = 0;
}
/* Load the value out of the object. */
masm.loadSlot(pic.objReg, pic.objReg, sprop->slot, pic.shapeReg, pic.objReg);
Jump done = masm.jump();
JSC::ExecutablePool *ep = getExecPool(masm.size());
if (!ep) {
js_ReportOutOfMemory(f.cx);
return false;
}
// :TODO: this can OOM
JSC::LinkBuffer buffer(&masm, ep);
if (!pic.execPools.append(ep)) {
ep->release();
js_ReportOutOfMemory(f.cx);
return false;
}
// The guard exit jumps to the original slow case.
for (Jump *pj = shapeMismatches.begin(); pj != shapeMismatches.end(); ++pj)
buffer.link(*pj, pic.slowPathStart);
// The final exit jumps to the store-back in the inline stub.
buffer.link(done, pic.storeBack);
CodeLocationLabel cs = buffer.finalizeCodeAddendum();
#if DEBUG
char *chars = js_DeflateString(f.cx, id->chars(), id->length());
JaegerSpew(JSpew_PICs, "generated %s stub at %p for atom 0x%x (\"%s\") shape 0x%x (%s: %d)\n",
type, cs.executableAddress(), id, chars, holder->shape(), script->filename,
js_FramePCToLineNumber(f.cx, f.fp));
f.cx->free(chars);
#endif
PICRepatchBuffer repatcher(pic, pic.lastPathStart());
patchPreviousToHere(repatcher, cs);
pic.stubsGenerated++;
pic.lastStubStart = buffer.locationOf(start);
if (pic.stubsGenerated == MAX_STUBS)
disable("max stubs reached");
if (obj->isDenseArray())
disable("dense array");
return true;
}
bool update()
{
if (!pic.hit) {
spew("first hit", "nop");
pic.hit = true;
return true;
}
JSAtom *atom = js_AtomizeString(f.cx, id, 0);
if (!atom)
return false;
JSObject *holder;
JSProperty *prop;
if (!obj->lookupProperty(f.cx, ATOM_TO_JSID(atom), &holder, &prop))
return false;
if (!prop)
return disable("lookup failed");
AutoPropertyDropper dropper(f.cx, holder, prop);
if (!holder->isNative())
return disable("non-native holder");
JSScopeProperty *sprop = (JSScopeProperty *)prop;
if (!sprop->hasDefaultGetterOrIsMethod())
return disable("getter");
if (!SPROP_HAS_VALID_SLOT(sprop, holder->scope()))
return disable("invalid slot");
if (obj == holder && !pic.inlinePathPatched)
return patchInline(holder, sprop);
else
return generateStub(holder, sprop);
}
bool disable(const char *reason)
{
return PICStubCompiler::disable(reason, stub);
@ -1334,6 +1591,36 @@ ic::GetProp(VMFrame &f, uint32 index)
f.regs.sp[-1] = v;
}
void JS_FASTCALL
ic::GetElem(VMFrame &f, uint32 picIndex)
{
JSScript *script = f.fp->script;
PICInfo &pic = script->pics[picIndex];
JSObject *obj = ValueToObject(f.cx, &f.regs.sp[-2]);
if (!obj)
THROW();
Value idval = f.regs.sp[-1];
JS_ASSERT(idval.isString());
JSString *id = idval.toString();
if (pic.shouldGenerate()) {
GetElemCompiler cc(f, script, obj, pic, id, stubs::GetElem);
if (!cc.update()) {
cc.disable("error");
THROW();
}
}
JSAtom *atom = js_AtomizeString(f.cx, id, 0);
if (!atom)
THROW();
Value v;
if (!obj->getProperty(f.cx, ATOM_TO_JSID(atom), &v))
THROW();
f.regs.sp[-2] = v;
}
static void JS_FASTCALL
SetPropDumb(VMFrame &f, uint32 index)
{

View File

@ -69,11 +69,12 @@ struct PICInfo {
CALL,
SET,
NAME,
BIND
BIND,
GETELEM
};
union {
// This struct comes out to 61 bits.
// This struct comes out to 70 bits.
struct {
RegisterID typeReg : 5; // reg used for checking type
bool hasTypeCheck : 1; // type check and reg are present
@ -82,8 +83,11 @@ struct PICInfo {
uint16 typeCheckOffset : 9;
// Remat info for the object reg.
uint32 objRemat : 20;
bool objNeedsRemat : 1;
uint32 objRemat : 20;
bool objNeedsRemat : 1;
RegisterID idReg : 5; // only used in GETELEM PICs.
uint32 idRemat : 20;
bool idNeedsRemat : 1;
// Offset from start of stub to jump target of second shape guard as Nitro
// asm data location. This is 0 if there is only one shape guard in the
@ -106,8 +110,20 @@ struct PICInfo {
RegisterID shapeReg : 5; // also the out type reg
RegisterID objReg : 5; // also the out data reg
// Number of stubs generated.
uint32 stubsGenerated : 5;
// Offset from start of fast path to initial shape guard.
int shapeGuard : 8;
// Return address of slow path call, as an offset from slowPathStart.
uint16 callReturn : 9;
unsigned unused : 24;
inline bool isGet() {
return kind == GET || kind == CALL;
return kind == GET || kind == CALL || kind == GETELEM;
}
inline RegisterID typeReg() {
JS_ASSERT(isGet());
@ -121,10 +137,18 @@ struct PICInfo {
JS_ASSERT(isGet());
return u.get.objRemat;
}
inline uint32 idRemat() {
JS_ASSERT(isGet());
return u.get.idRemat;
}
inline bool objNeedsRemat() {
JS_ASSERT(isGet());
return u.get.objNeedsRemat;
}
inline bool idNeedsRemat() {
JS_ASSERT(isGet());
return u.get.idNeedsRemat;
}
inline bool shapeNeedsRemat() {
return !shapeRegHasBaseShape;
}
@ -133,12 +157,6 @@ struct PICInfo {
return !hasTypeCheck();
}
// Number of stubs generated.
uint32 stubsGenerated : 5;
// Offset from start of fast path to initial shape guard.
int shapeGuard : 8;
// Index into the script's atom table.
JSAtom *atom;
@ -148,9 +166,6 @@ struct PICInfo {
// Address of store back at the end of the inline fast-path.
JSC::CodeLocationLabel storeBack;
// Return address of slow path call, as an offset from slowPathStart.
uint16 callReturn : 9;
// Offset from callReturn to the start of the slow case.
JSC::CodeLocationLabel slowPathStart;
@ -200,6 +215,7 @@ struct PICInfo {
void PurgePICs(JSContext *cx, JSScript *script);
void JS_FASTCALL GetProp(VMFrame &f, uint32 index);
void JS_FASTCALL GetElem(VMFrame &f, uint32 index);
void JS_FASTCALL SetProp(VMFrame &f, uint32 index);
void JS_FASTCALL CallProp(VMFrame &f, uint32 index);
void JS_FASTCALL Name(VMFrame &f, uint32 index);

View File

@ -231,6 +231,14 @@ class Assembler : public BaseAssembler
Jump testBoolean(Assembler::Condition cond, Address address) {
return branch32(cond, tagOf(address), ImmTag(JSVAL_TAG_BOOLEAN));
}
Jump testString(Assembler::Condition cond, RegisterID reg) {
return branch32(cond, reg, ImmTag(JSVAL_TAG_STRING));
}
Jump testString(Assembler::Condition cond, Address address) {
return branch32(cond, tagOf(address), ImmTag(JSVAL_TAG_STRING));
}
};
} /* namespace js */

View File

@ -1144,35 +1144,13 @@ mjit::Compiler::jsop_setelem()
}
void
mjit::Compiler::jsop_getelem()
mjit::Compiler::jsop_getelem_dense(FrameEntry *obj, FrameEntry *id, RegisterID objReg,
MaybeRegisterID &idReg, RegisterID tmpReg)
{
FrameEntry *obj = frame.peek(-2);
FrameEntry *id = frame.peek(-1);
if ((obj->isTypeKnown() && obj->getKnownType() != JSVAL_TYPE_OBJECT) ||
(id->isTypeKnown() && id->getKnownType() != JSVAL_TYPE_INT32) ||
(id->isConstant() && id->getValue().toInt32() < 0)) {
jsop_getelem_slow();
return;
}
/* id.isInt32() */
if (!id->isTypeKnown()) {
Jump j = frame.testInt32(Assembler::NotEqual, id);
stubcc.linkExit(j, Uses(2));
}
/* obj.isNonFunObj() */
if (!obj->isTypeKnown()) {
Jump j = frame.testObject(Assembler::NotEqual, obj);
stubcc.linkExit(j, Uses(2));
}
/* obj.isDenseArray() */
RegisterID objReg = frame.copyDataIntoReg(obj);
/* Note: idReg is only valid if id is not a constant. */
Jump guardDense = masm.branchPtr(Assembler::NotEqual,
Address(objReg, offsetof(JSObject, clasp)),
ImmPtr(&js_ArrayClass));
Address(objReg, offsetof(JSObject, clasp)),
ImmPtr(&js_ArrayClass));
stubcc.linkExit(guardDense, Uses(2));
/* dslots non-NULL */
@ -1192,37 +1170,185 @@ mjit::Compiler::jsop_getelem()
Jump notHole = masm.branch32(Assembler::Equal, masm.tagOf(slot), ImmTag(JSVAL_TAG_MAGIC));
stubcc.linkExit(notHole, Uses(2));
stubcc.leave();
stubcc.call(stubs::GetElem);
frame.popn(2);
frame.freeReg(objReg);
frame.push(slot);
/* Load slot address into regs. */
masm.loadTypeTag(slot, tmpReg);
masm.loadData32(slot, objReg);
} else {
RegisterID idReg = frame.copyDataIntoReg(id);
JS_ASSERT(idReg.isSet());
Jump inRange = masm.branch32(Assembler::AboveOrEqual,
idReg,
idReg.getReg(),
masm.payloadOf(Address(objReg, -int(sizeof(Value)))));
stubcc.linkExit(inRange, Uses(2));
/* guard not a hole */
BaseIndex slot(objReg, idReg, Assembler::JSVAL_SCALE);
BaseIndex slot(objReg, idReg.getReg(), Assembler::JSVAL_SCALE);
Jump notHole = masm.branch32(Assembler::Equal, masm.tagOf(slot), ImmTag(JSVAL_TAG_MAGIC));
stubcc.linkExit(notHole, Uses(2));
masm.loadTypeTag(slot, tmpReg);
masm.loadData32(slot, objReg);
}
/* Postcondition: type must be in tmpReg, data must be in objReg. */
/* Note: linkExits will be hooked up to a leave() after this method completes. */
}
void
mjit::Compiler::jsop_getelem_known_type(FrameEntry *obj, FrameEntry *id, RegisterID tmpReg)
{
switch (id->getKnownType()) {
case JSVAL_TYPE_INT32:
{
/* Prologue. */
RegisterID objReg = frame.copyDataIntoReg(obj);
MaybeRegisterID idReg;
if (!id->isConstant())
idReg.setReg(frame.copyDataIntoReg(id));
/* Meat. */
jsop_getelem_dense(obj, id, objReg, idReg, tmpReg);
stubcc.leave();
stubcc.call(stubs::GetElem);
/* Epilogue. */
if (idReg.isSet())
frame.freeReg(idReg.getReg());
frame.popn(2);
frame.pushRegs(tmpReg, objReg);
stubcc.rejoin(Changes(1));
return;
}
#ifdef JS_POLYIC
case JSVAL_TYPE_STRING:
{
/* Prologue. */
RegisterID objReg = frame.copyDataIntoReg(obj);
RegisterID idReg = frame.copyDataIntoReg(id);
RegisterID reg = frame.allocReg();
masm.loadTypeTag(slot, reg);
masm.loadData32(slot, idReg);
frame.freeReg(objReg);
frame.pushRegs(reg, idReg);
/* Meat. */
jsop_getelem_pic(obj, id, objReg, idReg, tmpReg);
/* Epilogue. */
frame.popn(2);
frame.pushRegs(tmpReg, objReg);
frame.freeReg(idReg);
stubcc.rejoin(Changes(1));
return;
}
#endif
default:
JS_NOT_REACHED("Invalid known id type.");
}
}
#ifdef JS_POLYIC
void
mjit::Compiler::jsop_getelem_with_pic(FrameEntry *obj, FrameEntry *id, RegisterID tmpReg)
{
JS_ASSERT(!id->isTypeKnown());
RegisterID objReg = frame.copyDataIntoReg(obj);
MaybeRegisterID idReg(frame.copyDataIntoReg(id));
RegisterID typeReg = frame.tempRegForType(id, tmpReg);
Jump intGuard = masm.testInt32(Assembler::NotEqual, typeReg);
JaegerSpew(JSpew_Insns, " ==== BEGIN DENSE ARRAY CODE ==== \n");
jsop_getelem_dense(obj, id, objReg, idReg, tmpReg);
Jump performedDense = masm.jump();
JaegerSpew(JSpew_Insns, " ==== END DENSE ARRAY CODE ==== \n");
intGuard.linkTo(masm.label(), &masm);
Jump stringGuard = masm.testString(Assembler::NotEqual, typeReg);
stubcc.linkExit(stringGuard, Uses(2)); /* Neither int nor string at this point. */
stubcc.leave();
stubcc.call(stubs::GetElem);
Jump toFinalMerge = stubcc.masm.jump();
jsop_getelem_pic(obj, id, objReg, idReg.getReg(), tmpReg);
performedDense.linkTo(masm.label(), &masm);
frame.popn(2);
frame.pushRegs(tmpReg, objReg);
frame.freeReg(idReg.getReg());
toFinalMerge.linkTo(stubcc.masm.label(), &stubcc.masm);
stubcc.rejoin(Changes(1));
}
#endif
void
mjit::Compiler::jsop_getelem_nopic(FrameEntry *obj, FrameEntry *id, RegisterID tmpReg)
{
/* Only handle the int32 case. */
RegisterID objReg = frame.copyDataIntoReg(obj);
MaybeRegisterID idReg(frame.copyDataIntoReg(id));
RegisterID typeReg = frame.tempRegForType(id, tmpReg);
Jump intGuard = masm.testInt32(Assembler::NotEqual, typeReg);
stubcc.linkExit(intGuard, Uses(2));
/* Meat. */
jsop_getelem_dense(obj, id, objReg, idReg, tmpReg);
stubcc.leave();
stubcc.call(stubs::GetElem);
/* Epilogue. */
frame.freeReg(idReg.getReg());
frame.popn(2);
frame.pushRegs(tmpReg, objReg);
stubcc.rejoin(Changes(1));
}
void
mjit::Compiler::jsop_getelem()
{
FrameEntry *obj = frame.peek(-2);
FrameEntry *id = frame.peek(-1);
if (obj->isTypeKnown() && obj->getKnownType() != JSVAL_TYPE_OBJECT) {
jsop_getelem_slow();
return;
}
stubcc.rejoin(Changes(1));
if (id->isTypeKnown() &&
!(id->getKnownType() == JSVAL_TYPE_INT32
#ifdef JS_POLYIC
|| id->getKnownType() == JSVAL_TYPE_STRING
#endif
)) {
jsop_getelem_slow();
return;
}
if (id->isTypeKnown() && id->getKnownType() == JSVAL_TYPE_INT32 && id->isConstant() &&
id->getValue().toInt32() < 0) {
jsop_getelem_slow();
return;
}
if (id->isTypeKnown() && id->getKnownType() == JSVAL_TYPE_STRING && id->isConstant()) {
/* Never happens, or I'd optimize it. */
jsop_getelem_slow();
return;
}
RegisterID tmpReg;
if (obj->isTypeKnown()) {
tmpReg = frame.allocReg();
} else {
tmpReg = frame.copyTypeIntoReg(obj);
Jump objGuard = masm.testObject(Assembler::NotEqual, tmpReg);
stubcc.linkExit(objGuard, Uses(2));
}
if (id->isTypeKnown())
return jsop_getelem_known_type(obj, id, tmpReg);
#ifdef JS_POLYIC
return jsop_getelem_with_pic(obj, id, tmpReg);
#else
return jsop_getelem_nopic(obj, id, tmpReg);
#endif
}
static inline bool

View File

@ -0,0 +1,9 @@
var obj = {attr: 'value'};
(function() {
var name = 'attr';
for (var i = 0; i < 10; ++i)
assertEq(obj[name], 'value');
})();
/* Look up a string id. */

View File

@ -0,0 +1,22 @@
var obj = {firstAttr: 'value', secondAttr: 'another value'};
(function() {
for (var i = 0; i < 12; ++i) {
var name;
if (i < 4)
name = 'firstAttr';
else if (i < 8)
name = 'secondAttr';
else
name = 'firstAttr';
var result = obj[name];
switch (name) {
case 'firstAttr': assertEq(result, 'value'); break;
case 'secondAttr': assertEq(result, 'another value'); break;
}
}
})();
/* Toggle lookup between two ids. */

View File

@ -0,0 +1,22 @@
var obj = {firstAttr: 'value', secondAttr: 'another value', thirdAttr: 'the last value'};
(function() {
for (var i = 0; i < 64; ++i) {
var name;
switch (~~(i / 4) % 3) {
case 0: name = 'firstAttr'; break;
case 1: name = 'secondAttr'; break;
case 2: name = 'thirdAttr'; break;
}
var result = obj[name];
switch (name) {
case 'firstAttr': assertEq(result, 'value'); break;
case 'secondAttr': assertEq(result, 'another value'); break;
case 'thirdAttr': assertEq(result, 'the last value'); break;
}
}
})();
/* Rotate lookup between three ids. */

View File

@ -0,0 +1,51 @@
var obj = {
attr0: 'val0',
attr1: 'val1',
attr2: 'val2',
attr3: 'val3',
attr4: 'val4',
attr5: 'val5',
attr6: 'val6',
attr7: 'val7',
attr8: 'val8',
attr9: 'val9',
attr10: 'val10',
attr11: 'val11',
attr12: 'val12',
attr13: 'val13',
attr14: 'val14',
attr15: 'val15',
attr16: 'val16',
attr17: 'val17',
}
var baseName = 'attr';
(function() {
for (var i = 0; i < 128; ++i) {
var name = baseName + (i % 18);
var result = obj[name];
switch (i) {
case 0: assertEq('val0', result); break;
case 1: assertEq('val1', result); break;
case 2: assertEq('val2', result); break;
case 3: assertEq('val3', result); break;
case 4: assertEq('val4', result); break;
case 5: assertEq('val5', result); break;
case 6: assertEq('val6', result); break;
case 7: assertEq('val7', result); break;
case 8: assertEq('val8', result); break;
case 9: assertEq('val9', result); break;
case 10: assertEq('val10', result); break;
case 11: assertEq('val11', result); break;
case 12: assertEq('val12', result); break;
case 13: assertEq('val13', result); break;
case 14: assertEq('val14', result); break;
case 15: assertEq('val15', result); break;
case 16: assertEq('val16', result); break;
case 17: assertEq('val17', result); break;
}
}
})();
/* Megamorphic index atom. */

View File

@ -0,0 +1,4 @@
var x = { 0: 5, 1: 5 };
assertEq(x[0] + x[1], 10);
/* int32 getelem on object. */

View File

@ -0,0 +1,4 @@
var x = {1: 2, 3: 4};
assertEq(x[1], 2);
/* getelem with non-dense array and known type int32. */

View File

@ -0,0 +1,10 @@
var obj = {count: 24};
var key = 'count';
for (var i = 0; i < 1024; ++i) {
var result = obj[key];
if (i === 2)
obj.newAttr = 42;
}
/* Perform getelem across shape change. */

View File

@ -0,0 +1,7 @@
var arr = ['this', 'works', 'for', 'me'];
assertEq('this', arr[0]);
assertEq('works', arr[1]);
assertEq('for', arr[2]);
assertEq('me', arr[3]);
/* Multiple int32 getelem for dense array. */

View File

@ -0,0 +1,12 @@
var arr = ['this', 'works', 'for', 'me'];
for (var i = 0; i < arr.length; ++i) {
var result = arr[i];
switch (i) {
case 0: assertEq('this', result); break;
case 1: assertEq('works', result); break;
case 2: assertEq('for', result); break;
case 3: assertEq('me', result); break;
}
}
/* int32 getelem for dense array. */

View File

@ -0,0 +1,19 @@
var a = [1, 2];
a[3.1415926535] = 'value';
for (var i = 0; i < 3; i++) {
var attr;
switch (i) {
case 0: attr = 0; break;
case 1: attr = 1; break;
case 2: attr = 3.1415926535; break;
}
var result = a[attr];
switch (i) {
case 0: assertEq(result, 1); break;
case 1: assertEq(result, 2); break;
case 2: assertEq(result, 'value'); break;
}
}
/* int32 and string getelem for non-dense array. */

View File

@ -139,7 +139,7 @@ def run_test(test, lib_dir):
cmd = valgrind_prefix + cmd
if OPTIONS.show_cmd:
print(cmd)
print(subprocess.list2cmdline(cmd))
# close_fds is not supported on Windows and will cause a ValueError.
close_fds = sys.platform != 'win32'
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=close_fds, env=env)