Bug 599214 - JM: Add a fast path for object equality (r=dvander,nnethercote)

This commit is contained in:
Bill McCloskey 2010-10-18 17:04:43 -07:00
parent 234bccd8ef
commit af25c84c93
15 changed files with 434 additions and 148 deletions

View File

@ -88,6 +88,7 @@ namespace js {
* - ACCSET_STACKFRAME: All JSStackFrame objects.
* - ACCSET_RUNTIME: The JSRuntime object.
* - ACCSET_OBJ_CLASP: The 'clasp' field of all JSObjects.
* - ACCSET_OBJ_FLAGS: The 'flags' field of all JSObjects.
* - ACCSET_OBJ_SHAPE: The 'shape' field of all JSObjects.
* - ACCSET_OBJ_PROTO: The 'proto' field of all JSObjects.
* - ACCSET_OBJ_PARENT: The 'parent' field of all JSObjects.
@ -115,28 +116,29 @@ static const nanojit::AccSet ACCSET_FRAMEREGS = (1 << 6);
static const nanojit::AccSet ACCSET_STACKFRAME = (1 << 7);
static const nanojit::AccSet ACCSET_RUNTIME = (1 << 8);
// Nb: JSObject::{lastProp,map,flags} don't have an AccSet because they are never accessed on trace
// Nb: JSObject::{lastProp,map} don't have an AccSet because they are never accessed on trace
static const nanojit::AccSet ACCSET_OBJ_CLASP = (1 << 9);
static const nanojit::AccSet ACCSET_OBJ_SHAPE = (1 << 10);
static const nanojit::AccSet ACCSET_OBJ_PROTO = (1 << 11);
static const nanojit::AccSet ACCSET_OBJ_PARENT = (1 << 12);
static const nanojit::AccSet ACCSET_OBJ_PRIVATE = (1 << 13);
static const nanojit::AccSet ACCSET_OBJ_CAPACITY = (1 << 14);
static const nanojit::AccSet ACCSET_OBJ_SLOTS = (1 << 15); // the pointer to the slots
static const nanojit::AccSet ACCSET_OBJ_FLAGS = (1 << 10);
static const nanojit::AccSet ACCSET_OBJ_SHAPE = (1 << 11);
static const nanojit::AccSet ACCSET_OBJ_PROTO = (1 << 12);
static const nanojit::AccSet ACCSET_OBJ_PARENT = (1 << 13);
static const nanojit::AccSet ACCSET_OBJ_PRIVATE = (1 << 14);
static const nanojit::AccSet ACCSET_OBJ_CAPACITY = (1 << 15);
static const nanojit::AccSet ACCSET_OBJ_SLOTS = (1 << 16); // the pointer to the slots
static const nanojit::AccSet ACCSET_SLOTS = (1 << 16); // the slots themselves
static const nanojit::AccSet ACCSET_TARRAY = (1 << 17);
static const nanojit::AccSet ACCSET_TARRAY_DATA = (1 << 18);
static const nanojit::AccSet ACCSET_ITER = (1 << 19);
static const nanojit::AccSet ACCSET_ITER_PROPS = (1 << 20);
static const nanojit::AccSet ACCSET_STRING = (1 << 21);
static const nanojit::AccSet ACCSET_STRING_MCHARS = (1 << 22);
static const nanojit::AccSet ACCSET_TYPEMAP = (1 << 23);
static const nanojit::AccSet ACCSET_FCSLOTS = (1 << 24);
static const nanojit::AccSet ACCSET_ARGS_DATA = (1 << 25);
static const nanojit::AccSet ACCSET_SLOTS = (1 << 17); // the slots themselves
static const nanojit::AccSet ACCSET_TARRAY = (1 << 18);
static const nanojit::AccSet ACCSET_TARRAY_DATA = (1 << 19);
static const nanojit::AccSet ACCSET_ITER = (1 << 20);
static const nanojit::AccSet ACCSET_ITER_PROPS = (1 << 21);
static const nanojit::AccSet ACCSET_STRING = (1 << 22);
static const nanojit::AccSet ACCSET_STRING_MCHARS = (1 << 23);
static const nanojit::AccSet ACCSET_TYPEMAP = (1 << 24);
static const nanojit::AccSet ACCSET_FCSLOTS = (1 << 25);
static const nanojit::AccSet ACCSET_ARGS_DATA = (1 << 26);
}
static const uint8_t TM_NUM_USED_ACCS = 26; // number of access regions used by TraceMonkey
static const uint8_t TM_NUM_USED_ACCS = 27; // number of access regions used by TraceMonkey
enum JSTNErrType { INFALLIBLE, FAIL_STATUS, FAIL_NULL, FAIL_NEG, FAIL_NEITHER };
enum {

View File

@ -6570,6 +6570,7 @@ js_DumpObject(JSObject *obj)
if (flags & JSObject::METHOD_BARRIER) fprintf(stderr, " method_barrier");
if (flags & JSObject::INDEXED) fprintf(stderr, " indexed");
if (flags & JSObject::OWN_SHAPE) fprintf(stderr, " own_shape");
if (flags & JSObject::HAS_EQUALITY) fprintf(stderr, " has_equality");
bool anyFlags = flags != 0;
if (obj->isNative()) {

View File

@ -331,9 +331,10 @@ struct JSObject : js::gc::Cell {
BRANDED = 0x08,
GENERIC = 0x10,
METHOD_BARRIER = 0x20,
INDEXED = 0x40,
OWN_SHAPE = 0x80,
BOUND_FUNCTION = 0x100
INDEXED = 0x40,
OWN_SHAPE = 0x80,
BOUND_FUNCTION = 0x100,
HAS_EQUALITY = 0x200
};
/*
@ -419,6 +420,8 @@ struct JSObject : js::gc::Cell {
bool generic() { return !!(flags & GENERIC); }
void setGeneric() { flags |= GENERIC; }
bool hasSpecialEquality() { return !!(flags & HAS_EQUALITY); }
private:
void generateOwnShape(JSContext *cx);

View File

@ -193,23 +193,24 @@ nanojit::LInsPrinter::accNames[] = {
"rt", // (1 << 8) == ACCSET_RUNTIME
"objclasp", // (1 << 9) == ACCSET_OBJ_CLASP
"objshape", // (1 << 10) == ACCSET_OBJ_SHAPE
"objproto", // (1 << 11) == ACCSET_OBJ_PROTO
"objparent", // (1 << 12) == ACCSET_OBJ_PARENT
"objprivate", // (1 << 13) == ACCSET_OBJ_PRIVATE
"objcapacity", // (1 << 14) == ACCSET_OBJ_CAPACITY
"objslots", // (1 << 15) == ACCSET_OBJ_SLOTS
"objflags", // (1 << 10) == ACCSET_OBJ_FLAGS
"objshape", // (1 << 11) == ACCSET_OBJ_SHAPE
"objproto", // (1 << 12) == ACCSET_OBJ_PROTO
"objparent", // (1 << 13) == ACCSET_OBJ_PARENT
"objprivate", // (1 << 14) == ACCSET_OBJ_PRIVATE
"objcapacity", // (1 << 15) == ACCSET_OBJ_CAPACITY
"objslots", // (1 << 16) == ACCSET_OBJ_SLOTS
"slots", // (1 << 16) == ACCSET_SLOTS
"tarray", // (1 << 17) == ACCSET_TARRAY
"tdata", // (1 << 18) == ACCSET_TARRAY_DATA
"iter", // (1 << 19) == ACCSET_ITER
"iterprops", // (1 << 20) == ACCSET_ITER_PROPS
"str", // (1 << 21) == ACCSET_STRING
"strmchars", // (1 << 22) == ACCSET_STRING_MCHARS
"typemap", // (1 << 23) == ACCSET_TYPEMAP
"fcslots", // (1 << 24) == ACCSET_FCSLOTS
"argsdata", // (1 << 25) == ACCSET_ARGS_DATA
"slots", // (1 << 17) == ACCSET_SLOTS
"tarray", // (1 << 18) == ACCSET_TARRAY
"tdata", // (1 << 19) == ACCSET_TARRAY_DATA
"iter", // (1 << 20) == ACCSET_ITER
"iterprops", // (1 << 21) == ACCSET_ITER_PROPS
"str", // (1 << 22) == ACCSET_STRING
"strmchars", // (1 << 23) == ACCSET_STRING_MCHARS
"typemap", // (1 << 24) == ACCSET_TYPEMAP
"fcslots", // (1 << 25) == ACCSET_FCSLOTS
"argsdata", // (1 << 26) == ACCSET_ARGS_DATA
"?!" // this entry should never be used, have it just in case
};
@ -9067,6 +9068,11 @@ TraceRecorder::equalityHelper(Value& l, Value& r, LIns* l_ins, LIns* r_ins,
} else if (l.isObject()) {
if (l.toObject().getClass()->ext.equality)
RETURN_STOP_A("Can't trace extended class equality operator");
LIns* flags_ins = lir->insLoad(LIR_ldi, l_ins, offsetof(JSObject, flags),
ACCSET_OBJ_FLAGS);
LIns* flag_ins = lir->ins2(LIR_andi, flags_ins, INS_CONSTU(JSObject::HAS_EQUALITY));
guard(true, lir->insEqI_0(flag_ins), BRANCH_EXIT);
op = LIR_eqp;
cond = (l == r);
} else if (l.isBoolean()) {
@ -16908,6 +16914,10 @@ void ValidateWriter::checkAccSet(LOpcode op, LIns* base, int32_t disp, AccSet ac
ok = OK_OBJ_FIELD(LIR_ldp, clasp);
break;
case ACCSET_OBJ_FLAGS:
ok = OK_OBJ_FIELD(LIR_ldi, flags);
break;
case ACCSET_OBJ_SHAPE:
ok = OK_OBJ_FIELD(LIR_ldi, objShape);
break;

View File

@ -213,6 +213,20 @@ AppendString(JSCharBuffer &cb, JSString *str)
return cb.append(chars, end);
}
/*
* This wrapper is needed because NewBuiltinClassInstance doesn't
* call the constructor, and we need a place to set the
* HAS_EQUALITY bit.
*/
static inline JSObject *
NewBuiltinClassInstanceXML(JSContext *cx, Class *clasp)
{
JSObject *obj = NewBuiltinClassInstance(cx, clasp);
if (obj && clasp->ext.equality)
obj->flags |= JSObject::HAS_EQUALITY;
return obj;
}
#define DEFINE_GETTER(name,code) \
static JSBool \
name(JSContext *cx, JSObject *obj, jsid id, jsval *vp) \
@ -309,7 +323,7 @@ NewXMLNamespace(JSContext *cx, JSString *prefix, JSString *uri, JSBool declared)
{
JSObject *obj;
obj = NewBuiltinClassInstance(cx, &js_NamespaceClass);
obj = NewBuiltinClassInstanceXML(cx, &js_NamespaceClass);
if (!obj)
return JS_FALSE;
JS_ASSERT(JSVAL_IS_VOID(obj->getNamePrefix()));
@ -520,7 +534,7 @@ static JSObject *
NewXMLQName(JSContext *cx, JSString *uri, JSString *prefix, JSString *localName,
Class *clasp = &js_QNameClass)
{
JSObject *obj = NewBuiltinClassInstance(cx, clasp);
JSObject *obj = NewBuiltinClassInstanceXML(cx, clasp);
if (!obj)
return NULL;
JS_ASSERT(obj->isQName());
@ -633,7 +647,7 @@ NamespaceHelper(JSContext *cx, JSObject *obj, intN argc, jsval *argv,
return JS_TRUE;
}
obj = NewBuiltinClassInstance(cx, &js_NamespaceClass);
obj = NewBuiltinClassInstanceXML(cx, &js_NamespaceClass);
if (!obj)
return JS_FALSE;
}
@ -698,6 +712,8 @@ Namespace(JSContext *cx, uintN argc, Value *vp)
{
JSObject *thisobj = NULL;
(void)IsConstructing_PossiblyWithGivenThisObject(vp, &thisobj);
if (thisobj)
thisobj->flags |= JSObject::HAS_EQUALITY;
return NamespaceHelper(cx, thisobj, argc, Jsvalify(vp + 2), Jsvalify(vp));
}
@ -739,7 +755,7 @@ QNameHelper(JSContext *cx, JSObject *obj, Class *clasp, intN argc,
* Create and return a new QName or AttributeName object exactly as if
* constructed.
*/
obj = NewBuiltinClassInstance(cx, clasp);
obj = NewBuiltinClassInstanceXML(cx, clasp);
if (!obj)
return JS_FALSE;
}
@ -833,6 +849,8 @@ QName(JSContext *cx, uintN argc, Value *vp)
{
JSObject *thisobj = NULL;
(void)IsConstructing_PossiblyWithGivenThisObject(vp, &thisobj);
if (thisobj)
thisobj->flags |= JSObject::HAS_EQUALITY;
return QNameHelper(cx, thisobj, &js_QNameClass, argc, Jsvalify(vp + 2), Jsvalify(vp));
}

View File

@ -98,6 +98,7 @@ mjit::Compiler::Compiler(JSContext *cx, JSStackFrame *fp)
#if defined JS_MONOIC
mics(ContextAllocPolicy(cx)),
callICs(ContextAllocPolicy(cx)),
equalityICs(ContextAllocPolicy(cx)),
#endif
#if defined JS_POLYIC
pics(ContextAllocPolicy(cx)),
@ -361,6 +362,7 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
#if defined JS_MONOIC
sizeof(ic::MICInfo) * mics.length() +
sizeof(ic::CallICInfo) * callICs.length() +
sizeof(ic::EqualityICInfo) * equalityICs.length() +
#endif
#if defined JS_POLYIC
sizeof(ic::PICInfo) * pics.length() +
@ -428,12 +430,9 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
JS_ASSERT(jumpMap[offs].isValid());
scriptMICs[i].traceHint = fullCode.locationOf(mics[i].traceHint);
scriptMICs[i].load = fullCode.locationOf(jumpMap[offs]);
scriptMICs[i].u.hints.hasSlowTraceHintOne = mics[i].slowTraceHintOne.isSet();
if (mics[i].slowTraceHintOne.isSet())
scriptMICs[i].slowTraceHintOne = stubCode.locationOf(mics[i].slowTraceHintOne.get());
scriptMICs[i].u.hints.hasSlowTraceHintTwo = mics[i].slowTraceHintTwo.isSet();
if (mics[i].slowTraceHintTwo.isSet())
scriptMICs[i].slowTraceHintTwo = stubCode.locationOf(mics[i].slowTraceHintTwo.get());
scriptMICs[i].u.hints.hasSlowTraceHint = mics[i].slowTraceHint.isSet();
if (mics[i].slowTraceHint.isSet())
scriptMICs[i].slowTraceHint = stubCode.locationOf(mics[i].slowTraceHint.get());
break;
}
default:
@ -503,6 +502,34 @@ mjit::Compiler::finishThisUp(JITScript **jitp)
stubCode.patch(callICs[i].addrLabel2, &cics[i]);
}
}
jit->nEqualityICs = equalityICs.length();
if (equalityICs.length()) {
jit->equalityICs = (ic::EqualityICInfo *)cursor;
cursor += sizeof(ic::EqualityICInfo) * equalityICs.length();
} else {
jit->equalityICs = NULL;
}
if (ic::EqualityICInfo *scriptEICs = jit->equalityICs) {
for (size_t i = 0; i < equalityICs.length(); i++) {
uint32 offs = uint32(equalityICs[i].jumpTarget - script->code);
JS_ASSERT(jumpMap[offs].isValid());
scriptEICs[i].target = fullCode.locationOf(jumpMap[offs]);
scriptEICs[i].stubEntry = stubCode.locationOf(equalityICs[i].stubEntry);
scriptEICs[i].stubCall = stubCode.locationOf(equalityICs[i].stubCall);
scriptEICs[i].stub = equalityICs[i].stub;
scriptEICs[i].lvr = equalityICs[i].lvr;
scriptEICs[i].rvr = equalityICs[i].rvr;
scriptEICs[i].tempReg = equalityICs[i].tempReg;
scriptEICs[i].cond = equalityICs[i].cond;
if (equalityICs[i].jumpToStub.isSet())
scriptEICs[i].jumpToStub = fullCode.locationOf(equalityICs[i].jumpToStub.get());
scriptEICs[i].fallThrough = fullCode.locationOf(equalityICs[i].fallThrough);
stubCode.patch(equalityICs[i].addrLabel, &scriptEICs[i]);
}
}
#endif /* JS_MONOIC */
for (size_t i = 0; i < callPatches.length(); i++) {
@ -4277,21 +4304,17 @@ mjit::Compiler::jsop_instanceof()
* after rejoin()s.
*/
void
mjit::Compiler::jumpAndTrace(Jump j, jsbytecode *target, Jump *slowOne, Jump *slowTwo)
mjit::Compiler::jumpAndTrace(Jump j, jsbytecode *target, Jump *slow)
{
#ifndef JS_TRACER
jumpInScript(j, target);
if (slowOne)
stubcc.jumpInScript(*slowOne, target);
if (slowTwo)
stubcc.jumpInScript(*slowTwo, target);
if (slow)
stubcc.jumpInScript(*slow, target);
#else
if (!addTraceHints || target >= PC || JSOp(*target) != JSOP_TRACE) {
jumpInScript(j, target);
if (slowOne)
stubcc.jumpInScript(*slowOne, target);
if (slowTwo)
stubcc.jumpInScript(*slowTwo, target);
if (slow)
stubcc.jumpInScript(*slow, target);
return;
}
@ -4301,19 +4324,15 @@ mjit::Compiler::jumpAndTrace(Jump j, jsbytecode *target, Jump *slowOne, Jump *sl
mic.entry = masm.label();
mic.jumpTarget = target;
mic.traceHint = j;
if (slowOne)
mic.slowTraceHintOne = *slowOne;
if (slowTwo)
mic.slowTraceHintTwo = *slowTwo;
if (slow)
mic.slowTraceHint = *slow;
# endif
Label traceStart = stubcc.masm.label();
stubcc.linkExitDirect(j, traceStart);
if (slowOne)
slowOne->linkTo(traceStart, &stubcc.masm);
if (slowTwo)
slowTwo->linkTo(traceStart, &stubcc.masm);
if (slow)
slow->linkTo(traceStart, &stubcc.masm);
# if JS_MONOIC
passMICAddress(mic);
# endif

View File

@ -81,8 +81,7 @@ class Compiler : public BaseCompiler
ic::MICInfo::Kind kind;
jsbytecode *jumpTarget;
Jump traceHint;
MaybeJump slowTraceHintOne;
MaybeJump slowTraceHintTwo;
MaybeJump slowTraceHint;
union {
struct {
bool typeConst;
@ -94,6 +93,19 @@ class Compiler : public BaseCompiler
} u;
};
struct EqualityGenInfo {
DataLabelPtr addrLabel;
Label stubEntry;
Call stubCall;
BoolStub stub;
MaybeJump jumpToStub;
Label fallThrough;
jsbytecode *jumpTarget;
ValueRemat lvr, rvr;
Assembler::Condition cond;
JSC::MacroAssembler::RegisterID tempReg;
};
/* InlineFrameAssembler wants to see this. */
public:
struct CallGenInfo {
@ -215,6 +227,7 @@ class Compiler : public BaseCompiler
#if defined JS_MONOIC
js::Vector<MICGenInfo, 64> mics;
js::Vector<CallGenInfo, 64> callICs;
js::Vector<EqualityGenInfo, 64> equalityICs;
#endif
#if defined JS_POLYIC
js::Vector<PICGenInfo, 16> pics;
@ -274,7 +287,7 @@ class Compiler : public BaseCompiler
bool constructThis();
/* Opcode handlers. */
void jumpAndTrace(Jump j, jsbytecode *target, Jump *slowOne = NULL, Jump *slowTwo = NULL);
void jumpAndTrace(Jump j, jsbytecode *target, Jump *slow = NULL);
void jsop_bindname(uint32 index);
void jsop_setglobal(uint32 index);
void jsop_getglobal(uint32 index);

View File

@ -957,8 +957,6 @@ mjit::Compiler::jsop_equality_int_string(JSOp op, BoolStub stub, jsbytecode *tar
bool lhsInt = lhs->isType(JSVAL_TYPE_INT32);
bool rhsInt = rhs->isType(JSVAL_TYPE_INT32);
bool lhsString = lhs->isType(JSVAL_TYPE_STRING);
bool rhsString = rhs->isType(JSVAL_TYPE_STRING);
/* Invert the condition if fusing with an IFEQ branch. */
bool flipCondition = (target && fused == JSOP_IFEQ);
@ -995,15 +993,14 @@ mjit::Compiler::jsop_equality_int_string(JSOp op, BoolStub stub, jsbytecode *tar
*/
frame.syncAndKill(Registers(Registers::AvailRegs), Uses(frame.frameDepth()), Uses(2));
/* Temporary for OOL string path. */
RegisterID T1 = frame.allocReg();
RegisterID tempReg = frame.allocReg();
frame.pop();
frame.pop();
frame.discardFrame();
/* Start of the slow path for equality stub call. */
Label stubCall = stubcc.masm.label();
Label stubEntry = stubcc.masm.label();
JaegerSpew(JSpew_Insns, " ---- BEGIN STUB CALL CODE ---- \n");
@ -1011,8 +1008,31 @@ mjit::Compiler::jsop_equality_int_string(JSOp op, BoolStub stub, jsbytecode *tar
frame.ensureValueSynced(stubcc.masm, lhs, lvr);
frame.ensureValueSynced(stubcc.masm, rhs, rvr);
/* Call the stub, adjusting for the two values just pushed. */
stubcc.call(stub, frame.stackDepth() + script->nfixed + 2);
bool needStub = true;
#ifdef JS_MONOIC
EqualityGenInfo ic;
ic.cond = cond;
ic.tempReg = tempReg;
ic.lvr = lvr;
ic.rvr = rvr;
ic.stubEntry = stubEntry;
ic.stub = stub;
bool useIC = !addTraceHints || target >= PC;
/* Call the IC stub, which may generate a fast path. */
if (useIC) {
/* Adjust for the two values just pushed. */
ic.addrLabel = stubcc.masm.moveWithPatch(ImmPtr(NULL), Registers::ArgReg1);
ic.stubCall = stubcc.call(ic::Equality, frame.stackDepth() + script->nfixed + 2);
needStub = false;
}
#endif
if (needStub)
stubcc.call(stub, frame.stackDepth() + script->nfixed + 2);
/*
* The stub call has no need to rejoin, since state is synced.
@ -1027,94 +1047,59 @@ mjit::Compiler::jsop_equality_int_string(JSOp op, BoolStub stub, jsbytecode *tar
JaegerSpew(JSpew_Insns, " ---- END STUB CALL CODE ---- \n");
/* Emit an OOL string path if both sides might be strings. */
bool stringPath = !(lhsInt || rhsInt);
Label missedInt = stubCall;
Jump stringFallthrough;
Jump stringMatched;
if (stringPath) {
missedInt = stubcc.masm.label();
if (!lhsString) {
Jump lhsFail = stubcc.masm.testString(Assembler::NotEqual, lvr.typeReg());
lhsFail.linkTo(stubCall, &stubcc.masm);
}
if (!rhsString) {
JS_ASSERT(!rhsConst);
Jump rhsFail = stubcc.masm.testString(Assembler::NotEqual, rvr.typeReg());
rhsFail.linkTo(stubCall, &stubcc.masm);
}
/* Test if lhs/rhs are atomized. */
Imm32 atomizedFlags(JSString::FLAT | JSString::ATOMIZED);
stubcc.masm.load32(Address(lvr.dataReg(), offsetof(JSString, mLengthAndFlags)), T1);
stubcc.masm.and32(Imm32(JSString::TYPE_FLAGS_MASK), T1);
Jump lhsNotAtomized = stubcc.masm.branch32(Assembler::NotEqual, T1, atomizedFlags);
lhsNotAtomized.linkTo(stubCall, &stubcc.masm);
if (!rhsConst) {
stubcc.masm.load32(Address(rvr.dataReg(), offsetof(JSString, mLengthAndFlags)), T1);
stubcc.masm.and32(Imm32(JSString::TYPE_FLAGS_MASK), T1);
Jump rhsNotAtomized = stubcc.masm.branch32(Assembler::NotEqual, T1, atomizedFlags);
rhsNotAtomized.linkTo(stubCall, &stubcc.masm);
}
if (rhsConst) {
JSString *str = rval.toString();
JS_ASSERT(str->isAtomized());
stringMatched = stubcc.masm.branchPtr(cond, lvr.dataReg(), ImmPtr(str));
} else {
stringMatched = stubcc.masm.branchPtr(cond, lvr.dataReg(), rvr.dataReg());
}
stringFallthrough = stubcc.masm.jump();
}
Jump fast;
if (lhsString || rhsString) {
/* Jump straight to the OOL string path. */
Jump jump = masm.jump();
stubcc.linkExitDirect(jump, missedInt);
fast = masm.jump();
} else {
/* Emit inline integer path. */
MaybeJump firstStubJump;
if (lhsInt || rhsInt || (!lhs->isTypeKnown() && !rhs->isTypeKnown())) {
if (!lhsInt) {
Jump lhsFail = masm.testInt32(Assembler::NotEqual, lvr.typeReg());
stubcc.linkExitDirect(lhsFail, missedInt);
stubcc.linkExitDirect(lhsFail, stubEntry);
firstStubJump = lhsFail;
}
if (!rhsInt) {
if (rhsConst) {
Jump rhsFail = masm.jump();
stubcc.linkExitDirect(rhsFail, missedInt);
} else {
Jump rhsFail = masm.testInt32(Assembler::NotEqual, rvr.typeReg());
stubcc.linkExitDirect(rhsFail, missedInt);
}
Jump rhsFail = masm.testInt32(Assembler::NotEqual, rvr.typeReg());
stubcc.linkExitDirect(rhsFail, stubEntry);
if (!firstStubJump.isSet())
firstStubJump = rhsFail;
}
if (rhsConst)
fast = masm.branch32(cond, lvr.dataReg(), Imm32(rval.toInt32()));
else
fast = masm.branch32(cond, lvr.dataReg(), rvr.dataReg());
jumpInScript(fast, target);
} else {
Jump j = masm.jump();
stubcc.linkExitDirect(j, stubEntry);
firstStubJump = j;
/* This is just a dummy jump. */
fast = masm.jump();
}
/* Jump from the stub call and string path fallthroughs to here. */
#ifdef JS_MONOIC
ic.jumpToStub = firstStubJump;
if (useIC) {
ic.fallThrough = masm.label();
ic.jumpTarget = target;
equalityICs.append(ic);
}
#endif
/* Jump from the stub call fallthrough to here. */
stubcc.crossJump(stubFallthrough, masm.label());
if (stringPath)
stubcc.crossJump(stringFallthrough, masm.label());
/*
* NB: jumpAndTrace emits to the OOL path, so make sure not to use it
* in the middle of an in-progress slow path.
*/
jumpAndTrace(fast, target, &stubBranch, stringPath ? &stringMatched : NULL);
jumpAndTrace(fast, target, &stubBranch);
} else {
/* No fusing. Compare, set, and push a boolean. */
/* Should have filtered these out in the caller. */
JS_ASSERT(!lhsString && !rhsString);
JS_ASSERT(!lhs->isType(JSVAL_TYPE_STRING) && !rhs->isType(JSVAL_TYPE_STRING));
/* Test the types. */
if (!lhsInt) {

View File

@ -781,11 +781,8 @@ DisableTraceHint(VMFrame &f, ic::MICInfo &mic)
DisableTraceHintSingle(mic.traceHint, mic.load);
if (mic.u.hints.hasSlowTraceHintOne)
DisableTraceHintSingle(mic.slowTraceHintOne, mic.load);
if (mic.u.hints.hasSlowTraceHintTwo)
DisableTraceHintSingle(mic.slowTraceHintTwo, mic.load);
if (mic.u.hints.hasSlowTraceHint)
DisableTraceHintSingle(mic.slowTraceHint, mic.load);
}
#endif

View File

@ -818,6 +818,13 @@ mjit::JITScript::release()
#endif
#if defined JS_MONOIC
for (JSC::ExecutablePool **pExecPool = execPools.begin();
pExecPool != execPools.end();
++pExecPool)
{
(*pExecPool)->release();
}
for (uint32 i = 0; i < nCallICs; i++)
callICs[i].releasePools();
#endif

View File

@ -146,6 +146,7 @@ namespace ic {
# endif
# if defined JS_MONOIC
struct MICInfo;
struct EqualityICInfo;
struct CallICInfo;
# endif
}
@ -175,6 +176,7 @@ typedef void (JS_FASTCALL *VoidStubCallIC)(VMFrame &, js::mjit::ic::CallICInfo *
typedef void * (JS_FASTCALL *VoidPtrStubCallIC)(VMFrame &, js::mjit::ic::CallICInfo *);
typedef void (JS_FASTCALL *VoidStubMIC)(VMFrame &, js::mjit::ic::MICInfo *);
typedef void * (JS_FASTCALL *VoidPtrStubMIC)(VMFrame &, js::mjit::ic::MICInfo *);
typedef JSBool (JS_FASTCALL *BoolStubEqualityIC)(VMFrame &, js::mjit::ic::EqualityICInfo *);
#endif
#ifdef JS_POLYIC
typedef void (JS_FASTCALL *VoidStubPIC)(VMFrame &, js::mjit::ic::PICInfo *);
@ -196,6 +198,12 @@ struct JITScript {
uint32 nMICs; /* number of MonoICs */
ic::CallICInfo *callICs; /* CallICs in this script. */
uint32 nCallICs; /* number of call ICs */
ic::EqualityICInfo *equalityICs;
uint32 nEqualityICs;
// Additional ExecutablePools that IC stubs were generated into.
typedef Vector<JSC::ExecutablePool *, 0, SystemAllocPolicy> ExecPoolVector;
ExecPoolVector execPools;
#endif
#ifdef JS_POLYIC
ic::PICInfo *pics; /* PICs in this script */

View File

@ -211,6 +211,206 @@ ic::SetGlobalName(VMFrame &f, ic::MICInfo *ic)
GetStubForSetGlobalName(f)(f, atom);
}
class EqualityICLinker : public LinkerHelper
{
VMFrame &f;
public:
EqualityICLinker(JSContext *cx, VMFrame &f)
: LinkerHelper(cx), f(f)
{ }
bool init(Assembler &masm) {
JSC::ExecutablePool *pool = LinkerHelper::init(masm);
if (!pool)
return false;
JSScript *script = f.fp()->script();
JITScript *jit = script->getJIT(f.fp()->isConstructing());
if (!jit->execPools.append(pool)) {
pool->release();
js_ReportOutOfMemory(cx);
return false;
}
return true;
}
};
/* Rough over-estimate of how much memory we need to unprotect. */
static const uint32 INLINE_PATH_LENGTH = 64;
class EqualityCompiler : public BaseCompiler
{
VMFrame &f;
EqualityICInfo &ic;
Vector<Jump, 4, SystemAllocPolicy> jumpList;
Jump trueJump;
Jump falseJump;
public:
EqualityCompiler(VMFrame &f, EqualityICInfo &ic)
: BaseCompiler(f.cx), f(f), ic(ic), jumpList(SystemAllocPolicy())
{
}
void linkToStub(Jump j)
{
jumpList.append(j);
}
void linkTrue(Jump j)
{
trueJump = j;
}
void linkFalse(Jump j)
{
falseJump = j;
}
void generateStringPath(Assembler &masm)
{
ValueRemat &lvr = ic.lvr;
ValueRemat &rvr = ic.rvr;
if (!lvr.isConstant && !lvr.isType(JSVAL_TYPE_STRING)) {
Jump lhsFail = masm.testString(Assembler::NotEqual, lvr.typeReg());
linkToStub(lhsFail);
}
if (!rvr.isConstant && !rvr.isType(JSVAL_TYPE_STRING)) {
Jump rhsFail = masm.testString(Assembler::NotEqual, rvr.typeReg());
linkToStub(rhsFail);
}
RegisterID tmp = ic.tempReg;
/* Test if lhs/rhs are atomized. */
Imm32 atomizedFlags(JSString::FLAT | JSString::ATOMIZED);
masm.load32(Address(lvr.dataReg(), offsetof(JSString, mLengthAndFlags)), tmp);
masm.and32(Imm32(JSString::TYPE_FLAGS_MASK), tmp);
Jump lhsNotAtomized = masm.branch32(Assembler::NotEqual, tmp, atomizedFlags);
linkToStub(lhsNotAtomized);
if (!rvr.isConstant) {
masm.load32(Address(rvr.dataReg(), offsetof(JSString, mLengthAndFlags)), tmp);
masm.and32(Imm32(JSString::TYPE_FLAGS_MASK), tmp);
Jump rhsNotAtomized = masm.branch32(Assembler::NotEqual, tmp, atomizedFlags);
linkToStub(rhsNotAtomized);
}
if (rvr.isConstant) {
JSString *str = Valueify(rvr.u.v).toString();
JS_ASSERT(str->isAtomized());
Jump test = masm.branchPtr(ic.cond, lvr.dataReg(), ImmPtr(str));
linkTrue(test);
} else {
Jump test = masm.branchPtr(ic.cond, lvr.dataReg(), rvr.dataReg());
linkTrue(test);
}
Jump fallthrough = masm.jump();
linkFalse(fallthrough);
}
void generateObjectPath(Assembler &masm)
{
ValueRemat &lvr = ic.lvr;
ValueRemat &rvr = ic.rvr;
if (!lvr.isConstant && !lvr.isType(JSVAL_TYPE_OBJECT)) {
Jump lhsFail = masm.testObject(Assembler::NotEqual, lvr.typeReg());
linkToStub(lhsFail);
}
if (!rvr.isConstant && !rvr.isType(JSVAL_TYPE_OBJECT)) {
Jump rhsFail = masm.testObject(Assembler::NotEqual, rvr.typeReg());
linkToStub(rhsFail);
}
Jump lhsHasEq = masm.branchTest32(Assembler::NonZero,
Address(lvr.dataReg(),
offsetof(JSObject, flags)),
Imm32(JSObject::HAS_EQUALITY));
linkToStub(lhsHasEq);
if (rvr.isConstant) {
JSObject *obj = &Valueify(rvr.u.v).toObject();
Jump test = masm.branchPtr(ic.cond, lvr.dataReg(), ImmPtr(obj));
linkTrue(test);
} else {
Jump test = masm.branchPtr(ic.cond, lvr.dataReg(), rvr.dataReg());
linkTrue(test);
}
Jump fallthrough = masm.jump();
linkFalse(fallthrough);
}
bool linkForIC(Assembler &masm)
{
EqualityICLinker buffer(cx, f);
if (!buffer.init(masm))
return false;
/* Set the targets of all type test failures to go to the stub. */
for (size_t i = 0; i < jumpList.length(); i++)
buffer.link(jumpList[i], ic.stubEntry);
jumpList.clear();
/* Set the targets for the the success and failure of the actual equality test. */
buffer.link(trueJump, ic.target);
buffer.link(falseJump, ic.fallThrough);
CodeLocationLabel cs = buffer.finalizeCodeAddendum();
/* Jump to the newly generated code instead of to the IC. */
JSC::RepatchBuffer jumpRepatcher(ic.jumpToStub.executableAddress(), INLINE_PATH_LENGTH);
jumpRepatcher.relink(ic.jumpToStub, cs);
/* Overwrite the call to the IC with a call to the stub. */
JSC::RepatchBuffer stubRepatcher(ic.stubCall.executableAddress(), INLINE_PATH_LENGTH);
JSC::FunctionPtr fptr(JS_FUNC_TO_DATA_PTR(void *, ic.stub));
stubRepatcher.relink(ic.stubCall, fptr);
return true;
}
bool update()
{
if (!ic.generated) {
Assembler masm;
Value rval = f.regs.sp[-1];
Value lval = f.regs.sp[-2];
if (rval.isObject() && lval.isObject()) {
generateObjectPath(masm);
ic.generated = true;
} else if (rval.isString() && lval.isString()) {
generateStringPath(masm);
ic.generated = true;
} else {
return true;
}
return linkForIC(masm);
}
return true;
}
};
JSBool JS_FASTCALL
ic::Equality(VMFrame &f, ic::EqualityICInfo *ic)
{
EqualityCompiler cc(f, *ic);
if (!cc.update())
THROWV(JS_FALSE);
return ic->stub(f);
}
static void * JS_FASTCALL
SlowCallFromIC(VMFrame &f, ic::CallICInfo *ic)
{

View File

@ -89,8 +89,7 @@ struct MICInfo {
/* Used by TRACER. */
JSC::CodeLocationJump traceHint;
JSC::CodeLocationJump slowTraceHintOne;
JSC::CodeLocationJump slowTraceHintTwo;
JSC::CodeLocationJump slowTraceHint;
/* Used by all MICs. */
Kind kind : 3;
@ -103,8 +102,7 @@ struct MICInfo {
} name;
/* Used by TRACER. */
struct {
bool hasSlowTraceHintOne : 1;
bool hasSlowTraceHintTwo : 1;
bool hasSlowTraceHint : 1;
} hints;
} u;
};
@ -112,6 +110,25 @@ struct MICInfo {
void JS_FASTCALL GetGlobalName(VMFrame &f, ic::MICInfo *ic);
void JS_FASTCALL SetGlobalName(VMFrame &f, ic::MICInfo *ic);
struct EqualityICInfo {
typedef JSC::MacroAssembler::RegisterID RegisterID;
JSC::CodeLocationLabel stubEntry;
JSC::CodeLocationCall stubCall;
BoolStub stub;
JSC::CodeLocationLabel target;
JSC::CodeLocationLabel fallThrough;
JSC::CodeLocationJump jumpToStub;
ValueRemat lvr, rvr;
bool generated : 1;
JSC::MacroAssembler::RegisterID tempReg : 5;
Assembler::Condition cond : 6;
};
JSBool JS_FASTCALL Equality(VMFrame &f, ic::EqualityICInfo *ic);
/* See MonoIC.cpp, CallCompiler for more information on call ICs. */
struct CallICInfo {
typedef JSC::MacroAssembler::RegisterID RegisterID;

View File

@ -70,6 +70,11 @@ struct ValueRemat {
JS_ASSERT(!isConstant && !u.s.isTypeKnown);
return u.s.type.reg;
}
bool isType(JSValueType type_) const {
JS_ASSERT(!isConstant);
return u.s.isTypeKnown && u.s.type.knownType == type_;
}
};
/*

View File

@ -133,6 +133,7 @@ class StubCompiler
STUB_CALL_TYPE(VoidPtrStubMIC);
STUB_CALL_TYPE(VoidStubCallIC);
STUB_CALL_TYPE(VoidPtrStubCallIC);
STUB_CALL_TYPE(BoolStubEqualityIC);
#endif
#undef STUB_CALL_TYPE