[llvm] [IndVarSimplify] Keep additional nuw and nsw flags during LinearFunctionTestReplace (PR #177433)

via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 30 18:44:36 PDT 2026


https://github.com/yasmincs updated https://github.com/llvm/llvm-project/pull/177433

>From 8fdbef5107d19a6b62c8db8a94c4c80fd62071ca Mon Sep 17 00:00:00 2001
From: ysarita <ysarita at nvidia.com>
Date: Thu, 22 Jan 2026 18:50:02 +0000
Subject: [PATCH 1/5] Kept additional nsw nuw flags during
 linearfunctiontestreplace

---
 llvm/lib/Transforms/Scalar/IndVarSimplify.cpp | 39 ++++++++++++++++++-
 .../IndVarSimplify/lftr-multi-exit.ll         |  6 +--
 ...ple-unreachable-exits-for-vectorization.ll |  4 +-
 .../loop-rotation-vs-common-code-hoisting.ll  |  2 +-
 4 files changed, 43 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
index f377c192371ea..b049eab5ffcea 100644
--- a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
+++ b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
@@ -1095,10 +1095,45 @@ linearFunctionTestReplace(Loop *L, BasicBlock *ExitingBB,
   // dead, it could not be poison on the first iteration in the first place.)
   if (auto *BO = dyn_cast<BinaryOperator>(IncVar)) {
     const SCEVAddRecExpr *AR = cast<SCEVAddRecExpr>(SE->getSCEV(IncVar));
+
+    // However, if we successfully computed an ExitCount, we can infer nowrap
+    // properties from the original loop exit condition. If the original
+    // comparison was signed and SCEV computed an exit count, then NSW is safe.
+    // Similarly, if it was unsigned, NUW is safe. This is because SCEV's exit
+    // count analysis proves the loop exits before overflow occurs for that
+    // signedness.
+    bool KeepNSW = false;
+    bool KeepNUW = false;
+
+    BranchInst *BI = cast<BranchInst>(ExitingBB->getTerminator());
+    if (auto *OrigCmp = dyn_cast<ICmpInst>(BI->getCondition())) {
+      // Check if the chosen IV is actually used in the original comparison.
+      bool IVUsedInOriginalCmp = (OrigCmp->getOperand(0) == IndVar ||
+                                  OrigCmp->getOperand(0) == IncVar ||
+                                  OrigCmp->getOperand(1) == IndVar ||
+                                  OrigCmp->getOperand(1) == IncVar);
+
+      if (IVUsedInOriginalCmp) {
+        ICmpInst::Predicate Pred = OrigCmp->getPredicate();
+
+        // Don't keep flags if the predicate sign was changed by SimplifyIndvar.
+        // The samesign flag indicates the predicate was originally signed but
+        // was canonicalized to unsigned (e.g., slt -> samesign ult).
+        if (!OrigCmp->hasSameSign()) {
+          // If the original comparison was signed, SCEV proved NSW is safe.
+          KeepNSW = ICmpInst::isSigned(Pred);
+          // If the original comparison was unsigned, SCEV proved NUW is safe.
+          KeepNUW = ICmpInst::isUnsigned(Pred);
+        }
+      }
+    }
+
+    // Use SCEV's analysis, but also keep flags if the original comparison
+    // guarantees safety for that signedness.
     if (BO->hasNoUnsignedWrap())
-      BO->setHasNoUnsignedWrap(AR->hasNoUnsignedWrap());
+      BO->setHasNoUnsignedWrap(AR->hasNoUnsignedWrap() || KeepNUW);
     if (BO->hasNoSignedWrap())
-      BO->setHasNoSignedWrap(AR->hasNoSignedWrap());
+      BO->setHasNoSignedWrap(AR->hasNoSignedWrap() || KeepNSW);
   }
 
   Value *ExitCnt = genLoopLimit(
diff --git a/llvm/test/Transforms/IndVarSimplify/lftr-multi-exit.ll b/llvm/test/Transforms/IndVarSimplify/lftr-multi-exit.ll
index 0898c6c2dae34..a2cdcaaff59db 100644
--- a/llvm/test/Transforms/IndVarSimplify/lftr-multi-exit.ll
+++ b/llvm/test/Transforms/IndVarSimplify/lftr-multi-exit.ll
@@ -172,7 +172,7 @@ define void @unanalyzeable_latch(i32 %n) {
 ; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp ne i32 [[IV]], [[N:%.*]]
 ; CHECK-NEXT:    br i1 [[EXITCOND]], label [[LATCH]], label [[EXIT:%.*]]
 ; CHECK:       latch:
-; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[IV_NEXT]] = add nuw i32 [[IV]], 1
 ; CHECK-NEXT:    store i32 [[IV]], ptr @A, align 4
 ; CHECK-NEXT:    [[VOL:%.*]] = load volatile i32, ptr @A, align 4
 ; CHECK-NEXT:    [[C:%.*]] = icmp ult i32 [[VOL]], 1000
@@ -208,7 +208,7 @@ define void @single_exit_no_latch(i32 %n) {
 ; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp ne i32 [[IV]], [[N:%.*]]
 ; CHECK-NEXT:    br i1 [[EXITCOND]], label [[LATCH]], label [[EXIT:%.*]]
 ; CHECK:       latch:
-; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[IV_NEXT]] = add nuw i32 [[IV]], 1
 ; CHECK-NEXT:    store i32 [[IV]], ptr @A, align 4
 ; CHECK-NEXT:    br label [[LOOP]]
 ; CHECK:       exit:
@@ -247,7 +247,7 @@ define void @no_latch_exit(i32 %n, i32 %m) {
 ; CHECK-NEXT:    br i1 [[EXITCOND1]], label [[LATCH]], label [[EXIT]]
 ; CHECK:       latch:
 ; CHECK-NEXT:    store volatile i32 [[IV]], ptr @A, align 4
-; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[IV_NEXT]] = add nuw i32 [[IV]], 1
 ; CHECK-NEXT:    br label [[LOOP]]
 ; CHECK:       exit:
 ; CHECK-NEXT:    ret void
diff --git a/llvm/test/Transforms/PhaseOrdering/AArch64/peel-multiple-unreachable-exits-for-vectorization.ll b/llvm/test/Transforms/PhaseOrdering/AArch64/peel-multiple-unreachable-exits-for-vectorization.ll
index 2fe49a31b7722..4a0d81bb4fc4a 100644
--- a/llvm/test/Transforms/PhaseOrdering/AArch64/peel-multiple-unreachable-exits-for-vectorization.ll
+++ b/llvm/test/Transforms/PhaseOrdering/AArch64/peel-multiple-unreachable-exits-for-vectorization.ll
@@ -79,7 +79,7 @@ define i64 @sum_2_at_with_int_conversion(ptr %A, ptr %B, i64 %N) {
 ; CHECK-NEXT:    [[LV_I9:%.*]] = load i64, ptr [[GEP_IDX_I8]], align 8
 ; CHECK-NEXT:    [[ADD:%.*]] = add i64 [[LV_I]], [[SUM]]
 ; CHECK-NEXT:    [[SUM_NEXT]] = add i64 [[ADD]], [[LV_I9]]
-; CHECK-NEXT:    [[IV_NEXT]] = add nuw i64 [[IV]], 1
+; CHECK-NEXT:    [[IV_NEXT]] = add nuw nsw i64 [[IV]], 1
 ; CHECK-NEXT:    [[EXITCOND_NOT:%.*]] = icmp eq i64 [[IV]], [[SMAX]]
 ; CHECK-NEXT:    br i1 [[EXITCOND_NOT]], label [[EXIT]], label [[LOOP]], !llvm.loop [[LOOP3:![0-9]+]]
 ; CHECK:       error.i:
@@ -203,7 +203,7 @@ define i64 @sum_3_at_with_int_conversion(ptr %A, ptr %B, ptr %C, i64 %N) {
 ; CHECK-NEXT:    [[ADD_1:%.*]] = add i64 [[LV_I]], [[SUM]]
 ; CHECK-NEXT:    [[ADD_2:%.*]] = add i64 [[ADD_1]], [[LV_I9]]
 ; CHECK-NEXT:    [[SUM_NEXT]] = add i64 [[ADD_2]], [[LV_I20]]
-; CHECK-NEXT:    [[IV_NEXT]] = add nuw i64 [[IV]], 1
+; CHECK-NEXT:    [[IV_NEXT]] = add nuw nsw i64 [[IV]], 1
 ; CHECK-NEXT:    [[EXITCOND_NOT:%.*]] = icmp eq i64 [[IV]], [[SMAX]]
 ; CHECK-NEXT:    br i1 [[EXITCOND_NOT]], label [[EXIT]], label [[LOOP]], !llvm.loop [[LOOP5:![0-9]+]]
 ; CHECK:       error.i:
diff --git a/llvm/test/Transforms/PhaseOrdering/loop-rotation-vs-common-code-hoisting.ll b/llvm/test/Transforms/PhaseOrdering/loop-rotation-vs-common-code-hoisting.ll
index 5ff57eae3a773..9333be4e6c4c7 100644
--- a/llvm/test/Transforms/PhaseOrdering/loop-rotation-vs-common-code-hoisting.ll
+++ b/llvm/test/Transforms/PhaseOrdering/loop-rotation-vs-common-code-hoisting.ll
@@ -59,7 +59,7 @@ define void @_Z4loopi(i32 %width) {
 ; HOIST-NEXT:    br label [[RETURN]]
 ; HOIST:       for.body:
 ; HOIST-NEXT:    tail call void @f1()
-; HOIST-NEXT:    [[INC]] = add nuw i32 [[I_0]], 1
+; HOIST-NEXT:    [[INC]] = add nuw nsw i32 [[I_0]], 1
 ; HOIST-NEXT:    br label [[FOR_COND]]
 ; HOIST:       return:
 ; HOIST-NEXT:    ret void

>From 402c2e7663310a4b85d1b52fa0600b5d4855a2ee Mon Sep 17 00:00:00 2001
From: ysarita <ysarita at nvidia.com>
Date: Tue, 21 Apr 2026 18:56:32 +0000
Subject: [PATCH 2/5] updated lit test

---
 .../Transforms/IndVarSimplify/X86/loop-invariant-conditions.ll  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/Transforms/IndVarSimplify/X86/loop-invariant-conditions.ll b/llvm/test/Transforms/IndVarSimplify/X86/loop-invariant-conditions.ll
index 2e19311294fcb..65527329a87ca 100644
--- a/llvm/test/Transforms/IndVarSimplify/X86/loop-invariant-conditions.ll
+++ b/llvm/test/Transforms/IndVarSimplify/X86/loop-invariant-conditions.ll
@@ -526,7 +526,7 @@ define void @test3_neg(i64 %start) {
 ; CHECK-NEXT:    br label [[LOOP:%.*]]
 ; CHECK:       loop:
 ; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[START]], [[ENTRY:%.*]] ], [ [[INDVARS_IV_NEXT:%.*]], [[LOOP]] ]
-; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add i64 [[INDVARS_IV]], 1
+; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nsw i64 [[INDVARS_IV]], 1
 ; CHECK-NEXT:    [[CMP1:%.*]] = icmp ne i64 [[INDVARS_IV_NEXT]], [[TMP0]]
 ; CHECK-NEXT:    br i1 [[CMP1]], label [[LOOP]], label [[FOR_END:%.*]]
 ; CHECK:       for.end:

>From 82fe2476f459f40807eb0d79631d9b9a63dcfe43 Mon Sep 17 00:00:00 2001
From: ysarita <ysarita at nvidia.com>
Date: Tue, 21 Apr 2026 22:39:43 +0000
Subject: [PATCH 3/5] updated to CondBrInst

---
 llvm/lib/Transforms/Scalar/IndVarSimplify.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
index 1eb08736c60f3..49d6d3b6b99fb 100644
--- a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
+++ b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
@@ -1120,7 +1120,7 @@ linearFunctionTestReplace(Loop *L, BasicBlock *ExitingBB,
     bool KeepNSW = false;
     bool KeepNUW = false;
 
-    BranchInst *BI = cast<BranchInst>(ExitingBB->getTerminator());
+    CondBrInst *BI = cast<CondBrInst>(ExitingBB->getTerminator());
     if (auto *OrigCmp = dyn_cast<ICmpInst>(BI->getCondition())) {
       // Check if the chosen IV is actually used in the original comparison.
       bool IVUsedInOriginalCmp = (OrigCmp->getOperand(0) == IndVar ||

>From 64b7a81e5f6204df83b40de188abe16efe3138cf Mon Sep 17 00:00:00 2001
From: ysarita <ysarita at nvidia.com>
Date: Thu, 23 Apr 2026 23:08:04 +0000
Subject: [PATCH 4/5] Included samesign for nsw

---
 llvm/lib/Transforms/Scalar/IndVarSimplify.cpp | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
index 49d6d3b6b99fb..cb4bf64791311 100644
--- a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
+++ b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
@@ -1131,12 +1131,13 @@ linearFunctionTestReplace(Loop *L, BasicBlock *ExitingBB,
       if (IVUsedInOriginalCmp) {
         ICmpInst::Predicate Pred = OrigCmp->getPredicate();
 
-        // Don't keep flags if the predicate sign was changed by SimplifyIndvar.
-        // The samesign flag indicates the predicate was originally signed but
-        // was canonicalized to unsigned (e.g., slt -> samesign ult).
+        // If the original comparison was signed, SCEV proved NSW is safe.
+        KeepNSW = ICmpInst::isSigned(Pred);
+        // Don't keep nuw flag if the predicate sign was changed by
+        // SimplifyIndvar. The samesign flag indicates the predicate was
+        // originally signed but was canonicalized to unsigned (e.g., slt ->
+        // samesign ult).
         if (!OrigCmp->hasSameSign()) {
-          // If the original comparison was signed, SCEV proved NSW is safe.
-          KeepNSW = ICmpInst::isSigned(Pred);
           // If the original comparison was unsigned, SCEV proved NUW is safe.
           KeepNUW = ICmpInst::isUnsigned(Pred);
         }

>From 0517e44605010cea228457510c51b856cb218689 Mon Sep 17 00:00:00 2001
From: ysarita <ysarita at nvidia.com>
Date: Fri, 1 May 2026 01:43:56 +0000
Subject: [PATCH 5/5] excluded samesign flag and added tests

---
 llvm/lib/Transforms/Scalar/IndVarSimplify.cpp |  11 +-
 .../Transforms/IndVarSimplify/lftr-pr31181.ll | 110 ++++++++++++++++++
 2 files changed, 115 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
index cb4bf64791311..49d6d3b6b99fb 100644
--- a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
+++ b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
@@ -1131,13 +1131,12 @@ linearFunctionTestReplace(Loop *L, BasicBlock *ExitingBB,
       if (IVUsedInOriginalCmp) {
         ICmpInst::Predicate Pred = OrigCmp->getPredicate();
 
-        // If the original comparison was signed, SCEV proved NSW is safe.
-        KeepNSW = ICmpInst::isSigned(Pred);
-        // Don't keep nuw flag if the predicate sign was changed by
-        // SimplifyIndvar. The samesign flag indicates the predicate was
-        // originally signed but was canonicalized to unsigned (e.g., slt ->
-        // samesign ult).
+        // Don't keep flags if the predicate sign was changed by SimplifyIndvar.
+        // The samesign flag indicates the predicate was originally signed but
+        // was canonicalized to unsigned (e.g., slt -> samesign ult).
         if (!OrigCmp->hasSameSign()) {
+          // If the original comparison was signed, SCEV proved NSW is safe.
+          KeepNSW = ICmpInst::isSigned(Pred);
           // If the original comparison was unsigned, SCEV proved NUW is safe.
           KeepNUW = ICmpInst::isUnsigned(Pred);
         }
diff --git a/llvm/test/Transforms/IndVarSimplify/lftr-pr31181.ll b/llvm/test/Transforms/IndVarSimplify/lftr-pr31181.ll
index e836a7dfccea0..7c11d09f8b928 100644
--- a/llvm/test/Transforms/IndVarSimplify/lftr-pr31181.ll
+++ b/llvm/test/Transforms/IndVarSimplify/lftr-pr31181.ll
@@ -358,3 +358,113 @@ always_taken:
 for.end:
   ret i32 0
 }
+
+; Below are similar tests with explicit samesign flag
+
+define i32 @test_samesign_ult_negative_iv() {
+; CHECK-LABEL: @test_samesign_ult_negative_iv(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[STOREMERGE:%.*]] = phi i32 [ -2, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i32 [[STOREMERGE]], ptr @a, align 4
+; CHECK-NEXT:    [[INC]] = add nsw i32 [[STOREMERGE]], 1
+; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp ne i32 [[INC]], 0
+; CHECK-NEXT:    br i1 [[EXITCOND]], label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  br label %loop
+
+loop:
+  %storemerge = phi i32 [ -2, %entry ], [ %inc, %loop ]
+  store i32 %storemerge, ptr @a
+  %cmp = icmp samesign ult i32 %storemerge, -1
+  %inc = add nuw nsw i32 %storemerge, 1
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret i32 0
+}
+
+define i32 @test_samesign_slt_negative_iv() {
+; CHECK-LABEL: @test_samesign_slt_negative_iv(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[STOREMERGE:%.*]] = phi i32 [ -2, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i32 [[STOREMERGE]], ptr @a, align 4
+; CHECK-NEXT:    [[INC]] = add nsw i32 [[STOREMERGE]], 1
+; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp ne i32 [[INC]], 0
+; CHECK-NEXT:    br i1 [[EXITCOND]], label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  br label %loop
+
+loop:
+  %storemerge = phi i32 [ -2, %entry ], [ %inc, %loop ]
+  store i32 %storemerge, ptr @a
+  %cmp = icmp samesign slt i32 %storemerge, -1
+  %inc = add nuw nsw i32 %storemerge, 1
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret i32 0
+}
+
+define i32 @test_samesign_ult_positive_iv() {
+; CHECK-LABEL: @test_samesign_ult_positive_iv(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[STOREMERGE:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i32 [[STOREMERGE]], ptr @a, align 4
+; CHECK-NEXT:    [[INC]] = add nuw i32 [[STOREMERGE]], 1
+; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp ne i32 [[INC]], -2147483648
+; CHECK-NEXT:    br i1 [[EXITCOND]], label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  br label %loop
+
+loop:
+  %storemerge = phi i32 [ 0, %entry ], [ %inc, %loop ]
+  store i32 %storemerge, ptr @a
+  %cmp = icmp samesign ult i32 %storemerge, 2147483647
+  %inc = add nuw nsw i32 %storemerge, 1
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret i32 0
+}
+
+define i32 @test_samesign_slt_positive_iv() {
+; CHECK-LABEL: @test_samesign_slt_positive_iv(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[STOREMERGE:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i32 [[STOREMERGE]], ptr @a, align 4
+; CHECK-NEXT:    [[INC]] = add nuw i32 [[STOREMERGE]], 1
+; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp ne i32 [[INC]], -2147483648
+; CHECK-NEXT:    br i1 [[EXITCOND]], label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  br label %loop
+
+loop:
+  %storemerge = phi i32 [ 0, %entry ], [ %inc, %loop ]
+  store i32 %storemerge, ptr @a
+  %cmp = icmp samesign slt i32 %storemerge, 2147483647
+  %inc = add nuw nsw i32 %storemerge, 1
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret i32 0
+}



More information about the llvm-commits mailing list