8373355: C2: CompileCommand PrintIdealPhase should also print nodes that are not "reachable from below"

Reviewed-by: rcastanedalo, mchevalier, bmaillard
This commit is contained in:
Emanuel Peter 2025-12-16 09:34:42 +00:00
parent 78c2d57259
commit 8402891889
5 changed files with 210 additions and 55 deletions

View File

@ -595,7 +595,9 @@ void Compile::print_ideal_ir(const char* phase_name) {
if (_output == nullptr) {
ss.print_cr("AFTER: %s", phase_name);
// Print out all nodes in ascending order of index.
root()->dump_bfs(MaxNodeLimit, nullptr, "+S$", &ss);
// It is important that we traverse both inputs and outputs of nodes,
// so that we reach all nodes that are connected to Root.
root()->dump_bfs(MaxNodeLimit, nullptr, "-+S$", &ss);
} else {
// Dump the node blockwise if we have a scheduling
_output->print_scheduling(&ss);

View File

@ -63,17 +63,29 @@ public class ModDNodeTests {
Asserts.assertEQ(unusedResultAfterLoopOpt3(1.1d, 2.2d), 0.d);
}
// Note: we used to check for ConD nodes in the IR. But that is a bit brittle:
// Constant nodes can appear during IR transformations, and then lose their outputs.
// During IGNV, the constants stay in the graph even if they lose the inputs. But
// CCP cleans them out because they are not in the useful set. So for now, we do not
// rely on any constant counting, just on counting the operation nodes.
@Test
@IR(failOn = {"drem"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_D, "1"})
@IR(counts = {IRNode.MOD_D, "2"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public double constant() {
// All constants available during parsing
return q % 72.0d % 30.0d;
}
@Test
@IR(failOn = {"drem"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_D, "1"})
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.PHASEIDEALLOOP1) // Only constant fold after some loop opts
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public double alsoConstant() {
// Make sure value is only available after second loop opts round
double val = 0;
@ -86,8 +98,12 @@ public class ModDNodeTests {
}
@Test
@IR(failOn = {"drem"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_D, "1"})
@IR(counts = {IRNode.MOD_D, "2"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "2"},
phase = CompilePhase.PHASEIDEALLOOP1) // Only constant fold after some loop opts
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public double nanLeftConstant() {
// Make sure value is only available after second loop opts round
double val = 134.18d;
@ -100,8 +116,12 @@ public class ModDNodeTests {
}
@Test
@IR(failOn = {"drem"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_D, "1"})
@IR(counts = {IRNode.MOD_D, "2"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "2"},
phase = CompilePhase.PHASEIDEALLOOP1) // Only constant fold after some loop opts
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public double nanRightConstant() {
// Make sure value is only available after second loop opts round
double val = 134.18d;
@ -114,29 +134,41 @@ public class ModDNodeTests {
}
@Test
@IR(counts = {"drem", "1"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_D, "1"})
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {".*CallLeaf.*drem.*", "1"},
phase = CompilePhase.BEFORE_MATCHING) // no constant folding
public double notConstant(double x) {
return x % 32.0d;
}
@Test
@IR(counts = {"drem", "2"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_D, "1"})
@IR(counts = {IRNode.MOD_D, "2"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {".*CallLeaf.*drem.*", "2"},
phase = CompilePhase.BEFORE_MATCHING) // no constant folding
public double veryNotConstant(double x, double y) {
return x % 32.0d % y;
}
@Test
@IR(failOn = IRNode.MOD_D, phase = CompilePhase.ITER_GVN1)
@IR(counts = {IRNode.MOD_D, "1"}, phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "0"},
phase = CompilePhase.ITER_GVN1) // IGVN removes unused nodes
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public void unusedResult(double x, double y) {
double unused = x % y;
}
@Test
@IR(failOn = IRNode.MOD_D, phase = CompilePhase.ITER_GVN1)
@IR(counts = {IRNode.MOD_D, "1"}, phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "0"},
phase = CompilePhase.ITER_GVN1) // IGVN removes unused nodes
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public void repeatedlyUnused(double x, double y) {
double unused = 1.d;
for (int i = 0; i < 100_000; i++) {
@ -149,8 +181,14 @@ public class ModDNodeTests {
// and thus a different execution path. In unusedResultAfterLoopOpt1 the modulo is
// used in the traps of the parse predicates. In unusedResultAfterLoopOpt2, it is not.
@Test
@IR(counts = {IRNode.MOD_D, "1"}, phase = CompilePhase.ITER_GVN2)
@IR(failOn = IRNode.MOD_D, phase = CompilePhase.BEFORE_MACRO_EXPANSION)
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.ITER_GVN2)
@IR(counts = {IRNode.MOD_D, "0"},
phase = CompilePhase.BEFORE_MACRO_EXPANSION)
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public double unusedResultAfterLoopOpt1(double x, double y) {
double unused = x % y;
@ -168,8 +206,14 @@ public class ModDNodeTests {
}
@Test
@IR(counts = {IRNode.MOD_D, "1"}, phase = CompilePhase.AFTER_CLOOPS)
@IR(failOn = IRNode.MOD_D, phase = CompilePhase.PHASEIDEALLOOP1)
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "1"},
phase = CompilePhase.AFTER_CLOOPS)
@IR(counts = {IRNode.MOD_D, "0"},
phase = CompilePhase.PHASEIDEALLOOP1)
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public double unusedResultAfterLoopOpt2(double x, double y) {
int a = 77;
int b = 0;
@ -187,8 +231,14 @@ public class ModDNodeTests {
}
@Test
@IR(counts = {IRNode.MOD_D, "2"}, phase = CompilePhase.AFTER_CLOOPS)
@IR(failOn = IRNode.MOD_D, phase = CompilePhase.PHASEIDEALLOOP1)
@IR(counts = {IRNode.MOD_D, "3"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_D, "2"},
phase = CompilePhase.AFTER_CLOOPS) // drop the useless one
@IR(counts = {IRNode.MOD_D, "0"},
phase = CompilePhase.PHASEIDEALLOOP1) // drop the rest
@IR(counts = {".*CallLeaf.*drem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public double unusedResultAfterLoopOpt3(double x, double y) {
double unused = x % y;

View File

@ -63,17 +63,29 @@ public class ModFNodeTests {
Asserts.assertEQ(unusedResultAfterLoopOpt3(1.1f, 2.2f), 0.f);
}
// Note: we used to check for ConF nodes in the IR. But that is a bit brittle:
// Constant nodes can appear during IR transformations, and then lose their outputs.
// During IGNV, the constants stay in the graph even if they lose the inputs. But
// CCP cleans them out because they are not in the useful set. So for now, we do not
// rely on any constant counting, just on counting the operation nodes.
@Test
@IR(failOn = {"frem"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_F, "1"})
@IR(counts = {IRNode.MOD_F, "2"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public float constant() {
// All constants available during parsing
return q % 72.0f % 30.0f;
}
@Test
@IR(failOn = {"frem"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_F, "1"})
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.PHASEIDEALLOOP1) // Only constant fold after some loop opts
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public float alsoConstant() {
// Make sure value is only available after second loop opts round
float val = 0;
@ -86,8 +98,12 @@ public class ModFNodeTests {
}
@Test
@IR(failOn = {"frem"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_F, "1"})
@IR(counts = {IRNode.MOD_F, "2"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "2"},
phase = CompilePhase.PHASEIDEALLOOP1) // Only constant fold after some loop opts
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public float nanLeftConstant() {
// Make sure value is only available after second loop opts round
float val = 134.18f;
@ -100,8 +116,12 @@ public class ModFNodeTests {
}
@Test
@IR(failOn = {"frem"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_F, "1"})
@IR(counts = {IRNode.MOD_F, "2"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "2"},
phase = CompilePhase.PHASEIDEALLOOP1) // Only constant fold after some loop opts
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public float nanRightConstant() {
// Make sure value is only available after second loop opts round
float val = 134.18f;
@ -114,29 +134,41 @@ public class ModFNodeTests {
}
@Test
@IR(counts = {"frem", "1"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_F, "1"})
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {".*CallLeaf.*frem.*", "1"},
phase = CompilePhase.BEFORE_MATCHING) // no constant folding
public float notConstant(float x) {
return x % 32.0f;
}
@Test
@IR(counts = {"frem", "2"}, phase = CompilePhase.BEFORE_MATCHING)
@IR(counts = {IRNode.CON_F, "1"})
@IR(counts = {IRNode.MOD_F, "2"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {".*CallLeaf.*frem.*", "2"},
phase = CompilePhase.BEFORE_MATCHING) // no constant folding
public float veryNotConstant(float x, float y) {
return x % 32.0f % y;
}
@Test
@IR(failOn = IRNode.MOD_F, phase = CompilePhase.ITER_GVN1)
@IR(counts = {IRNode.MOD_F, "1"}, phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "0"},
phase = CompilePhase.ITER_GVN1) // IGVN removes unused nodes
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public void unusedResult(float x, float y) {
float unused = x % y;
}
@Test
@IR(failOn = IRNode.MOD_F, phase = CompilePhase.ITER_GVN1)
@IR(counts = {IRNode.MOD_F, "1"}, phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "0"},
phase = CompilePhase.ITER_GVN1) // IGVN removes unused nodes
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public void repeatedlyUnused(float x, float y) {
float unused = 1.f;
for (int i = 0; i < 100_000; i++) {
@ -149,8 +181,14 @@ public class ModFNodeTests {
// and thus a different execution path. In unusedResultAfterLoopOpt1 the modulo is
// used in the traps of the parse predicates. In unusedResultAfterLoopOpt2, it is not.
@Test
@IR(counts = {IRNode.MOD_F, "1"}, phase = CompilePhase.ITER_GVN2)
@IR(failOn = IRNode.MOD_F, phase = CompilePhase.BEFORE_MACRO_EXPANSION)
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.ITER_GVN2)
@IR(counts = {IRNode.MOD_F, "0"},
phase = CompilePhase.BEFORE_MACRO_EXPANSION)
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public float unusedResultAfterLoopOpt1(float x, float y) {
float unused = x % y;
@ -168,8 +206,14 @@ public class ModFNodeTests {
}
@Test
@IR(counts = {IRNode.MOD_F, "1"}, phase = CompilePhase.AFTER_CLOOPS)
@IR(failOn = IRNode.MOD_F, phase = CompilePhase.PHASEIDEALLOOP1)
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "1"},
phase = CompilePhase.AFTER_CLOOPS)
@IR(counts = {IRNode.MOD_F, "0"},
phase = CompilePhase.PHASEIDEALLOOP1)
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public float unusedResultAfterLoopOpt2(float x, float y) {
int a = 77;
int b = 0;
@ -187,8 +231,14 @@ public class ModFNodeTests {
}
@Test
@IR(counts = {IRNode.MOD_F, "2"}, phase = CompilePhase.AFTER_CLOOPS)
@IR(failOn = IRNode.MOD_F, phase = CompilePhase.PHASEIDEALLOOP1)
@IR(counts = {IRNode.MOD_F, "3"},
phase = CompilePhase.AFTER_PARSING)
@IR(counts = {IRNode.MOD_F, "2"},
phase = CompilePhase.AFTER_CLOOPS) // drop the useless one
@IR(counts = {IRNode.MOD_F, "0"},
phase = CompilePhase.PHASEIDEALLOOP1) // drop the rest
@IR(counts = {".*CallLeaf.*frem.*", "0"},
phase = CompilePhase.BEFORE_MATCHING)
public float unusedResultAfterLoopOpt3(float x, float y) {
float unused = x % y;

View File

@ -690,16 +690,6 @@ public class IRNode {
beforeMatchingNameRegex(CON_L, "ConL");
}
public static final String CON_D = PREFIX + "CON_D" + POSTFIX;
static {
beforeMatchingNameRegex(CON_D, "ConD");
}
public static final String CON_F = PREFIX + "CON_F" + POSTFIX;
static {
beforeMatchingNameRegex(CON_F, "ConF");
}
public static final String COUNTED_LOOP = PREFIX + "COUNTED_LOOP" + POSTFIX;
static {
String regex = START + "CountedLoop\\b" + MID + END;

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2025, 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.
*/
package ir_framework.tests;
import compiler.lib.ir_framework.*;
/*
* @test
* @bug 8373355
* @summary Test that IR matching happens on the whole graph, not just nodes
* that can be found by traversing up from the Root.
* @library /test/lib /
* @run main ${test.main.class}
*/
public class TestIRFindFromAbove {
public static boolean flag = false;
public static int fld = 0;
public static void main(String[] args) {
TestFramework.run();
}
@Test
@Warmup(0)
// Simulate Xcomp with no warmup: ensure the flag branch is not an unstable if
// but that we compile the infinite loop.
@IR(counts = {IRNode.LOAD_I, "1", IRNode.STORE_I, "1", ".*NeverBranch.*", "0"},
phase = CompilePhase.ITER_GVN1)
@IR(counts = {IRNode.LOAD_I, "1", IRNode.STORE_I, "1", ".*NeverBranch.*", "1"},
phase = CompilePhase.PHASEIDEALLOOP1)
public static void test() {
if (flag) {
// This loop has no exit. So it is at first not connected down to Root.
while (true) {
// During PHASEIDEALLOOP1, we insert a NeverBranch here, with a fake
// exit, that connects the loop down to Root.
fld++; // LoadI and StoreI
}
}
}
}