C2 Jit compiler fails accessing off-heap memory via Unsafe with ShenandoahGC
Sergey Korotkov
serge.korotkov at gmail.com
Fri May 30 04:56:48 UTC 2025
Hello,
Looks like there is a problem with the C2 Jit compiler that is
reproduced with the Shenandoah GC only. It sometimes may reorder the
Unsafe.getLong() and Unsafe.putLong() operations so that logic of method
is broken. Something more or less similar was already fixed back in
2019 as https://bugs.openjdk.org/browse/JDK-8220714.
For example such Java method would return 0L even if there is an
non-zero long value stored in the buffer before call:
public long run(long addr) {
long tmp = UNSAFE.getLong(addr);
UNSAFE.putLong(addr, 0L);
if (dummy != null)
System.err.println("never happen");
return tmp;
}
Tests show that it is important that run() is method of the inner class
(non-static). And dummy is field of the outer class.
Would you please look?
See more details and full reproducer below.
***
Tested on Linux Ubuntu 24.04
With releases and jdk17 nightly build:
- OpenJDK Runtime Environment (build 11.0.27+6-post-Ubuntu-0ubuntu124.04)
- OpenJDK Runtime Environment (build 17.0.15+6-Ubuntu-0ubuntu124.04)
- OpenJDK Runtime Environment (build
17.0.16-testing+0-builds.shipilev.net-openjdk-jdk17-dev-b818-20250527-1808)
- OpenJDK Runtime Environment (build 21.0.7+6-Ubuntu-0ubuntu124.04)
Problem is reproduced with the jdk17 or jdk21, ShenandoahGC and if
method is C2 compiled only.
Other options work fine:
- jdk11 + any GC
- jdk17/jdk21 + G1GC or ZGC
- jdk17/jdk21 + ShenandoahGC + -XX:TieredStopAtLevel=3
***
Reordering is obvious from the generated assembler code. Below are
fragments obtained running the testcase attached.
WRONG with SenandoahGC: write is reordered to be before the read:
# {method} {0x00007f4738401510} 'run' '(J)J' in 'Test$TestRunner'
# this: rsi:rsi = 'Test$TestRunner'
# parm0: rdx:rdx = long
.... skipped
0x00007f47e0fb0b67: mov rbx, rdx
0x00007f47e0fb0b6a: mov qword ptr [rbx], r12 <--- write 0L to buffer
0x00007f47e0fb0b6d: mov r8, r11
0x00007f47e0fb0b70: shl r8, 3
0x00007f47e0fb0b74: test byte ptr [r15 + 0x20], 1
0x00007f47e0fb0b79: jne L0002
L0001: mov r11d, dword ptr [r8 + 0xc]
0x00007f47e0fb0b7f: mov rbx, qword ptr [rbx] <--- read from buffer
CORRECT with G1GC: read goes first before the write:
# {method} {0x000072317b401510} 'run' '(J)J' in 'Test$TestRunner'
# this: rsi:rsi = 'Test$TestRunner'
# parm0: rdx:rdx = long
.... skipped
0x0000723214faa950: mov r11, rdx
0x0000723214faa953: mov rax, qword ptr [r11] <--- read from buffer
0x0000723214faa956: mov qword ptr [r11], r12 <--- write 0L to buffer
***
In the attached Test.cpp all iterations should be OK:
---------------------------
Test start
0 iter passed
1000000 iter passed
2000000 iter passed
3000000 iter passed
4000000 iter passed
5000000 iter passed
6000000 iter passed
7000000 iter passed
8000000 iter passed
9000000 iter passed
---------------------------
But with the jdk17 and Shenandoah GC it fails on one of the iteration
(once the run() is C2 compiled):
---------------------------
Test start
0 iter passed
1000000 iter passed
2000000 iter passed
3000000 iter passed
4000000 iter passed
5000000 iter passed
Exception in thread "main" java.lang.RuntimeException: unexpected 0L in
buffer
at Test.test(Test.java:24)
at Test.main(Test.java:16)
---------------------------
Thanks,
--
Sergey
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Test.java
Type: text/x-java
Size: 2466 bytes
Desc: not available
URL: <https://mail.openjdk.org/pipermail/shenandoah-dev/attachments/20250530/30d444ac/Test.java>
More information about the shenandoah-dev
mailing list