jdk-24/test/hotspot/jtreg/compiler/predicates/assertion/TestOpaqueInitializedAssertionPredicateNode.java
Christian Hagedorn 7ef2831293 8333644: C2: assert(is_Bool()) failed: invalid node class: Phi
Reviewed-by: thartmann, kvn
2024-06-06 06:58:05 +00:00

450 lines
17 KiB
Java

/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/*
* @test
* @bug 8330386
* @summary Test that replacing Opaque4 nodes with OpaqueInitializedAssertionPredicate for Initialized Assertion Predicates
* works. We test following cases explicitly:
* 1) Cloning down CmpUNode in Split If with involved OpaqueInitializedAssertionPredicateNodes
* 2) Special casing OpaqueInitializedAssertionPredicate in IdealLoopTree::policy_range_check()
* 3) Special casing Opaque4 node from non-null check for intrinsics and unsafe accesses inside
* PhaseIdealLoop::update_main_loop_assertion_predicates().
* @requires vm.compiler2.enabled
* @modules java.base/jdk.internal.misc:+open
* @run main/othervm -Xbatch -XX:LoopMaxUnroll=0
* -XX:CompileCommand=compileonly,*TestOpaqueInitializedAssertionPredicateNode::test*
* -XX:CompileCommand=dontinline,*TestOpaqueInitializedAssertionPredicateNode::dontInline
* compiler.predicates.assertion.TestOpaqueInitializedAssertionPredicateNode
* @run main/othervm -Xcomp -XX:LoopMaxUnroll=0 -XX:-LoopUnswitching
* -XX:CompileCommand=compileonly,*TestOpaqueInitializedAssertionPredicateNode::test*
* -XX:CompileCommand=dontinline,*TestOpaqueInitializedAssertionPredicateNode::dontInline
* compiler.predicates.assertion.TestOpaqueInitializedAssertionPredicateNode
* @run main/othervm -Xbatch -XX:LoopMaxUnroll=0 -XX:PerMethodTrapLimit=0
* -XX:CompileCommand=compileonly,*TestOpaqueInitializedAssertionPredicateNode::test*
* -XX:CompileCommand=dontinline,*TestOpaqueInitializedAssertionPredicateNode::dontInline
* compiler.predicates.assertion.TestOpaqueInitializedAssertionPredicateNode
*/
/*
* @test id=noflags
* @bug 8330386
* @modules java.base/jdk.internal.misc:+open
* @run main compiler.predicates.assertion.TestOpaqueInitializedAssertionPredicateNode
*/
/*
* @test id=clone_loop_handle_data_uses
* @bug 8333644
* @modules java.base/jdk.internal.misc:+open
* @summary Test that using OpaqueInitializedAssertionPredicate for Initialized Assertion Predicates instead of Opaque4
* nodes also works with clone_loop_handle_data_uses() which missed a case before.
* @run main/othervm -Xcomp -XX:CompileCommand=compileonly,*TestOpaqueInitializedAssertionPredicateNode::test*
* -XX:CompileCommand=dontinline,*TestOpaqueInitializedAssertionPredicateNode::dontInline
* compiler.predicates.assertion.TestOpaqueInitializedAssertionPredicateNode
*/
package compiler.predicates.assertion;
import jdk.internal.misc.Unsafe;
import java.lang.reflect.Field;
public class TestOpaqueInitializedAssertionPredicateNode {
static boolean flag, flag2;
static int iFld;
static long lFld;
static int x;
static int y = 51;
static int iArrLength;
static int[] iArr = new int[100];
static final Unsafe UNSAFE = Unsafe.getUnsafe();
static final long OFFSET;
static {
try {
Field fieldIFld = A.class.getDeclaredField("iFld");
OFFSET = UNSAFE.objectFieldOffset(fieldIFld);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Integer.compareUnsigned(23, 34); // Make sure loaded with -Xcomp.
A a = new A(34);
for (int i = 0; i < 10000; i++) {
iArrLength = i % 15 == 0 ? 30 : 100;
flag = i % 3 == 0;
flag2 = i % 5 == 0;
x = (i % 15 == 0 ? 100 : 0);
testCloneDown();
testOnlyCloneDownCmp();
testCloneDownInsideLoop();
maybeNull(null); // Make sure return value is sometimes null.
testPolicyRangeCheck(a);
testUnsafeAccess(a);
testOpaqueOutsideLoop();
testOpaqueOutsideLoop8333644();
testOpaqueInsideIfOutsideLoop();
}
}
// Profiling will tell us that the return value is sometimes null and sometimes not.
static A maybeNull(Object o) {
return (A)o;
}
static void testCloneDown() {
int a;
int b;
int[] iArr = new int[iArrLength];
for (int i = 2; i < 4; i *= 2) ; // Make sure to run with loop opts.
if (flag) {
a = 34;
} else {
a = 3;
}
// Region to split through
// --- BLOCK start ---
// CMoveI(Bool(CmpU(y, iArr.length))), 34, 23) (**)
if (Integer.compareUnsigned(y, iArr.length) < 0) {
b = 34;
} else {
b = 23;
}
iFld = b; // iFld = CMoveI -> make sure CMoveI is inside BLOCK
// --- BLOCK end ---
if (a > 23) { // If to split -> need to empty BLOCK
iFld = 34;
}
if (flag2) {
// Avoid out-of-bounds access in loop below
return;
}
// When peeling the loop, we create an Initialized Assertion Predicate with the same CmpU as (**) above:
// IAP(CmpU(y, iArr.length))
//
// At Split If: Need to clone CmpU down because it has two uses:
// - Bool of Cmove used in "iFld = b"
// - Bool for IAP
//
// => IAP uses OpaqueInitializedAssertionPredicate -> clone_cmp_down() therefore needs to handle that.
for (int i = y - 1; i < 100; i++) {
iArr[i] = 34; // Hoisted with Loop Predicate
if (flag) { // Reason to peel.
return;
}
}
}
// Same as test() but we only clone down the CmpU and not the Bool with the OpaqueInitializedAssertionPredicate
static void testOnlyCloneDownCmp() {
int a;
int b;
int[] iArr = new int[iArrLength];
for (int i = 2; i < 4; i *= 2) ; // Make sure to run with loop opts.
if (flag) {
a = 34;
} else {
a = 3;
}
// Region to split through
// --- BLOCK start ---
// CMoveI(Bool(CmpU(51, iArr.length))), 34, 23) (**)
// Using constant 51 -> cannot common up with Bool from Initialized Assertion Predicate
if (Integer.compareUnsigned(51, iArr.length) < 0) {
b = 34;
} else {
b = 23;
}
iFld = b; // iFld = CMoveI -> make sure CMoveI is inside BLOCK
// --- BLOCK end ---
if (a > 23) { // If to split -> need to empty BLOCK
iFld = 34;
}
if (flag2) {
// Avoid out-of-bounds access in loop below
return;
}
// When peeling the loop, we create an Initialized Assertion Predicate with the same CmpU as (**) above:
// IAP(CmpU(y, iArr.length))
//
// At Split If: Need to clone CmpU down because it has two uses:
// - Bool of Cmove used in "iFld = b"
// - Bool for IAP
//
// => IAP uses OpaqueInitializedAssertionPredicate -> clone_cmp_down() therefore needs to handle that.
for (int i = 50; i < 100; i++) {
iArr[i] = 34; // Hoisted with Loop Predicate
if (flag) { // Reason to peel.
return;
}
}
}
// Same as test() but everything inside another loop.
static void testCloneDownInsideLoop() {
int a;
int b;
int[] iArr = new int[iArrLength];
for (int i = 3; i < 30; i *= 2) { // Non-counted loop
if (i < 10) {
a = 34;
} else {
a = 3;
}
// Region to split through
// --- BLOCK start ---
// CMoveI(Bool(CmpU(a + i, iArr.length))), 34, 23) (**)
if (Integer.compareUnsigned(a + i, iArr.length) < 0) {
b = 34;
} else {
b = 23;
}
iFld = b; // iFld = CMoveI -> make sure CMoveI is inside BLOCK
// --- BLOCK end ---
if (a > 23) { // If to split -> need to empty BLOCK
iFld = 34;
}
if (i < x) {
// Avoid out-of-bounds access in loop below
return;
}
// When peeling the loop, we create an Initialized Assertion Predicate with the same CmpU as (**) above:
// IAP(CmpU(a + i, iArr.length))
//
// At Split If: Need to clone CmpU down because it has two uses:
// - Bool of Cmove used in "iFld = b"
// - Bool for IAP
//
// => IAP uses OpaqueInitializedAssertionPredicate -> clone_cmp_down() therefore needs to handle that.
for (int j = a + i - 1; j < 100; j++) {
iArr[j] = 34; // Hoisted with Loop Predicate
if (flag) { // Reason to peel.
return;
}
}
}
}
static void testPolicyRangeCheck(Object o) {
int two = 100;
int limit = 2;
for (; limit < 4; limit *= 2);
for (int i = 2; i < limit; i++) {
two = 2;
}
// 4) We call IdealLoopTree::policy_range_check() for this loop:
// - Initialized Assertion Predicate is now part of loop body.
// - Opaque4 node for null-check is also part of loop body.
// We also check the If nodes for these Opaque nodes could be eliminated with
// Range Check Elimination. We thus need to exclude Ifs with
// Opaque4 and OpaqueInitializedAssertionPredicate nodes in policy_range_check().
for (int i = 0; i < 100; i++) {
A a = maybeNull(o); // Profiling tells us that return value *might* be null.
iFld = UNSAFE.getInt(a, OFFSET); // Emits If with Opaque4Node for null check.
// 1) Apply Loop Predication: Loop Predicate + Template Assertion Predicate
// 2) Apply Loop Peeling: Create Initialized Assertion Predicate with
// OpaqueInitializedAssertionPredicate
// 3) After CCP: C2 knows that two == 2. CountedLoopEnd found to be true
// (only execute loop once) -> CountedLoop removed
for (int j = 0; j < two; j++) {
iArr[j] = 34; // Hoisted in Loop Predication
if (flag) {
return;
}
}
}
}
static void testUnsafeAccess(Object o) {
A a = maybeNull(o); // Profiling tells us that return value *might* be null.
iFld = UNSAFE.getInt(a, OFFSET); // Emits If with Opaque4Node for null check.
// We don't have any Parse Predicates with -XX:PerMethodTrapLimit=0. And therefore, If with Opaque4 will
// directly be above CountedLoop. When maximally unrolling the counted loop, we try to update any Assertion
// Predicate. We will find the If with the Opaque4 node for the non-null check which is not an Assertion
// Predicate. This needs to be handled separately in PhaseIdealLoop::update_main_loop_assertion_predicates().
for (int i = 0; i < 10; i++) {
iFld *= 34;
}
}
// [If->OpaqueInitializedAssertionPredicate]->Bool->Cmp, []: Inside Loop, other nodes outside.
static void testOpaqueOutsideLoop() {
int two = 100;
int limit = 2;
for (; limit < 4; limit *= 2);
for (int i = 2; i < limit; i++) {
two = 2;
}
// 4) After CCP, we can apply Loop Peeling since we removed enough nodes to bring the body size down below 255.
// When cloning the Bool for the IAP, we have a use inside the loop (initializedAssertionPredicateBool) and one
// outside for the IAP (input to the OpaqueInitializedAssertionPredicate being outside the loop)
// As a result, we add the OpaqueInitializedAssertionPredicate to the split if set in clone_loop_handle_data_uses().
for (short i = 3; i < 30; i*=2) { // Use short such that we do not need overflow protection for Loop Predicates
if (two == 100) {
// Before CCP: Uninlined method call prevents peeling.
// After CCP: C2 knows that two == 2 and we remove this call which enables Loop Peeling for i-loop.
dontInline();
}
// Same condition as used for IAP in j-loop below.
boolean initializedAssertionPredicateBool = Integer.compareUnsigned(1 + i, iArr.length) < 0;
if (flag) {
// 1) Loop Predicate + Template Assertion Predicate
// 2) Loop Peeling: Create IAP with same condition as initializedAssertionPredicateBool -> can be shared.
// The IAP is on a loop-exit and therefore outside the loop.
// 3) After CCP: C2 knows that two == 2 and loop is removed.
for (short j = 0; j < two; j++) {
iArr[i + j] = 34; // Hoisted in Loop Predication
if (flag2) {
break;
}
}
break;
}
// Use Bool inside i-loop such that when applying Loop Peeling for i-loop, ctrl of Bool is inside loop and
// OpaqueInitializedAssertionPredicate of IAP is outside of i-loop.
if (initializedAssertionPredicateBool) {
iFld = 3;
}
}
}
// Same as testOpaqueOutsideLoop() but we crash later when generating the Mach graph due to wrongly having an If
// with a Phi input instead of: If <- Bool <- CmpU <- [x, Phi]. Found by fuzzing.
static void testOpaqueOutsideLoop8333644() {
int a = 3, b = 7;
boolean bArr[] = new boolean[1];
for (int i = 1; i < 122; i++) {
float f = 1.729F;
while (++a < 7) {
iArr[a] *= lFld;
switch (i) {
case 26:
for (; b < 1; ) {}
case 27:
iArr[1] = 9;
case 28:
break;
case 33:
iArr[1] = a;
break;
case 35:
lFld = b;
break;
default:
;
}
}
}
}
// Similar to testOpaqueOutside loop but Opaque is now also inside loop.
// [If]->OpaqueInitializedAssertionPredicate->Bool->Cmp, []: Inside Loop, other nodes outside.
static void testOpaqueInsideIfOutsideLoop() {
int two = 100;
int limit = 2;
for (; limit < 4; limit *= 2);
for (int i = 2; i < limit; i++) {
two = 2;
}
for (short i = 3; i < 30; i*=2) {
if (two == 100) {
// Before CCP: Uninlined method call prevents peeling.
// After CCP: C2 knows that two == 2 and we remove this call which enables Loop Peeling for i-loop.
dontInline();
}
// 1) Loop Predicate + Template Assertion Predicate
// 2) Loop Peeling: Create IAP with same condition as initializedAssertionPredicateBool -> can be shared.
// The IAP is on a loop-exit and therefore outside the loop.
// 3) After CCP: C2 knows that two == 2 and loop is removed.
for (short j = 0; j < two; j++) {
iArr[i + j] = 34; // Hoisted in Loop Predication
if (flag2) {
break;
}
}
if (flag) {
// Same loop as above. We create the same IAP which can share the same OpaqueInitializedAssertionPredicate.
// Therefore, the OpaqueInitializedAssertionPredicate is inside the loop while this If is outside the loop.
// At Loop Peeling, we clone the Opaque node and create a Phi to merge both loop versions into the IAP If. In
// clone_loop_handle_data_uses(), we add the If for the IAP to the split if set in (). Later, we
// process its input phi with their OpaqueInitializedAssertionPredicate inputs.
for (short j = 0; j < two; j++) {
iArr[i + j] = 34; // Hoisted in Loop Predication
if (flag2) {
break;
}
}
break;
}
}
}
// Not inlined
static void dontInline() {}
static class A {
int iFld;
A(int i) {
this.iFld = i;
}
}
}