Bug 610793 - Add a per-script enableSingleStepInterrupts() to JSD [r=dmandelin]
authorSteve Fink <sfink@mozilla.com>
Tue, 16 Nov 2010 15:18:35 -0800
changeset 59882 9ff7b826eab6eceeca866fb5833752f5a9d59551
parent 59881 012947931c1dc4fbc21a03c617883a98b30e071b
child 59883 6539f1fcda72659397038969c47a05b11471a82b
push idunknown
push userunknown
push dateunknown
reviewersdmandelin
bugs610793
milestone2.0b8pre
Bug 610793 - Add a per-script enableSingleStepInterrupts() to JSD [r=dmandelin]
js/jsd/idl/jsdIDebuggerService.idl
js/jsd/jsd_scpt.c
js/jsd/jsd_xpc.cpp
js/jsd/jsd_xpc.h
js/jsd/jsdebug.c
js/jsd/jsdebug.h
js/src/jsdbgapi.cpp
js/src/jsdbgapi.h
js/src/jsscript.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/MethodJIT.h
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
--- a/js/jsd/idl/jsdIDebuggerService.idl
+++ b/js/jsd/idl/jsdIDebuggerService.idl
@@ -73,17 +73,17 @@ interface jsdIValue;
 interface jsdIObject;
 interface jsdIProperty;
 interface jsdIActivationCallback;
 
 /**
  * Debugger service.  It's not a good idea to have more than one active client of
  * the debugger service.
  */
-[scriptable, uuid(01769775-c77c-47f9-8848-0abbab404215)]
+[scriptable, uuid(1ad86ef3-5eca-4ed7-81c5-a757d1957dff)]
 interface jsdIDebuggerService : nsISupports
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext        JSDContext;
 
     /**
      * Called when an error or warning occurs.
      */
@@ -507,43 +507,43 @@ interface jsdIFilterEnumerator : nsISupp
      * debugger knows about.
      */
     void enumerateFilter (in jsdIFilter filter);
 };
 
 /**
  * Pass an instance of one of these to jsdIDebuggerService::enumerateScripts.
  */
-[scriptable, uuid(5ba76b99-acb1-4ed8-a4e4-a716a7d9097e)]
+[scriptable, uuid(4eef60c2-9bbc-48fa-b196-646a832c6c81)]
 interface jsdIScriptEnumerator : nsISupports
 {
     /**
      * The enumerateScript method will be called once for every script the
      * debugger knows about.
      */
     void enumerateScript (in jsdIScript script);
 };
 
 /**
  * Pass an instance of one of these to jsdIDebuggerService::enumerateContexts.
  */
-[scriptable, uuid(d96af02e-3379-4db5-885d-fee28d178701)]
+[scriptable, uuid(57d18286-550c-4ca9-ac33-56f12ebba91e)]
 interface jsdIContextEnumerator : nsISupports
 {
     /**
      * The enumerateContext method will be called once for every context
      * currently in use.
      */
     void enumerateContext (in jsdIContext executionContext);
 };
 
 /**
  * Set jsdIDebuggerService::scriptHook to an instance of one of these.
  */
-[scriptable, uuid(cf7ecc3f-361b-44af-84a7-4b0d6cdca204)]
+[scriptable, uuid(bb722893-0f63-45c5-b547-7a0947c7b6b6)]
 interface jsdIScriptHook : nsISupports
 {
     /**
      * Called when scripts are created.
      */
     void onScriptCreated (in jsdIScript script);
     /**
      * Called when the JavaScript engine destroys a script.  The jsdIScript
@@ -551,17 +551,17 @@ interface jsdIScriptHook : nsISupports
      */
     void onScriptDestroyed (in jsdIScript script);
 };
 
 /**
  * Hook instances of this interface up to the
  * jsdIDebuggerService::functionHook and toplevelHook properties.
  */
