From 0616f91d108b3db3d99edaf03633d52b2295aade Mon Sep 17 00:00:00 2001 From: Jason Orendorff Date: Tue, 14 Apr 2009 08:45:37 -0500 Subject: [PATCH] Bug 487845 - TM: After deep-bailing, we can lirbuf->rewind() and then return to a dead code page. r=gal. --- js/src/jscntxt.h | 20 ++++--- js/src/jstracer.cpp | 135 +++++++++++++++++++++++++------------------ js/src/jstracer.h | 3 - js/src/trace-test.js | 18 ++++++ 4 files changed, 107 insertions(+), 69 deletions(-) diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 60757fea535..88ef53c3250 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -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. diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp index c4a6e1b4b6c..8449ba90d72 100644 --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -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; diff --git a/js/src/jstracer.h b/js/src/jstracer.h index 1858fd27e63..f7c8a02ce46 100644 --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -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(); diff --git a/js/src/trace-test.js b/js/src/trace-test.js index 064bf009405..a797e81dc62 100644 --- a/js/src/trace-test.js +++ b/js/src/trace-test.js @@ -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]; + /***************************************************************************** * * * _____ _ _ _____ ______ _____ _______ *