[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