-[scriptable, uuid(191d2738-22e8-4756-b366-6c878c87d73b)]
+[scriptable, uuid(3eff1314-7ae3-4cf8-833b-c33c24a55633)]
 interface jsdICallHook : nsISupports
 {
     /**
      * TYPE_* values must be kept in sync with the JSD_HOOK_* #defines
      * in jsdebug.h.
      */
 
     /**
@@ -583,17 +583,17 @@ interface jsdICallHook : nsISupports
     
     /**
      * Called before the JavaScript engine executes a top level script or calls
      * a function.
      */
     void onCall (in jsdIStackFrame frame, in unsigned long type);
 };
 
-[scriptable, uuid(cea9ab1a-4b5d-416f-a197-9ffa7046f2ce)]
+[scriptable, uuid(e6b45eee-d974-4d85-9d9e-f5a67218deb4)]
 interface jsdIErrorHook : nsISupports
 {
     /**
      * REPORT_* values must be kept in sync with JSREPORT_* #defines in
      * jsapi.h
      */
     
     /**
@@ -875,17 +875,17 @@ interface jsdIStackFrame : jsdIEphemeral
                   in unsigned long line, out jsdIValue result);
     
 };
 
 /**
  * Script object.  In JavaScript engine terms, there's a single script for each
  * function, and one for the top level script.
  */
-[scriptable, uuid(7e6fb9ed-4382-421d-9a14-c80a486e983b)]
+[scriptable, uuid(e7935220-7def-4c8e-832f-fbc948a97490)]
 interface jsdIScript : jsdIEphemeral
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext JSDContext;
     /** Internal use only. */
     [noscript] readonly attribute JSDScript  JSDScript;
     
     /**
@@ -1033,25 +1033,29 @@ interface jsdIScript : jsdIEphemeral
     /**
      * Clear a breakpoint at a PC in this script.
      */
     void clearBreakpoint (in unsigned long pc);
     /**
      * Clear all breakpoints set in this script.
      */
     void clearAllBreakpoints ();
+    /**
+     * Call interrupt hook at least once per source line
+     */
+    void enableSingleStepInterrupts (in PRBool mode);
 };
 
 /**
  * Value objects.  Represents typeless JavaScript values (jsval in SpiderMonkey
  * terminology.)  These are valid until the debugger is turned off.  Holding a
  * jsdIValue adds a root for the underlying JavaScript value, so don't keep it
  * if you don't need to.
  */
-[scriptable, uuid(9cab158f-dc78-41dd-9d11-79e05cb3f2bd)]
+[scriptable, uuid(fd1311f7-096c-44a3-847b-9d478c8176c3)]
 interface jsdIValue : jsdIEphemeral
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext JSDContext;
     /** Internal use only. */
     [noscript] readonly attribute JSDValue   JSDValue;
 
     /**
@@ -1187,17 +1191,17 @@ interface jsdIValue : jsdIEphemeral
  * Properties specific to values which are also objects.
  * XXX We don't add roots for these yet, so make sure you hold on to the
  * jsdIValue from whence your jsdIObject instance came for at least as long as
  * you hold the jsdIObject.
  * XXX Maybe the jsClassName, jsConstructorName, and property related attribute/
  * functions from jsdIValue should move to this interface.  We could inherit from
  * jsdIValue or use interface flattening or something.
  */
-[scriptable, uuid(a735a94c-9d41-4997-8fcb-cfa8b649a5b7)]
+[scriptable, uuid(87d86308-7a27-4255-b23c-ce2394f02473)]
 interface jsdIObject : nsISupports
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext JSDContext;
     /** Internal use only. */
     [noscript] readonly attribute JSDObject  JSDObject;
 
     /**
@@ -1223,17 +1227,17 @@ interface jsdIObject : nsISupports
      */
     readonly attribute jsdIValue     value;
 };
 
 /**
  * Representation of a property of an object.  When an instance is invalid, all
  * method and property access will result in a NS_UNAVAILABLE error.
  */
