RFR: 8345067: C2: enable implicit null checks for ZGC reads
Roberto Castañeda Lozano
rcastanedalo at openjdk.org
Tue May 13 08:40:52 UTC 2025
On Tue, 13 May 2025 06:16:53 GMT, Emanuel Peter <epeter at openjdk.org> wrote:
> If I understand your statements above correctly:
> The first load and any subsequent loads are all from the exact same address. Hence, if any were null-pointer, the first one has to be a null-pointer.
Right.
> Assuming this is correct, it seems that this follows:
> Assuming the pointer is not a null-pointer, then wherever it points to cannot be moved by the GC. In your example code above, 0x10(%rsi) is the address, and presumably rsi refers to the base of some object, and 0x10 is the offset to a field. The object that rsi points to can thus not be moved by the GC, correct? But the object that the field at offset 0x10 points to may have been moved, and that is why we check its coloring, and then re-load from that field later. Does that sound correct to you? What guarantees that the object associated with rsi is not moved by the GC?
The inner workings of ZGC's guarantee that "root" addresses such as `%rsi` remain valid ("have a good color" in ZGC speak), but I am afraid I cannot offer a more detailed explanation. You may find more information in e.g. [1] (even though it is outdated by now as it describes non-generational ZGC), or perhaps some GC engineer may chime into the discussion and offer more detail?
In any case, to convince ourselves of the correctness of this RFE without needed to dive deep into ZGC internals, maybe it is enough to ensure that we preserve the same behavior as in mainline (where `zLoadP` cannot be used for implicit null checks). Here is how the compiled code looks for the above example before and after this change:
# Before the RFE (explicit null check):
testq %rsi, %rsi ; explicit null check on the base address
je #uncommon_trap block
movq 0x10(%rsi), %rax ; main OOP load
shrq $0xd, %rax ; uncolor, destroys the OOP loaded in %rax
ja #slow_barrier_path
continue:
(...)
slow_barrier_path:
movq 0x10(%rsi), %rax ; re-load OOP that was destroyed by uncoloring
(...) ; call into runtime (ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded(oopDesc*, oop*))
jmp #continue
# After the RFE (implicit null check):
movq 0x10(%rsi), %rax ; main OOP load with implicit exception: dispatches to #uncommon_trap block
shrq $0xd, %rax ; uncolor, destroys the OOP loaded in %rax
ja #slow_barrier_path
continue:
(...)
slow_barrier_path:
movq 0x10(%rsi), %rax ; re-load OOP that was destroyed by uncoloring
(...) ; call into runtime (ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded(oopDesc*, oop*))
jmp #continue
As you can see, both cases rely on the same assumptions about the validity of `%rsi` through the execution of the compiled code.
[1] Albert Mingkun Yang and Tobias Wrigstad. Deep Dive into ZGC: A Modern Garbage Collector in OpenJDK. In ACM TOPLAS, 2022. https://doi.org/10.1145/3538532
-------------
PR Comment: https://git.openjdk.org/jdk/pull/25066#issuecomment-2875559501
More information about the hotspot-gc-dev
mailing list