eclipse-openj9 / openj9

Eclipse OpenJ9: A Java Virtual Machine for OpenJDK that's optimized for small footprint, fast start-up, and high throughput. Builds on Eclipse OMR (https://github.com/eclipse/omr) and combines with the Extensions for OpenJDK for OpenJ9 repo.
Other
3.26k stars 717 forks source link

OpenJ9's optimization for array operations issue #19139

Open TemporaryRepos opened 5 months ago

TemporaryRepos commented 5 months ago

Affected versions

We found a test case with execution problems. To facilitate analysis, we simplified the test case and the simplified class file can ben found at attachment.

Linux Ubuntu20.04

Java -version output under Linux

openjdk version "1.8.0_402"
IBM Semeru Runtime Open Edition (build 1.8.0_402-b06)
Eclipse OpenJ9 VM (build openj9-0.43.0, JRE 1.8.0 Linux amd64-64-Bit Compressed References 20240131_861 (JIT enabled, AOT enabled)
OpenJ9   - 2c3d78b48
OMR      - ea8124dbc
JCL      - 0fa9d9c532 based on jdk8u402-b06)
openjdk version "11.0.22" 2024-01-16
IBM Semeru Runtime Open Edition 11.0.22.0 (build 11.0.22+7)
Eclipse OpenJ9 VM 11.0.22.0 (build openj9-0.43.0, JRE 11 Linux amd64-64-Bit Compressed References 20240131_966 (JIT enabled, AOT enabled)
OpenJ9   - 2c3d78b48
OMR      - ea8124dbc
JCL      - 7876cac747 based on jdk-11.0.22+7)
openjdk version "17.0.10" 2024-01-16
IBM Semeru Runtime Open Edition 17.0.10.0 (build 17.0.10+7)
Eclipse OpenJ9 VM 17.0.10.0 (build openj9-0.43.0, JRE 17 Linux amd64-64-Bit Compressed References 20240116_670 (JIT enabled, AOT enabled)
OpenJ9   - 2c3d78b48
OMR      - ea8124dbc
JCL      - 2aad089841f based on jdk-17.0.10+7)

Problem summary

The test case contains loops and simple array operations, but when we set -Xcomp OpenJ9-8 reports ArrayIndexOutOfBoundsException, but OpenJ9-11 and 17 execute fine. We think this is similar to the previous issue.

Steps to Reproduce

java/bin/java  compiler.c1.TestArrayCopy 

Expected Result

nothing

Actual Result

ArrayIndexOutOfBoundsException

Source Code

package compiler.c1;

public class TestArrayCopy {
    public TestArrayCopy() {
    }
    public static void main(String[] var0) {
        int var1 = Integer.MIN_VALUE;
        for(int var3 = 0; var3 < 1000000; ++var3) {
            boolean[] var4 = new boolean[100];
            if (var1 * 2 < var4.length) {
                var4[var1 * 2] = false;
            }
        }
    }
}

Attachment

202403141134.zip

pshipton commented 5 months ago

The previous issue being https://github.com/eclipse-openj9/openj9/issues/19014? I reproduced this one with jdk11 but not jdk8.

@hzongaro fyi

TemporaryRepos commented 5 months ago

I think it's similar with https://github.com/eclipse-openj9/openj9/issues/19014, it's an optimization issue with array operations. But the OpenJ9 version affected is different, so the root cause of the two test cases is different.

hzongaro commented 5 months ago

I am able to reproduce the problem at optLevel warm with lastOptIndex=30, which corresponds to Tree Simplification in this case, so it might well be a duplicate of #19014.

$ java -Xjit:{*TestArrayCopy.main*}\(optLevel=warm,lastOptIndex=29\)  compiler.c1.TestArrayCopy
root@ubuntu-1-hz1:/home/henry/docker-hostdir/defects/issue19139
$ java -Xjit:{*TestArrayCopy.main*}\(optLevel=warm,lastOptIndex=30\)  compiler.c1.TestArrayCopy
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
        at compiler.c1.TestArrayCopy.main(TestArrayCopy.java:11)

@nbhuiyan, may I ask you to look at this one together with #19014?

nbhuiyan commented 3 months ago

I can confirm that this issue and #19014 has the same root cause.

