Bug 673125: Maintain a list of active js::Interrupt frames, their FrameRegs, and their interruptors. r=jorendorff.
authorJim Blandy <jimb@mozilla.com>
Tue, 23 Aug 2011 14:44:03 -0500
changeset 77168 2b0b39d1a4858d5a98584d23d4a139c464af8d37
parent 77167 64d97a61fe681034ca819e65fb7d21654081e450
child 77169 337dc46b17a6f0de38213b6962e83b017705be93
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs673125
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 673125: Maintain a list of active js::Interrupt frames, their FrameRegs, and their interruptors. r=jorendorff. The comment atop InterpreterFrames explains why this is needed, although it is only used by later patches in the series.
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsinterp.cpp
js/src/jsinterp.h
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -106,17 +106,18 @@ ThreadData::ThreadData()
     onTraceCompartment(NULL),
     recordingCompartment(NULL),
     profilingCompartment(NULL),
     maxCodeCacheBytes(DEFAULT_JIT_CACHE_SIZE),
 #endif
     waiveGCQuota(false),
     dtoaState(NULL),
     nativeStackBase(GetNativeStackBase()),
-    pendingProxyOperation(NULL)
+    pendingProxyOperation(NULL),
+    interpreterFrames(NULL)
 {
 }
 
 ThreadData::~ThreadData()
 {
     if (dtoaState)
         js_DestroyDtoaState(dtoaState);
 }
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -221,16 +221,22 @@ struct ThreadData {
         gsnCache.purge();
 
         /* FIXME: bug 506341. */
         propertyCache.purge(cx);
     }
 
     /* This must be called with the GC lock held. */
     void triggerOperationCallback(JSRuntime *rt);
+
+    /*
+     * Frames currently running in js::Interpret. See InterpreterFrames for
+     * details.
+     */
+    InterpreterFrames *interpreterFrames;
 };
 
 } /* namespace js */
 
 #ifdef JS_THREADSAFE
 
 /*
  * Structure uniquely representing a thread.  It holds thread-private data
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -1513,16 +1513,40 @@ CanIncDecWithoutOverflow(int32_t i)
     (__IBMC__ >= 700 && defined __IBM_COMPUTED_GOTO) ||                       \
     __SUNPRO_C >= 0x570)
 #  define JS_THREADED_INTERP 1
 # else
 #  define JS_THREADED_INTERP 0
 # endif
 #endif
 
+template<typename T>
+class GenericInterruptEnabler : public InterpreterFrames::InterruptEnablerBase {
+  public:
+    GenericInterruptEnabler(T *variable, T value) : variable(variable), value(value) { }
+    void enableInterrupts() const { *variable = value; }
+
+  private:
+    T *variable;
+    T value;
+};
+
+inline InterpreterFrames::InterpreterFrames(JSContext *cx, FrameRegs *regs, 
+                                            const InterruptEnablerBase &enabler)
+  : context(cx), regs(regs), enabler(enabler)
+{
+    older = JS_THREAD_DATA(cx)->interpreterFrames;
+    JS_THREAD_DATA(cx)->interpreterFrames = this;
+}
+ 
+inline InterpreterFrames::~InterpreterFrames()
+{
+    JS_THREAD_DATA(context)->interpreterFrames = older;
+}
+
 /*
  * Deadlocks or else bad races are likely if JS_THREADSAFE, so we must rely on
  * single-thread DEBUG js shell testing to verify property cache hits.
  */
 #if defined DEBUG && !defined JS_THREADSAFE
 
 # define ASSERT_VALID_PROPERTY_CACHE_HIT(pcoff,obj,pobj,entry)                \
     JS_BEGIN_MACRO                                                            \
@@ -1700,17 +1724,18 @@ Interpret(JSContext *cx, StackFrame *ent
 # define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format)              \
         JS_EXTENSION &&interrupt,
 # include "jsopcode.tbl"
 # undef OPDEF
     };
 
     register void * const *jumpTable = normalJumpTable;
 
-# define ENABLE_INTERRUPTS() ((void) (jumpTable = interruptJumpTable))
+    typedef GenericInterruptEnabler<void * const *> InterruptEnabler;
+    InterruptEnabler interruptEnabler(&jumpTable, interruptJumpTable);
 
 # ifdef JS_TRACER
 #  define CHECK_RECORDER()                                                    \
     JS_ASSERT_IF(TRACE_RECORDER(cx), jumpTable == interruptJumpTable)
 # else
 #  define CHECK_RECORDER()  ((void)0)
 # endif
 
