1 /*
  2  * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test id=serialgc
 26  * @bug 8321734 8348961 8332406
 27  * @requires vm.gc.Serial
 28  * @summary Test that CmpPNode::sub and SubTypeCheckNode::sub correctly identify unrelated classes based on the flat
 29  *          in array property of the types. Additionally check that the type system properly handles the case of a
 30  *          super class being flat in array while the sub klass could be flat in array.
 31  * @library /test/lib /
 32  * @enablePreview
 33  * @modules java.base/jdk.internal.value
 34  *          java.base/jdk.internal.vm.annotation
 35  * @run main compiler.valhalla.inlinetypes.TestFlatInArraysFolding serial
 36  */
 37 
 38 /*
 39  * @test
 40  * @bug 8321734 8348961 8332406
 41  * @summary Test that CmpPNode::sub and SubTypeCheckNode::sub correctly identify unrelated classes based on the flat
 42  *          in array property of the types. Additionally check that the type system properly handles the case of a
 43  *          super class being flat in array while the sub klass could be flat in array.
 44  * @library /test/lib /
 45  * @enablePreview
 46  * @modules java.base/jdk.internal.value
 47  *          java.base/jdk.internal.vm.annotation
 48  * @run main compiler.valhalla.inlinetypes.TestFlatInArraysFolding
 49  */
 50 
 51 package compiler.valhalla.inlinetypes;
 52 
 53 import compiler.lib.ir_framework.*;
 54 
 55 import jdk.internal.vm.annotation.LooselyConsistentValue;
 56 
 57 public class TestFlatInArraysFolding {
 58     static Object[] oArrArr = new Object[100][100];
 59     static Object[] oArr = new Object[100];
 60     static Object o = new Object();
 61     static Object[] oArrSmall = new Object[2];
 62     static Object[] vArr = new V[2];
 63     static {
 64         oArrSmall[0] = new Object();
 65         oArrSmall[1] = new Object();
 66         vArr[0] = new V();
 67         vArr[1] = new V();
 68     }
 69     static int limit = 2;
 70 
 71     // Make sure these are loaded such that A has a flat in array and a not flat in array sub class.
 72     static FlatInArray flat = new FlatInArray(34);
 73     static NotFlatInArray notFlat = new NotFlatInArray(34);
 74 
 75     // Make sure PUnique is the unique concrete sub class loaded from AUnique.
 76     static PUnique pUnique = new PUnique(34);
 77 
 78     static int iFld;
 79 
 80     public static void main(String[] args) {
 81         // TODO 8350865 Scenarios are equivalent, FlatArrayElementMaxSize does not exist anymore
 82         Scenario flatArrayElementMaxSize1Scenario = new Scenario(1, "-XX:-UseArrayFlattening");
 83         Scenario flatArrayElementMaxSize4Scenario = new Scenario(2, "-XX:-UseArrayFlattening");
 84         Scenario noFlagsScenario = new Scenario(3);
 85         TestFramework testFramework = new TestFramework();
 86         testFramework.setDefaultWarmup(0)
 87                 .addFlags("--enable-preview",
 88                           "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED",
 89                           "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED")
 90                 .addScenarios(flatArrayElementMaxSize1Scenario,
 91                               flatArrayElementMaxSize4Scenario, noFlagsScenario);
 92 
 93         if (args.length > 0) {
 94             // Disable Loop Unrolling for IR matching in testCmpP().
 95             // Use IgnoreUnrecognizedVMOptions since LoopMaxUnroll is a C2 flag.
 96             // testSubTypeCheck() only triggers with SerialGC.
 97             Scenario serialGCScenario = new Scenario(4, "-XX:+UseSerialGC", "-XX:+IgnoreUnrecognizedVMOptions",
 98                                                      "-XX:LoopMaxUnroll=0", "-XX:+UseArrayFlattening");
 99             testFramework.addScenarios(serialGCScenario);
100         }
101         Scenario noMethodTraps = new Scenario(5, "-XX:PerMethodTrapLimit=0", "-Xbatch");
102         testFramework.addScenarios(noMethodTraps);
103         testFramework.start();
104     }
105 
106     // SubTypeCheck is not folded while CheckCastPPNode is replaced by top which results in a bad graph (data dies while
107     // control does not).
108     @Test
109     static void testSubTypeCheck() {
110         for (int i = 0; i < 100; i++) {
111             Object arrayElement = oArrArr[i];
112             oArr = (Object[])arrayElement;
113         }
114     }
115 
116     @Test
117     @IR(counts = {IRNode.COUNTED_LOOP, "2", // Loop Unswitching done?
118                   IRNode.STORE_I, "1"}, // CmpP folded in unswitched loop version with flat in array?
119         applyIf = {"LoopMaxUnroll", "0"})
120     static void testCmpP() {
121         Object[] arr = oArr;
122         if (arr == null) {
123             // Needed for the 'arrayElement == arr' to fold in
124             // the flat array case because both could be null.
125             throw new NullPointerException("arr is null");
126         }
127         for (int i = 0; i < 100; i++) {
128             Object arrayElement = oArrArr[i];
129             if (arrayElement == arr) {
130                 iFld = 34;
131             }
132         }
133     }
134 
135     // Type system does not correctly handle the case that a super klass is flat in array while the sub klass is
136     // maybe flat in array. This leads to a bad graph.
137     @Test
138     static void testUnswitchingAbstractClass() {
139         Object[] arr = oArr;
140         for (int i = 0; i < 100; i++) {
141             Object arrayElement = arr[i];
142             if (arrayElement instanceof A) {
143                 A a = (A)arrayElement;
144                 if (a == o) {
145                     a.foo();
146                 }
147             }
148         }
149     }
150 
151     // Same as testUnswitchingAbstractClass() but with interfaces. This worked before because I has type Object(I)
152     // from which Object is a sub type of.
153     @Test
154     static void testUnswitchingInterface() {
155         Object[] arr = oArr;
156         for (int i = 0; i < 100; i++) {
157             Object arrayElement = arr[i];
158             if (arrayElement instanceof I) {
159                 I iVar = (I)arrayElement;
160                 if (iVar == o) {
161                     iVar.bar();
162                 }
163             }
164         }
165     }
166 
167     // TODO 8350865 FlatArrayElementMaxSize does not exist anymore
168     // PUnique is the unique concrete sub class of AUnique and is not flat in array (with FlatArrayElementMaxSize=4).
169     // The CheckCastPP output of the sub type check uses PUnique while the sub type check itself uses AUnique. This leads
170     // to a bad graph because the type system determines that the flat in array super klass cannot be met with the
171     // not flat in array sub klass. But the sub type check does not fold away because AUnique *could* be flat in array.
172     // Fixed with in JDK-8328480 in mainline but not yet merged in. Applied manually to make this work.
173     @Test
174     static void testSubTypeCheckNotFoldedParsingAbstractClass() {
175         Object[] arr = oArr;
176         for (int i = 0; i < 100; i++) {
177             Object arrayElement = arr[i];
178             if (arrayElement instanceof AUnique) {
179                 AUnique aUnique = (AUnique)arrayElement;
180                 if (aUnique == o) {
181                     aUnique.foo();
182                 }
183             }
184         }
185     }
186 
187     // Same as testSubTypeCheckNotFoldedParsingAbstractClass() but with interfaces. This worked before because IUnique
188     // has type Object(IUnique) from which Object is a sub type of.
189     @Test
190     static void testSubTypeCheckNotFoldedParsingInterface() {
191         Object[] arr = oArr;
192         for (int i = 0; i < 100; i++) {
193             Object arrayElement = arr[i];
194             if (arrayElement instanceof IUnique) {
195                 IUnique iUnique = (IUnique)arrayElement;
196                 if (iUnique == o) {
197                     iUnique.bar();
198                 }
199             }
200         }
201     }
202 
203     @Test
204     @Warmup(10000)
205     static void testJoinSameKlassDifferentFlatInArray() {
206         // Accessing the array: It could be a flat array. We therefore add a Phi to select from the normal vs.
207         // flat in array access:
208         //     Phi(Object:flat_in_array, Object) -> CheckCastPP[Object:NotNull:exact]
209         for (Object o : oArrSmall) {
210             // We speculate that we always call Object::hashCode() and thus add a CheckCastPP[Object:NotNull:exact]
211             // together with a speculate_class_check trap on the failing path.
212             // We decide to unswitch the loop to get a loop version where we only have flat in array accesses. This
213             // means we can get rid of the Phi. During IGVN and folding some nodes we eventually end up with:
214             //    CheckCastPP[Object:NotNull (flat_in_array)] -> CheckCastPP[Object:NotNull:exact]
215             //
216             // We know that we have some kind of Value class that needs to be joined with an exact Object that is not
217             // a value class. Thus, the result in an empty set. But this is missing in the type system. We fail with
218             // an assertion. This is fixed with 8348961.
219             //
220             // Pre-8332406:
221             // To make this type system change work, we require that the TypeInstKlassPtr::not_flat_in_array() takes
222             // exactness information into account to also fold the corresponding control path. This requires another
223             // follow up fix: The super class of a sub type check is always an exact class, i.e. "o instanceof Super".
224             // We need a version of TypeInstKlassPtr::not_flat_in_array() that treats "Super" as inexact. Failing to do
225             // so will erroneously fold a sub type check away (covered by testSubTypeCheckForObjectReceiver()).
226             //
227             // Post-8332406:
228             // We now directly cast the klass pointer in SubTypeCheckNode::sub() to inexact which triggers a
229             // recomputation of the flat in array property. This will turn an exact TypeInstKlassPtr such as Object,
230             // which is not flat in array, into an inexact maybe flat in array TypeInstKlassPtr.
231             o.hashCode();
232         }
233     }
234 
235     @Test
236     @Warmup(10000)
237     static void testSubTypeCheckForObjectReceiver() {
238         for (int i = 0; i < limit; i++) {
239             // We perform a sub type check that V is a sub type of Object. This is obviously true
240             vArr[i].hashCode();
241         }
242     }
243 
244     interface IUnique {
245         abstract void bar();
246     }
247 
248     static abstract value class AUnique implements IUnique {
249         abstract void foo();
250     }
251 
252     @LooselyConsistentValue
253     static value class PUnique extends AUnique {
254         int x;
255         int y;
256         PUnique(int x) {
257             this.x = x;
258             this.y = 34;
259         }
260 
261         public void foo() {}
262         public void bar() {}
263     }
264 
265     interface I {
266         void bar();
267     }
268 
269     static abstract value class A implements I {
270         abstract void foo();
271     }
272 
273     @LooselyConsistentValue
274     static value class FlatInArray extends A implements I {
275         int x;
276         FlatInArray(int x) {
277             this.x = x;
278         }
279 
280         public void foo() {}
281         public void bar() {}
282     }
283 
284     // TODO 8350865 FlatArrayElementMaxSize does not exist anymore
285     // Not flat in array with -XX:FlatArrayElementMaxSize=4
286     static value class NotFlatInArray extends A implements I {
287         int x;
288         int y;
289         NotFlatInArray(int x) {
290             this.x = x;
291             this.y = 34;
292         }
293 
294         public void foo() {}
295         public void bar() {}
296     }
297 
298     static value class V {}
299 
300     static final Object OBJ = new Object();
301 
302     @Test
303     static Object testEqualMeet1(boolean b, Object[] array, int i) {
304         return b ? array[i] : OBJ;
305     }
306 
307     @Run(test = "testEqualMeet1")
308     public void testEqualMeet1_verifier() {
309         testEqualMeet1(true, new Object[1], 0);
310         testEqualMeet1(false, new Object[1], 0);
311     }
312 
313     @Test
314     static Object testEqualMeet2(boolean b, Object[] array, int i) {
315         return b ? OBJ : array[i];
316     }
317 
318     @Run(test = "testEqualMeet2")
319     public void testEqualMeet2_verifier() {
320         testEqualMeet2(true, new Object[1], 0);
321         testEqualMeet2(false, new Object[1], 0);
322     }
323 
324     @Test
325     public static Object test8332406(boolean b, Object[] array, int i) {
326         return b ? array[i] : OBJ;
327     }
328 
329     @Run(test = "test8332406")
330     public static void runTest8332406() {
331         test8332406(true, new Object[1], 0);
332         test8332406(false, new Object[1], 0);
333     }
334 }