From 4187f585f2692e0559df400fba34a37a89052260 Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Tue, 16 Nov 2010 15:18:35 -0800 Subject: [PATCH] Bug 610793 - Add a per-script enableSingleStepInterrupts() to JSD [r=dmandelin] --HG-- extra : rebase_source : 248eb8bf3d3a94cce626614da2be1449c8b27a8f --- js/jsd/idl/jsdIDebuggerService.idl | 24 ++++++++++-------- js/jsd/jsd_scpt.c | 13 +++++++++- js/jsd/jsd_xpc.cpp | 14 +++++++++++ js/jsd/jsd_xpc.h | 2 ++ js/jsd/jsdebug.c | 8 ++++++ js/jsd/jsdebug.h | 6 +++++ js/src/jsdbgapi.cpp | 35 ++++++++++++++++++++++++++- js/src/jsdbgapi.h | 8 ++++++ js/src/jsscript.h | 1 + js/src/methodjit/Compiler.cpp | 39 +++++++++++++++++++++++++++--- js/src/methodjit/MethodJIT.h | 1 + js/src/methodjit/StubCalls.cpp | 25 +++++++++++++++++-- js/src/methodjit/StubCalls.h | 8 +++++- 13 files changed, 165 insertions(+), 19 deletions(-) diff --git a/js/jsd/idl/jsdIDebuggerService.idl b/js/jsd/idl/jsdIDebuggerService.idl index c9c2c3b03f9..4dec26923b8 100644 --- a/js/jsd/idl/jsdIDebuggerService.idl +++ b/js/jsd/idl/jsdIDebuggerService.idl @@ -78,7 +78,7 @@ 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. */ @@ -512,7 +512,7 @@ interface jsdIFilterEnumerator : nsISupports /** * 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 { /** @@ -525,7 +525,7 @@ interface jsdIScriptEnumerator : nsISupports /** * 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 { /** @@ -538,7 +538,7 @@ interface jsdIContextEnumerator : nsISupports /** * 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 { /** @@ -556,7 +556,7 @@ interface jsdIScriptHook : nsISupports * 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 { /** @@ -588,7 +588,7 @@ interface jsdICallHook : nsISupports 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 { /** @@ -880,7 +880,7 @@ interface jsdIStackFrame : jsdIEphemeral * 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. */ @@ -1038,6 +1038,10 @@ interface jsdIScript : jsdIEphemeral * Clear all breakpoints set in this script. */ void clearAllBreakpoints (); + /** + * Call interrupt hook at least once per source line + */ + void enableSingleStepInterrupts (in PRBool mode); }; /** @@ -1046,7 +1050,7 @@ interface jsdIScript : jsdIEphemeral * 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. */ @@ -1192,7 +1196,7 @@ interface jsdIValue : jsdIEphemeral * 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. */ @@ -1228,7 +1232,7 @@ interface jsdIObject : nsISupports * 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. */ diff --git a/js/jsd/jsd_scpt.c b/js/jsd/jsd_scpt.c index 4b4c3297086..2dd3676f6ff 100644 --- a/js/jsd/jsd_scpt.c +++ b/js/jsd/jsd_scpt.c @@ -587,6 +587,17 @@ jsd_GetScriptHook(JSDContext* jsdc, JSD_ScriptHookProc* hook, void** callerdata) 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 @@ -751,7 +762,7 @@ jsd_TrapHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, } 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); diff --git a/js/jsd/jsd_xpc.cpp b/js/jsd/jsd_xpc.cpp index d9a27073a59..0e895026f53 100644 --- a/js/jsd/jsd_xpc.cpp +++ b/js/jsd/jsd_xpc.cpp @@ -1480,6 +1480,20 @@ jsdScript::LineToPc(PRUint32 aLine, PRUint32 aPcmap, PRUint32 *_rval) 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) { diff --git a/js/jsd/jsd_xpc.h b/js/jsd/jsd_xpc.h index 06e5c2aeab8..ed759f48c84 100644 --- a/js/jsd/jsd_xpc.h +++ b/js/jsd/jsd_xpc.h @@ -288,6 +288,8 @@ class jsdService : public jsdIDebuggerService virtual ~jsdService(); static jsdService *GetService (); + + PRBool CheckInterruptHook() { return !!mInterruptHook; } private: PRBool mOn; diff --git a/js/jsd/jsdebug.c b/js/jsd/jsdebug.c index 05638f6f26e..2317f86f7ac 100644 --- a/js/jsd/jsdebug.c +++ b/js/jsd/jsdebug.c @@ -576,6 +576,14 @@ JSD_SetInterruptHook(JSDContext* 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) { diff --git a/js/jsd/jsdebug.h b/js/jsd/jsdebug.h index 7f1f3114fdb..e65028468f5 100644 --- a/js/jsd/jsdebug.h +++ b/js/jsd/jsdebug.h @@ -803,6 +803,12 @@ 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. */ diff --git a/js/src/jsdbgapi.cpp b/js/src/jsdbgapi.cpp index dc5e61b95bd..c1e5ff3733c 100644 --- a/js/src/jsdbgapi.cpp +++ b/js/src/jsdbgapi.cpp @@ -145,7 +145,7 @@ js_SetDebugMode(JSContext *cx, JSBool debug) 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)) { /* @@ -182,6 +182,30 @@ JS_SetDebugMode(JSContext *cx, JSBool debug) 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) { @@ -198,6 +222,15 @@ CheckDebugMode(JSContext *cx) 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. */ diff --git a/js/src/jsdbgapi.h b/js/src/jsdbgapi.h index 0776b1eaca1..50921615545 100644 --- a/js/src/jsdbgapi.h +++ b/js/src/jsdbgapi.h @@ -78,6 +78,14 @@ js_SetDebugMode(JSContext *cx, JSBool debug); 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 diff --git a/js/src/jsscript.h b/js/src/jsscript.h index cd9ee325496..0968fdebafb 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -242,6 +242,7 @@ struct JSScript { 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 */ diff --git a/js/src/methodjit/Compiler.cpp b/js/src/methodjit/Compiler.cpp index 37d8fac9048..34e197ffe79 100644 --- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -42,6 +42,7 @@ #include "MethodJIT.h" #include "jsnum.h" #include "jsbool.h" +#include "jsemit.h" #include "jsiter.h" #include "Compiler.h" #include "StubCalls.h" @@ -440,6 +441,7 @@ mjit::Compiler::finishThisUp(JITScript **jitp) 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; @@ -791,6 +793,32 @@ mjit::Compiler::finishThisUp(JITScript **jitp) 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 \ @@ -815,16 +843,19 @@ 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); @@ -850,7 +881,7 @@ mjit::Compiler::generateMethod() 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); diff --git a/js/src/methodjit/MethodJIT.h b/js/src/methodjit/MethodJIT.h index 628d2156114..0c039080efc 100644 --- a/js/src/methodjit/MethodJIT.h +++ b/js/src/methodjit/MethodJIT.h @@ -331,6 +331,7 @@ struct JITScript { void *invokeEntry; /* invoke address */ void *fastEntry; /* cached entry, fastest */ void *arityCheckEntry; /* arity check address */ + bool singleStepMode; /* compiled in "single step mode" */ ~JITScript(); diff --git a/js/src/methodjit/StubCalls.cpp b/js/src/methodjit/StubCalls.cpp index 3f422bdd853..d07345a29a2 100644 --- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -1316,11 +1316,32 @@ stubs::Interrupt(VMFrame &f, jsbytecode *pc) } 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; diff --git a/js/src/methodjit/StubCalls.h b/js/src/methodjit/StubCalls.h index dd3698a4411..577b680ed94 100644 --- a/js/src/methodjit/StubCalls.h +++ b/js/src/methodjit/StubCalls.h @@ -47,10 +47,16 @@ 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);