Conversation
添加位置变动(操作变动)回调接口,为外部实现调试功能实现可能
Introduces JS_SetOPChangedHandler to allow setting a callback for operation changes in the JSContext. Also adds calls to emit_source_loc in various statement parsing locations to improve source location tracking during parsing.
假如没有,位置跟踪会发生异常。
解决在函数内出现静态错误时,返回的堆栈信息中的列号错误的bug。
Introduces functions to get stack depth and retrieve local variables at a specific stack frame level, along with a struct for local variable info and a function to free the allocated array. Also updates the JSOPChangedHandler signature to include JSContext for improved debugging capabilities.
假如采用旧的代码,会发生下面的错误:
function add(a, b){
return a + b;
var b // OP_return会出现在这里
while(1){}
}
add(1, 2)
|
At a quick glance, this looks better than the other approaches I've seen, kudos! Now, since this will have a performance impact, I'd say we want it gated with a compile time time macro. @bnoordhuis thoughts? |
|
Thanks for the feedback @saghul! I've added a compile-time macro Compile-time gating (
|
|
@bnoordhuis WDYT? |
bnoordhuis
left a comment
There was a problem hiding this comment.
Behind a compile-time flag would be good. The diff mostly looks good to me but there are a couple of changes I'd like to see:
-
the API functions should always be available but return an error when compiled without debugger support
-
can you rename the new emit_source_loc calls to something like emit_source_loc_debug and turn that into a no-op when there's no debugger support?
-
I don't love the name JSOPChangedHandler. Suggestions for a better one? Something like JSBytecodeTraceFunc perhaps?
I'm assuming this good enough as a building block to assemble a functional debugger from? I can see how it lets you single-step through code, inspect stack frames, set breakpoints, etc., so I'm guessing... yes?
Rename the old operation_changed/JSOPChangedHandler to bytecode_trace/JSBytecodeTraceFunc and replace JS_SetOPChangedHandler with JS_SetBytecodeTraceHandler. Add conditional compilation guards so debugger-related code is compiled only when QJS_ENABLE_DEBUGGER is set (including stack depth, local-variable APIs, and freeing logic). Introduce emit_source_loc_debug no-op macro when debugger is disabled and make JS_GetStackDepth return -1 without the debugger. Update public header comments to reflect the new API and behavior.
Okay, I will first conduct a comparative test based on the tools you provided to see the actual impact. |
Web Tooling Benchmark Performance Test ResultsI ran web-tooling-benchmark (17 real-world JavaScript workloads) to measure the performance impact of this PR's Test Environment
Detailed Results (runs/s, higher is better)
Raw Run DataUpstream: 1.43, 1.42, 1.45 (avg: 1.433) SummaryThe unconditional |
|
I'm a bit confused. @bnoordhuis, do you mean we should keep OP_debug but compile it conditionally? Also, regarding the emit_source_loc_debug issue mentioned earlier, there might be a problem. If we adopt emit_source_loc_debug, we not only need to add it at specific locations, but also replace the existing emit_source_loc with emit_source_loc_debug. Otherwise, the statements represented by the original emit_source_loc won't respond to OP_debug. |
|
Sorry @G-Yong i added to the confusion. As your benchmarks show, just by having the debug opcodes things get slower so it should be opt-in. Not sure if compile time or config, @bnoordhuis what's on your mind? |
Agent-Logs-Url: https://github.com/G-Yong/quickjs/sessions/f6258d46-0a54-473d-9197-b85a494a7bb2 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: G-Yong <21030893+G-Yong@users.noreply.github.com>
Agent-Logs-Url: https://github.com/G-Yong/quickjs/sessions/a7eecad6-7beb-49a4-90cf-0a7c0ad40461 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: G-Yong <21030893+G-Yong@users.noreply.github.com>
The previous codegen on Windows embedded CRLF line endings from fib_module.js source into the bytecode, causing an 8-byte size difference vs upstream. Updated codegen.ps1 to normalize JS input files to LF before compilation.
|
I didn't quite understand why emit_source_loc_debug was needed before. Now I get it a little bit— the newly added emit_source_loc calls are debug-specific (for stepping through if/for/return/etc.), unlike the original 5 upstream calls which are always needed for error reporting. Having emit_source_loc_debug as a no-op without debugger support keeps the bytecode identical to upstream and avoids the performance overhead. |
* fix: wrap debug_trace test with #ifdef QJS_ENABLE_DEBUGGER Agent-Logs-Url: https://github.com/G-Yong/quickjs/sessions/c47f60f2-9eab-4e86-a75e-3755d5e81466 Co-authored-by: G-Yong <21030893+G-Yong@users.noreply.github.com> * chore: remove accidentally committed build-debug directory Agent-Logs-Url: https://github.com/G-Yong/quickjs/sessions/c47f60f2-9eab-4e86-a75e-3755d5e81466 Co-authored-by: G-Yong <21030893+G-Yong@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: G-Yong <21030893+G-Yong@users.noreply.github.com>
Yes, exactly. :-) The reason I suggested it is because it lets the debugger be a runtime option instead of a compile-time option. |
…nstead of a compile-time option
|
Thanks @bnoordhuis, I now understand the intent! I've refactored the implementation to make the debugger a runtime option instead of a compile-time option: Key changes:
This means a single QuickJS binary can serve both as a normal engine and a debug-capable engine, controlled entirely at runtime by whether |
|
@dblnz A note about the The earlier approach (from my fork's restart:
for(;;) {
#ifdef QJS_ENABLE_DEBUGGER
if (b && ctx->bytecode_trace != NULL) {
// callback here...
}
#endif
SWITCH(pc) {
CASE(OP_push_i32): ...This had a fundamental problem with #if defined(EMSCRIPTEN) || defined(_MSC_VER) || defined(QJS_ENABLE_DEBUGGER)
#define DIRECT_DISPATCH 0
The current This is why this PR's approach is superior to what I originally proposed in #119. |
This sounds great, thanks! |
|
@G-Yong looks like there are some conflicts with master now, could you please resolve them? 🙏 |
|
Asked Claude for a review: At a quick glance, some of these are legit. |
Apply fixes for the issues raised in the latest adversarial review on the "Add Debugging Interface" PR (quickjs-ng#1421): * OP_debug callback (JS_CallInternal): - Use JS_AtomToCString instead of a 64-byte stack buffer so filename and funcname are passed to the trace handler at full length. - When the callback returns non-zero without raising an exception, throw a default InternalError("aborted by debugger") so JS_GetException() never returns the JS_UNINITIALIZED sentinel. - When the callback calls JS_Throw* but returns 0, also unwind to the exception handler so a pending exception is never silently carried over into the next opcode / next eval. * JS_GetLocalVariablesAtLevel: - Skip compiler-generated names starting with '<' (e.g. <ret>, <this_active_func>) so they no longer appear in debugger listings. - Substitute JS_UNINITIALIZED slots (let/const TDZ) with JS_UNDEFINED so the internal sentinel never escapes to C callers. - On JS_AtomToCString OOM, free what was allocated, clear the pending exception, set *pcount = 0 and return NULL instead of leaving a half-built array and a leaked exception state. - Forward-declare JS_AtomGetStr so the helper macro can use it (it is defined later in the file). * emit_source_loc / emit_source_loc_debug: - Stop appending OP_debug from inside emit_source_loc; that function is invoked from expression parsers and was emitting multiple OP_debug per statement. OP_debug is now confined to emit_source_loc_debug, which is only called at statement boundaries. - Move the emit_source_loc_debug for TOK_LET / TOK_CONST out of the 'haslet:' label so the let/const fall-through into TOK_VAR no longer fires the trace twice. - Add emit_source_loc_debug at the top of throw and default expression statements where it was missing. - Drop dead emit_source_loc_debug calls after emit_break, before label_finally and before the final OP_ret of a try/catch/finally. * find_jump_target / code_has_label: - Stop skipping OP_debug while resolving jump targets / scanning for labels. OP_debug is an executable opcode with a side effect (the user callback) and must not be jumped over by the optimizer. * next_token: replace a stray hard-tab indent with spaces. quickjs.h: rewrite the JSDebugTraceFunc / JS_SetDebugTraceHandler doc comments to match the implementation contract: * return non-zero OR raising via JS_Throw* both abort execution; the engine synthesizes a default exception when needed; * the filename/funcname pointers are only valid for the duration of the callback invocation; * OP_debug is only emitted for code parsed AFTER JS_SetDebugTraceHandler has been installed, so the handler must be set before evaluating any application code.
|
Thanks for the thorough review @saghul (and Claude)! I've pushed 1011587 on top of the branch addressing the issues raised. Summary: OP_debug callback (JS_CallInternal)
JS_GetLocalVariablesAtLevel
Duplicate / stray OP_debug emission
Optimizer
Misc
quickjs.h
I have not yet tackled the broader "handler attached after parse-time is silently a no-op" point as a behavior change — that one is now explicitly documented in the header. Happy to also implement option (a) (always emit OP_debug at statement boundaries, gate the dispatch at runtime) if you'd prefer that over the documented contract. |
|
Before changing the design I ran the web-tooling-benchmark v0.5.3 suite to compare both options. Setup: Windows / MSVC 2019 Release / x64, upstream c707cf5, three back-to-back runs each, geomean of runs/s. Option (a) — always emit OP_source_loc + OP_debug, gate at runtime in OP_debug case
Current PR design — parse-time gate (emit_source_loc_debug checks ctx->debug_trace once, at parse time)
|
… warning (#10) Agent-Logs-Url: https://github.com/G-Yong/quickjs/sessions/d47f1173-4bb5-49cb-895c-0ee2e898fddc Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: G-Yong <21030893+G-Yong@users.noreply.github.com>
Add a debugging interface to the existing architecture.

Using the added debugging interface, we implemented debugging for QuickJS in VSCode. The project address is:[QuickJS-Debugger]