From a211b0442dc292c4aa6c98d4da03ceb19528dd2d Mon Sep 17 00:00:00 2001 From: Guanqiang Han Date: Fri, 13 Mar 2026 14:59:03 +0000 Subject: [PATCH] 8378742: C2: constant folding for ModFloatingNode should be done in Value method Reviewed-by: roland, vlivanov, mchevalier --- src/hotspot/share/opto/divnode.cpp | 54 +++++++----------- src/hotspot/share/opto/divnode.hpp | 4 +- .../compiler/c2/irTests/ModDNodeTests.java | 54 +++++++++++++----- .../compiler/c2/irTests/ModFNodeTests.java | 56 +++++++++++++------ 4 files changed, 99 insertions(+), 69 deletions(-) diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index ed72d8a11cf..c38e784a7af 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1592,41 +1592,25 @@ const Type* ModDNode::get_result_if_constant(const Type* dividend, const Type* d return TypeD::make(jdouble_cast(xr)); } -Node* ModFloatingNode::Ideal(PhaseGVN* phase, bool can_reshape) { - if (can_reshape) { - PhaseIterGVN* igvn = phase->is_IterGVN(); - - // Either input is TOP ==> the result is TOP - const Type* dividend_type = phase->type(dividend()); - const Type* divisor_type = phase->type(divisor()); - if (dividend_type == Type::TOP || divisor_type == Type::TOP) { - return phase->C->top(); - } - const Type* constant_result = get_result_if_constant(dividend_type, divisor_type); - if (constant_result != nullptr) { - return make_tuple_of_input_state_and_constant_result(igvn, constant_result); - } +const Type* ModFloatingNode::Value(PhaseGVN* phase) const { + const Type* t = CallLeafPureNode::Value(phase); + if (t == Type::TOP) { return Type::TOP; } + const Type* dividend_type = phase->type(dividend()); + const Type* divisor_type = phase->type(divisor()); + if (dividend_type == Type::TOP || divisor_type == Type::TOP) { + return Type::TOP; } - - return CallLeafPureNode::Ideal(phase, can_reshape); -} - -/* Give a tuple node for ::Ideal to return, made of the input state (control to return addr) - * and the given constant result. Idealization of projections will make sure to transparently - * propagate the input state and replace the result by the said constant. - */ -TupleNode* ModFloatingNode::make_tuple_of_input_state_and_constant_result(PhaseIterGVN* phase, const Type* con) const { - Node* con_node = phase->makecon(con); - TupleNode* tuple = TupleNode::make( - tf()->range(), - in(TypeFunc::Control), - in(TypeFunc::I_O), - in(TypeFunc::Memory), - in(TypeFunc::FramePtr), - in(TypeFunc::ReturnAdr), - con_node); - - return tuple; + const Type* constant_result = get_result_if_constant(dividend_type, divisor_type); + if (constant_result != nullptr) { + const TypeTuple* tt = t->is_tuple(); + uint cnt = tt->cnt(); + uint param_cnt = cnt - TypeFunc::Parms; + const Type** fields = TypeTuple::fields(param_cnt); + fields[TypeFunc::Parms] = constant_result; + if (param_cnt > 1) { fields[TypeFunc::Parms + 1] = Type::HALF; } + return TypeTuple::make(cnt, fields); + } + return t; } //============================================================================= diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index 43432b271a4..2598429716f 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -175,8 +175,6 @@ public: // Base class for float and double modulus class ModFloatingNode : public CallLeafPureNode { - TupleNode* make_tuple_of_input_state_and_constant_result(PhaseIterGVN* phase, const Type* con) const; - protected: virtual Node* dividend() const = 0; virtual Node* divisor() const = 0; @@ -184,7 +182,7 @@ protected: public: ModFloatingNode(Compile* C, const TypeFunc* tf, address addr, const char* name); - Node* Ideal(PhaseGVN* phase, bool can_reshape) override; + const Type* Value(PhaseGVN* phase) const override; }; // Float Modulus diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ModDNodeTests.java b/test/hotspot/jtreg/compiler/c2/irTests/ModDNodeTests.java index 6b20b782923..ac427ea3831 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/ModDNodeTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/ModDNodeTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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 @@ -29,7 +29,7 @@ import compiler.lib.ir_framework.*; /* * @test - * @bug 8345766 + * @bug 8345766 8378742 * @key randomness * @summary Test that Ideal transformations of ModDNode are being performed as expected. * @library /test/lib / @@ -37,6 +37,7 @@ import compiler.lib.ir_framework.*; */ public class ModDNodeTests { public static final double q = Utils.getRandomInstance().nextDouble() * 100.0d; + public static volatile int volatileField; public static void main(String[] args) { TestFramework.run(); @@ -48,6 +49,7 @@ public class ModDNodeTests { "unusedResultAfterLoopOpt1", "unusedResultAfterLoopOpt2", "unusedResultAfterLoopOpt3", + "constantFoldInCCP" }) public void runMethod() { Asserts.assertEQ(constant(), q % 72.0d % 30.0d); @@ -61,6 +63,7 @@ public class ModDNodeTests { Asserts.assertEQ(unusedResultAfterLoopOpt1(1.1d, 2.2d), 0.d); Asserts.assertEQ(unusedResultAfterLoopOpt2(1.1d, 2.2d), 0.d); Asserts.assertEQ(unusedResultAfterLoopOpt3(1.1d, 2.2d), 0.d); + Asserts.assertEQ(constantFoldInCCP(), 4.0d); } // Note: we used to check for ConD nodes in the IR. But that is a bit brittle: @@ -72,7 +75,7 @@ public class ModDNodeTests { @Test @IR(counts = {IRNode.MOD_D, "2"}, phase = CompilePhase.AFTER_PARSING) - @IR(counts = {".*CallLeaf.*drem.*", "0"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public double constant() { // All constants available during parsing @@ -84,7 +87,7 @@ public class ModDNodeTests { 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"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public double alsoConstant() { // Make sure value is only available after second loop opts round @@ -102,7 +105,7 @@ public class ModDNodeTests { 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"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public double nanLeftConstant() { // Make sure value is only available after second loop opts round @@ -120,7 +123,7 @@ public class ModDNodeTests { 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"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public double nanRightConstant() { // Make sure value is only available after second loop opts round @@ -156,7 +159,7 @@ public class ModDNodeTests { phase = CompilePhase.AFTER_PARSING) @IR(counts = {IRNode.MOD_D, "0"}, phase = CompilePhase.ITER_GVN1) // IGVN removes unused nodes - @IR(counts = {".*CallLeaf.*drem.*", "0"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public void unusedResult(double x, double y) { double unused = x % y; @@ -165,9 +168,9 @@ public class ModDNodeTests { @Test @IR(counts = {IRNode.MOD_D, "1"}, phase = CompilePhase.AFTER_PARSING) - @IR(counts = {IRNode.MOD_D, "0"}, + @IR(failOn = {IRNode.MOD_D}, phase = CompilePhase.ITER_GVN1) // IGVN removes unused nodes - @IR(counts = {".*CallLeaf.*drem.*", "0"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public void repeatedlyUnused(double x, double y) { double unused = 1.d; @@ -185,9 +188,9 @@ public class ModDNodeTests { phase = CompilePhase.AFTER_PARSING) @IR(counts = {IRNode.MOD_D, "1"}, phase = CompilePhase.ITER_GVN2) - @IR(counts = {IRNode.MOD_D, "0"}, + @IR(failOn = {IRNode.MOD_D}, phase = CompilePhase.BEFORE_MACRO_EXPANSION) - @IR(counts = {".*CallLeaf.*drem.*", "0"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public double unusedResultAfterLoopOpt1(double x, double y) { double unused = x % y; @@ -210,9 +213,9 @@ public class ModDNodeTests { phase = CompilePhase.AFTER_PARSING) @IR(counts = {IRNode.MOD_D, "1"}, phase = CompilePhase.AFTER_CLOOPS) - @IR(counts = {IRNode.MOD_D, "0"}, + @IR(failOn = {IRNode.MOD_D}, phase = CompilePhase.PHASEIDEALLOOP1) - @IR(counts = {".*CallLeaf.*drem.*", "0"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public double unusedResultAfterLoopOpt2(double x, double y) { int a = 77; @@ -235,9 +238,9 @@ public class ModDNodeTests { phase = CompilePhase.AFTER_PARSING) @IR(counts = {IRNode.MOD_D, "2"}, phase = CompilePhase.AFTER_CLOOPS) // drop the useless one - @IR(counts = {IRNode.MOD_D, "0"}, + @IR(failOn = {IRNode.MOD_D}, phase = CompilePhase.PHASEIDEALLOOP1) // drop the rest - @IR(counts = {".*CallLeaf.*drem.*", "0"}, + @IR(failOn = {".*CallLeaf.*drem.*"}, phase = CompilePhase.BEFORE_MATCHING) public double unusedResultAfterLoopOpt3(double x, double y) { double unused = x % y; @@ -252,4 +255,25 @@ public class ModDNodeTests { int other = (b - 77) * (int)(x % y % 1.d); return (double)other; } + + @Test + @IR(failOn = {IRNode.CMP_D}, + phase = CompilePhase.CCP1) + @IR(failOn = {IRNode.MOD_D}, + phase = CompilePhase.BEFORE_MACRO_EXPANSION) + public double constantFoldInCCP(){ + int i; + for (i = 2; i < 4; i *= 2) { + } + int j; + for (j = 2; j < 4; j *= 2) { + } + volatileField = 42; + double v1 = (double) i / 2; + double v2 = j; + double v = v1 % v2; + for (; v < v2; v *= 2) { + } + return v; + } } diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ModFNodeTests.java b/test/hotspot/jtreg/compiler/c2/irTests/ModFNodeTests.java index 2f69578c2f0..f703df3d04e 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/ModFNodeTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/ModFNodeTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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 @@ -29,7 +29,7 @@ import compiler.lib.ir_framework.*; /* * @test - * @bug 8345766 + * @bug 8345766 8378742 * @key randomness * @summary Test that Ideal transformations of ModFNode are being performed as expected. * @library /test/lib / @@ -37,6 +37,7 @@ import compiler.lib.ir_framework.*; */ public class ModFNodeTests { public static final float q = Utils.getRandomInstance().nextFloat() * 100.0f; + public static volatile int volatileField; public static void main(String[] args) { TestFramework.run(); @@ -48,6 +49,7 @@ public class ModFNodeTests { "unusedResultAfterLoopOpt1", "unusedResultAfterLoopOpt2", "unusedResultAfterLoopOpt3", + "constantFoldInCCP" }) public void runMethod() { Asserts.assertEQ(constant(), q % 72.0f % 30.0f); @@ -61,6 +63,7 @@ public class ModFNodeTests { Asserts.assertEQ(unusedResultAfterLoopOpt1(1.1f, 2.2f), 0.f); Asserts.assertEQ(unusedResultAfterLoopOpt2(1.1f, 2.2f), 0.f); Asserts.assertEQ(unusedResultAfterLoopOpt3(1.1f, 2.2f), 0.f); + Asserts.assertEQ(constantFoldInCCP(), 4.0f); } // Note: we used to check for ConF nodes in the IR. But that is a bit brittle: @@ -72,7 +75,7 @@ public class ModFNodeTests { @Test @IR(counts = {IRNode.MOD_F, "2"}, phase = CompilePhase.AFTER_PARSING) - @IR(counts = {".*CallLeaf.*frem.*", "0"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public float constant() { // All constants available during parsing @@ -84,7 +87,7 @@ public class ModFNodeTests { 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"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public float alsoConstant() { // Make sure value is only available after second loop opts round @@ -102,7 +105,7 @@ public class ModFNodeTests { 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"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public float nanLeftConstant() { // Make sure value is only available after second loop opts round @@ -120,7 +123,7 @@ public class ModFNodeTests { 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"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public float nanRightConstant() { // Make sure value is only available after second loop opts round @@ -154,9 +157,9 @@ public class ModFNodeTests { @Test @IR(counts = {IRNode.MOD_F, "1"}, phase = CompilePhase.AFTER_PARSING) - @IR(counts = {IRNode.MOD_F, "0"}, + @IR(failOn = {IRNode.MOD_F}, phase = CompilePhase.ITER_GVN1) // IGVN removes unused nodes - @IR(counts = {".*CallLeaf.*frem.*", "0"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public void unusedResult(float x, float y) { float unused = x % y; @@ -165,9 +168,9 @@ public class ModFNodeTests { @Test @IR(counts = {IRNode.MOD_F, "1"}, phase = CompilePhase.AFTER_PARSING) - @IR(counts = {IRNode.MOD_F, "0"}, + @IR(failOn = {IRNode.MOD_F}, phase = CompilePhase.ITER_GVN1) // IGVN removes unused nodes - @IR(counts = {".*CallLeaf.*frem.*", "0"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public void repeatedlyUnused(float x, float y) { float unused = 1.f; @@ -185,9 +188,9 @@ public class ModFNodeTests { phase = CompilePhase.AFTER_PARSING) @IR(counts = {IRNode.MOD_F, "1"}, phase = CompilePhase.ITER_GVN2) - @IR(counts = {IRNode.MOD_F, "0"}, + @IR(failOn = {IRNode.MOD_F}, phase = CompilePhase.BEFORE_MACRO_EXPANSION) - @IR(counts = {".*CallLeaf.*frem.*", "0"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public float unusedResultAfterLoopOpt1(float x, float y) { float unused = x % y; @@ -210,9 +213,9 @@ public class ModFNodeTests { phase = CompilePhase.AFTER_PARSING) @IR(counts = {IRNode.MOD_F, "1"}, phase = CompilePhase.AFTER_CLOOPS) - @IR(counts = {IRNode.MOD_F, "0"}, + @IR(failOn = {IRNode.MOD_F}, phase = CompilePhase.PHASEIDEALLOOP1) - @IR(counts = {".*CallLeaf.*frem.*", "0"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public float unusedResultAfterLoopOpt2(float x, float y) { int a = 77; @@ -235,9 +238,9 @@ public class ModFNodeTests { phase = CompilePhase.AFTER_PARSING) @IR(counts = {IRNode.MOD_F, "2"}, phase = CompilePhase.AFTER_CLOOPS) // drop the useless one - @IR(counts = {IRNode.MOD_F, "0"}, + @IR(failOn = {IRNode.MOD_F}, phase = CompilePhase.PHASEIDEALLOOP1) // drop the rest - @IR(counts = {".*CallLeaf.*frem.*", "0"}, + @IR(failOn = {".*CallLeaf.*frem.*"}, phase = CompilePhase.BEFORE_MATCHING) public float unusedResultAfterLoopOpt3(float x, float y) { float unused = x % y; @@ -252,4 +255,25 @@ public class ModFNodeTests { int other = (b - 77) * (int)(x % y % 1.f); return (float)other; } + + @Test + @IR(failOn = {IRNode.CMP_F}, + phase = CompilePhase.CCP1) + @IR(failOn = {IRNode.MOD_F}, + phase = CompilePhase.BEFORE_MACRO_EXPANSION) + public float constantFoldInCCP(){ + int i; + for (i = 2; i < 4; i *= 2) { + } + int j; + for (j = 2; j < 4; j *= 2) { + } + volatileField = 42; + float v1 = (float) i / 2; + float v2 = j; + float v = v1 % v2; + for (; v < v2; v *= 2) { + } + return v; + } }