[flang-commits] [flang] 1738b7e - [flang][FIRToSCF] Build the typed induction variable in i64 and narrow with nsw (#219281)
via flang-commits
flang-commits at lists.llvm.org
Tue Sep 1 09:59:51 PDT 2026
Author: Matsu
Date: 2026-09-01T09:59:47-07:00
New Revision: 1738b7e7fb32953d7ab05eda759cab84c89a9c04
URL: https://github.com/llvm/llvm-project/commit/1738b7e7fb32953d7ab05eda759cab84c89a9c04
DIFF: https://github.com/llvm/llvm-project/commit/1738b7e7fb32953d7ab05eda759cab84c89a9c04.diff
LOG: [flang][FIRToSCF] Build the typed induction variable in i64 and narrow with nsw (#219281)
Example:
```fortran
do i = 1, n
s = s + a(i) * b(i)
end do
```
In this code, the typed DO variable is recomputed in closed form from
the
canonical `index` IV, so narrowing it back to `i32` is a plain
`fir.convert`,
i.e. a bare `trunc`. The `iter_args` phi it replaced carried `nsw`,
giving SCEV
a `{lb,+,step}<nsw>` recurrence in `i32`. Through the `trunc` SCEV no
longer
sees that recurrence, and the loops are unrolled differently. That puts
a
different number of copies in the block, so under `-ffast-math` the
MachineCombiner measures different dependency depths and rebalances the
FP
accumulation tree into `(A+C)+B` instead of `A+(C+B)`, which rounds
differently
in the last bit.
Fix: build the DO variable as `low + step * canonicalIV` in i64, then
narrow the
result with `arith.trunci` carrying `nsw`. The i64 arithmetic cannot
wrap for any
trip count flang supports, and the result is a value of the DO variable
by
construction, so the narrowing is exact. Not `nuw`: the variable may be
negative.
Wider IVs and `set-nsw=false` keep the plain conversion.
Added:
Modified:
flang/lib/Optimizer/Transforms/FIRToSCF.cpp
flang/test/Fir/FirToSCF/do-loop.fir
Removed:
################################################################################
diff --git a/flang/lib/Optimizer/Transforms/FIRToSCF.cpp b/flang/lib/Optimizer/Transforms/FIRToSCF.cpp
index a569a717fdca4..d6393ab53a647 100644
--- a/flang/lib/Optimizer/Transforms/FIRToSCF.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToSCF.cpp
@@ -26,6 +26,44 @@ class FIRToSCFPass : public fir::impl::FIRToSCFPassBase<FIRToSCFPass> {
void runOnOperation() override;
};
+// Rebuild the typed induction variable as low + step * canonicalIV. The
+// arithmetic is done in i64, which cannot wrap for any trip count flang
+// supports, so the result is a value of the loop variable and the final
+// narrowing is exact. Spelling it as an arith.trunci carrying nsw lets loop
+// analyses recover the recurrence in the narrower type; a fir.convert lowers
+// to a bare trunc and loses it. Not nuw: the loop variable may be negative.
+static mlir::Value buildTypedIV(mlir::PatternRewriter &rewriter,
+ mlir::Location loc, mlir::Value canonicalIV,
+ mlir::Value low, mlir::Value step,
+ mlir::arith::IntegerOverflowFlagsAttr iofAttr,
+ bool setNSW) {
+ mlir::Type ivType = low.getType();
+ auto intType = mlir::dyn_cast<mlir::IntegerType>(ivType);
+ if (!setNSW || !intType || intType.getWidth() >= 64) {
+ mlir::Value narrowIV =
+ fir::ConvertOp::create(rewriter, loc, ivType, canonicalIV);
+ mlir::Value scaled =
+ mlir::arith::MulIOp::create(rewriter, loc, narrowIV, step, iofAttr);
+ return mlir::arith::AddIOp::create(rewriter, loc, low, scaled, iofAttr);
+ }
+
+ mlir::Type wideType = rewriter.getI64Type();
+ mlir::Value wideIV =
+ fir::ConvertOp::create(rewriter, loc, wideType, canonicalIV);
+ mlir::Value wideLow = fir::ConvertOp::create(rewriter, loc, wideType, low);
+ mlir::Value wideStep = fir::ConvertOp::create(rewriter, loc, wideType, step);
+ mlir::Value scaled =
+ mlir::arith::MulIOp::create(rewriter, loc, wideIV, wideStep, iofAttr);
+ mlir::Value wideVal =
+ mlir::arith::AddIOp::create(rewriter, loc, wideLow, scaled, iofAttr);
+ mlir::arith::IntegerOverflowFlags flags{};
+ flags = bitEnumSet(flags, mlir::arith::IntegerOverflowFlags::nsw);
+ auto noWrapAttr =
+ mlir::arith::IntegerOverflowFlagsAttr::get(rewriter.getContext(), flags);
+ return mlir::arith::TruncIOp::create(rewriter, loc, intType, wideVal,
+ noWrapAttr);
+}
+
struct DoLoopConversion : public mlir::OpRewritePattern<fir::DoLoopOp> {
using OpRewritePattern<fir::DoLoopOp>::OpRewritePattern;
@@ -108,14 +146,14 @@ struct DoLoopConversion : public mlir::OpRewritePattern<fir::DoLoopOp> {
iv = scfLoopLikeOp.getRegionIterArgs().front();
} else {
mlir::Value canonicalIV = scfLoopLikeOp.getSingleInductionVar().value();
- if (hasTypedIV)
- canonicalIV =
- fir::ConvertOp::create(rewriter, loc, low.getType(), canonicalIV);
- // Keep the no-wrap flags the stepped increment carried, so a narrow IV
- // still folds into an affine recurrence.
- iv = mlir::arith::MulIOp::create(rewriter, loc, canonicalIV, step,
- iofAttr);
- iv = mlir::arith::AddIOp::create(rewriter, loc, low, iv, iofAttr);
+ if (hasTypedIV) {
+ iv = buildTypedIV(rewriter, loc, canonicalIV, low, step, iofAttr,
+ setNSW);
+ } else {
+ iv = mlir::arith::MulIOp::create(rewriter, loc, canonicalIV, step,
+ iofAttr);
+ iv = mlir::arith::AddIOp::create(rewriter, loc, low, iv, iofAttr);
+ }
}
mlir::Value firIV = doLoopOp.getInductionVar();
firIV.replaceAllUsesWith(iv);
diff --git a/flang/test/Fir/FirToSCF/do-loop.fir b/flang/test/Fir/FirToSCF/do-loop.fir
index 6d0813d10178a..64307e0541022 100644
--- a/flang/test/Fir/FirToSCF/do-loop.fir
+++ b/flang/test/Fir/FirToSCF/do-loop.fir
@@ -1,5 +1,9 @@
// RUN: fir-opt %s --fir-to-scf --split-input-file | FileCheck %s --check-prefixes=CHECK,NO-PARALLEL
// RUN: fir-opt %s --fir-to-scf='parallel-unordered' --split-input-file | FileCheck %s --check-prefixes=CHECK,PARALLEL
+// Without the no-wrap flags there is nothing to preserve, so the narrowing
+// stays a plain conversion.
+// RUN: fir-opt %s --fir-to-scf='set-nsw=false' --split-input-file | FileCheck %s --check-prefix=NONSW
+// NONSW-NOT: arith.trunci
// CHECK-LABEL: func.func @simple_loop(
// CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.array<100xi32>>) {
@@ -48,9 +52,12 @@ func.func @simple_loop(%arg0: !fir.ref<!fir.array<100xi32>>) {
// CHECK: %[[C0:.*]] = arith.constant 0 : index
// CHECK: %[[C1:.*]] = arith.constant 1 : index
// CHECK: scf.for %[[I:.*]] = %[[C0]] to %[[TRIP]] step %[[C1]] {
-// CHECK: %[[I_TYPED:.*]] = fir.convert %[[I]] : (index) -> i32
-// CHECK: %[[SCALED:.*]] = arith.muli %[[I_TYPED]], %[[STEP]] overflow<nsw> : i32
-// CHECK: %[[IV:.*]] = arith.addi %[[LB]], %[[SCALED]] overflow<nsw> : i32
+// CHECK: %[[I_WIDE:.*]] = fir.convert %[[I]] : (index) -> i64
+// CHECK: %[[LB_WIDE:.*]] = fir.convert %[[LB]] : (i32) -> i64
+// CHECK: %[[STEP_WIDE:.*]] = fir.convert %[[STEP]] : (i32) -> i64
+// CHECK: %[[SCALED:.*]] = arith.muli %[[I_WIDE]], %[[STEP_WIDE]] overflow<nsw> : i64
+// CHECK: %[[WIDE_IV:.*]] = arith.addi %[[LB_WIDE]], %[[SCALED]] overflow<nsw> : i64
+// CHECK: %[[IV:.*]] = arith.trunci %[[WIDE_IV]] overflow<nsw> : i64 to i32
// CHECK: fir.store %[[IV]] to %[[ADDR]] : !fir.ref<i32>
// CHECK: }
// CHECK-NOT: iter_args
@@ -72,9 +79,12 @@ func.func @typed_loop(%lb: i32, %ub: i32, %step: i32,
// CHECK: %[[C0_IDX:.*]] = arith.constant 0 : index
// CHECK: %[[C1_IDX:.*]] = arith.constant 1 : index
// CHECK: %[[RES:.*]] = scf.for %[[I:.*]] = %[[C0_IDX]] to %[[TRIP]] step %[[C1_IDX]] iter_args(%[[ACC:.*]] = %[[C0]]) -> (i32) {
-// CHECK: %[[I_TYPED:.*]] = fir.convert %[[I]] : (index) -> i32
-// CHECK: %[[SCALED:.*]] = arith.muli %[[I_TYPED]], %[[C1]] overflow<nsw> : i32
-// CHECK: %[[IV:.*]] = arith.addi %[[C1]], %[[SCALED]] overflow<nsw> : i32
+// CHECK: %[[I_WIDE:.*]] = fir.convert %[[I]] : (index) -> i64
+// CHECK: %[[LB_WIDE:.*]] = fir.convert %[[C1]] : (i32) -> i64
+// CHECK: %[[STEP_WIDE:.*]] = fir.convert %[[C1]] : (i32) -> i64
+// CHECK: %[[SCALED:.*]] = arith.muli %[[I_WIDE]], %[[STEP_WIDE]] overflow<nsw> : i64
+// CHECK: %[[WIDE_IV:.*]] = arith.addi %[[LB_WIDE]], %[[SCALED]] overflow<nsw> : i64
+// CHECK: %[[IV:.*]] = arith.trunci %[[WIDE_IV]] overflow<nsw> : i64 to i32
// CHECK: %[[SUM:.*]] = arith.addi %[[ACC]], %[[IV]] : i32
// CHECK: scf.yield %[[SUM]] : i32
// CHECK: }
@@ -108,9 +118,12 @@ func.func @typed_loop_iter_args(%addr: !fir.ref<i32>) -> i32 {
// CHECK: %[[C0:.*]] = arith.constant 0 : index
// CHECK: %[[C1_IDX:.*]] = arith.constant 1 : index
// CHECK: scf.for %[[I:.*]] = %[[C0]] to %[[TRIP]] step %[[C1_IDX]] {
-// CHECK: %[[I_TYPED:.*]] = fir.convert %[[I]] : (index) -> i32
-// CHECK: %[[SCALED:.*]] = arith.muli %[[I_TYPED]], %[[CM1]] overflow<nsw> : i32
-// CHECK: %[[IV:.*]] = arith.addi %[[C10]], %[[SCALED]] overflow<nsw> : i32
+// CHECK: %[[I_WIDE:.*]] = fir.convert %[[I]] : (index) -> i64
+// CHECK: %[[LB_WIDE:.*]] = fir.convert %[[C10]] : (i32) -> i64
+// CHECK: %[[STEP_WIDE:.*]] = fir.convert %[[CM1]] : (i32) -> i64
+// CHECK: %[[SCALED:.*]] = arith.muli %[[I_WIDE]], %[[STEP_WIDE]] overflow<nsw> : i64
+// CHECK: %[[WIDE_IV:.*]] = arith.addi %[[LB_WIDE]], %[[SCALED]] overflow<nsw> : i64
+// CHECK: %[[IV:.*]] = arith.trunci %[[WIDE_IV]] overflow<nsw> : i64 to i32
// CHECK: fir.store %[[IV]] to %[[ADDR]] : !fir.ref<i32>
// CHECK: }
// CHECK-NOT: iter_args
@@ -126,6 +139,49 @@ func.func @typed_loop_negative_step(%addr: !fir.ref<i32>) {
// -----
+// An induction variable that is not narrower than the canonical one needs no
+// truncation, so it keeps the plain conversion.
+// CHECK-LABEL: func.func @typed_loop_i64(
+// CHECK: scf.for %[[I:.*]] = %{{.*}} to %{{.*}} step %{{.*}} {
+// CHECK: %[[I_TYPED:.*]] = fir.convert %[[I]] : (index) -> i64
+// CHECK-NOT: arith.trunci
+// CHECK: %[[SCALED:.*]] = arith.muli %[[I_TYPED]], %{{.*}} overflow<nsw> : i64
+func.func @typed_loop_i64(%lb: i64, %ub: i64, %step: i64,
+ %addr: !fir.ref<i64>) {
+ fir.do_loop %iv = %lb to %ub step %step : i64 {
+ fir.store %iv to %addr : !fir.ref<i64>
+ }
+ return
+}
+
+// -----
+
+// do i = -huge(i), huge(i)-1 has a trip count of 2^32-2, which does not fit in
+// the i32 loop variable. The arithmetic stays in i64 so nothing wraps, and only
+// the result is narrowed.
+// CHECK-LABEL: func.func @typed_loop_huge_trip_count(
+// CHECK: %[[LB:.*]] = arith.constant -2147483647 : i32
+// CHECK: %[[UB:.*]] = arith.constant 2147483646 : i32
+// CHECK: %[[STEP:.*]] = arith.constant 1 : i32
+// CHECK: scf.for %[[I:.*]] = %{{.*}} to %{{.*}} step %{{.*}} {
+// CHECK: %[[I_WIDE:.*]] = fir.convert %[[I]] : (index) -> i64
+// CHECK: %[[LB_WIDE:.*]] = fir.convert %[[LB]] : (i32) -> i64
+// CHECK: %[[STEP_WIDE:.*]] = fir.convert %[[STEP]] : (i32) -> i64
+// CHECK: %[[SCALED:.*]] = arith.muli %[[I_WIDE]], %[[STEP_WIDE]] overflow<nsw> : i64
+// CHECK: %[[WIDE_IV:.*]] = arith.addi %[[LB_WIDE]], %[[SCALED]] overflow<nsw> : i64
+// CHECK: %[[IV:.*]] = arith.trunci %[[WIDE_IV]] overflow<nsw> : i64 to i32
+func.func @typed_loop_huge_trip_count(%addr: !fir.ref<i32>) {
+ %lb = arith.constant -2147483647 : i32
+ %ub = arith.constant 2147483646 : i32
+ %step = arith.constant 1 : i32
+ fir.do_loop %iv = %lb to %ub step %step : i32 {
+ fir.store %iv to %addr : !fir.ref<i32>
+ }
+ return
+}
+
+// -----
+
// CHECK-LABEL: func.func @loop_with_negtive_step(
// CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.array<100xi32>>) {
// CHECK: %[[VAL_0:.*]] = arith.constant 100 : index
More information about the flang-commits
mailing list