[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