From 84e9264e76ca6e5d984c8eecbf5c5d11128fc174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20H=C3=A4ssig?= Date: Thu, 24 Apr 2025 08:29:08 +0000 Subject: [PATCH] 8346552: C2: Add IR tests to check that Predicate cloning in Loop Unswitching works as expected Co-authored-by: Christian Hagedorn Reviewed-by: chagedorn, epeter --- src/hotspot/share/opto/ifnode.cpp | 4 +- src/hotspot/share/opto/predicates.cpp | 13 +- src/hotspot/share/opto/predicates.hpp | 1 + .../compiler/lib/ir_framework/IRNode.java | 28 ++- .../TestUnswitchPredicateCloning.java | 183 ++++++++++++++++++ 5 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/loopopts/TestUnswitchPredicateCloning.java diff --git a/src/hotspot/share/opto/ifnode.cpp b/src/hotspot/share/opto/ifnode.cpp index 5d349caf103..8d810e4202f 100644 --- a/src/hotspot/share/opto/ifnode.cpp +++ b/src/hotspot/share/opto/ifnode.cpp @@ -2219,13 +2219,13 @@ void ParsePredicateNode::dump_spec(outputStream* st) const { st->print("Loop "); break; case Deoptimization::DeoptReason::Reason_profile_predicate: - st->print("Profiled Loop "); + st->print("Profiled_Loop "); break; case Deoptimization::DeoptReason::Reason_auto_vectorization_check: st->print("Auto_Vectorization_Check "); break; case Deoptimization::DeoptReason::Reason_loop_limit_check: - st->print("Loop Limit Check "); + st->print("Loop_Limit_Check "); break; default: fatal("unknown kind"); diff --git a/src/hotspot/share/opto/predicates.cpp b/src/hotspot/share/opto/predicates.cpp index 90df1ff05b4..78061801e6a 100644 --- a/src/hotspot/share/opto/predicates.cpp +++ b/src/hotspot/share/opto/predicates.cpp @@ -29,6 +29,7 @@ #include "opto/node.hpp" #include "opto/predicates.hpp" #include "opto/rootnode.hpp" +#include "runtime/deoptimization.hpp" // Walk over all Initialized Assertion Predicates and return the entry into the first Initialized Assertion Predicate // (i.e. not belonging to an Initialized Assertion Predicate anymore) @@ -1093,11 +1094,19 @@ CloneUnswitchedLoopPredicatesVisitor::CloneUnswitchedLoopPredicatesVisitor( PhaseIdealLoop* phase) : _clone_predicate_to_true_path_loop(true_path_loop_head, node_in_true_path_loop_body, phase), _clone_predicate_to_false_path_loop(false_path_loop_head, node_in_false_path_loop_body, phase), - _phase(phase) {} + _phase(phase), + _is_counted_loop(true_path_loop_head->is_CountedLoop()) {} -// Keep track of whether we are in the correct Predicate Block where Template Assertion Predicates can be found. // The PredicateIterator will always start at the loop entry and first visits the Loop Limit Check Predicate Block. +// Does not clone a Loop Limit Check Parse Predicate if a counted loop is unswitched, because it most likely will not be +// used anymore (it could only be used when both unswitched loop versions die and the Loop Limit Check Parse Predicate +// ends up at a LoopNode without Loop Limit Check Parse Predicate directly following the unswitched loop that can then +// be speculatively converted to a counted loop - this is rather rare). void CloneUnswitchedLoopPredicatesVisitor::visit(const ParsePredicate& parse_predicate) { + Deoptimization::DeoptReason deopt_reason = parse_predicate.head()->deopt_reason(); + if (_is_counted_loop && deopt_reason == Deoptimization::Reason_loop_limit_check) { + return; + } _clone_predicate_to_true_path_loop.clone_parse_predicate(parse_predicate, false); _clone_predicate_to_false_path_loop.clone_parse_predicate(parse_predicate, true); parse_predicate.kill(_phase->igvn()); diff --git a/src/hotspot/share/opto/predicates.hpp b/src/hotspot/share/opto/predicates.hpp index 5cea5598392..f19b2d73c56 100644 --- a/src/hotspot/share/opto/predicates.hpp +++ b/src/hotspot/share/opto/predicates.hpp @@ -1176,6 +1176,7 @@ class CloneUnswitchedLoopPredicatesVisitor : public PredicateVisitor { ClonePredicateToTargetLoop _clone_predicate_to_false_path_loop; PhaseIdealLoop* const _phase; + const bool _is_counted_loop; public: CloneUnswitchedLoopPredicatesVisitor(LoopNode* true_path_loop_head, diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index e7340dd0d8f..0b224523561 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -1470,6 +1470,11 @@ public class IRNode { optoOnly(OOPMAP_WITH, regex); } + public static final String OPAQUE_TEMPLATE_ASSERTION_PREDICATE = PREFIX + "OPAQUE_TEMPLATE_ASSERTION_PREDICATE" + POSTFIX; + static { + duringLoopOpts(OPAQUE_TEMPLATE_ASSERTION_PREDICATE, "OpaqueTemplateAssertionPredicate"); + } + public static final String OR_I = PREFIX + "OR_I" + POSTFIX; static { beforeMatchingNameRegex(OR_I, "OrI"); @@ -1576,12 +1581,17 @@ public class IRNode { public static final String LOOP_LIMIT_CHECK_PARSE_PREDICATE = PREFIX + "LOOP_LIMIT_CHECK_PARSE_PREDICATE" + POSTFIX; static { - parsePredicateNodes(LOOP_LIMIT_CHECK_PARSE_PREDICATE, "Loop Limit Check"); + parsePredicateNodes(LOOP_LIMIT_CHECK_PARSE_PREDICATE, "Loop_Limit_Check"); } public static final String PROFILED_LOOP_PARSE_PREDICATE = PREFIX + "PROFILED_LOOP_PARSE_PREDICATE" + POSTFIX; static { - parsePredicateNodes(PROFILED_LOOP_PARSE_PREDICATE, "Profiled Loop"); + parsePredicateNodes(PROFILED_LOOP_PARSE_PREDICATE, "Profiled_Loop"); + } + + public static final String AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE = PREFIX + "AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE" + POSTFIX; + static { + parsePredicateNodes(AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE, "Auto_Vectorization_Check"); } public static final String PREDICATE_TRAP = PREFIX + "PREDICATE_TRAP" + POSTFIX; @@ -2826,16 +2836,26 @@ public class IRNode { CompilePhase.BEFORE_MATCHING)); } + /** + * Apply {@code regex} on all ideal graph phases starting from {@link CompilePhase#BEFORE_LOOP_OPTS} + * up to and including {@link CompilePhase#AFTER_LOOP_OPTS}. + */ + private static void duringLoopOpts(String irNodePlaceholder, String regex) { + IR_NODE_MAPPINGS.put(irNodePlaceholder, new SinglePhaseRangeEntry(CompilePhase.AFTER_LOOP_OPTS, regex, + CompilePhase.BEFORE_LOOP_OPTS, + CompilePhase.AFTER_LOOP_OPTS)); + } + private static void trapNodes(String irNodePlaceholder, String trapReason) { String regex = START + "CallStaticJava" + MID + "uncommon_trap.*" + trapReason + END; beforeMatching(irNodePlaceholder, regex); } private static void parsePredicateNodes(String irNodePlaceholder, String label) { - String regex = START + "ParsePredicate" + MID + "#" + label + "[ ]*!jvms:" + END; + String regex = START + "ParsePredicate" + MID + "#" + label + " " + END; IR_NODE_MAPPINGS.put(irNodePlaceholder, new SinglePhaseRangeEntry(CompilePhase.AFTER_PARSING, regex, CompilePhase.AFTER_PARSING, - CompilePhase.PHASEIDEALLOOP_ITERATIONS)); + CompilePhase.AFTER_LOOP_OPTS)); } private static void loadOfNodes(String irNodePlaceholder, String irNodeRegex) { diff --git a/test/hotspot/jtreg/compiler/loopopts/TestUnswitchPredicateCloning.java b/test/hotspot/jtreg/compiler/loopopts/TestUnswitchPredicateCloning.java new file mode 100644 index 00000000000..01a438595de --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestUnswitchPredicateCloning.java @@ -0,0 +1,183 @@ +/* + * 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 compiler.loopopts; + +import compiler.lib.ir_framework.*; +import java.util.Random; +import jdk.test.lib.Utils; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8346552 + * @summary Test that all parse predicates are cloned after loop unswitching. + * @key randomness + * @library /test/lib / + * @run driver compiler.loopopts.TestUnswitchPredicateCloning + */ + +public class TestUnswitchPredicateCloning { + static final int SIZE = 100; + + private static final Random random = Utils.getRandomInstance(); + + public static void main(String[] strArr) { + TestFramework.run(); + } + + @Run(test = {"testUnswitchingBeforePredication", "testPredicationBeforeUnswitching", "testUnswitchingUncounted"}) + @Warmup(0) + private static void runNoWarmup() { + final int idx = random.nextInt(SIZE); + final boolean cond = random.nextBoolean(); + int res = testUnswitchingBeforePredication(idx); + Asserts.assertEQ(SIZE * idx, res); + res = testPredicationBeforeUnswitching(idx, cond); + Asserts.assertEQ((SIZE * (SIZE - 1)) / 2 + (cond ? SIZE * idx : 0), res); + res = testUnswitchingUncounted(cond); + Asserts.assertEQ((SIZE * (SIZE - 1)) / 2 + (cond ? SIZE : 0), res); + } + + @DontInline + private static int[] getArr() { + int[] arr = new int[SIZE]; + for (int i = 0; i < SIZE; i++) { + arr[i] = i; + } + return arr; + } + + @Test + // Check that Loop Unswitching doubled the number of Parse Predicates: We have + // them at the true- and false-path-loop. Note that the Loop Limit Check Parse + // Predicate is not cloned when we already have a counted loop. + @IR(counts = { IRNode.LOOP_PARSE_PREDICATE, "3", + IRNode.PROFILED_LOOP_PARSE_PREDICATE, "3", + IRNode.LOOP_LIMIT_CHECK_PARSE_PREDICATE, "3", + IRNode.AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE, "3" }, + phase = CompilePhase.BEFORE_LOOP_UNSWITCHING) + // Since we know that Loop Predication happens after Loop Unswitching, we can test the + // have already been removed in the beautify loop phase. + @IR(counts = { IRNode.LOOP_PARSE_PREDICATE, "4", + IRNode.PROFILED_LOOP_PARSE_PREDICATE, "4", + IRNode.LOOP_LIMIT_CHECK_PARSE_PREDICATE, "3", + IRNode.AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE, "4" }, + phase = CompilePhase.BEFORE_LOOP_PREDICATION_RC) + // Check that Opaque Template Assertion Predicates are added in Loop Predication + // even if Loop Predication only happens after Loop Unswitching. + @IR(failOn = { IRNode.OPAQUE_TEMPLATE_ASSERTION_PREDICATE }, + phase = CompilePhase.AFTER_LOOP_UNSWITCHING) + @IR(counts = { IRNode.OPAQUE_TEMPLATE_ASSERTION_PREDICATE, "2" }, + phase = CompilePhase.AFTER_LOOP_PREDICATION_RC) + static int testUnswitchingBeforePredication(int j) { + int zero = 34; + int limit = 2; + + // Ensure zero == 0 is only known after CCP + for (; limit < 4; limit *= 2) { + } + for (int i = 2; i < limit; i++) { + zero = 0; + } + + int[] arr = getArr(); + int res = 0; + for (int i = 0; i < arr.length; i++) { + // Trigger unswitching only after CCP + if (zero == 0) { + // Trigger range check after loop unswitching + res += arr[j]; + } else { + res += arr[i]; + } + } + + return res; + } + + @Test + // Check that Loop Unswitching doubled the number of Parse and Template + // Assertion Predicates. Again, the Loop Limit Check Parse Predicate + // remains at the Loop Selector since this is a counted loop. + @IR(failOn = { IRNode.OPAQUE_TEMPLATE_ASSERTION_PREDICATE }, + phase = CompilePhase.BEFORE_LOOP_PREDICATION_RC) + @IR(counts = { IRNode.OPAQUE_TEMPLATE_ASSERTION_PREDICATE, "2", + IRNode.LOOP_PARSE_PREDICATE, "1", + IRNode.PROFILED_LOOP_PARSE_PREDICATE, "1", + IRNode.LOOP_LIMIT_CHECK_PARSE_PREDICATE, "1", + IRNode.AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE, "1" }, + phase = CompilePhase.BEFORE_LOOP_UNSWITCHING) + // After Loop Unswitching and after removing the killed predicates. + @IR(counts = { IRNode.OPAQUE_TEMPLATE_ASSERTION_PREDICATE, "4", + IRNode.LOOP_PARSE_PREDICATE, "2", + IRNode.PROFILED_LOOP_PARSE_PREDICATE, "2", + IRNode.LOOP_LIMIT_CHECK_PARSE_PREDICATE, "1", + IRNode.AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE, "2" }, + phase = CompilePhase.PHASEIDEALLOOP2) + static int testPredicationBeforeUnswitching(int j, boolean cond) { + int[] arr = getArr(); + int res = 0; + for (int i = 0; i < arr.length; i++) { + if (cond) { + res += arr[j]; + } + res += arr[i]; + } + return res; + } + + @Test + // Check that Loop Unswitching doubled the number of all Parse Predicates. + // Since this is not counted loop, the Loop Limit Check Parse Predicate + // has to be cloned to both unswitched loops. + @IR(counts = { IRNode.LOOP_PARSE_PREDICATE, "1", + IRNode.PROFILED_LOOP_PARSE_PREDICATE, "1", + IRNode.LOOP_LIMIT_CHECK_PARSE_PREDICATE, "1", + IRNode.AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE, "1" }, + phase = CompilePhase.BEFORE_LOOP_UNSWITCHING) + // After Loop Unswitching and after removing the killed predicates all + // Parse Predicates are doubled. + @IR(counts = { IRNode.LOOP_PARSE_PREDICATE, "2", + IRNode.PROFILED_LOOP_PARSE_PREDICATE, "2", + IRNode.LOOP_LIMIT_CHECK_PARSE_PREDICATE, "2", + IRNode.AUTO_VECTORIZATION_CHECK_PARSE_PREDICATE, "2" }, + failOn = { IRNode.COUNTED_LOOP }, + phase = CompilePhase.PHASEIDEALLOOP1) + @IR(failOn = { IRNode.COUNTED_LOOP }) + static int testUnswitchingUncounted(boolean cond) { + int[] arr = getArr(); + int res = 0; + int i = 0; + while (i < arr.length) { + if (cond) { + res += 1; + } + res += arr[i]; + + i = arr[i] + 1; // effectively i += 1, but don't tell the compiler! + } + + return res; + } +} \ No newline at end of file