On Thu, 17 Oct 2024 14:28:30 GMT, Patricio Chilano Mateo 
<pchilanom...@openjdk.org> wrote:

> This is the implementation of JEP 491: Synchronize Virtual Threads without 
> Pinning. See [JEP 491](https://bugs.openjdk.org/browse/JDK-8337395) for 
> further details.
> 
> In order to make the code review easier the changes have been split into the 
> following initial 4 commits:
> 
> - Changes to allow unmounting a virtual thread that is currently holding 
> monitors.
> - Changes to allow unmounting a virtual thread blocked on synchronized trying 
> to acquire the monitor.
> - Changes to allow unmounting a virtual thread blocked in `Object.wait()` and 
> its timed-wait variants.
> - Changes to tests, JFR pinned event, and other changes in the JDK libraries.
> 
> The changes fix pinning issues for all 4 ports that currently implement 
> continuations: x64, aarch64, riscv and ppc. Note: ppc changes were added 
> recently and stand in its own commit after the initial ones.
> 
> The changes fix pinning issues when using `LM_LIGHTWEIGHT`, i.e. the default 
> locking mode, (and `LM_MONITOR` which comes for free), but not when using 
> `LM_LEGACY` mode. Note that the `LockingMode` flag has already been 
> deprecated ([JDK-8334299](https://bugs.openjdk.org/browse/JDK-8334299)), with 
> the intention to remove `LM_LEGACY` code in future releases.
> 
> 
> ## Summary of changes
> 
> ### Unmount virtual thread while holding monitors
> 
> As stated in the JEP, currently when a virtual thread enters a synchronized 
> method or block, the JVM records the virtual thread's carrier platform thread 
> as holding the monitor, not the virtual thread itself. This prevents the 
> virtual thread from being unmounted from its carrier, as ownership 
> information would otherwise go wrong. In order to fix this limitation we will 
> do two things:
> 
> - We copy the oops stored in the LockStack of the carrier to the stackChunk 
> when freezing (and clear the LockStack). We copy the oops back to the 
> LockStack of the next carrier when thawing for the first time (and clear them 
> from the stackChunk). Note that we currently assume carriers don't hold 
> monitors while mounting virtual threads.
> 
> - For inflated monitors we now record the `java.lang.Thread.tid` of the owner 
> in the ObjectMonitor's `_owner` field instead of a JavaThread*. This allows 
> us to tie the owner of the monitor to a `java.lang.Thread` instance, rather 
> than to a JavaThread which is only created per platform thread. The tid is 
> already a 64 bit field so we can ignore issues of the counter wrapping around.
> 
> #### General notes about this part:
> 
> - Since virtual threads don't need to worry about holding monitors anymo...

Marked as reviewed by dlong (Reviewer).

> On failure to acquire a monitor inside `ObjectMonitor::enter` a virtual 
> thread will call freeze to copy all Java frames to the heap. We will add the 
> virtual thread to the ObjectMonitor's queue and return back to Java. Instead 
> of continue execution in Java though, the virtual thread will jump to a 
> preempt stub which will clear the frames copied from the physical stack, and 
> will return to `Continuation.run()` to proceed with the unmount logic.

During this time, the Java frames are not changing, so it seems like it doesn't 
matter if the freeze/copy happens immediately or after we unwind the native 
frames and enter the preempt stub.  In fact, it seems like it could be more 
efficient to delay the freeze/copy, given the fact that the preemption can be 
canceled.

Looking at this reminds me of a paper I read a long time ago, "Using 
continuations to implement thread management and communication in operating 
systems" (https://dl.acm.org/doi/10.1145/121133.121155).

For some reason github thinks VirtualThreadPinnedEvent.java was renamed to 
libSynchronizedNative.c and libTracePinnedThreads.c was renamed to 
LockingMode.java.  Is there a way to fix that?

I finished looking at this, and it looks good.  Nice work!

src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp line 188:

> 186:   // Avoid using a leave instruction when this frame may
> 187:   // have been frozen, since the current value of rfp
> 188:   // restored from the stub would be invalid. We still

It sounds like freeze/thaw isn't preserving FP, even though it is a 
callee-saved register according to the ABI.  If the stubs tried to modify FP 
(or any other callee-saved register) and use that value after the native call, 
wouldn't that be a problem?
Do we actually need FP set by the enter() prologue for stubs?  If we can walk 
compiled frames based on SP and frame size, it seems like we should be able to 
do the same for stubs.  We could consider making stub prologue/epilogue look 
the same as compiled frames, then this FP issue goes away.

src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp line 191:

> 189:   // must restore the rfp value saved on enter though.
> 190:   if (use_pop) {
> 191:     ldp(rfp, lr, Address(post(sp, 2 * wordSize)));

leave() also calls authenticate_return_address(), which I assume we still want 
to call here.
How about adding an optional parameter to leave() that will skip the 
problematic `mov(sp, rfp)`?

src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp line 133:

> 131: 
> 132: inline void FreezeBase::prepare_freeze_interpreted_top_frame(const 
> frame& f) {
> 133:   assert(*f.addr_at(frame::interpreter_frame_last_sp_offset) == 0, 
> "should be null for top frame");

Suggestion:

  assert(f.interpreter_frame_last_sp() == nullptr, "should be null for top 
frame");

src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp line 135:

> 133:   assert(*f.addr_at(frame::interpreter_frame_last_sp_offset) == 0, 
> "should be null for top frame");
> 134:   intptr_t* lspp = f.addr_at(frame::interpreter_frame_last_sp_offset);
> 135:   *lspp = f.unextended_sp() - f.fp();

Suggestion:

  f.interpreter_frame_set_last_sp(f.unextended_sp());

src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp line 159:

> 157: 
> 158:   // The interpreter native wrapper code adds space in the stack equal 
> to size_of_parameters()
> 159:   // after the fixed part of the frame. For wait0 this is equal to 3 
> words (this + long parameter).

Suggestion:

  // after the fixed part of the frame. For wait0 this is equal to 2 words 
(this + long parameter).

Isn't that 2 words, not 3?

src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp line 310:

> 308:     sp -= 2;
> 309:     sp[-2] = sp[0];
> 310:     sp[-1] = sp[1];

This also seems fragile.  This seems to depend on an intimate knowledge of what 
the stub will do when returning.  We don't need this when doing a regular 
return from the native call, so why do we need it here?  I'm guessing 
freeze/thaw hasn't restored the state quite the same way that the stub expects. 
 Why is this needed for C2 and not C1?

src/hotspot/cpu/aarch64/continuationFreezeThaw_aarch64.inline.hpp line 338:

> 336:   // Make sure that extended_sp is kept relativized.
> 337:   DEBUG_ONLY(Method* m = hf.interpreter_frame_method();)
> 338:   DEBUG_ONLY(int extra_space = m->is_object_wait0() ? 
> m->size_of_parameters() : 0;) // see comment in 
> relativize_interpreted_frame_metadata()

Isn't m->size_of_parameters() always correct?  Why is wait0 a special case?

src/hotspot/cpu/aarch64/frame_aarch64.hpp line 77:

> 75:     // Interpreter frames
> 76:     interpreter_frame_result_handler_offset          =  3, // for native 
> calls only
> 77:     interpreter_frame_oop_temp_offset                =  2, // for native 
> calls only

This conflicts with sender_sp_offset.  Doesn't that cause a problem?

src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp line 1555:

> 1553:   // Make VM call. In case of preemption set last_pc to the one we want 
> to resume to.
> 1554:   adr(rscratch1, resume_pc);
> 1555:   str(rscratch1, Address(rthread, JavaThread::last_Java_pc_offset()));

Is it really needed to set an alternative last_Java_pc()?  I couldn't find 
where it's used in a way that would require a different value.

src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp line 1567:

> 1565: 
> 1566:   // In case of preemption, this is where we will resume once we 
> finally acquire the monitor.
> 1567:   bind(resume_pc);

If the idea is that we return directly to `resume_pc`, because of 
`last_Java_pc`(), then why do we poll `preempt_alternate_return_offset` above?

src/hotspot/cpu/aarch64/stackChunkFrameStream_aarch64.inline.hpp line 119:

> 117:   return  mask.num_oops()
> 118:         + 1 // for the mirror oop
> 119:         + (f.interpreter_frame_method()->is_native() ? 1 : 0) // temp 
> oop slot

Where is this temp oop slot set and used?

src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp line 1351:

> 1349:   // set result handler
> 1350:   __ mov(result_handler, r0);
> 1351:   __ str(r0, Address(rfp, 
> frame::interpreter_frame_result_handler_offset * wordSize));

I'm guessing this is here because preemption doesn't save/restore registers, 
even callee-saved registers, so we need to save this somewhere.  I think this 
deserves a comment.

src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp line 1509:

> 1507:     Label no_oop;
> 1508:     __ adr(t, 
> ExternalAddress(AbstractInterpreter::result_handler(T_OBJECT)));
> 1509:     __ ldr(result_handler, Address(rfp, 
> frame::interpreter_frame_result_handler_offset*wordSize));

We only need this when preempted, right?  So could this be moved into the block 
above, where we call restore_after_resume()?

src/hotspot/cpu/x86/c1_Runtime1_x86.cpp line 223:

> 221: }
> 222: 
> 223: void StubAssembler::epilogue(bool use_pop) {

Is there a better name we could use, like `trust_fp` or `after_resume`?

src/hotspot/cpu/x86/c1_Runtime1_x86.cpp line 643:

> 641: uint Runtime1::runtime_blob_current_thread_offset(frame f) {
> 642: #ifdef _LP64
> 643:   return r15_off / 2;

I think using r15_offset_in_bytes() would be less confusing.

src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp line 324:

> 322:   movq(scrReg, tmpReg);
> 323:   xorq(tmpReg, tmpReg);
> 324:   movptr(boxReg, Address(r15_thread, JavaThread::lock_id_offset()));

I don't know if it helps to schedule this load earlier (it is used in the next 
instruction), but it probably won't hurt.

src/hotspot/cpu/x86/continuationFreezeThaw_x86.inline.hpp line 146:

> 144:   // Make sure that locals is already relativized.
> 145:   DEBUG_ONLY(Method* m = f.interpreter_frame_method();)
> 146:   DEBUG_ONLY(int max_locals = !m->is_native() ? m->max_locals() : 
> m->size_of_parameters() + 2;)

What is the + 2 for?  Is the check for is_native because of wait0?  Please add 
a comment what this line is doing.

src/hotspot/cpu/x86/interp_masm_x86.cpp line 359:

> 357:   push_cont_fastpath();
> 358: 
> 359:   // Make VM call. In case of preemption set last_pc to the one we want 
> to resume to.

>From the comment, it sounds like we want to set last_pc to resume_pc, but I 
>don't see that happening.  The push/pop of rscratch1 doesn't seem to be doing 
>anything.

src/hotspot/cpu/x86/interp_masm_x86.cpp line 361:

> 359:   // Make VM call. In case of preemption set last_pc to the one we want 
> to resume to.
> 360:   lea(rscratch1, resume_pc);
> 361:   push(rscratch1);

Suggestion:

  push(rscratch1);  // call_VM_helper requires last_Java_pc for anchor to be at 
the top of the stack

src/hotspot/cpu/x86/stubGenerator_x86_64.cpp line 3796:

> 3794:   __ movbool(rscratch1, Address(r15_thread, 
> JavaThread::preemption_cancelled_offset()));
> 3795:   __ testbool(rscratch1);
> 3796:   __ jcc(Assembler::notZero, preemption_cancelled);

If preemption was canceled, then I wouldn't expect 
patch_return_pc_with_preempt_stub() to get called.  Does this mean preemption 
can get canceled (asynchronously be a different thread?) even afgter 
patch_return_pc_with_preempt_stub() is called?

src/hotspot/share/c1/c1_Runtime1.hpp line 138:

> 136:   static void initialize_pd();
> 137: 
> 138:   static uint runtime_blob_current_thread_offset(frame f);

I think this returns an offset in wordSize units, but it's not documented.  In 
some places we always return an offset in bytes and let the caller convert.

src/hotspot/share/code/nmethod.cpp line 712:

> 710:   JavaThread* thread = reg_map->thread();
> 711:   if ((thread->has_last_Java_frame() && fr.sp() == 
> thread->last_Java_sp())
> 712:       JVMTI_ONLY(|| (method()->is_continuation_enter_intrinsic() && 
> thread->on_monitor_waited_event()))) {

I'm guessing this is because JVMTI can cause a safepoint?  This might need a 
comment.

src/hotspot/share/code/nmethod.cpp line 1302:

> 1300:     _compiler_type           = type;
> 1301:     _orig_pc_offset          = 0;
> 1302:     _num_stack_arg_slots     = 0;

Was the old value wrong, unneeded, or is this set somewhere else?  If this 
field is not used, then we might want to set it to an illegal value in debug 
builds.

src/hotspot/share/oops/method.cpp line 870:

> 868: }
> 869: 
> 870: bool Method::is_object_wait0() const {

It might be worth mentioning that is not a general-purpose API, so we don't 
have to worry about false positives here.

src/hotspot/share/oops/stackChunkOop.inline.hpp line 255:

> 253:                          RegisterMap::WalkContinuation::include);
> 254:     full_map.set_include_argument_oops(false);
> 255:     closure->do_frame(f, map);

This could use a comment.  I guess we weren't looking at the stub frame before, 
only the caller.  Why is this using `map` instead of `full_map`?

src/hotspot/share/prims/jvmtiEnv.cpp line 1363:

> 1361:   }
> 1362: 
> 1363:   if (LockingMode == LM_LEGACY && java_thread == nullptr) {

Do we need to check for `java_thread == nullptr` for other locking modes?

src/hotspot/share/prims/jvmtiEnvBase.cpp line 1602:

> 1600:           // If the thread was found on the ObjectWaiter list, then
> 1601:           // it has not been notified.
> 1602:           Handle th(current_thread, w->threadObj());

Why use get_vthread_or_thread_oop() above but threadObj()?  It probably needs a 
comment.

src/hotspot/share/runtime/continuation.hpp line 50:

> 48: class JavaThread;
> 49: 
> 50: // should match Continuation.toPreemptStatus() in Continuation.java

I can't find Continuation.toPreemptStatus() and the enum in Continuation.java 
doesn't match.

src/hotspot/share/runtime/continuation.hpp line 50:

> 48: class JavaThread;
> 49: 
> 50: // should match Continuation.PreemptStatus() in Continuation.java

As far as I can tell, these enum values still don't match the Java values.  If 
they need to match, then maybe there should be asserts that check that.

src/hotspot/share/runtime/continuationEntry.cpp line 51:

> 49:   _return_pc = nm->code_begin() + _return_pc_offset;
> 50:   _thaw_call_pc = nm->code_begin() + _thaw_call_pc_offset;
> 51:   _cleanup_pc = nm->code_begin() + _cleanup_offset;

I don't see why we need these relative offsets.  Instead of doing

_thaw_call_pc_offset = __ pc() - start;

why not do

_thaw_call_pc = __ pc();

The only reason for the offsets would be if what gen_continuation_enter() 
generated was going to be relocated, but I don't think it is.

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 316:

> 314:     pc = ContinuationHelper::return_address_at(
> 315:            sp - frame::sender_sp_ret_address_offset());
> 316:   }

You could do this with an overload instead:

static void set_anchor(JavaThread* thread, intptr_t* sp, address pc) {
  assert(pc != nullptr, "");
  [...]
}
static void set_anchor(JavaThread* thread, intptr_t* sp) {
  address pc = ContinuationHelper::return_address_at(
    sp - frame::sender_sp_ret_address_offset());
  set_anchor(thread, sp, pc);
}

but the compiler probably optmizes the above check just fine.

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 696:

> 694:   // in a fresh chunk, we freeze *with* the bottom-most frame's stack 
> arguments.
> 695:   // They'll then be stored twice: in the chunk and in the parent 
> chunk's top frame
> 696:   const int chunk_start_sp = cont_size() + frame::metadata_words + 
> _monitors_in_lockstack;

`cont_size() + frame::metadata_words + _monitors_in_lockstack` is used more 
than once.  Would it make sense to add a helper function named something like 
`total_cont_size()`?

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 1063:

> 1061:   unwind_frames();
> 1062: 
> 1063:   chunk->set_max_thawing_size(chunk->max_thawing_size() + _freeze_size 
> - _monitors_in_lockstack - frame::metadata_words);

It seems a little weird to subtract these here only to add them back in other 
places (see my comment above suggesting total_cont_size).  I wonder if there is 
a way to simply these adjustments.  Having to replicate _monitors_in_lockstack 
+- frame::metadata_words in lots of places seems error-prone.

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 1411:

> 1409:     // zero out fields (but not the stack)
> 1410:     const size_t hs = oopDesc::header_size();
> 1411:     oopDesc::set_klass_gap(mem, 0);

Why, bug fix or cleanup?

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 1659:

> 1657:   int i = 0;
> 1658:   for (frame f = freeze_start_frame(); 
> Continuation::is_frame_in_continuation(ce, f); f = f.sender(&map), i++) {
> 1659:     if (!((f.is_compiled_frame() && !f.is_deoptimized_frame()) || (i == 
> 0 && (f.is_runtime_frame() || f.is_native_frame())))) {

OK, `i == 0` just means first frame here, so you could use a bool instead of an 
int, or even check for f == freeze_start_frame(), right?

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 1842:

> 1840:   size += frame::metadata_words; // For the top pc+fp in 
> push_return_frame or top = stack_sp - frame::metadata_words in thaw_fast
> 1841:   size += 2*frame::align_wiggle; // in case of alignments at the top 
> and bottom
> 1842:   size += frame::metadata_words; // for preemption case (see 
> possibly_adjust_frame)

So this means it's OK to over-estimate the size here?

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 2045:

> 2043:     // If we don't thaw the top compiled frame too, after restoring the 
> saved
> 2044:     // registers back in Java, we would hit the return barrier to thaw 
> one more
> 2045:     // frame effectively overwritting the restored registers during 
> that call.

Suggestion:

    // frame effectively overwriting the restored registers during that call.

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 2062:

> 2060:   }
> 2061: 
> 2062:   f.next(SmallRegisterMap::instance, true /* stop */);

Suggestion:

  f.next(SmallRegisterMap::instance(), true /* stop */);

This looks like a typo, so I wonder how it compiled.  I guess template magic is 
hiding it.

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 2650:

> 2648:     
> _cont.tail()->do_barriers<stackChunkOopDesc::BarrierType::Store>(_stream, 
> &map);
> 2649:   } else {
> 2650:     _stream.next(SmallRegisterMap::instance);

Suggestion:

    _stream.next(SmallRegisterMap::instance());

src/hotspot/share/runtime/continuationJavaClasses.inline.hpp line 189:

> 187: 
> 188: inline uint8_t jdk_internal_vm_StackChunk::lockStackSize(oop chunk) {
> 189:   return Atomic::load(chunk->field_addr<uint8_t>(_lockStackSize_offset));

If these accesses need to be atomic, could you add a comment explaining why?

src/hotspot/share/runtime/deoptimization.cpp line 125:

> 123: 
> 124: void DeoptimizationScope::mark(nmethod* nm, bool inc_recompile_counts) {
> 125:   if (!nm->can_be_deoptimized()) {

Is this a performance optimization?

src/hotspot/share/runtime/objectMonitor.cpp line 1612:

> 1610: 
> 1611: static void vthread_monitor_waited_event(JavaThread *current, 
> ObjectWaiter* node, ContinuationWrapper& cont, EventJavaMonitorWait* event, 
> jboolean timed_out) {
> 1612:   // Since we might safepoint set the anchor so that the stack can we 
> walked.

I was assuming the anchor would have been restored to what it was at preemption 
time.  What is the state of the anchor at resume time, and is it documented 
anywhere?
I'm a little fuzzy on what frames are on the stack at this point, so I'm not 
sure if entry_sp and entry_pc are the best choice or only choice here.

src/hotspot/share/runtime/objectMonitor.inline.hpp line 44:

> 42: inline int64_t ObjectMonitor::owner_from(JavaThread* thread) {
> 43:   int64_t tid = thread->lock_id();
> 44:   assert(tid >= 3 && tid < ThreadIdentifier::current(), "must be 
> reasonable");

Should the "3" be a named constant with a comment?

src/hotspot/share/runtime/objectMonitor.inline.hpp line 207:

> 205: }
> 206: 
> 207: inline bool ObjectMonitor::has_successor() {

Why are _succ accesses atomic here when previously they were not?

src/hotspot/share/runtime/vframe.cpp line 289:

> 287:       current >= f.interpreter_frame_monitor_end();
> 288:       current = f.previous_monitor_in_interpreter_frame(current)) {
> 289:       oop owner = !heap_frame ? current->obj() : 
> StackValue::create_stack_value_from_oop_location(stack_chunk(), 
> (void*)current->obj_adr())->get_obj()();

It looks like we don't really need the StackValue.  We might want to make it 
possible to call oop_from_oop_location() directly.

src/hotspot/share/runtime/vframe.inline.hpp line 130:

> 128:       // Waited event after target vthread was preempted. Since all 
> continuation frames
> 129:       // are freezed we get the top frame from the stackChunk instead.
> 130:       _frame = 
> Continuation::last_frame(java_lang_VirtualThread::continuation(_thread->vthread()),
>  &_reg_map);

What happens if we don't do this?  That might help explain why we are doing 
this.

src/hotspot/share/services/threadService.cpp line 467:

> 465:         if (waitingToLockMonitor->has_owner()) {
> 466:           currentThread = Threads::owning_thread_from_monitor(t_list, 
> waitingToLockMonitor);
> 467:         }

Please explain why it is safe to remvoe the above code.

src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java line 108:

> 106:         processDeregisterQueue();
> 107: 
> 108:         if (Thread.currentThread().isVirtual()) {

It looks like we have two implementations, depending on if the current thread 
is virtual or not.  The two implementations differ in the way they signal 
interrupted.  Can we unify the two somehow?

src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java line 57:

> 55:     static {
> 56:         try {
> 57:             
> MethodHandles.lookup().ensureInitialized(AnchorCertificates.class);

Why is this needed?  A comment would help.

test/hotspot/gtest/nmt/test_vmatree.cpp line 34:

> 32: 
> 33: using Tree = VMATree;
> 34: using TNode = Tree::TreapNode;

Why is this needed?

test/hotspot/jtreg/compiler/codecache/stress/OverloadCompileQueueTest.java line 
42:

> 40:  *                   
> -XX:CompileCommand=exclude,java.lang.Thread::beforeSleep
> 41:  *                   
> -XX:CompileCommand=exclude,java.lang.Thread::afterSleep
> 42:  *                   
> -XX:CompileCommand=exclude,java.util.concurrent.TimeUnit::toNanos

I'm guessing these changes have something to do with JDK-8279653?

test/hotspot/jtreg/serviceability/jvmti/events/MonitorContendedEnter/mcontenter01/libmcontenter01.cpp
 line 73:

> 71: /* 
> ========================================================================== */
> 72: 
> 73: static int prepare(JNIEnv* jni) {

Is this a bug fix?

test/jdk/java/lang/reflect/callerCache/ReflectionCallerCacheTest.java line 30:

> 28:  *         by reflection API
> 29:  * @library /test/lib/
> 30:  * @requires vm.compMode != "Xcomp"

If there is a problem with this test running with -Xcomp and virtual threads, 
maybe it should be handled as a separate bug fix.

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

PR Review: https://git.openjdk.org/jdk/pull/21565#pullrequestreview-2410825883
PR Comment: https://git.openjdk.org/jdk/pull/21565#issuecomment-2439180320
PR Comment: https://git.openjdk.org/jdk/pull/21565#issuecomment-2442765996
PR Comment: https://git.openjdk.org/jdk/pull/21565#issuecomment-2448962446
PR Comment: https://git.openjdk.org/jdk/pull/21565#issuecomment-2452534349
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817461936
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817426321
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817439076
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817437593
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817441437
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817464371
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817465037
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1819964369
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817537666
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817539657
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817549144
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1819973901
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1820002377
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1819979640
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1819982432
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821434823
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1819763504
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1819996648
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821706030
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817552633
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1819981522
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821503185
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821506576
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821558267
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821571623
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821594124
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821601480
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821617785
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821746421
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821623432
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821628036
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821632152
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821636581
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821644040
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821653194
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821656267
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821755997
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821670778
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1821685316
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1823640621
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1823644339
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1823580051
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1823663674
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1823665393
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825045757
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825050976
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825054769
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825111095
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825097245
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825109698
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825104359
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825107638
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1817413638

Reply via email to