On Thu, 15 Oct 2026 22:47:24 GMT, Patricio Chilano Mateo 
<[email protected]> wrote:

>> When a virtual thread suspended at a breakpoint gets unmounted and mounted 
>> again only the top frame is thawed. The caller stays frozen in the stack 
>> chunk and the popped frame returns into the continuation return barrier. The 
>> interpreter's popframe entry treats that return address like a deoptimized 
>> caller, preserves the arguments, sets the reexecution bit and returns. The 
>> barrier then thawed the caller as a plain return. Arguments and bit left 
>> behind, no reexecution of the invoke, run() went on with a zero result.
>> 
>> Thaw finishes it now. An interpreted caller gets the preserved arguments 
>> back on its expression stack and its return slot is patched to the 
>> interpreter deopt entry after push_return_frame, the x86 deopt entry reloads 
>> rsp from last_sp for that, aarch64 already does. A compiled caller frozen in 
>> the chunk is deoptimized from UpdateForPopTopFrameClosure::doit the same way 
>> set_frame_pop does it. Thaw leaves the arguments alone in that case and 
>> unpack_on_stack puts them back. A frozen compiled frame being popped stays 
>> opaque.
>> 
>> A linkTo caller needs one more thing. The linker consumed the trailing 
>> MemberName before the target ran and the preserved arguments are sized by 
>> the target, so the MemberName is not among them. Thaw takes it from the 
>> caller's local 0 the way the interpreter's popframe entry does, the lookup 
>> from member_name_arg_or_null moved into a plain helper both can call. The 
>> arguments go one slot up and the MemberName goes back on top.
>> 
>> popframes002 runs popframes001 on a virtual thread with the join Chris 
>> Plummer wrote enabled by a property in the debuggee. It unmounts the tested 
>> thread before the breakpoint so the caller is frozen when popped, and run() 
>> is excluded from compilation so this stays the interpreted case. 
>> popframes003 is the compiled caller case, -Xcomp with the breakpoint in a 
>> separate stop() so testedMethod keeps its compiled frame and run() stays 
>> frozen, the debugger pops frame 1 and the debuggee checks with WhiteBox that 
>> run() and testedMethod are compiled and that testedMethod ran twice. 
>> -XX:+PreserveFramePointer keeps the thaw on the slow path. Both fail on 
>> master without the hotspot change. popframes001 itself runs the plain case 
>> as before and is off the virtual problem list. If separate copies of the 
>> debugger and debuggee are preferred over the properties that is easy to 
>> change. PopFrameMethodNameInvariantTest got an unmount mode, the same 
>> linkToStatic pop on a virtual thread that sleeps
 before the breakp...
>
> src/hotspot/share/runtime/continuationFreezeThaw.cpp line 3114:
> 
>> 3112:         int top_element = f.interpreter_frame_expression_stack_size() 
>> - 1;
>> 3113:         intptr_t* base = 
>> f.interpreter_frame_expression_stack_at(top_element);
>> 3114:         Copy::conjoint_jbytes(_thread->popframe_preserved_args(), 
>> base, words * wordSize);
> 
> For `linkTo*` calls I see we restore the required trailing `MemberName` in 
> the normal interpreter caller case 
> [(1)](https://github.com/openjdk/jdk/blob/decb49e1f3d05eeae7ef48e43623e3795b21d266/src/hotspot/cpu/x86/templateInterpreterGenerator_x86.cpp#L1529).
>  We would need to do the same here, or alternatively reject this case in the 
> pop frame closure.

Right, the saved args are sized by the target so the MemberName was left out. 
restored it now the way the interpreter does, from local 0, and 
PopFrameMethodNameInvariantTest got an unmount mode that covers it on a virtual 
thread

> src/hotspot/share/runtime/continuationFreezeThaw.cpp line 3121:
> 
>> 3119:     } else {
>> 3120:       assert(f.is_deoptimized_frame(), "popframe reexecution into a 
>> frozen compiled caller that was not deoptimized");
>> 3121:     }
> 
> This body could be factored out into a separate static method along with the 
> comment above.

Done, it's split into two static helpers now, the check and the restore, with 
the comment moved over

> test/hotspot/jtreg/vmTestbase/nsk/jdwp/StackFrame/PopFrames/popframes001a.java
>  line 132:
> 
>> 130:                 try {
>> 131:                     Method run = 
>> TestedThreadClass.class.getDeclaredMethod("run");
>> 132:                     if 
>> (WhiteBox.getWhiteBox().getMethodCompilationLevel(run) == 0) {
> 
> Note that this check only verifies that a compiled version of `run()` exists, 
> not that we are running it. But even if we could guarantee that, we always 
> thaw the compiled caller of an interpreted frame. In order to exercise this 
> case we could do something like what you did in 
> `PopFrameMethodNameInvariantTest.java` with an extra `stop()` where we put 
> the breakpoint instead, and then in the `popFrames` request include its 
> caller. You might want to also use `-XX:+PreserveFramePointer` (avoids 
> thawing all frames at once).

Right, the thaw brought run() out with the interpreted testedMethod. Reworked 
as suggested: breakpoint in a new stop(), the debugger pops frame 1, with 
-XX:+PreserveFramePointer. On master this version fails, testedMethod runs once 
after the pop

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/32661#discussion_r4290836741
PR Review Comment: https://git.openjdk.org/jdk/pull/32661#discussion_r4290827545
PR Review Comment: https://git.openjdk.org/jdk/pull/32661#discussion_r4338768562

Reply via email to