Bug 487845 - TM: After deep-bailing, we can lirbuf->rewind() and then return to a dead code page. r=gal.

This commit is contained in:
Jason Orendorff 2009-04-14 08:45:37 -05:00
parent 225dc1322f
commit 0616f91d10
4 changed files with 107 additions and 69 deletions

View File

@ -131,20 +131,14 @@ struct JSTraceMonitor {
* !onTrace && !recorder: not on trace.
* onTrace && recorder: recording a trace.
* onTrace && !recorder: executing a trace.
* !onTrace && recorder && !prohibitRecording:
* !onTrace && recorder && !prohibitFlush:
* not on trace; deep-aborted while recording.
* !onTrace && recorder && prohibitRecording:
* !onTrace && recorder && prohibitFlush:
* not on trace; deep-bailed in SpiderMonkey code called from a
* trace. JITted code is on the stack.
*/
JSPackedBool onTrace;
/*
* Do not start recording after a deep bail. That would free JITted code
* pages that we will later return to.
*/
JSPackedBool prohibitRecording;
/* See reservedObjects below. */
JSPackedBool useReservedObjects;
@ -156,7 +150,15 @@ struct JSTraceMonitor {
struct GlobalState globalStates[MONITOR_N_GLOBAL_STATES];
struct VMFragment* vmfragments[FRAGMENT_TABLE_SIZE];
JSBool needFlush;
/*
* If nonzero, do not flush the JIT cache after a deep bail. That would
* free JITted code pages that we will later return to. Instead, set
* the needFlush flag so that it can be flushed later.
*/
uintN prohibitFlush;
JSBool needFlush;
/*
* reservedObjects is a linked list (via fslots[0]) of preallocated JSObjects.

View File

@ -2555,6 +2555,7 @@ checktype_fail_2:
JS_REQUIRES_STACK void
TraceRecorder::compile(JSTraceMonitor* tm)
{
JS_ASSERT(!tm->needFlush);
Fragmento* fragmento = tm->fragmento;
if (treeInfo->maxNativeStackSlots >= MAX_NATIVE_STACK_SLOTS) {
debug_only_v(printf("Blacklist: excessive stack use.\n"));
@ -2631,6 +2632,8 @@ TraceRecorder::closeLoop(JSTraceMonitor* tm, bool& demote)
*/
JS_ASSERT((*cx->fp->regs->pc == JSOP_LOOP || *cx->fp->regs->pc == JSOP_NOP) && !cx->fp->imacpc);
JS_ASSERT(!tm->needFlush);
bool stable;
LIns* exitIns;
Fragment* peer;
@ -3101,6 +3104,52 @@ nanojit::Fragment::onDestroy()
delete (TreeInfo *)vmprivate;
}
static JS_REQUIRES_STACK void
FlushJITCache(JSContext* cx)
{
if (!TRACING_ENABLED(cx))
return;
JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx);
debug_only_v(printf("Flushing cache.\n");)
if (tm->recorder)
js_AbortRecording(cx, "flush cache");
TraceRecorder* tr;
while ((tr = tm->abortStack) != NULL) {
tr->removeFragmentoReferences();
tr->deepAbort();
tr->popAbortStack();
}
Fragmento* fragmento = tm->fragmento;
if (fragmento) {
if (tm->prohibitFlush) {
debug_only_v(printf("Deferring fragmento flush due to deep bail.\n");)
tm->needFlush = JS_TRUE;
return;
}
fragmento->clearFrags();
#ifdef DEBUG
JS_ASSERT(fragmento->labels);
fragmento->labels->clear();
#endif
tm->lirbuf->rewind();
for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) {
VMFragment* f = tm->vmfragments[i];
while (f) {
VMFragment* next = f->next;
fragmento->clearFragment(f);
f = next;
}
tm->vmfragments[i] = NULL;
}
for (size_t i = 0; i < MONITOR_N_GLOBAL_STATES; ++i) {
tm->globalStates[i].globalShape = -1;
tm->globalStates[i].globalSlots->clear();
}
}
tm->needFlush = JS_FALSE;
}
static JS_REQUIRES_STACK bool
js_DeleteRecorder(JSContext* cx)
{
@ -3115,7 +3164,7 @@ js_DeleteRecorder(JSContext* cx)
*/
if (JS_TRACE_MONITOR(cx).fragmento->assm()->error() == OutOMem
|| js_OverfullFragmento(tm->fragmento, MAX_MEM_IN_MAIN_FRAGMENTO)) {
js_FlushJITCache(cx);
FlushJITCache(cx);
return false;
}
@ -3129,10 +3178,8 @@ static inline bool
js_CheckGlobalObjectShape(JSContext* cx, JSTraceMonitor* tm, JSObject* globalObj,
uint32 *shape=NULL, SlotList** slots=NULL)
{
if (tm->needFlush) {
tm->needFlush = JS_FALSE;
if (tm->needFlush)
return false;
}
uint32 globalShape = OBJ_SHAPE(globalObj);
@ -3189,7 +3236,7 @@ js_StartRecorder(JSContext* cx, VMSideExit* anchor, Fragment* f, TreeInfo* ti,
JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx);
JS_ASSERT(f->root != f || !cx->fp->imacpc);
if (JS_TRACE_MONITOR(cx).prohibitRecording)
if (JS_TRACE_MONITOR(cx).needFlush)
return false;
/* start recording if no exception during construction */
@ -3416,7 +3463,7 @@ js_RecordTree(JSContext* cx, JSTraceMonitor* tm, Fragment* f, jsbytecode* outer,
/* Make sure the global type map didn't change on us. */
if (!js_CheckGlobalObjectShape(cx, tm, globalObj)) {
js_FlushJITCache(cx);
FlushJITCache(cx);
return false;
}
@ -3429,7 +3476,7 @@ js_RecordTree(JSContext* cx, JSTraceMonitor* tm, Fragment* f, jsbytecode* outer,
f = getAnchor(&JS_TRACE_MONITOR(cx), f->root->ip, globalObj, globalShape);
if (!f) {
js_FlushJITCache(cx);
FlushJITCache(cx);
return false;
}
@ -3438,7 +3485,7 @@ js_RecordTree(JSContext* cx, JSTraceMonitor* tm, Fragment* f, jsbytecode* outer,
if (f->lirbuf->outOMem() ||
js_OverfullFragmento(tm->fragmento, MAX_MEM_IN_MAIN_FRAGMENTO)) {
js_FlushJITCache(cx);
FlushJITCache(cx);
debug_only_v(printf("Out of memory recording new tree, flushing cache.\n");)
return false;
}
@ -3452,9 +3499,12 @@ js_RecordTree(JSContext* cx, JSTraceMonitor* tm, Fragment* f, jsbytecode* outer,
ti->typeMap.captureTypes(cx, *globalSlots, 0/*callDepth*/);
ti->nStackTypes = ti->typeMap.length() - globalSlots->length();
/* Check for duplicate entry type maps. This is always wrong and hints at trace explosion
since we are trying to stabilize something without properly connecting peer edges. */
#ifdef DEBUG
#ifdef DEBUG
/*
* Check for duplicate entry type maps. This is always wrong and hints at
* trace explosion since we are trying to stabilize something without
* properly connecting peer edges.
*/
TreeInfo* ti_other;
for (Fragment* peer = getLoop(tm, f->root->ip, globalObj, globalShape); peer != NULL;
peer = peer->peer) {
@ -3467,7 +3517,7 @@ js_RecordTree(JSContext* cx, JSTraceMonitor* tm, Fragment* f, jsbytecode* outer,
ti->treeFileName = cx->fp->script->filename;
ti->treeLineNumber = js_FramePCToLineNumber(cx, cx->fp);
ti->treePCOffset = FramePCOffset(cx->fp);
#endif
#endif
/* determine the native frame layout at the entry point */
unsigned entryNativeStackSlots = ti->nStackTypes;
@ -3503,6 +3553,7 @@ JS_REQUIRES_STACK static bool
js_AttemptToStabilizeTree(JSContext* cx, VMSideExit* exit, jsbytecode* outer)
{
JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx);
JS_ASSERT(!tm->needFlush);
VMFragment* from = (VMFragment*)exit->from->root;
TreeInfo* from_ti = (TreeInfo*)from->vmprivate;
@ -3601,6 +3652,8 @@ js_AttemptToStabilizeTree(JSContext* cx, VMSideExit* exit, jsbytecode* outer)
static JS_REQUIRES_STACK bool
js_AttemptToExtendTree(JSContext* cx, VMSideExit* anchor, VMSideExit* exitedFrom, jsbytecode* outer)
{
JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx);
JS_ASSERT(!tm->needFlush);
Fragment* f = anchor->from->root;
JS_ASSERT(f->vmprivate);
TreeInfo* ti = (TreeInfo*)f->vmprivate;
@ -3746,7 +3799,7 @@ js_RecordLoopEdge(JSContext* cx, TraceRecorder* r, uintN& inlineCallCount)
if (!f) {
f = getAnchor(tm, cx->fp->regs->pc, globalObj, globalShape);
if (!f) {
js_FlushJITCache(cx);
FlushJITCache(cx);
return false;
}
}
@ -4073,6 +4126,10 @@ js_ExecuteTree(JSContext* cx, Fragment* f, uintN& inlineCallCount,
AUDIT(traceTriggered);
#ifdef DEBUG
cx->interpState = NULL;
#endif
JS_ASSERT(lr->exitType != LOOP_EXIT || !lr->calldepth);
tm->onTrace = false;
LeaveTree(*state, lr);
@ -4166,7 +4223,9 @@ LeaveTree(InterpState& state, VMSideExit* lr)
typeMap[innermost->numStackSlots - 1],
(jsdouble *) state.sp + innermost->sp_adj / sizeof(jsdouble) - 1);
}
JS_TRACE_MONITOR(cx).prohibitRecording = false;
JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx);
if (tm->prohibitFlush && --tm->prohibitFlush == 0 && tm->needFlush)
FlushJITCache(cx);
return;
}
@ -4342,7 +4401,7 @@ js_MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount)
SlotList* globalSlots = NULL;
if (!js_CheckGlobalObjectShape(cx, tm, globalObj, &globalShape, &globalSlots))
js_FlushJITCache(cx);
FlushJITCache(cx);
/* Do not enter the JIT code with a pending operation callback. */
if (cx->operationCallbackFlag)
@ -4355,7 +4414,7 @@ js_MonitorLoopEdge(JSContext* cx, uintN& inlineCallCount)
f = getAnchor(tm, pc, globalObj, globalShape);
if (!f) {
js_FlushJITCache(cx);
FlushJITCache(cx);
return false;
}
@ -4470,7 +4529,7 @@ TraceRecorder::monitorRecording(JSContext* cx, TraceRecorder* tr, JSOp op)
js_OverfullFragmento(JS_TRACE_MONITOR(cx).fragmento,
MAX_MEM_IN_MAIN_FRAGMENTO)) {
js_AbortRecording(cx, "no more LIR memory");
js_FlushJITCache(cx);
FlushJITCache(cx);
return JSMRS_STOP;
}
@ -4898,45 +4957,6 @@ js_OverfullFragmento(Fragmento *frago, size_t maxsz)
return (frago->_stats.pages > (maxsz >> NJ_LOG2_PAGE_SIZE));
}
JS_REQUIRES_STACK void
js_FlushJITCache(JSContext* cx)
{
if (!TRACING_ENABLED(cx))
return;
debug_only_v(printf("Flushing cache.\n");)
JSTraceMonitor* tm = &JS_TRACE_MONITOR(cx);
if (tm->recorder)
js_AbortRecording(cx, "flush cache");
TraceRecorder* tr;
while ((tr = tm->abortStack) != NULL) {
tr->removeFragmentoReferences();
tr->deepAbort();
tr->popAbortStack();
}
Fragmento* fragmento = tm->fragmento;
if (fragmento) {
fragmento->clearFrags();
#ifdef DEBUG
JS_ASSERT(fragmento->labels);
fragmento->labels->clear();
#endif
tm->lirbuf->rewind();
for (size_t i = 0; i < FRAGMENT_TABLE_SIZE; ++i) {
VMFragment* f = tm->vmfragments[i];
while(f) {
VMFragment* next = f->next;
fragmento->clearFragment(f);
f = next;
}
tm->vmfragments[i] = NULL;
}
for (size_t i = 0; i < MONITOR_N_GLOBAL_STATES; ++i) {
tm->globalStates[i].globalShape = -1;
tm->globalStates[i].globalSlots->clear();
}
}
}
JS_FORCES_STACK JS_FRIEND_API(void)
js_DeepBail(JSContext *cx)
{
@ -4946,7 +4966,8 @@ js_DeepBail(JSContext *cx)
JS_ASSERT(cx->bailExit);
JS_TRACE_MONITOR(cx).onTrace = false;
JS_TRACE_MONITOR(cx).prohibitRecording = true;
JS_TRACE_MONITOR(cx).prohibitFlush++;
debug_only_v(printf("Deep bail.\n");)
LeaveTree(*cx->interpState, cx->bailExit);
cx->bailExit = NULL;
cx->interpState->builtinStatus |= JSBUILTIN_BAILED;

View File

@ -661,9 +661,6 @@ js_PurgeScriptFragments(JSContext* cx, JSScript* script);
extern bool
js_OverfullFragmento(nanojit::Fragmento *frago, size_t maxsz);
extern void
js_FlushJITCache(JSContext* cx);
extern void
js_PurgeJITOracle();

View File

@ -4996,6 +4996,24 @@ function testDeepPropertyShadowing()
}
test(testDeepPropertyShadowing);
// Complicated whitebox test for bug 487845.
function testGlobalShapeChangeAfterDeepBail() {
function f(name) {
this[name] = 1; // may change global shape
for (var i = 0; i < 4; i++)
; // MonitorLoopEdge eventually triggers assertion
}
// When i==3, deep-bail, then change global shape enough times to exhaust
// the array of GlobalStates.
var arr = [[], [], [], ["bug0", "bug1", "bug2", "bug3", "bug4"]];
for (var i = 0; i < arr.length; i++)
arr[i].forEach(f);
}
test(testGlobalShapeChangeAfterDeepBail);
for (let i = 0; i < 5; i++)
delete this["bug" + i];
/*****************************************************************************
* *
* _____ _ _ _____ ______ _____ _______ *