Bug 479110 - TM: avoid frequent mismatch exits. r=brendan

This commit is contained in:
Andreas Gal 2009-04-04 17:23:34 -04:00
parent 3c19b8b7fc
commit fa2e6466b2
3 changed files with 174 additions and 66 deletions

View File

@ -1220,7 +1220,7 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _frag
TreeInfo* ti, unsigned stackSlots, unsigned ngslots, uint8* typeMap,
VMSideExit* innermostNestedGuard, jsbytecode* outer)
{
JS_ASSERT(!_fragment->vmprivate && ti);
JS_ASSERT(!_fragment->vmprivate && ti && cx->fp->regs->pc == (jsbytecode*)_fragment->ip);
/* Reset the fragment state we care about in case we got a recycled fragment. */
_fragment->lastIns = NULL;
@ -1241,6 +1241,7 @@ TraceRecorder::TraceRecorder(JSContext* cx, VMSideExit* _anchor, Fragment* _frag
this->loop = true; /* default assumption is we are compiling a loop */
this->wasRootFragment = _fragment == _fragment->root;
this->outer = outer;
this->pendingTraceableNative = NULL;
this->generatedTraceableNative = new JSTraceableNative();
JS_ASSERT(generatedTraceableNative);
@ -1754,7 +1755,7 @@ skip:
/* Emit load instructions onto the trace that read the initial stack state. */
JS_REQUIRES_STACK void
TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8& t,
TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8 t,
const char *prefix, uintN index, JSStackFrame *fp)
{
LIns* ins;
@ -1767,7 +1768,7 @@ TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8& t,
ins = lir->insLoadi(base, offset);
ins = lir->ins1(LIR_i2f, ins);
} else {
JS_ASSERT(t == JSVAL_BOXED || isNumber(*p) == (t == JSVAL_DOUBLE));
JS_ASSERT_IF(t != JSVAL_BOXED, isNumber(*p) == (t == JSVAL_DOUBLE));
if (t == JSVAL_DOUBLE) {
ins = lir->insLoad(LIR_ldq, base, offset);
} else if (t == JSVAL_BOOLEAN) {
@ -1778,6 +1779,7 @@ TraceRecorder::import(LIns* base, ptrdiff_t offset, jsval* p, uint8& t,
}
checkForGlobalObjectReallocation();
tracker.set(p, ins);
#ifdef DEBUG
char name[64];
JS_ASSERT(strlen(prefix) < 10);
@ -1834,25 +1836,49 @@ TraceRecorder::import(TreeInfo* treeInfo, LIns* sp, unsigned stackSlots, unsigne
uint8* globalTypeMap = typeMap + stackSlots;
unsigned length = treeInfo->nGlobalTypes();
/* This is potentially the typemap of the side exit and thus shorter than the tree's
global type map. */
if (ngslots < length)
/*
* This is potentially the typemap of the side exit and thus shorter than the tree's
* global type map.
*/
if (ngslots < length) {
mergeTypeMaps(&globalTypeMap/*out param*/, &ngslots/*out param*/,
treeInfo->globalTypeMap(), length,
(uint8*)alloca(sizeof(uint8) * length));
}
JS_ASSERT(ngslots == treeInfo->nGlobalTypes());
/* the first time we compile a tree this will be empty as we add entries lazily */
/*
* Check whether there are any values on the stack we have to unbox and do that first
* before we waste any time fetching the state from the stack.
*/
ptrdiff_t offset = -treeInfo->nativeStackBase;
uint8* m = typeMap;
FORALL_SLOTS_IN_PENDING_FRAMES(cx, callDepth,
if (*m == JSVAL_BOXED) {
import(sp, offset, vp, JSVAL_BOXED, "boxed", vpnum, cx->fp);
LIns* vp_ins = get(vp);
unbox_jsval(*vp, vp_ins, copy(anchor));
set(vp, vp_ins);
}
m++; offset += sizeof(double);
);
/*
* The first time we compile a tree this will be empty as we add entries lazily.
*/
uint16* gslots = treeInfo->globalSlots->data();
uint8* m = globalTypeMap;
m = globalTypeMap;
FORALL_GLOBAL_SLOTS(cx, ngslots, gslots,
JS_ASSERT(*m != JSVAL_BOXED);
import(lirbuf->state, nativeGlobalOffset(vp), vp, *m, vpname, vpnum, NULL);
m++;
);
ptrdiff_t offset = -treeInfo->nativeStackBase;
offset = -treeInfo->nativeStackBase;
m = typeMap;
FORALL_SLOTS_IN_PENDING_FRAMES(cx, callDepth,
import(sp, offset, vp, *m, vpname, vpnum, fp);
if (*m != JSVAL_BOXED) {
import(sp, offset, vp, *m, vpname, vpnum, fp);
}
m++; offset += sizeof(double);
);
}
@ -2080,7 +2106,11 @@ TraceRecorder::snapshot(ExitType exitType)
/* Check for a return-value opcode that needs to restart at the next instruction. */
const JSCodeSpec& cs = js_CodeSpec[*pc];
/* WARNING: don't return before restoring the original pc if (resumeAfter). */
/*
* When calling a _FAIL native, make the snapshot's pc point to the next
* instruction after the CALL or APPLY. Even on failure, a _FAIL native must not
* be called again from the interpreter.
*/
bool resumeAfter = (pendingTraceableNative &&
JSTN_ERRTYPE(pendingTraceableNative) == FAIL_STATUS);
if (resumeAfter) {
@ -2111,13 +2141,15 @@ TraceRecorder::snapshot(ExitType exitType)
);
JS_ASSERT(unsigned(m - typemap) == ngslots + stackSlots);
/* If we are capturing the stack state on a specific instruction, the value on
the top of the stack is a boxed value. */
if (resumeAfter) {
if (pendingTraceableNative->flags & JSTN_UNBOX_AFTER)
typemap[stackSlots - 1] = JSVAL_BOXED;
/*
* If we are currently executing a traceable native or we are attaching a second trace
* to it, the value on top of the stack is boxed. Make a note of this in the typemap.
*/
if (pendingTraceableNative && (pendingTraceableNative->flags & JSTN_UNBOX_AFTER))
typemap[stackSlots - 1] = JSVAL_BOXED;
/* Now restore the the original pc (after which early returns are ok). */
/* Now restore the the original pc (after which early returns are ok). */
if (resumeAfter) {
MUST_FLOW_LABEL(restore_pc);
regs->pc = pc - cs.length;
} else {
@ -2132,8 +2164,10 @@ TraceRecorder::snapshot(ExitType exitType)
JS_STATIC_ASSERT (sizeof(GuardRecord) + sizeof(VMSideExit) < MAX_SKIP_BYTES);
/* Check if we already have a matching side exit. If so use that side exit structure,
otherwise we have to create our own. */
/*
* Check if we already have a matching side exit. If so use that side exit structure
* by cloning it, otherwise we have to create our own.
*/
VMSideExit** exits = treeInfo->sideExits.data();
unsigned nexits = treeInfo->sideExits.length();
if (exitType == LOOP_EXIT) {
@ -2141,15 +2175,8 @@ TraceRecorder::snapshot(ExitType exitType)
VMSideExit* e = exits[n];
if (e->pc == pc && e->imacpc == fp->imacpc &&
!memcmp(getFullTypeMap(exits[n]), typemap, typemap_size)) {
LIns* data = lir->skip(sizeof(GuardRecord));
GuardRecord* rec = (GuardRecord*)data->payload();
/* setup guard record structure with shared side exit */
memset(rec, 0, sizeof(GuardRecord));
VMSideExit* exit = exits[n];
rec->exit = exit;
exit->addGuard(rec);
AUDIT(mergedLoopExits);
return data;
return clone(exits[n]);
}
}
}
@ -2157,7 +2184,7 @@ TraceRecorder::snapshot(ExitType exitType)
if (sizeof(GuardRecord) +
sizeof(VMSideExit) +
(stackSlots + ngslots) * sizeof(uint8) >= MAX_SKIP_BYTES) {
/**
/*
* ::snapshot() is infallible in the sense that callers don't
* expect errors; but this is a trace-aborting error condition. So
* mangle the request to consume zero slots, and mark the tree as
@ -2176,10 +2203,12 @@ TraceRecorder::snapshot(ExitType exitType)
(stackSlots + ngslots) * sizeof(uint8));
GuardRecord* rec = (GuardRecord*)data->payload();
VMSideExit* exit = (VMSideExit*)(rec + 1);
/* setup guard record structure */
/* Setup guard record structure. */
memset(rec, 0, sizeof(GuardRecord));
rec->exit = exit;
/* setup side exit structure */
/* Setup side exit structure. */
memset(exit, 0, sizeof(VMSideExit));
exit->from = fragment;
exit->calldepth = callDepth;
@ -2206,24 +2235,77 @@ TraceRecorder::snapshot(ExitType exitType)
return data;
}
JS_REQUIRES_STACK LIns*
TraceRecorder::clone(VMSideExit* exit)
{
LIns* data = lir->skip(sizeof(GuardRecord));
GuardRecord* rec = (GuardRecord*)data->payload();
/* setup guard record structure with shared side exit */
memset(rec, 0, sizeof(GuardRecord));
rec->exit = exit;
exit->addGuard(rec);
return data;
}
JS_REQUIRES_STACK LIns*
TraceRecorder::copy(VMSideExit* copy)
{
unsigned typemap_size = copy->numGlobalSlots + copy->numStackSlots;
LIns* data = lir->skip(sizeof(GuardRecord) +
sizeof(VMSideExit) +
typemap_size * sizeof(uint8));
GuardRecord* rec = (GuardRecord*)data->payload();
VMSideExit* exit = (VMSideExit*)(rec + 1);
/* Setup guard record structure. */
memset(rec, 0, sizeof(GuardRecord));
rec->exit = exit;
/* Copy side exit structure. */
memcpy(exit, copy, sizeof(VMSideExit) + typemap_size * sizeof(uint8));
exit->guards = rec;
exit->from = fragment;
exit->target = NULL;
/* BIG FAT WARNING: If compilation fails, we currently don't reset the lirbuf so its safe
to keep references to the side exits here. If we ever start rewinding those lirbufs,
we have to make sure we purge the side exits that then no longer will be in valid
memory. */
if (exit->exitType == LOOP_EXIT)
treeInfo->sideExits.add(exit);
return data;
}
/* Emit a guard for condition (cond), expecting to evaluate to boolean result (expected)
and using the supplied side exit if the conditon doesn't hold. */
LIns*
JS_REQUIRES_STACK void
TraceRecorder::guard(bool expected, LIns* cond, LIns* exit)
{
if (!cond->isCond()) {
expected = !expected;
cond = lir->ins_eq0(cond);
}
return lir->insGuard(expected ? LIR_xf : LIR_xt, cond, exit);
#ifdef DEBUG
LIns* guard =
#endif
lir->insGuard(expected ? LIR_xf : LIR_xt, cond, exit);
#ifdef DEBUG
if (guard) {
GuardRecord* lr = guard->record();
VMSideExit* e = (VMSideExit*)lr->exit;
debug_only_v(printf(" lr=%p exitType=%d\n", (SideExit*)e, e->exitType);)
} else {
debug_only_v(printf(" redundant guard, eliminated\n");)
}
#endif
}
/* Emit a guard for condition (cond), expecting to evaluate to boolean result (expected)
and generate a side exit with type exitType to jump to if the condition does not hold. */
JS_REQUIRES_STACK LIns*
JS_REQUIRES_STACK void
TraceRecorder::guard(bool expected, LIns* cond, ExitType exitType)
{
return guard(expected, cond, snapshot(exitType));
guard(expected, cond, snapshot(exitType));
}
/* Try to match the type of a slot to type t. checkType is used to verify that the type of
@ -2806,19 +2888,27 @@ JS_REQUIRES_STACK void
TraceRecorder::emitTreeCall(Fragment* inner, VMSideExit* exit)
{
TreeInfo* ti = (TreeInfo*)inner->vmprivate;
/* Invoke the inner tree. */
LIns* args[] = { INS_CONSTPTR(inner), lirbuf->state }; /* reverse order */
LIns* ret = lir->insCall(&js_CallTree_ci, args);
/* Read back all registers, in case the called tree changed any of them. */
JS_ASSERT(!memchr(getGlobalTypeMap(exit), JSVAL_BOXED, exit->numGlobalSlots) &&
!memchr(getStackTypeMap(exit), JSVAL_BOXED, exit->numStackSlots));
import(ti, inner_sp_ins, exit->numStackSlots, exit->numGlobalSlots,
exit->calldepth, getFullTypeMap(exit));
/* Restore sp and rp to their original values (we still have them in a register). */
if (callDepth > 0) {
lir->insStorei(lirbuf->sp, lirbuf->state, offsetof(InterpState, sp));
lir->insStorei(lirbuf->rp, lirbuf->state, offsetof(InterpState, rp));
}
/* Guard that we come out of the inner tree along the same side exit we came out when
we called the inner tree at recording time. */
/*
* Guard that we come out of the inner tree along the same side exit we came out when
* we called the inner tree at recording time.
*/
guard(true, lir->ins2(LIR_eq, ret, INS_CONSTPTR(exit)), NESTED_EXIT);
/* Register us as a dependent tree of the inner tree. */
((TreeInfo*)inner->vmprivate)->dependentTrees.addUnique(fragment->root);
@ -6057,7 +6147,7 @@ TraceRecorder::box_jsval(jsval v, LIns*& v_ins)
}
JS_REQUIRES_STACK void
TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins)
TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins, LIns* exit)
{
if (isNumber(v)) {
// JSVAL_IS_NUMBER(v)
@ -6068,7 +6158,7 @@ TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins)
lir->ins2(LIR_piand, v_ins,
INS_CONST(JSVAL_TAGMASK)),
JSVAL_DOUBLE))),
MISMATCH_EXIT);
exit);
LIns* args[] = { v_ins };
v_ins = lir->insCall(&js_UnboxDouble_ci, args);
return;
@ -6079,16 +6169,15 @@ TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins)
lir->ins2i(LIR_eq,
lir->ins2(LIR_piand, v_ins, INS_CONST(JSVAL_TAGMASK)),
JSVAL_BOOLEAN),
MISMATCH_EXIT);
exit);
v_ins = lir->ins2i(LIR_ush, v_ins, JSVAL_TAGBITS);
return;
case JSVAL_OBJECT:
if (JSVAL_IS_NULL(v)) {
// JSVAL_NULL maps to type JSVAL_TNULL, so insist that v_ins == 0 here.
guard(true, lir->ins_eq0(v_ins), MISMATCH_EXIT);
guard(true, lir->ins_eq0(v_ins), exit);
} else {
// We must guard that v_ins has JSVAL_OBJECT tag but is not JSVAL_NULL.
LIns* exit = snapshot(MISMATCH_EXIT);
guard(true,
lir->ins2i(LIR_eq,
lir->ins2(LIR_piand, v_ins, INS_CONST(JSVAL_TAGMASK)),
@ -6103,7 +6192,7 @@ TraceRecorder::unbox_jsval(jsval v, LIns*& v_ins)
lir->ins2i(LIR_eq,
lir->ins2(LIR_piand, v_ins, INS_CONST(JSVAL_TAGMASK)),
JSVAL_STRING),
MISMATCH_EXIT);
exit);
v_ins = lir->ins2(LIR_piand, v_ins, INS_CONST(~JSVAL_TAGMASK));
return;
}
@ -6151,10 +6240,11 @@ TraceRecorder::guardDenseArrayIndex(JSObject* obj, jsint idx, LIns* obj_ins,
bool cond = (jsuint(idx) < jsuint(obj->fslots[JSSLOT_ARRAY_LENGTH]) && jsuint(idx) < capacity);
if (cond) {
LIns* exit = snapshot(exitType);
/* Guard array length */
LIns* exit = guard(true,
lir->ins2(LIR_ult, idx_ins, stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH)),
exitType)->oprnd2();
guard(true,
lir->ins2(LIR_ult, idx_ins, stobj_get_fslot(obj_ins, JSSLOT_ARRAY_LENGTH)),
exit);
/* dslots must not be NULL */
guard(false,
lir->ins_eq0(dslots_ins),
@ -7862,13 +7952,12 @@ TraceRecorder::record_FastNativeCallComplete()
because that would cause the interpreter to re-execute the native
function, which might have side effects.
Instead, snapshot(), which is invoked from unbox_jsval() below, will see
that we are currently parked on a traceable native's JSOP_CALL
instruction, and it will advance the pc to restore by the length of the
current opcode. If the native's return type is jsval, snapshot() will
also indicate in the type map that the element on top of the stack is a
boxed value which doesn't need to be boxed if the type guard generated
by unbox_jsval() fails. */
Instead, the snapshot() call below sees that we are currently parked on
a traceable native's JSOP_CALL instruction, and it will advance the pc
to restore by the length of the current opcode. If the native's return
type is jsval, snapshot() will also indicate in the type map that the
element on top of the stack is a boxed value which doesn't need to be
boxed if the type guard generated by unbox_jsval() fails. */
if (JSTN_ERRTYPE(pendingTraceableNative) == FAIL_STATUS) {
#ifdef DEBUG
@ -7922,7 +8011,13 @@ TraceRecorder::record_FastNativeCallComplete()
bool ok = true;
if (pendingTraceableNative->flags & JSTN_UNBOX_AFTER) {
unbox_jsval(v, v_ins);
/*
* If we side exit on the unboxing code due to a type change, make sure that the boxed
* value is actually currently associated with that location, and that we are talking
* about the top of the stack here, which is where we expected boxed values.
*/
JS_ASSERT(&v == &cx->fp->regs->sp[-1] && get(&v) == v_ins);
unbox_jsval(v, v_ins, snapshot(BRANCH_EXIT));
set(&v, v_ins);
} else if (JSTN_ERRTYPE(pendingTraceableNative) == FAIL_NEG) {
/* Already added i2f in functionCall. */
@ -8035,8 +8130,13 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32& slot, LIns*& v_ins)
LIns* args[] = { INS_CONSTPTR(sprop), obj_ins, cx_ins };
v_ins = lir->insCall(&js_CallGetter_ci, args);
guard(false, lir->ins2(LIR_eq, v_ins, INS_CONST(JSVAL_ERROR_COOKIE)), OOM_EXIT);
/*
* BIG FAT WARNING: This snapshot cannot be a BRANCH_EXIT, since
* the value to the top of the stack is not the value we unbox.
*/
unbox_jsval((sprop->shortid == REGEXP_SOURCE) ? JSVAL_STRING : JSVAL_BOOLEAN,
v_ins);
v_ins,
snapshot(MISMATCH_EXIT));
return true;
}
ABORT_TRACE("non-stub getter");
@ -8066,7 +8166,8 @@ TraceRecorder::prop(JSObject* obj, LIns* obj_ins, uint32& slot, LIns*& v_ins)
}
v_ins = stobj_get_slot(obj_ins, slot, dslots_ins);
unbox_jsval(STOBJ_GET_SLOT(obj, slot), v_ins);
unbox_jsval(STOBJ_GET_SLOT(obj, slot), v_ins, snapshot(BRANCH_EXIT));
return true;
}
@ -8129,7 +8230,7 @@ TraceRecorder::elem(jsval& oval, jsval& idx, jsval*& vp, LIns*& v_ins, LIns*& ad
/* Load the value and guard on its type to unbox it. */
v_ins = lir->insLoad(LIR_ldp, addr_ins, 0);
unbox_jsval(*vp, v_ins);
unbox_jsval(*vp, v_ins, snapshot(BRANCH_EXIT));
if (JSVAL_TAG(*vp) == JSVAL_BOOLEAN) {
// Optimize to guard for a hole only after untagging, so we know that

View File

@ -415,7 +415,7 @@ class TraceRecorder : public avmplus::GCObject {
bool isGlobal(jsval* p) const;
ptrdiff_t nativeGlobalOffset(jsval* p) const;
JS_REQUIRES_STACK ptrdiff_t nativeStackOffset(jsval* p) const;
JS_REQUIRES_STACK void import(nanojit::LIns* base, ptrdiff_t offset, jsval* p, uint8& t,
JS_REQUIRES_STACK void import(nanojit::LIns* base, ptrdiff_t offset, jsval* p, uint8 t,
const char *prefix, uintN index, JSStackFrame *fp);
JS_REQUIRES_STACK void import(TreeInfo* treeInfo, nanojit::LIns* sp, unsigned stackSlots,
unsigned callDepth, unsigned ngslots, uint8* typeMap);
@ -424,9 +424,8 @@ class TraceRecorder : public avmplus::GCObject {
JS_REQUIRES_STACK bool isValidSlot(JSScope* scope, JSScopeProperty* sprop);
JS_REQUIRES_STACK bool lazilyImportGlobalSlot(unsigned slot);
JS_REQUIRES_STACK nanojit::LIns* guard(bool expected, nanojit::LIns* cond,
ExitType exitType);
nanojit::LIns* guard(bool expected, nanojit::LIns* cond, nanojit::LIns* exit);
JS_REQUIRES_STACK void guard(bool expected, nanojit::LIns* cond, ExitType exitType);
JS_REQUIRES_STACK void guard(bool expected, nanojit::LIns* cond, nanojit::LIns* exit);
nanojit::LIns* addName(nanojit::LIns* ins, const char* name);
@ -520,7 +519,7 @@ class TraceRecorder : public avmplus::GCObject {
JS_REQUIRES_STACK bool getThis(nanojit::LIns*& this_ins);
JS_REQUIRES_STACK void box_jsval(jsval v, nanojit::LIns*& v_ins);
JS_REQUIRES_STACK void unbox_jsval(jsval v, nanojit::LIns*& v_ins);
JS_REQUIRES_STACK void unbox_jsval(jsval v, nanojit::LIns*& v_ins, nanojit::LIns* exit);
JS_REQUIRES_STACK bool guardClass(JSObject* obj, nanojit::LIns* obj_ins, JSClass* clasp,
nanojit::LIns* exit);
JS_REQUIRES_STACK bool guardDenseArray(JSObject* obj, nanojit::LIns* obj_ins,
@ -561,6 +560,8 @@ public:
JS_REQUIRES_STACK uint8 determineSlotType(jsval* vp);
JS_REQUIRES_STACK nanojit::LIns* snapshot(ExitType exitType);
nanojit::LIns* clone(VMSideExit* exit);
nanojit::LIns* copy(VMSideExit* exit);
nanojit::Fragment* getFragment() const { return fragment; }
TreeInfo* getTreeInfo() const { return treeInfo; }
JS_REQUIRES_STACK void compile(JSTraceMonitor* tm);

View File

@ -48,11 +48,17 @@ function testmath(funcname, args, expected) {
}
testfunc.name = funcname + "(" + args + ")";
testfunc.expected = expected;
testfunc.jitstats = {
recorderStarted: 1,
recorderAborted: 0,
traceTriggered: 1
};
// Disable jitstats check. This never worked right. The actual part of the
// loop we cared about was never traced. We traced the filler parts early
// and then took a mismatch side exit on every subequent array read with
// a different type (gal, discovered when fixing bug 479110).
// testfunc.jitstats = {
// recorderStarted: 1,
// recorderAborted: 0,
// traceTriggered: 1
// };
test(testfunc);
}