We face this problem due to an optimization during tree simplification that simplifies the BNDCHK calculation for the store into the boolean array.

This is the snippet of the IL that are of interest to us before the transformation:

n48n      BNDCHK [#1]                                                                         [0x7f04241c4650] bci=[-1,31,-] rc=0 vc=38 vn=- li=- udi=- nc=2
n46n        ==>iconst 100
n43n        imul (cannotOverflow )                                                            [0x7f04241c44c0] bci=[-1,29,-] rc=2 vc=38 vn=- li=- udi=- nc=2 flg=0x1000
n41n          iload  <auto slot 2>[#425  Auto] [flags 0x3 0x0 ] (cannotOverflow )             [0x7f04241c4420] bci=[-1,27,-] rc=1 vc=38 vn=- li=12 udi=12 nc=0 flg=0x1000
n42n          iconst 2 (X!=0 X>=0 )                                                           [0x7f04241c4470] bci=[-1,28,-] rc=1 vc=38 vn=- li=- udi=- nc=0 flg=0x104
n53n      bstorei  <array-shadow>[#227  Shadow] [flags 0x80000601 0x0 ]                       [0x7f042423f050] bci=[-1,31,-] rc=0 vc=38 vn=- li=- udi=- nc=2
n52n        aladd (X>=0 internalPtr )                                                         [0x7f042423f000] bci=[-1,31,-] rc=1 vc=38 vn=- li=- udi=- nc=2 flg=0x8100
n40n          ==>aload
n51n          lsub (highWordZero X>=0 cannotOverflow )                                        [0x7f04241c4740] bci=[-1,31,-] rc=1 vc=38 vn=- li=- udi=- nc=2 flg=0x5100
n50n            i2l (highWordZero X>=0 )                                                      [0x7f04241c46f0] bci=[-1,31,-] rc=1 vc=38 vn=- li=- udi=- nc=1 flg=0x4100
n43n              ==>imul
n49n            lconst -8 (X!=0 X<=0 )                                                        [0x7f04241c46a0] bci=[-1,31,-] rc=1 vc=38 vn=- li=- udi=- nc=0 flg=0x204
n83n        bconst   0 (X==0 X>=0 X<=0 )                                                      [0x7f042423f9b0] bci=[-1,0,-] rc=1 vc=38 vn=- li=- udi=- nc=0 flg=0x302
n4n       BBEnd </block_5> =====                                                              [0x7f04241c3890] bci=[-1,31,-] rc=0 vc=35 vn=- li=- udi=- nc=0

For the BNDCHK node above, the first child is the array length which is constant 100 as set in the Java code. For getting the index where the write will be attempted, we obtain the result of the following multiplication of a variable with a constant value: <auto slot 2> * 2. The BNDCHK would evaluate to true if the index child (the result of the multiplication) is >= 0 but less than 100. However, since the array size is divisible by the fixed multiplication factor used to obtain the index, and the array size is known, we can simplify the operation such that this multiplication can be skipped by dividing the array length by 2, and transform the trees into the following:

n48n      BNDCHK [#1]                                                                         [0x7f04241c4650] bci=[-1,31,-] rc=0 vc=41 vn=- li=- udi=- nc=2
n95n        iconst 50 (X!=0 X>=0 )                                                            [0x7f042423fd70] bci=[-1,31,-] rc=1 vc=0 vn=- li=- udi=- nc=0 flg=0x104
n41n        iload  <auto slot 2>[#425  Auto] [flags 0x3 0x0 ] (cannotOverflow )               [0x7f04241c4420] bci=[-1,27,-] rc=2 vc=41 vn=- li=- udi=12 nc=0 flg=0x1000

Here is where things go wrong. <auto slot 2> corresponds to the following in the Java code:

int var1 = Integer.MIN_VALUE;

Integer.MIN_VALUE is 0x80000000. This Java code was relying on causing an overflow to be able to write to the array at all. 0x80000000 * 2, i.e., 0x80000000 << 2 would overflow into 0, so every array store was being made at that slot in the loop.

When the tree simplifier eliminates the multiplication operation which results in the overflow into a valid array index, we run into this issue.

If we wanted to fix this, we would need to disable the tree simplification optimization that technically does the right thing, only to benefit a very specific sort of use case that relies on intentionally overflowing an int value. I do not think we should be going that route.