[llvm] [flang-rt] Consider the signedness of zero for PRODUCT (PR #226918)
Yusuke MINATO via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 29 19:34:43 PDT 2026
https://github.com/yus3710-fj updated https://github.com/llvm/llvm-project/pull/226918
>From 4959f2fbb9ae777fe17e67fd329f1a9b9a65c6f8 Mon Sep 17 00:00:00 2001
From: Yusuke MINATO <minato.yusuke at fujitsu.com>
Date: Fri, 7 Aug 2026 14:59:18 +0900
Subject: [PATCH 1/3] [flang][Runtime] Consider the signedness of zero for
PRODUCT
Currently, the result of PRODUCT can be calculated in three places:
1. constant folding in Semantics
2. the runtime library
3. inlined code
Only the runtime ignored the signedness of zero. This patch fixes this discrepancy.
---
flang-rt/lib/runtime/product.cpp | 67 +++++++++++++++++++-------------
1 file changed, 40 insertions(+), 27 deletions(-)
diff --git a/flang-rt/lib/runtime/product.cpp b/flang-rt/lib/runtime/product.cpp
index 02fdc2bfcd576..8d5ee8bd93777 100644
--- a/flang-rt/lib/runtime/product.cpp
+++ b/flang-rt/lib/runtime/product.cpp
@@ -16,9 +16,9 @@
#include <complex>
namespace Fortran::runtime {
-template <typename INTERMEDIATE> class NonComplexProductAccumulator {
+template <typename INTERMEDIATE> class IntegerProductAccumulator {
public:
- explicit RT_API_ATTRS NonComplexProductAccumulator(const Descriptor &array)
+ explicit RT_API_ATTRS IntegerProductAccumulator(const Descriptor &array)
: array_{array} {}
RT_API_ATTRS void Reinitialize() { product_ = 1; }
template <typename A>
@@ -36,6 +36,28 @@ template <typename INTERMEDIATE> class NonComplexProductAccumulator {
INTERMEDIATE product_{1};
};
+template <typename INTERMEDIATE> class RealProductAccumulator {
+public:
+ explicit RT_API_ATTRS RealProductAccumulator(const Descriptor &array)
+ : array_{array} {}
+ RT_API_ATTRS void Reinitialize() { product_ = 1; }
+ template <typename A>
+ RT_API_ATTRS void GetResult(A *p, int /*zeroBasedDim*/ = -1) const {
+ *p = static_cast<A>(product_);
+ }
+ template <typename A>
+ RT_API_ATTRS bool AccumulateAt(const SubscriptValue at[]) {
+ product_ *= *array_.Element<A>(at);
+ // If nsz (No Signed Zeros) flag is set, we could cut off the product early
+ // when we hit a zero.
+ return true;
+ }
+
+private:
+ const Descriptor &array_;
+ INTERMEDIATE product_{1};
+};
+
template <typename PART> class ComplexProductAccumulator {
public:
explicit RT_API_ATTRS ComplexProductAccumulator(const Descriptor &array)
@@ -64,25 +86,25 @@ RT_EXT_API_GROUP_BEGIN
CppTypeFor<TypeCategory::Integer, 1> RTDEF(ProductInteger1)(const Descriptor &x,
const char *source, int line, int dim, const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Integer, 1>(x, source, line, dim, mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Integer, 4>>{x},
+ IntegerProductAccumulator<CppTypeFor<TypeCategory::Integer, 4>>{x},
"PRODUCT");
}
CppTypeFor<TypeCategory::Integer, 2> RTDEF(ProductInteger2)(const Descriptor &x,
const char *source, int line, int dim, const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Integer, 2>(x, source, line, dim, mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Integer, 4>>{x},
+ IntegerProductAccumulator<CppTypeFor<TypeCategory::Integer, 4>>{x},
"PRODUCT");
}
CppTypeFor<TypeCategory::Integer, 4> RTDEF(ProductInteger4)(const Descriptor &x,
const char *source, int line, int dim, const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Integer, 4>(x, source, line, dim, mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Integer, 4>>{x},
+ IntegerProductAccumulator<CppTypeFor<TypeCategory::Integer, 4>>{x},
"PRODUCT");
}
CppTypeFor<TypeCategory::Integer, 8> RTDEF(ProductInteger8)(const Descriptor &x,
const char *source, int line, int dim, const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Integer, 8>(x, source, line, dim, mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Integer, 8>>{x},
+ IntegerProductAccumulator<CppTypeFor<TypeCategory::Integer, 8>>{x},
"PRODUCT");
}
#ifdef __SIZEOF_INT128__
@@ -90,8 +112,7 @@ CppTypeFor<TypeCategory::Integer, 16> RTDEF(ProductInteger16)(
const Descriptor &x, const char *source, int line, int dim,
const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Integer, 16>(x, source, line, dim,
- mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Integer, 16>>{x},
+ mask, IntegerProductAccumulator<CppTypeFor<TypeCategory::Integer, 16>>{x},
"PRODUCT");
}
#endif
@@ -100,32 +121,28 @@ CppTypeFor<TypeCategory::Unsigned, 1> RTDEF(ProductUnsigned1)(
const Descriptor &x, const char *source, int line, int dim,
const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Unsigned, 1>(x, source, line, dim,
- mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 4>>{x},
+ mask, IntegerProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 4>>{x},
"PRODUCT");
}
CppTypeFor<TypeCategory::Unsigned, 2> RTDEF(ProductUnsigned2)(
const Descriptor &x, const char *source, int line, int dim,
const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Unsigned, 2>(x, source, line, dim,
- mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 4>>{x},
+ mask, IntegerProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 4>>{x},
"PRODUCT");
}
CppTypeFor<TypeCategory::Unsigned, 4> RTDEF(ProductUnsigned4)(
const Descriptor &x, const char *source, int line, int dim,
const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Unsigned, 4>(x, source, line, dim,
- mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 4>>{x},
+ mask, IntegerProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 4>>{x},
"PRODUCT");
}
CppTypeFor<TypeCategory::Unsigned, 8> RTDEF(ProductUnsigned8)(
const Descriptor &x, const char *source, int line, int dim,
const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Unsigned, 8>(x, source, line, dim,
- mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 8>>{x},
+ mask, IntegerProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 8>>{x},
"PRODUCT");
}
#ifdef __SIZEOF_INT128__
@@ -134,7 +151,7 @@ CppTypeFor<TypeCategory::Unsigned, 16> RTDEF(ProductUnsigned16)(
const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Unsigned, 16>(x, source, line, dim,
mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 16>>{x},
+ IntegerProductAccumulator<CppTypeFor<TypeCategory::Unsigned, 16>>{x},
"PRODUCT");
}
#endif
@@ -143,29 +160,25 @@ CppTypeFor<TypeCategory::Unsigned, 16> RTDEF(ProductUnsigned16)(
CppTypeFor<TypeCategory::Real, 4> RTDEF(ProductReal4)(const Descriptor &x,
const char *source, int line, int dim, const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Real, 4>(x, source, line, dim, mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Real, 4>>{x},
- "PRODUCT");
+ RealProductAccumulator<CppTypeFor<TypeCategory::Real, 4>>{x}, "PRODUCT");
}
CppTypeFor<TypeCategory::Real, 8> RTDEF(ProductReal8)(const Descriptor &x,
const char *source, int line, int dim, const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Real, 8>(x, source, line, dim, mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Real, 8>>{x},
- "PRODUCT");
+ RealProductAccumulator<CppTypeFor<TypeCategory::Real, 8>>{x}, "PRODUCT");
}
#if HAS_FLOAT80
CppTypeFor<TypeCategory::Real, 10> RTDEF(ProductReal10)(const Descriptor &x,
const char *source, int line, int dim, const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Real, 10>(x, source, line, dim, mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Real, 10>>{x},
- "PRODUCT");
+ RealProductAccumulator<CppTypeFor<TypeCategory::Real, 10>>{x}, "PRODUCT");
}
#endif
#if HAS_LDBL128 || HAS_FLOAT128
CppTypeFor<TypeCategory::Real, 16> RTDEF(ProductReal16)(const Descriptor &x,
const char *source, int line, int dim, const Descriptor *mask) {
return GetTotalReduction<TypeCategory::Real, 16>(x, source, line, dim, mask,
- NonComplexProductAccumulator<CppTypeFor<TypeCategory::Real, 16>>{x},
- "PRODUCT");
+ RealProductAccumulator<CppTypeFor<TypeCategory::Real, 16>>{x}, "PRODUCT");
}
#endif
@@ -204,8 +217,8 @@ void RTDEF(CppProductComplex16)(CppTypeFor<TypeCategory::Complex, 16> &result,
void RTDEF(ProductDim)(Descriptor &result, const Descriptor &x, int dim,
const char *source, int line, const Descriptor *mask) {
- TypedPartialNumericReduction<NonComplexProductAccumulator,
- NonComplexProductAccumulator, ComplexProductAccumulator,
+ TypedPartialNumericReduction<IntegerProductAccumulator,
+ RealProductAccumulator, ComplexProductAccumulator,
/*MIN_REAL_KIND=*/4>(result, x, dim, source, line, mask, "PRODUCT");
}
>From 7720a33e5ad6e55076c19222a88a6994a37d3a87 Mon Sep 17 00:00:00 2001
From: Yusuke MINATO <minato.yusuke at fujitsu.com>
Date: Mon, 7 Sep 2026 16:57:21 +0900
Subject: [PATCH 2/3] Add test
---
flang-rt/unittests/Runtime/Reduction.cpp | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/flang-rt/unittests/Runtime/Reduction.cpp b/flang-rt/unittests/Runtime/Reduction.cpp
index 821d89f41ba7e..fd763f2faffa9 100644
--- a/flang-rt/unittests/Runtime/Reduction.cpp
+++ b/flang-rt/unittests/Runtime/Reduction.cpp
@@ -688,3 +688,27 @@ TEST(Reductions, InfSums) {
auto t3{RTNAME(SumReal4)(*infMinusInf, __FILE__, __LINE__)};
EXPECT_NE(t3, t3) << t3;
}
+
+TEST(Reductions, NegZeroProduct) {
+ auto intVector{MakeArray<TypeCategory::Integer, 4>(
+ std::vector<int>{2}, std::vector<std::int32_t>{0, -1})};
+ int resultI4{RTNAME(ProductInteger4)(*intVector, __FILE__, __LINE__)};
+ EXPECT_EQ(resultI4, 0);
+ EXPECT_NE(resultI4, 1 << 31); // not -0 but -2^31 in two's complement
+ auto realVector{MakeArray<TypeCategory::Real, 4>(
+ std::vector<int>{2}, std::vector<float>{0.0f, -1.0f})};
+ float resultR4{RTNAME(ProductReal4)(*realVector, __FILE__, __LINE__)};
+ EXPECT_EQ(resultR4, 0.0f);
+ EXPECT_TRUE(std::signbit(resultR4));
+ auto complexVector1{MakeArray<TypeCategory::Complex, 4>(std::vector<int>{2},
+ std::vector<std::complex<float>>{{0.0f, 1.0f}, {-1.0f, 0.0f}})};
+ std::complex<float> resultC4;
+ RTNAME(CppProductComplex4)(resultC4, *complexVector1, __FILE__, __LINE__);
+ EXPECT_EQ(resultC4.real(), 0.0f);
+ EXPECT_TRUE(std::signbit(resultC4.real()));
+ auto complexVector2{MakeArray<TypeCategory::Complex, 4>(std::vector<int>{2},
+ std::vector<std::complex<float>>{{0.0f, 0.0f}, {-1.0f, -1.0f}})};
+ RTNAME(CppProductComplex4)(resultC4, *complexVector2, __FILE__, __LINE__);
+ EXPECT_EQ(resultC4.imag(), 0.0f);
+ EXPECT_TRUE(std::signbit(resultC4.imag()));
+}
>From d84be20ff7c680d3599b0471866cd79df32fab7e Mon Sep 17 00:00:00 2001
From: Yusuke MINATO <minato.yusuke at fujitsu.com>
Date: Wed, 30 Sep 2026 11:33:15 +0900
Subject: [PATCH 3/3] address comments
Co-authored-by: Eugene Epshteyn <eepshteyn at nvidia.com>
---
flang-rt/lib/runtime/product.cpp | 5 ++--
flang-rt/unittests/Runtime/Reduction.cpp | 36 +++++++++++++++++++++++-
2 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/flang-rt/lib/runtime/product.cpp b/flang-rt/lib/runtime/product.cpp
index 8d5ee8bd93777..ee4e2a79a9712 100644
--- a/flang-rt/lib/runtime/product.cpp
+++ b/flang-rt/lib/runtime/product.cpp
@@ -48,8 +48,9 @@ template <typename INTERMEDIATE> class RealProductAccumulator {
template <typename A>
RT_API_ATTRS bool AccumulateAt(const SubscriptValue at[]) {
product_ *= *array_.Element<A>(at);
- // If nsz (No Signed Zeros) flag is set, we could cut off the product early
- // when we hit a zero.
+ // Unlike the integer case, a zero running product cannot end the
+ // reduction early: the remaining elements determine the sign of the
+ // zero, and a later NaN or infinity must still produce NaN.
return true;
}
diff --git a/flang-rt/unittests/Runtime/Reduction.cpp b/flang-rt/unittests/Runtime/Reduction.cpp
index fd763f2faffa9..5d7bb20e011ad 100644
--- a/flang-rt/unittests/Runtime/Reduction.cpp
+++ b/flang-rt/unittests/Runtime/Reduction.cpp
@@ -15,8 +15,10 @@
#include "flang/Runtime/allocatable.h"
#include "flang/Runtime/cpp-type.h"
#include "flang/Runtime/reduce.h"
+#include <cmath>
#include <cstdint>
#include <cstring>
+#include <limits>
#include <string>
#include <vector>
@@ -694,7 +696,6 @@ TEST(Reductions, NegZeroProduct) {
std::vector<int>{2}, std::vector<std::int32_t>{0, -1})};
int resultI4{RTNAME(ProductInteger4)(*intVector, __FILE__, __LINE__)};
EXPECT_EQ(resultI4, 0);
- EXPECT_NE(resultI4, 1 << 31); // not -0 but -2^31 in two's complement
auto realVector{MakeArray<TypeCategory::Real, 4>(
std::vector<int>{2}, std::vector<float>{0.0f, -1.0f})};
float resultR4{RTNAME(ProductReal4)(*realVector, __FILE__, __LINE__)};
@@ -712,3 +713,36 @@ TEST(Reductions, NegZeroProduct) {
EXPECT_EQ(resultC4.imag(), 0.0f);
EXPECT_TRUE(std::signbit(resultC4.imag()));
}
+
+TEST(Reductions, RealProductNoEarlyExit) {
+ // A leading -0 must not end the reduction: -0 * -3 * 1 == +0.
+ auto negZeroFirst{MakeArray<TypeCategory::Real, 8>(
+ std::vector<int>{3}, std::vector<double>{-0.0, -3.0, 1.0})};
+ double r8{RTNAME(ProductReal8)(*negZeroFirst, __FILE__, __LINE__)};
+ EXPECT_EQ(r8, 0.0);
+ EXPECT_FALSE(std::signbit(r8));
+ // A NaN after a zero must propagate.
+ auto zeroThenNaN{MakeArray<TypeCategory::Real, 4>(std::vector<int>{2},
+ std::vector<float>{0.0f, std::numeric_limits<float>::quiet_NaN()})};
+ float r4{RTNAME(ProductReal4)(*zeroThenNaN, __FILE__, __LINE__)};
+ EXPECT_NE(r4, r4) << r4;
+ // No zero element: the running product underflows to +0 before the
+ // negative factor is reached, so the result is -0.
+ auto tiny{std::numeric_limits<double>::min()};
+ auto underflow{MakeArray<TypeCategory::Real, 8>(
+ std::vector<int>{3}, std::vector<double>{tiny, tiny, -2.0})};
+ double u8{RTNAME(ProductReal8)(*underflow, __FILE__, __LINE__)};
+ EXPECT_EQ(u8, 0.0);
+ EXPECT_TRUE(std::signbit(u8));
+ // DIM= goes through ProductDim: columns [0,-1] -> -0 and [0,1] -> +0.
+ auto matrix{MakeArray<TypeCategory::Real, 4>(
+ std::vector<int>{2, 2}, std::vector<float>{0.0f, -1.0f, 0.0f, 1.0f})};
+ StaticDescriptor<maxRank, true> statDesc;
+ Descriptor &prod{statDesc.descriptor()};
+ RTNAME(ProductDim)(prod, *matrix, 1, __FILE__, __LINE__);
+ EXPECT_EQ(prod.rank(), 1);
+ EXPECT_EQ(prod.GetDimension(0).Extent(), 2);
+ EXPECT_TRUE(std::signbit(*prod.ZeroBasedIndexedElement<float>(0)));
+ EXPECT_FALSE(std::signbit(*prod.ZeroBasedIndexedElement<float>(1)));
+ prod.Destroy();
+}
More information about the llvm-commits
mailing list