[flang-commits] [flang] [flang] Set nsw on FIRToSCF loop induction increments (PR #211796)

via flang-commits flang-commits at lists.llvm.org
Fri Jul 24 06:51:05 PDT 2026


https://github.com/khaki3 created https://github.com/llvm/llvm-project/pull/211796

Example:
```fortran
subroutine sum_loop(a, n)
  integer :: a(n), i
  do i = 1, n
    a(i) = i
  end do
end
```

In this code, the DO induction variable `i` is stepped each iteration. `FIRToSCF` lowered that increment to a plain `arith.addi` with no overflow flag — unlike the CFG-conversion path (`ControlFlowConverter`), which marks it `overflow<nsw>` — so LLVM must assume the IV may wrap and cannot form an affine recurrence, blocking analysis/vectorization of loops lowered via `fir-to-scf`.

Fix: add a `set-nsw` pass option (default `true`, disabled under `-fwrapv`) and mark the typed-IV, reconstructed final-value, and `iterate_while` increments `nsw`, matching `ControlFlowConverter`; `FirToSCF` lit tests updated accordingly.

>From 0437937c111f18f1ea06eac19cbe7aa5bab99b9d Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Thu, 23 Jul 2026 16:48:16 -0700
Subject: [PATCH] [flang] Set nsw on FIRToSCF loop induction increments

FIRToSCF lowered fir.do_loop and fir.iterate_while induction-variable
increments to plain arith.addi with no overflow flags, unlike the
CFG-conversion path (ControlFlowConverter), which applies nsw to loop
variable increments by default.

Without nsw, LLVM cannot assume the induction variable does not wrap.
This prevents it from folding the sign-extension of a narrow typed IV
into an affine recurrence (hurting analysis/vectorization), and for
deeply nested constant-trip loops it prevents pruning statically dead
inner loops before LoopFullUnroll clones them.

Add a "set-nsw" option to the FIRToSCF pass (default true, so callers
can disable it under -fwrapv) and apply it to the typed-IV increment,
the reconstructed final-value increment, and the iterate_while
increment, matching ControlFlowConverter.
---
 .../flang/Optimizer/Transforms/Passes.h       |  3 +-
 .../flang/Optimizer/Transforms/Passes.td      |  5 ++-
 flang/lib/Optimizer/Transforms/FIRToSCF.cpp   | 41 ++++++++++++++-----
 flang/test/Fir/FirToSCF/do-extra.fir          |  2 +-
 flang/test/Fir/FirToSCF/do-loop.fir           |  4 +-
 flang/test/Fir/FirToSCF/iter-while.fir        | 12 +++---
 .../test/Fir/FirToSCF/iterate-while-extra.fir |  4 +-
 7 files changed, 48 insertions(+), 23 deletions(-)

diff --git a/flang/include/flang/Optimizer/Transforms/Passes.h b/flang/include/flang/Optimizer/Transforms/Passes.h
index eeb7b4ea07e4b..0dc6182187aa3 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.h
+++ b/flang/include/flang/Optimizer/Transforms/Passes.h
@@ -59,7 +59,8 @@ std::unique_ptr<mlir::Pass>
 createVScaleAttrPass(std::pair<unsigned, unsigned> vscaleAttr);
 
 void populateFIRToSCFRewrites(mlir::RewritePatternSet &patterns,
-                              bool parallelUnordered = false);
+                              bool parallelUnordered = false,
+                              bool setNSW = true);
 
 void populateCfgConversionRewrites(mlir::RewritePatternSet &patterns,
                                    bool forceLoopToExecuteOnce = false,
diff --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index 8de944277e758..db02fa0180ae5 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -87,7 +87,10 @@ def FIRToSCFPass : Pass<"fir-to-scf"> {
   let options = [Option<"parallelUnordered", "parallel-unordered", "bool",
                         /*default=*/"false",
                         "Allow converting a fir.do_loop with the `unordered` "
-                        "attribute to scf.parallel (experimental).">];
+                        "attribute to scf.parallel (experimental).">,
+                 Option<"setNSW", "set-nsw", "bool",
+                        /*default=*/"true",
+                        "Add nsw flag to loop variable increments.">];
 }
 
 def AnnotateConstantOperands : Pass<"annotate-constant"> {
diff --git a/flang/lib/Optimizer/Transforms/FIRToSCF.cpp b/flang/lib/Optimizer/Transforms/FIRToSCF.cpp
index 477371c1e7385..d1ea97cac1e88 100644
--- a/flang/lib/Optimizer/Transforms/FIRToSCF.cpp
+++ b/flang/lib/Optimizer/Transforms/FIRToSCF.cpp
@@ -30,15 +30,20 @@ struct DoLoopConversion : public mlir::OpRewritePattern<fir::DoLoopOp> {
   using OpRewritePattern<fir::DoLoopOp>::OpRewritePattern;
 
   DoLoopConversion(mlir::MLIRContext *context,
-                   bool parallelUnorderedLoop = false,
+                   bool parallelUnorderedLoop = false, bool setNSW = true,
                    mlir::PatternBenefit benefit = 1)
       : OpRewritePattern<fir::DoLoopOp>(context, benefit),
-        parallelUnorderedLoop(parallelUnorderedLoop) {}
+        parallelUnorderedLoop(parallelUnorderedLoop), setNSW(setNSW) {}
 
   mlir::LogicalResult
   matchAndRewrite(fir::DoLoopOp doLoopOp,
                   mlir::PatternRewriter &rewriter) const override {
     mlir::Location loc = doLoopOp.getLoc();
+    mlir::arith::IntegerOverflowFlags flags{};
+    if (setNSW)
+      flags = bitEnumSet(flags, mlir::arith::IntegerOverflowFlags::nsw);
+    auto iofAttr = mlir::arith::IntegerOverflowFlagsAttr::get(
+        rewriter.getContext(), flags);
     bool hasFinalValue = doLoopOp.getFinalValue().has_value();
     bool isUnordered = doLoopOp.getUnordered().has_value();
 
@@ -111,7 +116,8 @@ struct DoLoopConversion : public mlir::OpRewritePattern<fir::DoLoopOp> {
 
     mlir::Value finalValue;
     if (hasTypedIV) {
-      finalValue = mlir::arith::AddIOp::create(rewriter, loc, iv, step);
+      finalValue =
+          mlir::arith::AddIOp::create(rewriter, loc, iv, step, iofAttr);
     } else if (hasFinalValue) {
       // Prefer re-using an existing `arith.addi` in the moved loop body if it
       // already computes the next `iv + step`.
@@ -124,7 +130,8 @@ struct DoLoopConversion : public mlir::OpRewritePattern<fir::DoLoopOp> {
         }
       }
       if (!finalValue)
-        finalValue = mlir::arith::AddIOp::create(rewriter, loc, iv, step);
+        finalValue =
+            mlir::arith::AddIOp::create(rewriter, loc, iv, step, iofAttr);
     }
 
     if (hasTypedIV || hasFinalValue || !results.empty()) {
@@ -163,16 +170,24 @@ struct DoLoopConversion : public mlir::OpRewritePattern<fir::DoLoopOp> {
 
 private:
   bool parallelUnorderedLoop;
+  bool setNSW;
 };
 
 struct IterWhileConversion : public mlir::OpRewritePattern<fir::IterWhileOp> {
-  using OpRewritePattern<fir::IterWhileOp>::OpRewritePattern;
+  IterWhileConversion(mlir::MLIRContext *context, bool setNSW = true,
+                      mlir::PatternBenefit benefit = 1)
+      : OpRewritePattern<fir::IterWhileOp>(context, benefit), setNSW(setNSW) {}
 
   mlir::LogicalResult
   matchAndRewrite(fir::IterWhileOp iterWhileOp,
                   mlir::PatternRewriter &rewriter) const override {
 
     mlir::Location loc = iterWhileOp.getLoc();
+    mlir::arith::IntegerOverflowFlags flags{};
+    if (setNSW)
+      flags = bitEnumSet(flags, mlir::arith::IntegerOverflowFlags::nsw);
+    auto iofAttr = mlir::arith::IntegerOverflowFlagsAttr::get(
+        rewriter.getContext(), flags);
     mlir::Value lowerBound = iterWhileOp.getLowerBound();
     mlir::Value upperBound = iterWhileOp.getUpperBound();
     mlir::Value step = iterWhileOp.getStep();
@@ -235,7 +250,8 @@ struct IterWhileConversion : public mlir::OpRewritePattern<fir::IterWhileOp> {
     mlir::Value iv = scfWhileOp.getAfterArguments()[0];
 
     rewriter.setInsertionPointToStart(afterBody);
-    results.push_back(mlir::arith::AddIOp::create(rewriter, loc, iv, step));
+    results.push_back(
+        mlir::arith::AddIOp::create(rewriter, loc, iv, step, iofAttr));
     llvm::append_range(results, hasFinalValue
                                     ? resultOp->getOperands().drop_front()
                                     : resultOp->getOperands());
@@ -249,6 +265,9 @@ struct IterWhileConversion : public mlir::OpRewritePattern<fir::IterWhileOp> {
                                      : scfWhileOp->getResults().drop_front());
     return mlir::success();
   }
+
+private:
+  bool setNSW;
 };
 
 void copyBlockAndTransformResult(mlir::PatternRewriter &rewriter,
@@ -294,13 +313,15 @@ struct IfConversion : public mlir::OpRewritePattern<fir::IfOp> {
 } // namespace
 
 void fir::populateFIRToSCFRewrites(mlir::RewritePatternSet &patterns,
-                                   bool parallelUnordered) {
-  patterns.add<IterWhileConversion, IfConversion>(patterns.getContext());
-  patterns.add<DoLoopConversion>(patterns.getContext(), parallelUnordered);
+                                   bool parallelUnordered, bool setNSW) {
+  patterns.add<IfConversion>(patterns.getContext());
+  patterns.add<IterWhileConversion>(patterns.getContext(), setNSW);
+  patterns.add<DoLoopConversion>(patterns.getContext(), parallelUnordered,
+                                 setNSW);
 }
 
 void FIRToSCFPass::runOnOperation() {
   mlir::RewritePatternSet patterns(&getContext());
-  fir::populateFIRToSCFRewrites(patterns, parallelUnordered);
+  fir::populateFIRToSCFRewrites(patterns, parallelUnordered, setNSW);
   walkAndApplyPatterns(getOperation(), std::move(patterns));
 }
diff --git a/flang/test/Fir/FirToSCF/do-extra.fir b/flang/test/Fir/FirToSCF/do-extra.fir
index 4d4e2c9d7e069..2d473e07a9407 100644
--- a/flang/test/Fir/FirToSCF/do-extra.fir
+++ b/flang/test/Fir/FirToSCF/do-extra.fir
@@ -137,7 +137,7 @@ func.func @mv_(%arg0: !fir.ref<!fir.array<3xi32>> {fir.bindc_name = "a", llvm.no
 // CHECK:           scf.for %{{.*}} = %[[C0]] to %[[VAL_2]] step %[[C1]] iter_args(%{{.*}} = %[[ARG0]]) -> (index) {
 // CHECK:             %[[MUL:.*]] = arith.muli %{{.*}}, %[[ARG2]] : index
 // CHECK:             %[[ADD0:.*]] = arith.addi %[[ARG0]], %[[MUL]] : index
-// CHECK:             %[[ADD1:.*]] = arith.addi %[[ADD0]], %[[ARG2]] : index
+// CHECK:             %[[ADD1:.*]] = arith.addi %[[ADD0]], %[[ARG2]] overflow<nsw> : index
 // CHECK:             scf.yield %[[ADD1]] : index
 // CHECK:           }
 
diff --git a/flang/test/Fir/FirToSCF/do-loop.fir b/flang/test/Fir/FirToSCF/do-loop.fir
index f81dbfc52015c..b13fa033b118a 100644
--- a/flang/test/Fir/FirToSCF/do-loop.fir
+++ b/flang/test/Fir/FirToSCF/do-loop.fir
@@ -46,7 +46,7 @@ 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 %{{.*}} = %[[C0]] to %[[TRIP]] step %[[C1]] iter_args(%[[IV:.*]] = %[[LB]]) -> (i32) {
-// CHECK:   %[[NEXT:.*]] = arith.addi %[[IV]], %[[STEP]] : i32
+// CHECK:   %[[NEXT:.*]] = arith.addi %[[IV]], %[[STEP]] overflow<nsw> : i32
 // CHECK:   fir.store %[[IV]] to %[[ADDR]] : !fir.ref<i32>
 // CHECK:   scf.yield %[[NEXT]] : i32
 // CHECK: }
@@ -212,7 +212,7 @@ func.func @loop_with_final_value_yielding_iv() {
 // CHECK:           %[[C0:.*]] = arith.constant 0 : index
 // CHECK:           %[[ONE:.*]] = arith.constant 1 : index
 // CHECK:           %[[LOOP:.*]]:2 = scf.for %{{.*}} = %[[C0]] to %[[TRIP]] step %[[ONE]] iter_args(%[[IVIN:.*]] = %[[C1]], %[[ACCIN:.*]] = %[[C0I32]]) -> (index, i32) {
-// CHECK:             %[[IVNEXT:.*]] = arith.addi %{{.*}}, %[[C1]] : index
+// CHECK:             %[[IVNEXT:.*]] = arith.addi %{{.*}}, %[[C1]] overflow<nsw> : index
 // CHECK:             %[[ACCOUT:.*]] = arith.addi %[[ACCIN]], %[[ACCIN]] : i32
 // CHECK:             scf.yield %[[IVNEXT]], %[[ACCOUT]] : index, i32
 // CHECK:           }
diff --git a/flang/test/Fir/FirToSCF/iter-while.fir b/flang/test/Fir/FirToSCF/iter-while.fir
index d980fa6fff93b..1e9bd827338cc 100644
--- a/flang/test/Fir/FirToSCF/iter-while.fir
+++ b/flang/test/Fir/FirToSCF/iter-while.fir
@@ -20,7 +20,7 @@
 // CHECK:             scf.condition(%[[ANDI_2]]) %[[VAL_0]], %[[VAL_1]], %[[VAL_2]], %[[VAL_3]] : index, i1, i16, i32
 // CHECK:           } do {
 // CHECK:           ^bb0(%[[VAL_4:.*]]: index, %[[VAL_5:.*]]: i1, %[[VAL_6:.*]]: i16, %[[VAL_7:.*]]: i32):
-// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_4]], %[[CONSTANT_2]] : index
+// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_4]], %[[CONSTANT_2]] overflow<nsw> : index
 // CHECK:             %[[CONSTANT_7:.*]] = arith.constant true
 // CHECK:             %[[CONSTANT_8:.*]] = arith.constant 22 : i16
 // CHECK:             %[[CONSTANT_9:.*]] = arith.constant 33 : i32
@@ -62,7 +62,7 @@ func.func @test_simple_iterate_while_1() -> (index, i1, i16, i32) {
 // CHECK:             scf.condition(%[[ANDI_2]]) %[[VAL_0]], %[[VAL_1]], %[[VAL_2]] : index, i1, i32
 // CHECK:           } do {
 // CHECK:           ^bb0(%[[VAL_3:.*]]: index, %[[VAL_4:.*]]: i1, %[[VAL_5:.*]]: i32):
-// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_3]], %[[CONSTANT_0]] : index
+// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_3]], %[[CONSTANT_0]] overflow<nsw> : index
 // CHECK:             %[[CONSTANT_2:.*]] = arith.constant 123 : i32
 // CHECK:             %[[CONSTANT_3:.*]] = arith.constant true
 // CHECK:             scf.yield %[[ADDI_0]], %[[CONSTANT_3]], %[[CONSTANT_2]] : index, i1, i32
@@ -99,7 +99,7 @@ func.func @test_simple_iterate_while_2(%start: index, %stop: index, %cond: i1, %
 // CHECK:             scf.condition(%[[ANDI_2]]) %[[VAL_0]], %[[VAL_1]] : index, i1
 // CHECK:           } do {
 // CHECK:           ^bb0(%[[VAL_2:.*]]: index, %[[VAL_3:.*]]: i1):
-// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_2]], %[[CONSTANT_0]] : index
+// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_2]], %[[CONSTANT_0]] overflow<nsw> : index
 // CHECK:             %[[VAL_4:.*]] = "test.get_some_value"() : () -> i1
 // CHECK:             scf.yield %[[ADDI_0]], %[[VAL_4]] : index, i1
 // CHECK:           } attributes {finalValue}
@@ -133,7 +133,7 @@ func.func @loop_with_negtive_step(%lo : index, %up : index) -> i1 {
 // CHECK:             scf.condition(%[[ANDI_2]]) %[[VAL_0]], %[[VAL_1]] : index, i1
 // CHECK:           } do {
 // CHECK:           ^bb0(%[[VAL_2:.*]]: index, %[[VAL_3:.*]]: i1):
-// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_2]], %[[CONSTANT_0]] : index
+// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_2]], %[[CONSTANT_0]] overflow<nsw> : index
 // CHECK:             %[[VAL_4:.*]] = "test.get_some_value"() : () -> i1
 // CHECK:             scf.yield %[[ADDI_0]], %[[VAL_4]] : index, i1
 // CHECK:           } attributes {finalValue}
@@ -168,7 +168,7 @@ func.func @loop_with_zero_step(%lo : index, %up : index) -> i1 {
 // CHECK:             scf.condition(%[[ANDI_2]]) %[[VAL_0]], %[[VAL_1]], %[[VAL_2]] : index, i1, i8
 // CHECK:           } do {
 // CHECK:           ^bb0(%[[VAL_3:.*]]: index, %[[VAL_4:.*]]: i1, %[[VAL_5:.*]]: i8):
-// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_3]], %[[CONSTANT_2]] : index
+// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_3]], %[[CONSTANT_2]] overflow<nsw> : index
 // CHECK:             scf.yield %[[ADDI_0]], %[[VAL_4]], %[[VAL_5]] : index, i1, i8
 // CHECK:           } attributes {finalValue}
 // CHECK:           return %[[VAL_6:.*]]#0, %[[VAL_6]]#1, %[[VAL_6]]#2 : index, i1, i8
@@ -205,7 +205,7 @@ func.func @test_zero_iterations() -> (index, i1, i8) {
 // CHECK:             scf.condition(%[[ANDI_2]]) %[[VAL_0]], %[[VAL_1]] : index, i1
 // CHECK:           } do {
 // CHECK:           ^bb0(%[[VAL_2:.*]]: index, %[[VAL_3:.*]]: i1):
-// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_2]], %[[CONSTANT_0]] : index
+// CHECK:             %[[ADDI_0:.*]] = arith.addi %[[VAL_2]], %[[CONSTANT_0]] overflow<nsw> : index
 // CHECK:             %[[VAL_4:.*]] = "test.get_some_value"() : () -> i1
 // CHECK:             scf.yield %[[ADDI_0]], %[[VAL_4]] : index, i1
 // CHECK:           }
diff --git a/flang/test/Fir/FirToSCF/iterate-while-extra.fir b/flang/test/Fir/FirToSCF/iterate-while-extra.fir
index e825a587d3e23..5901bb9992db0 100644
--- a/flang/test/Fir/FirToSCF/iterate-while-extra.fir
+++ b/flang/test/Fir/FirToSCF/iterate-while-extra.fir
@@ -21,7 +21,7 @@
 // CHECK:         scf.condition(%[[AND2]]) %[[IV]], %[[OK]] : index, i1
 // CHECK:       } do {
 // CHECK:       ^bb0(%[[IV2:.*]]: index, %[[OK2:.*]]: i1):
-// CHECK:         %[[NEXT:.*]] = arith.addi %[[IV2]], %c1
+// CHECK:         %[[NEXT:.*]] = arith.addi %[[IV2]], %c1 overflow<nsw> : index
 // CHECK:         scf.if %[[OK2]]
 // CHECK:         scf.yield
 
@@ -85,7 +85,7 @@ func.func @iterate_while() {
 // CHECK:         scf.condition([[AND2]]) %[[IVIN]], %[[OKIN]], %[[IV2IN]] : index, i1, index
 // CHECK:       } do {
 // CHECK:       ^bb0(%[[IV:.*]]: index, %[[OK:.*]]: i1, %[[IV2:.*]]: index):
-// CHECK:         [[NEXT:%[0-9]+]] = arith.addi %[[IV]], %c-1 : index
+// CHECK:         [[NEXT:%[0-9]+]] = arith.addi %[[IV]], %c-1 overflow<nsw> : index
 // CHECK:         [[ISSPACE:%[0-9]+]] = arith.cmpi eq, %{{.*}}, %c32_i8 : i8
 // CHECK:         scf.yield [[NEXT]], [[ISSPACE]], %[[IV]] : index, i1, index
 



More information about the flang-commits mailing list