[flang-commits] [flang] [Flang] Skip SimplifyIntrinsics for unsigned reductions (PR #220850)
via flang-commits
flang-commits at lists.llvm.org
Thu Sep 3 02:06:24 PDT 2026
https://github.com/blazie2004 created https://github.com/llvm/llvm-project/pull/220850
SimplifyIntrinsics assumes that the reduction argument element type matches the runtime call result type.
For UNSIGNED reductions, this is not true. For example, MAXVAL on an UNSIGNED(4) array is lowered with an array element type of ui32, while the runtime call returns i32 and is converted back to ui32 afterward.
This causes an assertions-enabled build to fail at -O1 and above with:
```
Assertion `*argType == resultType &&
"Argument/result types mismatch in reduction"' failed.
```
The existing simplified integer reduction path is also not safe for unsigned values. In particular, MAXVAL currently uses a signed minimum value as the reduction identity and arith::MaxSIOp for the comparison.
Instead of weakening the assertion, skip SimplifyIntrinsics when the input element type is unsigned and preserve the existing runtime call.
This keeps the correct unsigned semantics while leaving signed integer and floating-point reductions unchanged.
fixes : [218926](https://github.com/llvm/llvm-project/issues/218926)
>From 3ba31e7d2eef1796eb03ad3d9a2d930d9fe975e0 Mon Sep 17 00:00:00 2001
From: Jay Satish Kumar Patel <kumarpat at pe31.hpc.amslabs.hpecorp.net>
Date: Thu, 3 Sep 2026 02:13:48 -0500
Subject: [PATCH] [flang] Do not simplify unsigned MAXVAL/SUM reductions in
SimplifyIntrinsics
The SimplifyIntrinsics pass replaces 1-D MAXVAL/SUM runtime calls with an inlined reduction loop. For UNSIGNED arguments the runtime returns a signless integer (i32) that differs from the unsigned element type (ui32), tripping the '*argType == resultType' assertion at -O1+. Even without the assertion the generated loop uses a signed identity/comparison (getSignedMinValue + arith.maxsi) and would miscompile unsigned reductions (values above INT_MAX, empty arrays), and arith ops reject unsigned operands.
Bail out of the reduction simplification when the argument element type is an unsigned integer, leaving the correct runtime call in place.
---
.../Transforms/SimplifyIntrinsics.cpp | 6 ++
flang/test/Transforms/simplifyintrinsics.fir | 76 +++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/flang/lib/Optimizer/Transforms/SimplifyIntrinsics.cpp b/flang/lib/Optimizer/Transforms/SimplifyIntrinsics.cpp
index 6319fa67f644b..6e1608c3c5877 100644
--- a/flang/lib/Optimizer/Transforms/SimplifyIntrinsics.cpp
+++ b/flang/lib/Optimizer/Transforms/SimplifyIntrinsics.cpp
@@ -1058,6 +1058,12 @@ void SimplifyIntrinsicsPass::simplifyIntOrFloatReduction(
auto argType = getArgElementType(args[0]);
if (!argType)
return;
+ // Unsigned reductions (e.g. MaxvalUnsigned/SumUnsigned) lower to runtime
+ // calls whose signless result type intentionally differs from the unsigned
+ // element type, and the inline reduction generated below would use a signed
+ // identity/comparison. Leave the correct runtime call in place.
+ if (argType->isUnsignedInteger())
+ return;
assert(*argType == resultType &&
"Argument/result types mismatch in reduction");
diff --git a/flang/test/Transforms/simplifyintrinsics.fir b/flang/test/Transforms/simplifyintrinsics.fir
index 2ef470f7f2ebd..b0542ee63b74a 100644
--- a/flang/test/Transforms/simplifyintrinsics.fir
+++ b/flang/test/Transforms/simplifyintrinsics.fir
@@ -849,6 +849,82 @@ module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", llvm.targ
// -----
+// Call to MAXVAL with a 1D UNSIGNED array is not replaced: the runtime call
+// returns a signless integer that differs from the unsigned element type, so
+// the inline reduction (signed identity/comparison) would be incorrect. The
+// runtime call must be preserved.
+module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", llvm.target_triple = "native"} {
+ func.func @maxval_1d_array_unsigned(%arg0: !fir.ref<!fir.array<10xui32>> {fir.bindc_name = "a"}) -> ui32 {
+ %c10 = arith.constant 10 : index
+ %0 = fir.alloca ui32
+ %1 = fir.shape %c10 : (index) -> !fir.shape<1>
+ %2 = fir.embox %arg0(%1) : (!fir.ref<!fir.array<10xui32>>, !fir.shape<1>) -> !fir.box<!fir.array<10xui32>>
+ %3 = fir.absent !fir.box<i1>
+ %c0 = arith.constant 0 : index
+ %4 = fir.address_of(@_QQclXumax) : !fir.ref<!fir.char<1,13>>
+ %c5_i32 = arith.constant 5 : i32
+ %5 = fir.convert %2 : (!fir.box<!fir.array<10xui32>>) -> !fir.box<none>
+ %6 = fir.convert %4 : (!fir.ref<!fir.char<1,13>>) -> !fir.ref<i8>
+ %7 = fir.convert %c0 : (index) -> i32
+ %8 = fir.convert %3 : (!fir.box<i1>) -> !fir.box<none>
+ %9 = fir.call @_FortranAMaxvalUnsigned4(%5, %6, %c5_i32, %7, %8) : (!fir.box<none>, !fir.ref<i8>, i32, i32, !fir.box<none>) -> i32
+ %10 = fir.convert %9 : (i32) -> ui32
+ fir.store %10 to %0 : !fir.ref<ui32>
+ %11 = fir.load %0 : !fir.ref<ui32>
+ return %11 : ui32
+ }
+ func.func private @_FortranAMaxvalUnsigned4(!fir.box<none>, !fir.ref<i8>, i32, i32, !fir.box<none>) -> i32 attributes {fir.runtime}
+ fir.global linkonce @_QQclXumax constant : !fir.char<1,13> {
+ %0 = fir.string_lit "./umax_1.f90\00"(13) : !fir.char<1,13>
+ fir.has_value %0 : !fir.char<1,13>
+ }
+}
+
+// CHECK-LABEL: func.func @maxval_1d_array_unsigned(
+// CHECK-NOT: fir.call @_FortranAMaxvalUnsigned4x1_simplified({{.*}})
+// CHECK: fir.call @_FortranAMaxvalUnsigned4({{.*}}) : (!fir.box<none>, !fir.ref<i8>, i32, i32, !fir.box<none>) -> i32
+// CHECK-NOT: fir.call @_FortranAMaxvalUnsigned4x1_simplified({{.*}})
+
+// -----
+
+// Call to SUM with a 1D UNSIGNED array is not replaced: the runtime call
+// returns a signless integer that differs from the unsigned element type, so
+// the inline reduction (arith.addi on an unsigned value) would be invalid. The
+// runtime call must be preserved.
+module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", llvm.target_triple = "native"} {
+ func.func @sum_1d_array_unsigned(%arg0: !fir.ref<!fir.array<10xui32>> {fir.bindc_name = "a"}) -> ui32 {
+ %c10 = arith.constant 10 : index
+ %0 = fir.alloca ui32
+ %1 = fir.shape %c10 : (index) -> !fir.shape<1>
+ %2 = fir.embox %arg0(%1) : (!fir.ref<!fir.array<10xui32>>, !fir.shape<1>) -> !fir.box<!fir.array<10xui32>>
+ %3 = fir.absent !fir.box<i1>
+ %c0 = arith.constant 0 : index
+ %4 = fir.address_of(@_QQclXusum) : !fir.ref<!fir.char<1,13>>
+ %c5_i32 = arith.constant 5 : i32
+ %5 = fir.convert %2 : (!fir.box<!fir.array<10xui32>>) -> !fir.box<none>
+ %6 = fir.convert %4 : (!fir.ref<!fir.char<1,13>>) -> !fir.ref<i8>
+ %7 = fir.convert %c0 : (index) -> i32
+ %8 = fir.convert %3 : (!fir.box<i1>) -> !fir.box<none>
+ %9 = fir.call @_FortranASumUnsigned4(%5, %6, %c5_i32, %7, %8) : (!fir.box<none>, !fir.ref<i8>, i32, i32, !fir.box<none>) -> i32
+ %10 = fir.convert %9 : (i32) -> ui32
+ fir.store %10 to %0 : !fir.ref<ui32>
+ %11 = fir.load %0 : !fir.ref<ui32>
+ return %11 : ui32
+ }
+ func.func private @_FortranASumUnsigned4(!fir.box<none>, !fir.ref<i8>, i32, i32, !fir.box<none>) -> i32 attributes {fir.runtime}
+ fir.global linkonce @_QQclXusum constant : !fir.char<1,13> {
+ %0 = fir.string_lit "./usum_1.f90\00"(13) : !fir.char<1,13>
+ fir.has_value %0 : !fir.char<1,13>
+ }
+}
+
+// CHECK-LABEL: func.func @sum_1d_array_unsigned(
+// CHECK-NOT: fir.call @_FortranASumUnsigned4x1_simplified({{.*}})
+// CHECK: fir.call @_FortranASumUnsigned4({{.*}}) : (!fir.box<none>, !fir.ref<i8>, i32, i32, !fir.box<none>) -> i32
+// CHECK-NOT: fir.call @_FortranASumUnsigned4x1_simplified({{.*}})
+
+// -----
+
// Call to MAXVAL with 1D F64 is replaced.
module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", llvm.target_triple = "native"} {
func.func @maxval_1d_real(%arg0: !fir.ref<!fir.array<10xf64>> {fir.bindc_name = "a"}) -> f64 {
More information about the flang-commits
mailing list