@@ -1733,18 +1758,18 @@ Interpret(JSContext *cx, StackFrame *ent
                                 DO_OP();
 
 # define END_EMPTY_CASES
 
 #else /* !JS_THREADED_INTERP */
 
     register intN switchMask = 0;
     intN switchOp;
-
-# define ENABLE_INTERRUPTS() ((void) (switchMask = -1))
+    typedef GenericInterruptEnabler<intN> InterruptEnabler;
+    InterruptEnabler interruptEnabler(&switchMask, -1);
 
 # ifdef JS_TRACER
 #  define CHECK_RECORDER()                                                    \
     JS_ASSERT_IF(TRACE_RECORDER(cx), switchMask == -1)
 # else
 #  define CHECK_RECORDER()  ((void)0)
 # endif
 
@@ -1769,16 +1794,18 @@ Interpret(JSContext *cx, StackFrame *ent
 # define END_CASE_LEN4      len = 4; goto advance_pc;
 # define END_CASE_LEN5      len = 5; goto advance_pc;
 # define END_VARLEN_CASE    goto advance_pc;
 # define ADD_EMPTY_CASE(OP) BEGIN_CASE(OP)
 # define END_EMPTY_CASES    goto advance_pc_by_one;
 
 #endif /* !JS_THREADED_INTERP */
 
+#define ENABLE_INTERRUPTS() (interruptEnabler.enableInterrupts())
+
 #define LOAD_ATOM(PCOFF, atom)                                                \
     JS_BEGIN_MACRO                                                            \
         JS_ASSERT(regs.fp()->hasImacropc()                                    \
                   ? atoms == rt->atomState.commonAtomsStart() &&              \
                     GET_INDEX(regs.pc + PCOFF) < js_common_atom_count         \
                   : (size_t)(atoms - script->atomMap.vector) <                \
                     (size_t)(script->atomMap.length -                         \
                              GET_INDEX(regs.pc + PCOFF)));                    \
@@ -1950,16 +1977,22 @@ Interpret(JSContext *cx, StackFrame *ent
 #define CHECK_INTERRUPT_HANDLER()                                             \
     JS_BEGIN_MACRO                                                            \
         if (cx->debugHooks->interruptHook)                                    \
             ENABLE_INTERRUPTS();                                              \
     JS_END_MACRO
 
     FrameRegs regs = cx->regs();
 
+    /*
+     * Help Debugger find frames running scripts that it has put in
+     * single-step mode.
+     */
+    InterpreterFrames interpreterFrame(cx, &regs, interruptEnabler);
+
     /* Repoint cx->regs to a local variable for faster access. */
     struct InterpExitGuard {
         JSContext *cx;
         const FrameRegs &regs;
         FrameRegs *prevContextRegs;
         InterpExitGuard(JSContext *cx, FrameRegs &regs)
           : cx(cx), regs(regs), prevContextRegs(&cx->regs()) {
             cx->stack.repointRegs(&regs);
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -301,16 +301,62 @@ ValueToId(JSContext *cx, const Value &v,
  */
 extern const Value &
 GetUpvar(JSContext *cx, uintN level, UpvarCookie cookie);
 
 /* Search the call stack for the nearest frame with static level targetLevel. */
 extern StackFrame *
 FindUpvarFrame(JSContext *cx, uintN targetLevel);
 
+/*
+ * A linked list of the |FrameRegs regs;| variables belonging to all
+ * js::Interpret C++ frames on this thread's stack.
+ *
+ * Note that this is *not* a list of all JS frames running under the
+ * interpreter; that would include inlined frames, whose FrameRegs are
+ * saved in various pieces in various places. Rather, this lists each
+ * js::Interpret call's live 'regs'; when control returns to that call, it
+ * will resume execution with this 'regs' instance.
+ *
+ * When Debugger puts a script in single-step mode, all js::Interpret
+ * invocations that might be presently running that script must have
+ * interrupts enabled. It's not practical to simply check
+ * script->stepModeEnabled() at each point some callee could have changed
+ * it, because there are so many places js::Interpret could possibly cause
+ * JavaScript to run: each place an object might be coerced to a primitive
+ * or a number, for example. So instead, we simply expose a list of the
+ * 'regs' those frames are using, and let Debugger tweak the affected
+ * js::Interpret frames when an onStep handler is established.
+ *
+ * Elements of this list are allocated within the js::Interpret stack
+ * frames themselves; the list is headed by this thread's js::ThreadData.
+ */
+class InterpreterFrames {
+  public:
+    class InterruptEnablerBase {
+      public:
+        virtual void enableInterrupts() const = 0;
+    };
+
+    InterpreterFrames(JSContext *cx, FrameRegs *regs, const InterruptEnablerBase &enabler);
+    ~InterpreterFrames();
+
+    /* If this js::Interpret frame is running |script|, enable interrupts. */
+    void enableInterruptsIfRunning(JSScript *script) {
+        if (script == regs->fp()->script())
+            enabler.enableInterrupts();
+    }
+
+  private:
+    JSContext *context;
+    FrameRegs *regs;
+    const InterruptEnablerBase &enabler;
+    InterpreterFrames *older;
+};
+
 } /* namespace js */
 
 /*
  * JS_LONE_INTERPRET indicates that the compiler should see just the code for
  * the js_Interpret function when compiling jsinterp.cpp. The rest of the code
  * from the file should be visible only when compiling jsinvoke.cpp. It allows
  * platform builds to optimize selectively js_Interpret when the granularity
  * of the optimizations with the given compiler is a compilation unit.