8378742: C2: constant folding for ModFloatingNode should be done in Value method

Reviewed-by: roland, vlivanov, mchevalier
This commit is contained in:
Guanqiang Han 2026-03-13 14:59:03 +00:00 committed by Marc Chevalier
parent 28830e2155
commit a211b0442d
4 changed files with 99 additions and 69 deletions

View File

@ -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;
}
//=============================================================================

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}