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