Repository navigation
Conversation
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline nit, I also checked that every DFG/FTL inlining path reaches the new refusal: both attemptToInlineCall (DFGByteCodeParser.cpp:2440) and handleVarargsInlining (DFGByteCodeParser.cpp:2508) go through inliningCost, and the new check sits after the baseline CodeBlock null check, so no inlining route skips it and there is no null dereference. The private-builtin exclusion matches BytecodeGenerator::shouldEmitDebugHooks (BytecodeGenerator.h:1097), which never emits op_debug for private builtins.
Extended reasoning...
The change adds one USE(BUN_JSC_ADDITIONS)-guarded refusal in ByteCodeParser::inliningCost so a callee whose bytecode carries debug hooks is never inlined, plus a new jsc-shell stress test; it touches no security-sensitive surface. The check can only make the DFG/FTL more conservative (a real call instead of an inlined one) and only while a debugger is attached, but it is core JIT inlining policy whose correctness rests on the mark semantics introduced by the stacked PRs this one builds on, so a human maintainer look is still worthwhile.
|
Preview build of a8110c4: |
11b5867 to
71da639
Compare
f940adf to
756d5a2
Compare
71da639 to
c19a150
Compare
756d5a2 to
a664b85
Compare
There was a problem hiding this comment.
I re-reviewed the latest push and found no bugs; the earlier testLoopCount nit is addressed. Since this changes the DFG inlining gate in code covered by the JSC reviewers in CODEOWNERS and depends on the base PR's debugger-mark guarantees, a human look is still worthwhile.
What was reviewed:
- The new gate in
inliningCostmirrorsBytecodeGenerator::shouldEmitDebugHooks(Debugger mode and not a private builtin), so it refuses exactly the callees that carryop_debug; excluding private builtins is consistent with that. - FTL path:
targetCodeBlockand the baseline block share the executable's UnlinkedCodeBlock, so checking the baseline block's mark covers both tiers; the check only ever removes inlining, so no new JIT behavior is introduced. - The test's
$vm.enableDebuggerModeWhenIdle,createGlobalObjectandtestLoopCountall exist in this fork's shell, and the singlerunDefaultmode pins its options.
Extended reasoning...
The change adds one USE(BUN_JSC_ADDITIONS)-guarded early return in ByteCodeParser::inliningCost (Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp) refusing to inline a callee whose bytecode was compiled with debugging opcodes unless it is a private builtin, plus a new stress test. It touches no security-sensitive surface; the effect is strictly to inline less when a debugger is attached. Approval was withheld because /Source/JavaScriptCore and /JSTests are covered by CODEOWNERS entries and the fix's correctness rests on the debugger mark being reliable, which the author attributes to a base PR not in this diff. The latest commit addressed the only prior feedback (iteration count now comes from testLoopCount), and the bug hunt ran dry with no findings.
…the profilers and a client that disconnects The preview has oven-sh/WebKit#836, oven-sh/WebKit#830 and oven-sh/WebKit#837 on top of the WebKit commit that main pins. New tests: a vm.Script that runs twice in one global object, with a debugger, the type profiler and the control flow profiler turned on while its first generator is suspended. A generator and an async function that start under a profiler and resume after the profiler is turned off, and after the inspector client is gone.
c19a150 to
93c3757
Compare
a664b85 to
d14e779
Compare
…bKit main WebKit main (e8a8efc09c) is one commit past the WebKit commit that main pins (7d0c8a7d09): oven-sh/WebKit#862.
The DFG turns an inlined op_debug into nothing (ByteCodeParser, op_debug), on the grounds that a breakpoint jettisons every optimized CodeBlock that contains it. It does not: CodeBlock::addBreakpoint jettisons the optimized code of the function the breakpoint is in, not the code of a caller that has inlined that function. The one guard, in ByteCodeParser::inliningCost, looks at the caller: code with the debugger's mark inlines nothing. A caller without the mark can call a function that has debug hooks: - code of a global object without a debugger that calls a function of a global object with one, - the body of a generator or an async function that is suspended when the debugger attaches, which keeps the code it started in. In both, a breakpoint or a `debugger` statement in the callee was skipped for every call that ran inlined: 8,507 of 10,000 calls in the test. inliningCost now also refuses a callee whose code has debug hooks. A private builtin has the mark and no hooks, and stays inlinable. JSTests/stress/dfg-does-not-inline-a-function-that-has-debug-hooks.js has the three callers.
93c3757 to
fcb227a
Compare
d14e779 to
a8110c4
Compare
There was a problem hiding this comment.
I reviewed the latest push and found no bugs; the earlier testLoopCount nit is addressed. Since this changes DFG/FTL inlining policy under the JavaScriptCore codeowner paths, a human look is still worthwhile.
What was reviewed:
- The new guard in ByteCodeParser::inliningCost():
wasCompiledWithDebuggingOpcodes() && !isPrivateBuiltinFunction()is the same predicate as BytecodeGenerator::shouldEmitDebugHooks(), so it refuses exactly the callees whose bytecode contains op_debug, and it runs before the targetCodeBlock/FTL selection so both tiers are covered. - Checked the ordering relative to the existing m_hasDebuggerEnabled and null-codeBlock checks — the callee CodeBlock is non-null when dereferenced.
- The test now takes its count from testLoopCount, fails only by throwing, and the $vm helpers it uses (dfgTrue, enableDebuggerModeWhenIdle) exist in this fork.
Extended reasoning...
The change adds a USE(BUN_JSC_ADDITIONS)-guarded 12-line check to Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp that refuses to inline a callee compiled with debugger opcodes unless it is a private builtin, plus a new JSTests/stress test covering two cross-realm callers and a suspended generator. No security-sensitive surface is touched; this is JIT inlining policy interacting with the debugger. The decision to defer rather than approve rests on the files being covered by .github/CODEOWNERS (/Source/JavaScriptCore and /JSTests) and on the change altering DFG/FTL inlining behaviour, where the test's reliance on the callee not tiering up on its own is an empirical property best confirmed by a maintainer and CI. The new commit since the last review addressed the one prior nit (testLoopCount), and no outstanding CHANGES_REQUESTED review exists in the timeline.
Based on #830, which is based on #836 (the third commit here).
Problem
debuggerstatement in a function is skipped when the function runs inlined in DFG or FTL code: 8,507 of 10,000 calls in the test.ByteCodeParser::inliningCost(dfg/DFGByteCodeParser.cpp:2021) refuses to inline only when the caller has the debugger's mark. The callee is not checked.Fix
inliningCostalso refuses a callee whose bytecode has debug hooks. A private builtin has the mark but no hooks, and stays inlinable.op_debugis a nop, andCodeBlock::addBreakpointjettisons only the optimized code of its own function.JSTests/stress/dfg-does-not-inline-a-function-that-has-debug-hooks.js(3 callers, all fail without the change).Background
op_debuginstructions. The interpreter and the Baseline JIT call the debugger from them. The DFG compiles them to nothing.UnlinkedCodeBlock::wasCompiledWithDebuggingOpcodes(): the bytecode was generated for a global object with a debugger.Downsides
Notes
jsc shell, Release with assertions,
--useConcurrentJIT=0. The callee has adebuggerstatement, which keeps it from tiering up by itself, as a breakpoint does.$vm.dfgTrue()in the callee is true only when it runs inlined. Calls that reached the statement in the callee's own frame, of 10,000:e8a8efc09cThe first row is 10,000 on main because main hands the other global object the debugged one's marked bytecode. #830 stops that, and this PR puts the guard where it belongs.
Upstream JavaScriptCore has the same guard and the same gap.