java-25-openjdk/jdk8378713-c2_missing_pow_constant_folding.patch
2026-07-15 14:45:51 +01:00

627 lines
24 KiB
Diff

From 1f61b6754b5abce07db45fd385a19c2cf9296a08 Mon Sep 17 00:00:00 2001
From: Kangcheng Xu <kxu@redhat.com>
Date: Mon, 27 Apr 2026 10:14:39 -0400
Subject: [PATCH 2/2] Backport 7f631ea958e30246b927f3524f0fbf37334422b9
---
src/hotspot/share/opto/callnode.cpp | 175 ++++++++++++++
src/hotspot/share/opto/callnode.hpp | 17 ++
src/hotspot/share/opto/classes.hpp | 1 +
src/hotspot/share/opto/library_call.cpp | 62 +----
src/hotspot/share/opto/macro.cpp | 19 +-
.../intrinsics/math/PowDNodeTests.java | 218 ++++++++++++++++++
.../compiler/lib/ir_framework/IRNode.java | 11 +
7 files changed, 437 insertions(+), 66 deletions(-)
create mode 100644 test/hotspot/jtreg/compiler/intrinsics/math/PowDNodeTests.java
diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp
index fc4b0c35dff..cb6cc2082b3 100644
--- a/src/hotspot/share/opto/callnode.cpp
+++ b/src/hotspot/share/opto/callnode.cpp
@@ -42,6 +42,7 @@
#include "opto/rootnode.hpp"
#include "opto/runtime.hpp"
#include "runtime/sharedRuntime.hpp"
+#include "runtime/stubRoutines.hpp"
#include "utilities/powerOfTwo.hpp"
#include "code/vmreg.hpp"
@@ -1354,6 +1355,25 @@ TupleNode* CallLeafPureNode::make_tuple_of_input_state_and_top_return_values(con
return tuple;
}
+CallLeafPureNode* CallLeafPureNode::inline_call_leaf_pure_node(Node* control) const {
+ Node* top = Compile::current()->top();
+ if (control == nullptr) {
+ control = in(TypeFunc::Control);
+ }
+
+ CallLeafPureNode* call = new CallLeafPureNode(tf(), entry_point(), _name, nullptr);
+ call->init_req(TypeFunc::Control, control);
+ call->init_req(TypeFunc::I_O, top);
+ call->init_req(TypeFunc::Memory, top);
+ call->init_req(TypeFunc::ReturnAdr, top);
+ call->init_req(TypeFunc::FramePtr, top);
+ for (unsigned int i = 0; i < tf()->domain()->cnt() - TypeFunc::Parms; i++) {
+ call->init_req(TypeFunc::Parms + i, in(TypeFunc::Parms + i));
+ }
+
+ return call;
+}
+
Node* CallLeafPureNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (is_dead()) {
return nullptr;
@@ -2424,3 +2444,158 @@ bool CallNode::may_modify_arraycopy_helper(const TypeOopPtr* dest_t, const TypeO
return true;
}
+
+PowDNode::PowDNode(Compile* C, Node* base, Node* exp)
+ : CallLeafPureNode(
+ OptoRuntime::Math_DD_D_Type(),
+ StubRoutines::dpow() != nullptr ? StubRoutines::dpow() : CAST_FROM_FN_PTR(address, SharedRuntime::dpow),
+ "pow",
+ nullptr) {
+ add_flag(Flag_is_macro);
+ C->add_macro_node(this);
+
+ init_req(TypeFunc::Parms + 0, base);
+ init_req(TypeFunc::Parms + 1, C->top()); // double slot padding
+ init_req(TypeFunc::Parms + 2, exp);
+ init_req(TypeFunc::Parms + 3, C->top()); // double slot padding
+}
+
+const Type* PowDNode::Value(PhaseGVN* phase) const {
+ const Type* t_base = phase->type(base());
+ const Type* t_exp = phase->type(exp());
+
+ if (t_base == Type::TOP || t_exp == Type::TOP) {
+ return Type::TOP;
+ }
+
+ const TypeD* base_con = t_base->isa_double_constant();
+ const TypeD* exp_con = t_exp->isa_double_constant();
+ const TypeD* result_t = nullptr;
+
+ // constant folding: both inputs are constants
+ if (base_con != nullptr && exp_con != nullptr) {
+ result_t = TypeD::make(SharedRuntime::dpow(base_con->getd(), exp_con->getd()));
+ }
+
+ // Special cases when only the exponent is known:
+ if (exp_con != nullptr) {
+ double e = exp_con->getd();
+
+ // If the second argument is positive or negative zero, then the result is 1.0.
+ // i.e., pow(x, +/-0.0D) => 1.0
+ if (e == 0.0) { // true for both -0.0 and +0.0
+ result_t = TypeD::ONE;
+ }
+
+ // If the second argument is NaN, then the result is NaN.
+ // i.e., pow(x, NaN) => NaN
+ if (g_isnan(e)) {
+ result_t = TypeD::make(NAN);
+ }
+ }
+
+ if (result_t != nullptr) {
+ // We can't simply return a TypeD here, it must be a tuple type to be compatible with call nodes.
+ const Type** fields = TypeTuple::fields(2);
+ fields[TypeFunc::Parms + 0] = result_t;
+ fields[TypeFunc::Parms + 1] = Type::HALF;
+ return TypeTuple::make(TypeFunc::Parms + 2, fields);
+ }
+
+ return tf()->range();
+}
+
+Node* PowDNode::Ideal(PhaseGVN* phase, bool can_reshape) {
+ if (!can_reshape) {
+ return nullptr; // wait for igvn
+ }
+
+ PhaseIterGVN* igvn = phase->is_IterGVN();
+ Node* base = this->base();
+ Node* exp = this->exp();
+
+ const Type* t_exp = phase->type(exp);
+ const TypeD* exp_con = t_exp->isa_double_constant();
+
+ // Special cases when only the exponent is known:
+ if (exp_con != nullptr) {
+ double e = exp_con->getd();
+
+ // If the second argument is 1.0, then the result is the same as the first argument.
+ // i.e., pow(x, 1.0) => x
+ if (e == 1.0) {
+ return make_tuple_of_input_state_and_result(igvn, base);
+ }
+
+ // If the second argument is 2.0, then strength reduce to multiplications.
+ // i.e., pow(x, 2.0) => x * x
+ if (e == 2.0) {
+ Node* mul = igvn->transform(new MulDNode(base, base));
+ return make_tuple_of_input_state_and_result(igvn, mul);
+ }
+
+ // If the second argument is 0.5, the strength reduce to square roots.
+ // i.e., pow(x, 0.5) => sqrt(x) iff x > 0
+ if (e == 0.5 && Matcher::match_rule_supported(Op_SqrtD)) {
+ Node* ctrl = in(TypeFunc::Control);
+ Node* zero = igvn->zerocon(T_DOUBLE);
+
+ // According to the API specs, pow(-0.0, 0.5) = 0.0 and sqrt(-0.0) = -0.0.
+ // So pow(-0.0, 0.5) shouldn't be replaced with sqrt(-0.0).
+ // -0.0/+0.0 are both excluded since floating-point comparison doesn't distinguish -0.0 from +0.0.
+ Node* cmp = igvn->register_new_node_with_optimizer(new CmpDNode(base, zero));
+ Node* test = igvn->register_new_node_with_optimizer(new BoolNode(cmp, BoolTest::le));
+
+ IfNode* iff = new IfNode(ctrl, test, PROB_UNLIKELY_MAG(3), COUNT_UNKNOWN);
+ igvn->register_new_node_with_optimizer(iff);
+ Node* if_slow = igvn->register_new_node_with_optimizer(new IfTrueNode(iff)); // x <= 0
+ Node* if_fast = igvn->register_new_node_with_optimizer(new IfFalseNode(iff)); // x > 0
+
+ // slow path: call pow(x, 0.5)
+ Node* call = igvn->register_new_node_with_optimizer(inline_call_leaf_pure_node(if_slow));
+ Node* call_ctrl = igvn->register_new_node_with_optimizer(new ProjNode(call, TypeFunc::Control));
+ Node* call_result = igvn->register_new_node_with_optimizer(new ProjNode(call, TypeFunc::Parms + 0));
+
+ // fast path: sqrt(x)
+ Node* sqrt = igvn->register_new_node_with_optimizer(new SqrtDNode(igvn->C, if_fast, base));
+
+ // merge paths
+ RegionNode* region = new RegionNode(3);
+ igvn->register_new_node_with_optimizer(region);
+ region->init_req(1, call_ctrl); // slow path
+ region->init_req(2, if_fast); // fast path
+
+ PhiNode* phi = new PhiNode(region, Type::DOUBLE);
+ igvn->register_new_node_with_optimizer(phi);
+ phi->init_req(1, call_result); // slow: pow() result
+ phi->init_req(2, sqrt); // fast: sqrt() result
+
+ igvn->C->set_has_split_ifs(true); // Has chance for split-if optimization
+
+ return make_tuple_of_input_state_and_result(igvn, phi, region);
+ }
+ }
+
+ return CallLeafPureNode::Ideal(phase, can_reshape);
+}
+
+// We can't simply have Ideal() returning a Con or MulNode since the users are still expecting a Call node, but we could
+// produce a tuple that follows the same pattern so users can still get control, io, memory, etc..
+TupleNode* PowDNode::make_tuple_of_input_state_and_result(PhaseIterGVN* phase, Node* result, Node* control) {
+ if (control == nullptr) {
+ control = in(TypeFunc::Control);
+ }
+
+ Compile* C = phase->C;
+ C->remove_macro_node(this);
+ TupleNode* tuple = TupleNode::make(
+ tf()->range(),
+ control,
+ in(TypeFunc::I_O),
+ in(TypeFunc::Memory),
+ in(TypeFunc::FramePtr),
+ in(TypeFunc::ReturnAdr),
+ result,
+ C->top());
+ return tuple;
+}
diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp
index 2462a88143f..bb87746a3f6 100644
--- a/src/hotspot/share/opto/callnode.hpp
+++ b/src/hotspot/share/opto/callnode.hpp
@@ -953,6 +953,8 @@ class CallLeafPureNode : public CallLeafNode {
}
int Opcode() const override;
Node* Ideal(PhaseGVN* phase, bool can_reshape) override;
+
+ CallLeafPureNode* inline_call_leaf_pure_node(Node* control = nullptr) const;
};
//------------------------------CallLeafNoFPNode-------------------------------
@@ -1304,4 +1306,19 @@ class UnlockNode : public AbstractLockNode {
JVMState* dbg_jvms() const { return nullptr; }
#endif
};
+
+//------------------------------PowDNode--------------------------------------
+class PowDNode : public CallLeafPureNode {
+ TupleNode* make_tuple_of_input_state_and_result(PhaseIterGVN* phase, Node* result, Node* control = nullptr);
+
+public:
+ PowDNode(Compile* C, Node* base, Node* exp);
+ int Opcode() const override;
+ const Type* Value(PhaseGVN* phase) const override;
+ Node* Ideal(PhaseGVN* phase, bool can_reshape) override;
+
+ Node* base() const { return in(TypeFunc::Parms + 0); }
+ Node* exp() const { return in(TypeFunc::Parms + 2); }
+};
+
#endif // SHARE_OPTO_CALLNODE_HPP
diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp
index 587d5fad8f2..b7ba16e99a0 100644
--- a/src/hotspot/share/opto/classes.hpp
+++ b/src/hotspot/share/opto/classes.hpp
@@ -283,6 +283,7 @@ macro(OpaqueZeroTripGuard)
macro(OpaqueNotNull)
macro(OpaqueInitializedAssertionPredicate)
macro(OpaqueTemplateAssertionPredicate)
+macro(PowD)
macro(ProfileBoolean)
macro(OrI)
macro(OrL)
diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp
index 12960d101a8..6c71a61ba75 100644
--- a/src/hotspot/share/opto/library_call.cpp
+++ b/src/hotspot/share/opto/library_call.cpp
@@ -1817,61 +1817,17 @@ bool LibraryCallKit::runtime_math(const TypeFunc* call_type, address funcAddr, c
//------------------------------inline_math_pow-----------------------------
bool LibraryCallKit::inline_math_pow() {
+ Node* base = argument(0);
Node* exp = argument(2);
- const TypeD* d = _gvn.type(exp)->isa_double_constant();
- if (d != nullptr) {
- if (d->getd() == 2.0) {
- // Special case: pow(x, 2.0) => x * x
- Node* base = argument(0);
- set_result(_gvn.transform(new MulDNode(base, base)));
- return true;
- } else if (d->getd() == 0.5 && Matcher::match_rule_supported(Op_SqrtD)) {
- // Special case: pow(x, 0.5) => sqrt(x)
- Node* base = argument(0);
- Node* zero = _gvn.zerocon(T_DOUBLE);
-
- RegionNode* region = new RegionNode(3);
- Node* phi = new PhiNode(region, Type::DOUBLE);
-
- Node* cmp = _gvn.transform(new CmpDNode(base, zero));
- // According to the API specs, pow(-0.0, 0.5) = 0.0 and sqrt(-0.0) = -0.0.
- // So pow(-0.0, 0.5) shouldn't be replaced with sqrt(-0.0).
- // -0.0/+0.0 are both excluded since floating-point comparison doesn't distinguish -0.0 from +0.0.
- Node* test = _gvn.transform(new BoolNode(cmp, BoolTest::le));
-
- Node* if_pow = generate_slow_guard(test, nullptr);
- Node* value_sqrt = _gvn.transform(new SqrtDNode(C, control(), base));
- phi->init_req(1, value_sqrt);
- region->init_req(1, control());
-
- if (if_pow != nullptr) {
- set_control(if_pow);
- address target = StubRoutines::dpow() != nullptr ? StubRoutines::dpow() :
- CAST_FROM_FN_PTR(address, SharedRuntime::dpow);
- const TypePtr* no_memory_effects = nullptr;
- Node* trig = make_runtime_call(RC_LEAF, OptoRuntime::Math_DD_D_Type(), target, "POW",
- no_memory_effects, base, top(), exp, top());
- Node* value_pow = _gvn.transform(new ProjNode(trig, TypeFunc::Parms+0));
-#ifdef ASSERT
- Node* value_top = _gvn.transform(new ProjNode(trig, TypeFunc::Parms+1));
- assert(value_top == top(), "second value must be top");
-#endif
- phi->init_req(2, value_pow);
- region->init_req(2, _gvn.transform(new ProjNode(trig, TypeFunc::Control)));
- }
-
- C->set_has_split_ifs(true); // Has chance for split-if optimization
- set_control(_gvn.transform(region));
- record_for_igvn(region);
- set_result(_gvn.transform(phi));
- return true;
- }
- }
-
- return StubRoutines::dpow() != nullptr ?
- runtime_math(OptoRuntime::Math_DD_D_Type(), StubRoutines::dpow(), "dpow") :
- runtime_math(OptoRuntime::Math_DD_D_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::dpow), "POW");
+ CallNode* pow = new PowDNode(C, base, exp);
+ set_predefined_input_for_runtime_call(pow);
+ pow = _gvn.transform(pow)->as_CallLeafPure();
+ set_predefined_output_for_runtime_call(pow);
+ Node* result = _gvn.transform(new ProjNode(pow, TypeFunc::Parms + 0));
+ record_for_igvn(pow);
+ set_result(result);
+ return true;
}
//------------------------------inline_math_native-----------------------------
diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp
index aafc5d3c170..a28043c8d8f 100644
--- a/src/hotspot/share/opto/macro.cpp
+++ b/src/hotspot/share/opto/macro.cpp
@@ -2484,6 +2484,7 @@ void PhaseMacroExpand::eliminate_macro_nodes() {
assert(n->Opcode() == Op_LoopLimit ||
n->Opcode() == Op_ModD ||
n->Opcode() == Op_ModF ||
+ n->Opcode() == Op_PowD ||
n->is_OpaqueNotNull() ||
n->is_OpaqueInitializedAssertionPredicate() ||
n->Opcode() == Op_MaxL ||
@@ -2637,19 +2638,11 @@ bool PhaseMacroExpand::expand_macro_nodes() {
default:
switch (n->Opcode()) {
case Op_ModD:
- case Op_ModF: {
- CallNode* mod_macro = n->as_Call();
- CallNode* call = new CallLeafPureNode(mod_macro->tf(), mod_macro->entry_point(),
- mod_macro->_name, TypeRawPtr::BOTTOM);
- call->init_req(TypeFunc::Control, mod_macro->in(TypeFunc::Control));
- call->init_req(TypeFunc::I_O, C->top());
- call->init_req(TypeFunc::Memory, C->top());
- call->init_req(TypeFunc::ReturnAdr, C->top());
- call->init_req(TypeFunc::FramePtr, C->top());
- for (unsigned int i = 0; i < mod_macro->tf()->domain()->cnt() - TypeFunc::Parms; i++) {
- call->init_req(TypeFunc::Parms + i, mod_macro->in(TypeFunc::Parms + i));
- }
- _igvn.replace_node(mod_macro, call);
+ case Op_ModF:
+ case Op_PowD: {
+ CallLeafPureNode* call_macro = n->as_CallLeafPure();
+ CallLeafPureNode* call = call_macro->inline_call_leaf_pure_node();
+ _igvn.replace_node(call_macro, call);
transform_later(call);
break;
}
diff --git a/test/hotspot/jtreg/compiler/intrinsics/math/PowDNodeTests.java b/test/hotspot/jtreg/compiler/intrinsics/math/PowDNodeTests.java
new file mode 100644
index 00000000000..e28cc5ab346
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/intrinsics/math/PowDNodeTests.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2026, IBM 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.intrinsics.math;
+
+import jdk.test.lib.Asserts;
+
+import compiler.lib.ir_framework.*;
+import compiler.lib.generators.*;
+import static compiler.lib.generators.Generators.*;
+
+import java.util.Random;
+
+/*
+ * @test
+ * @bug 8378713
+ * @key randomness
+ * @summary Math.pow(base, exp) should constant propagate
+ * @library /test/lib /
+ * @run driver ${test.main.class}
+ */
+public class PowDNodeTests {
+ public static final Generator<Double> UNIFORMS = G.uniformDoubles(); // [0, 1)
+
+ public static final double B = UNIFORMS.next() * 1000.0d;
+ public static final double E = UNIFORMS.next() * 1000.0d + 3.0d; // e >= 3 to avoid strength reduction code
+
+ public static void main(String[] args) {
+ TestFramework.run();
+
+ testCorrectness();
+ }
+
+ // Test 1: pow(2.0, 10.0) -> 1024.0
+ @Test
+ @IR(failOn = {IRNode.POW_D})
+ public static double constantLiteralFolding() {
+ return Math.pow(2.0, 10.0); // should fold to 1024.0
+ }
+
+ // Test 2: pow(final B, final E) -> B^E
+ @Test
+ @IR(failOn = {IRNode.POW_D})
+ public static double constantStaticFolding() {
+ return Math.pow(B, E); // should fold to B^E
+ }
+
+ // Test 3: pow(b, 0.0) -> 1.0
+ @Test
+ @IR(failOn = {IRNode.POW_D})
+ @Arguments(values = {Argument.RANDOM_EACH})
+ public static double expZero(double b) {
+ return Math.pow(b, 0.0);
+ }
+
+ // Test 4: pow(b, 1.0) -> b (identity)
+ @Test
+ @IR(failOn = {IRNode.POW_D})
+ @Arguments(values = {Argument.RANDOM_EACH})
+ public static double expOne(double b) {
+ return Math.pow(b, 1.0);
+ }
+
+ // Test 5: pow(b, NaN) -> NaN
+ @Test
+ @IR(failOn = {IRNode.POW_D})
+ @Arguments(values = {Argument.RANDOM_EACH})
+ public static double expNaN(double b) {
+ return Math.pow(b, Double.NaN);
+ }
+
+ // Test 6: pow(b, 2.0) -> b * b
+ // More tests in TestPow2Opt.java
+ @Test
+ @IR(failOn = {IRNode.POW_D})
+ @IR(counts = {IRNode.MUL_D, "1"})
+ @Arguments(values = {Argument.RANDOM_EACH})
+ public static double expTwo(double b) {
+ return Math.pow(b, 2.0);
+ }
+
+ // Test 7: pow(b, 0.5) -> b <= 0.0 ? pow(b, 0.5) : sqrt(b)
+ // More tests in TestPow0Dot5Opt.java
+ @Test
+ @IR(counts = {IRNode.IF, "1"})
+ @IR(counts = {IRNode.SQRT_D, "1"})
+ @IR(counts = {".*CallLeaf.*pow.*", "1"}, phase = CompilePhase.BEFORE_MATCHING)
+ @Arguments(values = {Argument.RANDOM_EACH})
+ public static double expDot5(double b) {
+ return Math.pow(b, 0.5); // expand to: if (b > 0) { sqrt(b) } else { call(b) }
+ }
+
+ // Test 8: non-constant exponent stays as call
+ @Test
+ @IR(counts = {IRNode.POW_D, "1"})
+ @Arguments(values = {Argument.RANDOM_EACH, Argument.RANDOM_EACH})
+ public static double nonConstant(double b, double e) {
+ return Math.pow(b, e);
+ }
+
+ // Test 9: late constant discovery on base (after loop opts)
+ @Test
+ @IR(counts = {IRNode.POW_D, "1"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.POW_D})
+ public static double lateBaseConstant() {
+ double base = 0;
+ for (int i = 0; i < 4; i++) {
+ if ((i % 2) == 0) {
+ base = B;
+ }
+ }
+ // After loop opts, base == B (constant), so pow(B, E) folds
+ return Math.pow(base, E);
+ }
+
+ // Test 10: late constant discovery on exp (after loop opts)
+ @Test
+ @IR(counts = {IRNode.POW_D, "1"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.POW_D})
+ public static double lateExpConstant() {
+ double exp = 0;
+ for (int i = 0; i < 4; i++) {
+ if ((i % 2) == 0) {
+ exp = E;
+ }
+ }
+ // After loop opts, exp == E (constant), so pow(B, E) folds
+ return Math.pow(B, exp);
+ }
+
+ // Test 11: late constant discoveries on both base and exp (after loop opts)
+ @Test
+ @IR(counts = {IRNode.POW_D, "1"}, phase = CompilePhase.AFTER_PARSING)
+ @IR(failOn = {IRNode.POW_D})
+ public static double lateBothConstant() {
+ double base = 0, exp = 0;
+ for (int i = 0; i < 4; i++) {
+ if ((i % 2) == 0) {
+ base = B;
+ exp = E;
+ }
+ }
+ // After loop opts, base = B, exp == E, so pow(B, E) folds
+ return Math.pow(base, exp);
+ }
+
+ private static void assertEQWithinOneUlp(double expected, double observed) {
+ if (Double.isNaN(expected) && Double.isNaN(observed)) return;
+
+ // Math.pow() requires result must be within 1 ulp of the respective magnitude
+ double ulp = Math.max(Math.ulp(expected), Math.ulp(observed));
+ if (Math.abs(expected - observed) > ulp) {
+ throw new AssertionError(String.format(
+ "expect = %x, observed = %x, ulp = %x",
+ Double.doubleToRawLongBits(expected), Double.doubleToRawLongBits(observed), Double.doubleToRawLongBits(ulp)
+ ));
+ }
+ }
+
+ private static void testCorrectness() {
+ // No need to warm up for intrinsics
+ Asserts.assertEQ(1024.0d, constantLiteralFolding());
+
+ double BE = StrictMath.pow(B, E);
+ assertEQWithinOneUlp(BE, constantStaticFolding());
+ assertEQWithinOneUlp(BE, lateBaseConstant());
+ assertEQWithinOneUlp(BE, lateExpConstant());
+ assertEQWithinOneUlp(BE, lateBothConstant());
+
+ Generator<Double> anyBits = G.anyBitsDouble();
+ Generator<Double> largeDoubles = G.uniformDoubles(Long.MAX_VALUE, Double.MAX_VALUE);
+ Generator<Double> doubles = G.doubles();
+ double[] values = {
+ Double.MIN_VALUE, Double.MIN_NORMAL, -42.0d, -1.0d, -0.0d, +0.0d, 0.5d, 1.0d, 2.0d, 123d, Double.MAX_VALUE,
+ Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NaN,
+ UNIFORMS.next(), UNIFORMS.next(),
+ largeDoubles.next(), -largeDoubles.next(), // some sufficiently large magnitudes
+ anyBits.next(), anyBits.next(), // any bits with potentially more NaN representation
+ doubles.next(), doubles.next() // a healthy sprinkle of whatever else is possible
+ };
+
+ for (double b : values) {
+ // Strength reduced, so we know the bits matches exactly
+ Asserts.assertEQ(1.0d, expZero(b));
+ Asserts.assertEQ(b, expOne(b));
+ Asserts.assertEQ(b * b, expTwo(b));
+
+ assertEQWithinOneUlp(Double.NaN, expNaN(b));
+
+ // Runtime calls, so make sure the result is within 1 ulp
+ assertEQWithinOneUlp(StrictMath.pow(b, 0.5d), expDot5(b));
+
+ for (double e : values) {
+ assertEQWithinOneUlp(StrictMath.pow(b, e), nonConstant(b, e));
+ }
+ }
+ }
+}
diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
index 7b751f1a72c..6a95a2b5da7 100644
--- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
@@ -1858,6 +1858,11 @@ public class IRNode {
beforeMatchingNameRegex(SQRT_HF, "SqrtHF");
}
+ public static final String SQRT_D = PREFIX + "SQRT_D" + POSTFIX;
+ static {
+ beforeMatchingNameRegex(SQRT_D, "SqrtD");
+ }
+
public static final String SQRT_F = PREFIX + "SQRT_F" + POSTFIX;
static {
beforeMatchingNameRegex(SQRT_F, "SqrtF");
@@ -2825,6 +2830,12 @@ public class IRNode {
macroNodes(MOD_D, regex);
}
+ public static final String POW_D = PREFIX + "POW_D" + POSTFIX;
+ static {
+ String regex = START + "PowD" + MID + END;
+ macroNodes(POW_D, regex);
+ }
+
public static final String BLACKHOLE = PREFIX + "BLACKHOLE" + POSTFIX;
static {
fromBeforeRemoveUselessToFinalCode(BLACKHOLE, "Blackhole");
--
2.53.0