[INFER] Only allow expansion of all inline frames in a compartment, bug 675251.
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 01 Aug 2011 09:09:39 -0700
changeset 76087 d763fda00eb9a264e53e67f0188581757b81f0e8
parent 76086 e5b57c9ebbe94042069d978567a2ba217eee0670
child 76088 674160662e80b4537796dec10668fb3117fd41db
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
bugs675251
milestone8.0a1
[INFER] Only allow expansion of all inline frames in a compartment, bug 675251.
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsinfer.cpp
js/src/jsobj.cpp
js/src/jsopcode.cpp
js/src/jswrapper.cpp
js/src/methodjit/InvokeHelpers.cpp
js/src/methodjit/MethodJIT.h
js/src/methodjit/Retcon.cpp
js/src/methodjit/StubCalls.cpp
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1263,17 +1263,17 @@ TriggerAllOperationCallbacks(JSRuntime *
 }
 
 } /* namespace js */
 
 StackFrame *
 js_GetScriptedCaller(JSContext *cx, StackFrame *fp)
 {
     if (!fp)
-        fp = js_GetTopStackFrame(cx, FRAME_EXPAND_TOP);
+        fp = js_GetTopStackFrame(cx, FRAME_EXPAND_ALL);
     while (fp && fp->isDummyFrame())
         fp = fp->prev();
     JS_ASSERT_IF(fp, fp->isScriptFrame());
     return fp;
 }
 
 jsbytecode*
 js_GetCurrentBytecodePC(JSContext* cx)
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2309,39 +2309,42 @@ namespace js {
 extern JS_FORCES_STACK JS_FRIEND_API(void)
 LeaveTrace(JSContext *cx);
 
 extern bool
 CanLeaveTrace(JSContext *cx);
 
 #ifdef JS_METHODJIT
 namespace mjit {
-    void ExpandInlineFrames(JSCompartment *compartment, bool all);
+    void ExpandInlineFrames(JSCompartment *compartment);
 }
 #endif
 
 } /* namespace js */
 
+/* How much expansion of inlined frames to do when inspecting the stack. */
+enum FrameExpandKind {
+    FRAME_EXPAND_NONE = 0,
+    FRAME_EXPAND_ALL = 1
+};
+
 /*
  * Get the current frame, first lazily instantiating stack frames if needed.
  * (Do not access cx->fp() directly except in JS_REQUIRES_STACK code.)
  *
  * LeaveTrace is defined in jstracer.cpp if JS_TRACER is defined.
- *
- * If the stack contains frames inlined by the method JIT, kind specifies
- * which ones to expand.
  */
 static JS_FORCES_STACK JS_INLINE js::StackFrame *