-[scriptable, uuid(4491ecd4-fb6b-43fb-bd6f-5d1473f1df24)]
+[scriptable, uuid(09332485-1419-42bc-ba1f-070815ed4b82)]
 interface jsdIProperty : jsdIEphemeral
 {
     /** Internal use only. */
     [noscript] readonly attribute JSDContext  JSDContext;
     /** Internal use only. */
     [noscript] readonly attribute JSDProperty JSDProperty;
 
     /**
--- a/js/jsd/jsd_scpt.c
+++ b/js/jsd/jsd_scpt.c
@@ -582,16 +582,27 @@ jsd_GetScriptHook(JSDContext* jsdc, JSD_
     if( hook )
         *hook = jsdc->scriptHook;
     if( callerdata )
         *callerdata = jsdc->scriptHookData;
     JSD_UNLOCK();
     return JS_TRUE;
 }    
 
+JSBool
+jsd_EnableSingleStepInterrupts(JSDContext* jsdc, JSDScript* jsdscript, JSBool enable)
+{
+    JSBool rv;
+    JSD_LOCK();
+    rv = JS_SetSingleStepMode(jsdc->dumbContext, jsdscript->script, enable);
+    JSD_UNLOCK();
+    return rv;
+}
+
+
 /***************************************************************************/
 
 void
 jsd_NewScriptHookProc( 
                 JSContext   *cx,
                 const char  *filename,      /* URL this script loads from */
                 uintN       lineno,         /* line where this script starts */
                 JSScript    *script,
@@ -746,17 +757,17 @@ jsd_TrapHandler(JSContext *cx, JSScript 
     if( NULL == (jsdc = jsd_JSDContextForJSContext(cx)) ||
         ! _isActiveHook(jsdc, script, jsdhook) )
     {
         JSD_UNLOCK();
         return JSTRAP_CONTINUE;
     }
 
     JSD_ASSERT_VALID_EXEC_HOOK(jsdhook);
-    JS_ASSERT(jsdhook->pc == (jsuword)pc);
+    JS_ASSERT(!jsdhook->pc || jsdhook->pc == (jsuword)pc);
     JS_ASSERT(jsdhook->jsdscript->script == script);
     JS_ASSERT(jsdhook->jsdscript->jsdc == jsdc);
 
     hook = jsdhook->hook;
     hookData = jsdhook->callerdata;
     jsdscript = jsdhook->jsdscript;
 
     /* do not use jsdhook-> after this point */
--- a/js/jsd/jsd_xpc.cpp
+++ b/js/jsd/jsd_xpc.cpp
@@ -1476,16 +1476,30 @@ jsdScript::LineToPc(PRUint32 aLine, PRUi
     } else {
         return NS_ERROR_INVALID_ARG;
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
+jsdScript::EnableSingleStepInterrupts(PRBool enable)
+{
+    ASSERT_VALID_EPHEMERAL;
+
+    /* Must have set interrupt hook before enabling */
+    if (enable && !jsdService::GetService()->CheckInterruptHook())
+        return NS_ERROR_NOT_INITIALIZED;
+
+    JSD_EnableSingleStepInterrupts(mCx, mScript, enable);
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 jsdScript::IsLineExecutable(PRUint32 aLine, PRUint32 aPcmap, PRBool *_rval)
 {
     ASSERT_VALID_EPHEMERAL;
     if (aPcmap == PCMAP_SOURCETEXT) {    
         jsuword pc = JSD_GetClosestPC (mCx, mScript, aLine);
         *_rval = (aLine == JSD_GetClosestLine (mCx, mScript, pc));
     } else if (aPcmap == PCMAP_PRETTYPRINT) {
         if (!mPPLineMap && !CreatePPLineMap())
--- a/js/jsd/jsd_xpc.h
+++ b/js/jsd/jsd_xpc.h
@@ -283,16 +283,18 @@ class jsdService : public jsdIDebuggerSe
                    mInterruptHook(0), mScriptHook(0), mThrowHook(0),
                    mTopLevelHook(0), mFunctionHook(0)
     {
     }
 
     virtual ~jsdService();
     
     static jsdService *GetService ();
+
+    PRBool CheckInterruptHook() { return !!mInterruptHook; }
     
   private:
     PRBool      mOn;
     PRUint32    mPauseLevel;
     PRUint32    mNestedLoopLevel;
     JSDContext *mCx;
     JSRuntime  *mRuntime;
 
--- a/js/jsd/jsdebug.c
+++ b/js/jsd/jsdebug.c
@@ -572,16 +572,24 @@ JSD_SetInterruptHook(JSDContext*        
                      JSD_ExecutionHookProc hook,
                      void*                 callerdata)
 {
     JSD_ASSERT_VALID_CONTEXT(jsdc);
     return jsd_SetInterruptHook(jsdc, hook, callerdata);
 }
 
 JSD_PUBLIC_API(JSBool)
+JSD_EnableSingleStepInterrupts(JSDContext* jsdc, JSDScript* jsdscript, JSBool enable)
+{
+    JSD_ASSERT_VALID_CONTEXT(jsdc);
+    JSD_ASSERT_VALID_SCRIPT(jsdscript);
+    return jsd_EnableSingleStepInterrupts(jsdc, jsdscript, enable);
+}
+
+JSD_PUBLIC_API(JSBool)
 JSD_ClearInterruptHook(JSDContext* jsdc)
 {
     JSD_ASSERT_VALID_CONTEXT(jsdc);
     return jsd_ClearInterruptHook(jsdc);
 }
 
 JSD_PUBLIC_API(JSBool)
 JSD_SetDebugBreakHook(JSDContext*           jsdc,
--- a/js/jsd/jsdebug.h
+++ b/js/jsd/jsdebug.h
@@ -799,16 +799,22 @@ JSD_ClearAllExecutionHooks(JSDContext* j
 * executes until cleared.
 */
 extern JSD_PUBLIC_API(JSBool)
 JSD_SetInterruptHook(JSDContext*           jsdc,
                      JSD_ExecutionHookProc hook,
                      void*                 callerdata);
 
 /*
+* Call the interrupt hook at least once per source line
+*/
+extern JSD_PUBLIC_API(JSBool)
+JSD_EnableSingleStepInterrupts(JSDContext* jsdc, JSDScript *jsdscript, JSBool enable);
+
+/*
 * Clear the current interrupt hook.
 */
 extern JSD_PUBLIC_API(JSBool)
 JSD_ClearInterruptHook(JSDContext* jsdc);
 
 /*
 * Set the hook that should be called whenever a JSD_ErrorReporter hook
 * returns JSD_ERROR_REPORTER_DEBUG.
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -140,17 +140,17 @@ PurgeCallICs(JSContext *cx, JSScript *st
 JS_FRIEND_API(JSBool)
 js_SetDebugMode(JSContext *cx, JSBool debug)
 {
     cx->compartment->debugMode = debug;
 #ifdef JS_METHODJIT
     for (JSScript *script = (JSScript *)cx->compartment->scripts.next;
          &script->links != &cx->compartment->scripts;
          script = (JSScript *)script->links.next) {
-        if (script->debugMode != (bool) debug &&
+        if (script->debugMode != !!debug &&
             script->hasJITCode() &&
             !IsScriptLive(cx, script)) {
             /*
              * In the event that this fails, debug mode is left partially on,
              * leading to a small performance overhead but no loss of
              * correctness. We set the debug flag to false so that the caller
              * will not later attempt to use debugging features.
              */
@@ -177,32 +177,65 @@ JS_SetDebugMode(JSContext *cx, JSBool de
 #ifdef DEBUG
     for (AllFramesIter i(cx); !i.done(); ++i)
         JS_ASSERT(!JS_IsScriptFrame(cx, i.fp()));
 #endif
 
     return js_SetDebugMode(cx, debug);
 }
 
+JS_FRIEND_API(JSBool)
+js_SetSingleStepMode(JSContext *cx, JSScript *script, JSBool singleStep)
+{
+    if (!script->singleStepMode == !singleStep)
+        return JS_TRUE;
+
+    JS_ASSERT_IF(singleStep, cx->compartment->debugMode);
+
+#ifdef JS_METHODJIT
+    /* request the next recompile to inject single step interrupts */
+    script->singleStepMode = !!singleStep;
+
+    js::mjit::JITScript *jit = script->jitNormal ? script->jitNormal : script->jitCtor;
+    if (jit && script->singleStepMode != jit->singleStepMode) {
+        js::mjit::Recompiler recompiler(cx, script);
+        if (!recompiler.recompile()) {
+            script->singleStepMode = !singleStep;
+            return JS_FALSE;
+        }
+    }
+#endif
+    return JS_TRUE;
+}
+
 static JSBool
 CheckDebugMode(JSContext *cx)
 {
     JSBool debugMode = JS_GetDebugMode(cx);
     /*
      * :TODO:
      * This probably should be an assertion, since it's indicative of a severe
      * API misuse.
      */
     if (!debugMode) {
         JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage,
                                      NULL, JSMSG_NEED_DEBUG_MODE);
     }
     return debugMode;
 }
 
+JS_PUBLIC_API(JSBool)
+JS_SetSingleStepMode(JSContext *cx, JSScript *script, JSBool singleStep)
+{
+    if (!CheckDebugMode(cx))
+        return JS_FALSE;
+
+    return js_SetSingleStepMode(cx, script, singleStep);
+}
+
 /*
  * NB: FindTrap must be called with rt->debuggerLock acquired.
  */
 static JSTrap *
 FindTrap(JSRuntime *rt, JSScript *script, jsbytecode *pc)
 {
     JSTrap *trap;
 
--- a/js/src/jsdbgapi.h
+++ b/js/src/jsdbgapi.h
@@ -73,16 +73,24 @@ JS_GetDebugMode(JSContext *cx);
 /* Turn on debugging mode, ignoring the presence of live frames. */
 extern JS_FRIEND_API(JSBool)
 js_SetDebugMode(JSContext *cx, JSBool debug);
 
 /* Turn on debugging mode. */
 extern JS_PUBLIC_API(JSBool)
 JS_SetDebugMode(JSContext *cx, JSBool debug);
 
+/* Turn on single step mode. Requires debug mode. */
+extern JS_FRIEND_API(JSBool)
+js_SetSingleStepMode(JSContext *cx, JSScript *script, JSBool singleStep);
+
+/* Turn on single step mode. */
+extern JS_PUBLIC_API(JSBool)
+JS_SetSingleStepMode(JSContext *cx, JSScript *script, JSBool singleStep);
+
 /*
  * Unexported library-private helper used to unpatch all traps in a script.
  * Returns script->code if script has no traps, else a JS_malloc'ed copy of
  * script->code which the caller must JS_free, or null on JS_malloc OOM.
  */
 extern jsbytecode *
 js_UntrapScriptCode(JSContext *cx, JSScript *script);
 
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -237,16 +237,17 @@ struct JSScript {
     bool            compileAndGo:1;   /* script was compiled with TCF_COMPILE_N_GO */
     bool            usesEval:1;       /* script uses eval() */
     bool            usesArguments:1;  /* script uses arguments */
     bool            warnedAboutTwoArgumentEval:1; /* have warned about use of
                                                      obsolete eval(s, o) in
                                                      this script */
 #ifdef JS_METHODJIT
     bool            debugMode:1;      /* script was compiled in debug mode */
+    bool            singleStepMode:1; /* compile script in single-step mode */
 #endif
 
     jsbytecode      *main;      /* main entry point, after predef'ing prolog */
     JSAtomMap       atomMap;    /* maps immediate index to literal struct */
     JSCompartment   *compartment; /* compartment the script was compiled for */
     const char      *filename;  /* source filename or null */
     uint32          lineno;     /* base line number of script */
     uint16          nslots;     /* vars plus maximum stack depth */
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -37,16 +37,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "MethodJIT.h"
 #include "jsnum.h"
 #include "jsbool.h"
+#include "jsemit.h"
 #include "jsiter.h"
 #include "Compiler.h"
 #include "StubCalls.h"
 #include "MonoIC.h"
 #include "PolyIC.h"
 #include "Retcon.h"
 #include "assembler/jit/ExecutableAllocator.h"
 #include "assembler/assembler/LinkBuffer.h"
@@ -435,16 +436,17 @@ mjit::Compiler::finishThisUp(JITScript *
     }
 
     JITScript *jit = new(cursor) JITScript;
     cursor += sizeof(JITScript);
 
     jit->code = JSC::MacroAssemblerCodeRef(result, execPool, masm.size() + stubcc.size());
     jit->nCallSites = callSites.length();
     jit->invokeEntry = result;
+    jit->singleStepMode = script->singleStepMode;
 
     /* Build the pc -> ncode mapping. */
     NativeMapEntry *nmap = (NativeMapEntry *)cursor;
     cursor += sizeof(NativeMapEntry) * nNmapLive;
 
     size_t ix = 0;
     if (nNmapLive > 0) {
         for (size_t i = 0; i < script->length; i++) {
@@ -786,16 +788,42 @@ mjit::Compiler::finishThisUp(JITScript *
 
     jit->nmap = nmap;
     jit->nNmapPairs = nNmapLive;
     *jitp = jit;
 
     return Compile_Okay;
 }
 
+class SrcNoteLineScanner {
+    ptrdiff_t offset;
+    jssrcnote *sn;
+
+public:
+    SrcNoteLineScanner(jssrcnote *sn) : offset(0), sn(sn) {}
+
+    bool firstOpInLine(ptrdiff_t relpc) {
+        while ((offset < relpc) && !SN_IS_TERMINATOR(sn)) {
+            offset += SN_DELTA(sn);
+            sn = SN_NEXT(sn);
+        }
+
+        while ((offset == relpc) && !SN_IS_TERMINATOR(sn)) {
+            JSSrcNoteType type = (JSSrcNoteType) SN_TYPE(sn);
+            if (type == SRC_SETLINE || type == SRC_NEWLINE)
+                return true;
+                
+            offset += SN_DELTA(sn);
+            sn = SN_NEXT(sn);
+        }
+
+        return false;
+    }
+};
+
 #ifdef DEBUG
 #define SPEW_OPCODE()                                                         \
     JS_BEGIN_MACRO                                                            \
         if (IsJaegerSpewChannelActive(JSpew_JSOps)) {                         \
             JaegerSpew(JSpew_JSOps, "    %2d ", frame.stackDepth());          \
             js_Disassemble1(cx, script, PC, PC - script->code,                \
                             JS_TRUE, stdout);                                 \
         }                                                                     \
@@ -810,26 +838,29 @@ mjit::Compiler::finishThisUp(JITScript *
         PC += name##_LENGTH;                \
     JS_END_MACRO;                           \
     break;
 
 CompileStatus
 mjit::Compiler::generateMethod()
 {
     mjit::AutoScriptRetrapper trapper(cx, script);
+    SrcNoteLineScanner scanner(script->notes());
 
     for (;;) {
         JSOp op = JSOp(*PC);
-        bool trap = (op == JSOP_TRAP);
-
-        if (trap) {
+        int trap = stubs::JSTRAP_NONE;
+        if (op == JSOP_TRAP) {
             if (!trapper.untrap(PC))
                 return Compile_Error;
             op = JSOp(*PC);
+            trap |= stubs::JSTRAP_TRAP;
         }
+        if (script->singleStepMode && scanner.firstOpInLine(PC - script->code))
+            trap |= stubs::JSTRAP_SINGLESTEP;
 
         analyze::Bytecode *opinfo = analysis->maybeCode(PC);
 
         if (!opinfo) {
             if (op == JSOP_STOP)
                 break;
             if (js_CodeSpec[op].length != -1)
                 PC += js_CodeSpec[op].length;
@@ -845,17 +876,17 @@ mjit::Compiler::generateMethod()
         }
         jumpMap[uint32(PC - script->code)] = masm.label();
 
         SPEW_OPCODE();
         JS_ASSERT(frame.stackDepth() == opinfo->stackDepth);
 
         if (trap) {
             prepareStubCall(Uses(0));
-            masm.move(ImmPtr(PC), Registers::ArgReg1);
+            masm.move(Imm32(trap), Registers::ArgReg1);
             Call cl = emitStubCall(JS_FUNC_TO_DATA_PTR(void *, stubs::Trap));
             InternalCallSite site(masm.callReturnOffset(cl), PC,
                                   CallSite::MAGIC_TRAP_ID, true, false);
             addCallSite(site);
         } else if (savedTraps && savedTraps[PC - script->code]) {
             // Normally when we patch return addresses, we have generated the
             // same exact code at that site. For example, patching a stub call's
             // return address will resume at the same stub call.
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -326,16 +326,17 @@ struct JITScript {
     ic::GetElementIC *getElems;
     uint32           nGetElems;
     ic::SetElementIC *setElems;
     uint32           nSetElems;
 #endif
     void            *invokeEntry;       /* invoke address */
     void            *fastEntry;         /* cached entry, fastest */
     void            *arityCheckEntry;   /* arity check address */
+    bool            singleStepMode;     /* compiled in "single step mode" */
 
     ~JITScript();
 
     bool isValidCode(void *ptr) {
         char *jitcode = (char *)code.m_code.executableAddress();
         char *jcheck = (char *)ptr;
         return jcheck >= jitcode && jcheck < jitcode + code.m_size;
     }
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -1311,21 +1311,42 @@ stubs::Debugger(VMFrame &f, jsbytecode *
 void JS_FASTCALL
 stubs::Interrupt(VMFrame &f, jsbytecode *pc)
 {
     if (!js_HandleExecutionInterrupt(f.cx))
         THROW();
 }
 
 void JS_FASTCALL
-stubs::Trap(VMFrame &f, jsbytecode *pc)
+stubs::Trap(VMFrame &f, uint32 trapTypes)
 {
     Value rval;
+    jsbytecode *pc = f.cx->regs->pc;
 
-    switch (JS_HandleTrap(f.cx, f.cx->fp()->script(), pc, Jsvalify(&rval))) {
+    /*
+     * Trap may be called for a single-step interrupt trap and/or a
+     * regular trap. Try the single-step first, and if it lets control
+     * flow through or does not exist, do the regular trap.
+     */
+    JSTrapStatus result = JSTRAP_CONTINUE;
+    if (trapTypes & JSTRAP_SINGLESTEP) {
+        /*
+         * single step mode may be paused without recompiling by
+         * setting the interruptHook to NULL.
+         */
+        JSInterruptHook hook = f.cx->debugHooks->interruptHook;
+        if (hook)
+            result = hook(f.cx, f.cx->fp()->script(), pc, Jsvalify(&rval),
+                          f.cx->debugHooks->interruptHookData);
+    }
+
+    if (result == JSTRAP_CONTINUE && (trapTypes & JSTRAP_TRAP))
+        result = JS_HandleTrap(f.cx, f.cx->fp()->script(), pc, Jsvalify(&rval));
+
+    switch (result) {
       case JSTRAP_THROW:
         f.cx->throwing = JS_TRUE;
         f.cx->exception = rval;
         THROW();
 
       case JSTRAP_RETURN:
         f.cx->throwing = JS_FALSE;
         f.cx->fp()->setReturnValue(rval);
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -42,20 +42,26 @@
 #define jslogic_h__
 
 #include "MethodJIT.h"
 
 namespace js {
 namespace mjit {
 namespace stubs {
 
+typedef enum JSTrapType {
+    JSTRAP_NONE = 0,
+    JSTRAP_TRAP = 1,
+    JSTRAP_SINGLESTEP = 2
+} JSTrapType;
+
 void JS_FASTCALL This(VMFrame &f);
 JSObject * JS_FASTCALL NewInitArray(VMFrame &f, uint32 count);
 JSObject * JS_FASTCALL NewInitObject(VMFrame &f, JSObject *base);
-void JS_FASTCALL Trap(VMFrame &f, jsbytecode *pc);
+void JS_FASTCALL Trap(VMFrame &f, uint32 trapTypes);
 void JS_FASTCALL Debugger(VMFrame &f, jsbytecode *pc);
 void JS_FASTCALL Interrupt(VMFrame &f, jsbytecode *pc);
 void JS_FASTCALL InitElem(VMFrame &f, uint32 last);
 void JS_FASTCALL InitProp(VMFrame &f, JSAtom *atom);
 void JS_FASTCALL InitMethod(VMFrame &f, JSAtom *atom);
 
 void JS_FASTCALL HitStackQuota(VMFrame &f);
 void * JS_FASTCALL FixupArity(VMFrame &f, uint32 argc);