-js_GetTopStackFrame(JSContext *cx, js::FrameExpandKind expand)
+js_GetTopStackFrame(JSContext *cx, FrameExpandKind expand)
 {
     js::LeaveTrace(cx);
 
 #ifdef JS_METHODJIT
-    if (expand != js::FRAME_EXPAND_NONE)
-        js::mjit::ExpandInlineFrames(cx->compartment, expand == js::FRAME_EXPAND_ALL);
+    if (expand)
+        js::mjit::ExpandInlineFrames(cx->compartment);
 #endif
 
     return cx->maybefp();
 }
 
 static JS_INLINE JSBool
 js_IsPropertyCacheDisabled(JSContext *cx)
 {
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1547,17 +1547,17 @@ types::MarkArgumentsCreated(JSContext *c
                         OBJECT_FLAG_CREATED_ARGUMENTS | OBJECT_FLAG_UNINLINEABLE);
 
     if (!script->usedLazyArgs)
         return;
 
     AutoEnterTypeInference enter(cx);
 
 #ifdef JS_METHODJIT
-    mjit::ExpandInlineFrames(cx->compartment, true);
+    mjit::ExpandInlineFrames(cx->compartment);
 #endif
 
     if (!script->ensureRanBytecode(cx))
         return;
 
     ScriptAnalysis *analysis = script->analysis();
 
     for (FrameRegsIter iter(cx); !iter.done(); ++iter) {
@@ -1950,17 +1950,17 @@ TypeCompartment::processPendingRecompile
     /* Steal the list of scripts to recompile, else we will try to recursively recompile them. */
     Vector<JSScript*> *pending = pendingRecompiles;
     pendingRecompiles = NULL;
 
     JS_ASSERT(!pending->empty());
 
 #ifdef JS_METHODJIT
 
-    mjit::ExpandInlineFrames(cx->compartment, true);
+    mjit::ExpandInlineFrames(cx->compartment);
 
     for (unsigned i = 0; i < pending->length(); i++) {
         JSScript *script = (*pending)[i];
         mjit::Recompiler recompiler(cx, script);
         if (script->hasJITCode())
             recompiler.recompile();
     }
 
@@ -2020,17 +2020,17 @@ TypeCompartment::nukeTypes(JSContext *cx
          cl != &cx->runtime->contextList;
          cl = cl->next) {
         JSContext *cx = js_ContextFromLinkField(cl);
         cx->setCompartment(cx->compartment);
     }
 
 #ifdef JS_METHODJIT
 
-    mjit::ExpandInlineFrames(cx->compartment, true);
+    mjit::ExpandInlineFrames(cx->compartment);
 
     /* Throw away all JIT code in the compartment, but leave everything else alone. */
     for (JSCList *cursor = compartment->scripts.next;
          cursor != &compartment->scripts;
          cursor = cursor->next) {
         JSScript *script = reinterpret_cast<JSScript *>(cursor);
         if (script->hasJITCode()) {
             mjit::Recompiler recompiler(cx, script);
@@ -2782,19 +2782,23 @@ TypeObject::markSlotReallocation(JSConte
 void
 TypeObject::setFlags(JSContext *cx, TypeObjectFlags flags)
 {
     if ((this->flags & flags) == flags)
         return;
 
     AutoEnterTypeInference enter(cx);
 
-    /* Sets of CREATED_ARGUMENTS should go through MarkArgumentsCreated. */
-    JS_ASSERT_IF(flags & OBJECT_FLAG_CREATED_ARGUMENTS,
-                 (flags & OBJECT_FLAG_UNINLINEABLE) && functionScript->createdArgs);
+    if (singleton) {
+        /* Make sure flags are consistent with persistent object state. */
+        JS_ASSERT_IF(flags & OBJECT_FLAG_CREATED_ARGUMENTS,
+                     (flags & OBJECT_FLAG_UNINLINEABLE) && functionScript->createdArgs);
+        JS_ASSERT_IF(flags & OBJECT_FLAG_UNINLINEABLE, functionScript->uninlineable);
+        JS_ASSERT_IF(flags & OBJECT_FLAG_ITERATED, singleton->flags & JSObject::ITERATED);
+    }
 
     this->flags |= flags;
 
     InferSpew(ISpewOps, "%s: setFlags %u", name(), flags);
 
     ObjectStateChange(cx, this, false, false);
 }
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5758,17 +5758,17 @@ js_GetMethod(JSContext *cx, JSObject *ob
         return js_GetXMLMethod(cx, obj, id, vp);
 #endif
     return op(cx, obj, obj, id, vp);
 }
 
 JS_FRIEND_API(bool)
 js_CheckUndeclaredVarAssignment(JSContext *cx, JSString *propname)
 {
-    StackFrame *const fp = js_GetTopStackFrame(cx, FRAME_EXPAND_TOP);
+    StackFrame *const fp = js_GetTopStackFrame(cx, FRAME_EXPAND_ALL);
     if (!fp)
         return true;
 
     /* If neither cx nor the code is strict, then no check is needed. */
     if (!(fp->isScriptFrame() && fp->script()->strictModeCode) &&
         !cx->hasStrictOption()) {
         return true;
     }
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -5067,17 +5067,17 @@ js_DecompileValueGenerator(JSContext *cx
               spindex == JSDVG_IGNORE_STACK ||
               spindex == JSDVG_SEARCH_STACK);
 
     LeaveTrace(cx);
     
     if (!cx->hasfp() || !cx->fp()->isScriptFrame())
         goto do_fallback;
 
-    fp = js_GetTopStackFrame(cx, FRAME_EXPAND_TOP);
+    fp = js_GetTopStackFrame(cx, FRAME_EXPAND_ALL);
     script = fp->script();
     pc = fp->hasImacropc() ? fp->imacropc() : cx->regs().pc;
     JS_ASSERT(script->code <= pc && pc < script->code + script->length);
 
     if (pc < script->main)
         goto do_fallback;
     
     if (spindex != JSDVG_IGNORE_STACK) {
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -431,16 +431,20 @@ AutoCompartment::~AutoCompartment()
 
 bool
 AutoCompartment::enter()
 {
     JS_ASSERT(!entered);
     if (origin != destination) {
         LeaveTrace(context);
 
+#ifdef JS_METHODJIT
+        mjit::ExpandInlineFrames(context->compartment);
+#endif
+
         JSObject *scopeChain = target->getGlobal();
         JS_ASSERT(scopeChain->isNative());
 
         frame.construct();
 
         /*
          * Set the compartment eagerly so that pushDummyFrame associates the
          * resource allocation request with 'destination' instead of 'origin'.
--- a/js/src/methodjit/InvokeHelpers.cpp
+++ b/js/src/methodjit/InvokeHelpers.cpp
@@ -361,17 +361,17 @@ UncachedInlineCall(VMFrame &f, InitialFr
 
     /*
      * Otherwise, run newscript in the interpreter. Expand any inlined frame we
      * are calling from, as the new frame is not associated with the VMFrame
      * and will not have its prevpc info updated if frame expansion is
      * triggered while interpreting.
      */
     if (f.regs.inlined()) {
-        ExpandInlineFrames(cx->compartment, false);
+        ExpandInlineFrames(cx->compartment);
         JS_ASSERT(!f.regs.inlined());
         regs.fp()->resetInlinePrev(f.fp(), f.regs.pc);
     }
 
     bool ok = !!Interpret(cx, cx->fp());
     f.cx->stack.popInlineFrame(regs);
 
     if (ok)
@@ -606,17 +606,17 @@ js_InternalThrow(VMFrame &f)
         cx->compartment->jaegerCompartment()->setLastUnfinished(Jaeger_Unfinished);
 
         /*
          * Expanding inline frames will ensure that prevpc values are filled in
          * for all frames on this VMFrame, without needing to walk the entire
          * stack: downFramesExpanded() on a StackFrame also means the prevpc()
          * values are also filled in.
          */
-        ExpandInlineFrames(cx->compartment, true);
+        ExpandInlineFrames(cx->compartment);
 
         if (!script->ensureRanBytecode(cx)) {
             js_ReportOutOfMemory(cx);
             return NULL;
         }
 
         analyze::AutoEnterAnalysis enter(cx);
 
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -669,19 +669,19 @@ void JS_FASTCALL
 ProfileStubCall(VMFrame &f);
 
 CompileStatus JS_NEVER_INLINE
 TryCompile(JSContext *cx, StackFrame *fp);
 
 void
 ReleaseScriptCode(JSContext *cx, JSScript *script, bool normal);
 
-// Expand either the topmost stack frame or all stack frames inlined by the JIT.
+// Expand all stack frames inlined by the JIT within a compartment.
 void
-ExpandInlineFrames(JSCompartment *compartment, bool all);
+ExpandInlineFrames(JSCompartment *compartment);
 
 // Return all VMFrames in a compartment to the interpreter. This must be
 // followed by destroying all JIT code in the compartment.
 void
 ClearAllFrames(JSCompartment *compartment);
 
 // Information about a frame inlined during compilation.
 struct InlineFrame
--- a/js/src/methodjit/Retcon.cpp
+++ b/js/src/methodjit/Retcon.cpp
@@ -324,28 +324,21 @@ Recompiler::expandInlineFrames(JSCompart
         if (JITCodeReturnAddress(*addr)) {
             innerfp->setRejoin(ScriptedRejoin(inlined->pcOffset));
             *addr = JS_FUNC_TO_DATA_PTR(void *, JaegerInterpolineScripted);
         }
     }
 }
 
 void
-ExpandInlineFrames(JSCompartment *compartment, bool all)
+ExpandInlineFrames(JSCompartment *compartment)
 {
     if (!compartment || !compartment->hasJaegerCompartment())
         return;
 
-    if (!all) {
-        VMFrame *f = compartment->jaegerCompartment()->activeFrame();
-        if (f && f->regs.inlined())
-            mjit::Recompiler::expandInlineFrames(compartment, f->fp(), f->regs.inlined(), NULL, f);
-        return;
-    }
-
     for (VMFrame *f = compartment->jaegerCompartment()->activeFrame();
          f != NULL;
          f = f->previous) {
 
         if (f->regs.inlined())
             mjit::Recompiler::expandInlineFrames(compartment, f->fp(), f->regs.inlined(), NULL, f);
 
         StackFrame *end = f->entryfp->prev();
@@ -372,17 +365,17 @@ ExpandInlineFrames(JSCompartment *compar
 }
 
 void
 ClearAllFrames(JSCompartment *compartment)
 {
     if (!compartment || !compartment->hasJaegerCompartment())
         return;
 
-    ExpandInlineFrames(compartment, true);
+    ExpandInlineFrames(compartment);
 
     for (VMFrame *f = compartment->jaegerCompartment()->activeFrame();
          f != NULL;
          f = f->previous) {
 
         Recompiler::patchFrame(compartment, f, f->fp()->script());
 
         // Clear ncode values from all frames associated with the VMFrame.
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -1235,17 +1235,17 @@ stubs::Interrupt(VMFrame &f, jsbytecode 
 {
     if (!js_HandleExecutionInterrupt(f.cx))
         THROW();
 }
 
 void JS_FASTCALL
 stubs::RecompileForInline(VMFrame &f)
 {
-    ExpandInlineFrames(f.cx->compartment, true);
+    ExpandInlineFrames(f.cx->compartment);
     Recompiler recompiler(f.cx, f.script());
     recompiler.recompile(/* resetUses */ false);
 }
 
 void JS_FASTCALL
 stubs::Trap(VMFrame &f, uint32 trapTypes)
 {
     Value rval;
@@ -2490,17 +2490,17 @@ stubs::InvariantFailure(VMFrame &f, void
     void **frameAddr = f.returnAddressLocation();
     *frameAddr = repatchCode;
 
     /* Recompile the outermost script, and don't hoist any bounds checks. */
     JSScript *script = f.fp()->script();
     JS_ASSERT(!script->failedBoundsCheck);
     script->failedBoundsCheck = true;
 
-    ExpandInlineFrames(f.cx->compartment, true);
+    ExpandInlineFrames(f.cx->compartment);
 
     Recompiler recompiler(f.cx, script);
     recompiler.recompile();
 
     /* Return the same value (if any) as the call triggering the invariant failure. */
     return rval;
 }
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -570,20 +570,37 @@ Value *
 ContextStack::ensureOnTop(JSContext *cx, MaybeReportError report, uintN nvars,
                           MaybeExtend extend, bool *pushedSeg)
 {
     Value *firstUnused = space().firstUnused();
 
 #ifdef JS_METHODJIT
     /*
      * The only calls made by inlined methodjit frames can be to other JIT
-     * frames associated with the same VMFrame.
+     * frames associated with the same VMFrame. If we try to Invoke(),
+     * Execute() or so forth, any topmost inline frame will need to be
+     * expanded (along with other inline frames in the compartment).
+     * To avoid pathological behavior here, make sure to mark any topmost
+     * function as uninlineable, which will expand inline frames if there are
+     * any and prevent the function from being inlined in the future.
      */
-    if (cx->hasfp() && cx->regs().inlined())
-        mjit::ExpandInlineFrames(cx->compartment, false);
+    if (cx->hasfp() && cx->fp()->isFunctionFrame() && cx->fp()->fun()->isInterpreted()) {
+        if (report) {
+            /*
+             * N.B. if we can't report errors then cx->compartment may not be
+             * consistent with the function's compartment (cross-compartment
+             * call or SaveFrameChain). In such cases the caller must have
+             * already expanded inline frames.
+             */
+            cx->fp()->fun()->script()->uninlineable = true;
+            types::MarkTypeObjectFlags(cx, cx->fp()->fun(),
+                                       types::OBJECT_FLAG_UNINLINEABLE);
+        }
+        JS_ASSERT(!cx->regs().inlined());
+    }
 #endif
 
     if (onTop() && extend) {
         if (!space().ensureSpace(cx, report, firstUnused, nvars))
             return NULL;
         return firstUnused;
     }
 
@@ -812,16 +829,20 @@ ContextStack::popGeneratorFrame(const Ge
 
     /* ~FrameGuard/popFrame will finish the popping. */
     JS_ASSERT(ImplicitCast<const FrameGuard>(gfg).pushed());
 }
 
 bool
 ContextStack::saveFrameChain()
 {
+#ifdef JS_METHODJIT
+    mjit::ExpandInlineFrames(cx_->compartment);
+#endif
+
     /*
      * The StackSpace uses the context's current compartment to determine
      * whether to allow access to the privileged end-of-stack buffer.
      * However, we always want saveFrameChain to have access to this privileged
      * buffer since it gets used to prepare calling trusted JS. To force this,
      * we clear the current compartment (which is interpreted by ensureSpace as
      * 'trusted') and either restore it on OOM or let resetCompartment()
      * clobber it.
@@ -1080,17 +1101,17 @@ StackIter::settleOnNewState()
     }
 }
 
 StackIter::StackIter(JSContext *cx, SavedOption savedOption)
   : cx_(cx),
     savedOption_(savedOption)
 {
 #ifdef JS_METHODJIT
-    mjit::ExpandInlineFrames(cx->compartment, true);
+    mjit::ExpandInlineFrames(cx->compartment);
 #endif
 
     LeaveTrace(cx);
 
     if (StackSegment *seg = cx->stack.seg_) {
         startOnSegment(seg);
         settleOnNewState();
     } else {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1748,23 +1748,16 @@ class GeneratorFrameGuard : public Frame
     JSGenerator *gen_;
     Value *stackvp_;
   public:
     ~GeneratorFrameGuard() { if (pushed()) stack_->popGeneratorFrame(*this); }
 };
 
 /*****************************************************************************/
 
-/* How much expansion of inlined frames to do when inspecting the stack. */
-enum FrameExpandKind {
-    FRAME_EXPAND_NONE,
-    FRAME_EXPAND_TOP,
-    FRAME_EXPAND_ALL
-};
-
 /*
  * Iterate through the callstack of the given context. Each element of said
  * callstack can either be the execution of a script (scripted function call,
  * global code, eval code, debugger code) or the invocation of a (C++) native.
  * Example usage:
  *
  *   for (Stackiter i(cx); !i.done(); ++i) {
  *     if (i.isScript()) {