[llvm] [SCEV] Generalize A + zext(-A + B) fold to A + zext(C + X) (PR #209160)

Aleksandr Popov via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 13 05:26:32 PDT 2026


https://github.com/aleks-tmb created https://github.com/llvm/llvm-project/pull/209160

[SCEV] Generalize A + zext(-A + B) fold to A + zext(C + X)

Extend the existing fold `A + zext(-A + B) -> zext(B)` to the more
general form `A + zext(C + X) -> (A + sext(C)) + zext(X)`, where C is
any negative constant and A is a constant so that `A + sext(C)` folds
to a single wide constant.

Correctness still relies on NUW of `(-C) + (C + X)`, which proves that
`C + X` does not wrap below zero, so `zext(C + X) == sext(C) + zext(X)`.

Related to #208778

>From ddf6de5f8d5c7d692bf07d0b5e73eaf8435c74b1 Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Mon, 13 Jul 2026 11:40:29 +0000
Subject: [PATCH 1/2] [SCEV] Generalize A + zext(-A + B) fold to A + zext(C +
 X)

Extend the existing fold `A + zext(-A + B) -> zext(B)` to the more
general form `A + zext(C + X) -> (A + sext(C)) + zext(X)`, where C is
any negative constant and A is a constant so that `A + sext(C)` folds
to a single wide constant.

Correctness still relies on NUW of `(-C) + (C + X)`, which proves that
`C + X` does not wrap below zero, so `zext(C + X) == sext(C) + zext(X)`.

Related to #208778
---
 llvm/lib/Analysis/ScalarEvolution.cpp         | 33 ++++++++++------
 .../ScalarEvolution/zext-add-nsw-fold.ll      |  2 +-
 .../single_early_exit_zext_trip_count.ll      | 38 ++++++++++++++++---
 3 files changed, 55 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 134be6ac097e0..c3b2693fe0373 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -2809,17 +2809,28 @@ const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
       }
     }
 
-    // Try to push the constant operand into a ZExt: A + zext (-A + B) -> zext
-    // (B), if trunc (A) + -A + B  does not unsigned-wrap.
-    const SCEVAddExpr *InnerAdd;
-    if (match(B, m_scev_ZExt(m_scev_Add(InnerAdd)))) {
-      const SCEV *NarrowA = getTruncateExpr(A, InnerAdd->getType());
-      if (NarrowA == getNegativeSCEV(InnerAdd->getOperand(0)) &&
-          getZeroExtendExpr(NarrowA, B->getType()) == A &&
-          hasFlags(StrengthenNoWrapFlags(this, scAddExpr, {NarrowA, InnerAdd},
-                                         SCEV::FlagAnyWrap),
-                   SCEV::FlagNUW)) {
-        return getZeroExtendExpr(getAddExpr(NarrowA, InnerAdd), B->getType());
+    // Push a negative constant addend out of a ZExt when the inner add is
+    // provably non-negative in the narrow type:
+    //
+    //   A + zext(C + X)  ->  WideAC + zext(X)   [WideAC = A + sext(C)]
+    //
+    // Require A to be a constant so that `A + sext(C)` folds into a single wide
+    // constant, actually simplifying the expression.
+    const SCEVAddExpr *Add;
+    if (isa<SCEVConstant>(A) && match(B, m_scev_ZExt(m_scev_Add(Add)))) {
+      const APInt *C;
+      const SCEV *X;
+      if (match(Add, m_scev_Add(m_scev_APInt(C), m_SCEV(X))) &&
+          C->isNegative() &&
+          // NUW on `(-C) + (C + X) = X` proves that `C + X` did not wrap
+          // below zero, so `zext(C + X) == sext(C) + zext(X)`.
+          hasFlags(StrengthenNoWrapFlags(this, scAddExpr,
+                                        {getConstant(-*C), Add},
+                                        SCEV::FlagAnyWrap),
+                  SCEV::FlagNUW)) {
+        return getAddExpr(
+            A, getSignExtendExpr(getConstant(*C), B->getType()),
+            getZeroExtendExpr(X, B->getType()));
       }
     }
   }
diff --git a/llvm/test/Analysis/ScalarEvolution/zext-add-nsw-fold.ll b/llvm/test/Analysis/ScalarEvolution/zext-add-nsw-fold.ll
index 06e1f48a5f2c4..a95a83c425c7d 100644
--- a/llvm/test/Analysis/ScalarEvolution/zext-add-nsw-fold.ll
+++ b/llvm/test/Analysis/ScalarEvolution/zext-add-nsw-fold.ll
@@ -107,7 +107,7 @@ define i64 @mul_umax_fold_round_up(i8 %x) {
 ; CHECK-NEXT:    %add = add nsw i32 -10, %mul
 ; CHECK-NEXT:    --> (-10 + (4 * (3 umax (zext i8 %x to i32)))<nuw><nsw>)<nsw> U: [2,1011) S: [2,1011)
 ; CHECK-NEXT:    %ext = zext i32 %add to i64
-; CHECK-NEXT:    --> (2 + (zext i32 (-12 + (4 * (3 umax (zext i8 %x to i32)))<nuw><nsw>)<nsw> to i64))<nuw><nsw> U: [2,1011) S: [2,1011)
+; CHECK-NEXT:    --> (-10 + (4 * (3 umax (zext i8 %x to i64)))<nuw><nsw>)<nsw> U: [2,1011) S: [2,1011)
 ; CHECK-NEXT:  Determining loop execution counts for: @mul_umax_fold_round_up
 ;
   %zx = zext i8 %x to i32
diff --git a/llvm/test/Transforms/LoopVectorize/single_early_exit_zext_trip_count.ll b/llvm/test/Transforms/LoopVectorize/single_early_exit_zext_trip_count.ll
index 59fe0321c0112..fb591fc3787b8 100644
--- a/llvm/test/Transforms/LoopVectorize/single_early_exit_zext_trip_count.ll
+++ b/llvm/test/Transforms/LoopVectorize/single_early_exit_zext_trip_count.ll
@@ -28,17 +28,43 @@ define void @test1(ptr %p) {
 ; CHECK:       [[PREHEADER]]:
 ; CHECK-NEXT:    [[EXIT_32:%.*]] = add nsw i32 [[LEN]], -1
 ; CHECK-NEXT:    [[EXIT:%.*]] = zext i32 [[EXIT_32]] to i64
+; CHECK-NEXT:    [[TMP0:%.*]] = add nuw nsw i64 [[EXIT]], 1
+; CHECK-NEXT:    [[MIN_ITERS_CHECK:%.*]] = icmp ult i64 [[TMP0]], 4
+; CHECK-NEXT:    br i1 [[MIN_ITERS_CHECK]], label %[[SCALAR_PH:.*]], label %[[VECTOR_PH:.*]]
+; CHECK:       [[VECTOR_PH]]:
+; CHECK-NEXT:    [[N_MOD_VF:%.*]] = urem i64 [[TMP0]], 4
+; CHECK-NEXT:    [[N_VEC:%.*]] = sub i64 [[TMP0]], [[N_MOD_VF]]
 ; CHECK-NEXT:    br label %[[LOOP:.*]]
 ; CHECK:       [[LOOP]]:
-; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ], [ 0, %[[PREHEADER]] ]
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY_INTERIM:.*]] ]
 ; CHECK-NEXT:    [[ELEM_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[BASE]], i64 [[IV]]
-; CHECK-NEXT:    [[ELEM:%.*]] = load i8, ptr [[ELEM_PTR]], align 1
+; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <4 x i8>, ptr [[ELEM_PTR]], align 1
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq <4 x i8> [[WIDE_LOAD]], zeroinitializer
+; CHECK-NEXT:    [[TMP3:%.*]] = freeze <4 x i1> [[TMP2]]
+; CHECK-NEXT:    [[TMP4:%.*]] = call i1 @llvm.vector.reduce.or.v4i1(<4 x i1> [[TMP3]])
+; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[IV]], 4
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp eq i64 [[INDEX_NEXT]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[TMP4]], label %[[VECTOR_EARLY_EXIT:.*]], label %[[VECTOR_BODY_INTERIM]]
+; CHECK:       [[VECTOR_BODY_INTERIM]]:
+; CHECK-NEXT:    br i1 [[TMP5]], label %[[MIDDLE_BLOCK:.*]], label %[[LOOP]], !llvm.loop [[LOOP1:![0-9]+]]
+; CHECK:       [[MIDDLE_BLOCK]]:
+; CHECK-NEXT:    [[CMP_N:%.*]] = icmp eq i64 [[TMP0]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[CMP_N]], label %[[RET_LOOPEXIT:.*]], label %[[SCALAR_PH]]
+; CHECK:       [[VECTOR_EARLY_EXIT]]:
+; CHECK-NEXT:    br label %[[RET_LOOPEXIT]]
+; CHECK:       [[SCALAR_PH]]:
+; CHECK-NEXT:    [[BC_RESUME_VAL:%.*]] = phi i64 [ [[N_VEC]], %[[MIDDLE_BLOCK]] ], [ 0, %[[PREHEADER]] ]
+; CHECK-NEXT:    br label %[[LOOP2:.*]]
+; CHECK:       [[LOOP2]]:
+; CHECK-NEXT:    [[IV1:%.*]] = phi i64 [ [[IV_NEXT:%.*]], %[[LATCH:.*]] ], [ [[BC_RESUME_VAL]], %[[SCALAR_PH]] ]
+; CHECK-NEXT:    [[ELEM_PTR1:%.*]] = getelementptr inbounds nuw i8, ptr [[BASE]], i64 [[IV1]]
+; CHECK-NEXT:    [[ELEM:%.*]] = load i8, ptr [[ELEM_PTR1]], align 1
 ; CHECK-NEXT:    [[IS_TARGET:%.*]] = icmp eq i8 [[ELEM]], 0
-; CHECK-NEXT:    br i1 [[IS_TARGET]], label %[[RET_LOOPEXIT:.*]], label %[[LATCH]]
+; CHECK-NEXT:    br i1 [[IS_TARGET]], label %[[RET_LOOPEXIT]], label %[[LATCH]]
 ; CHECK:       [[LATCH]]:
-; CHECK-NEXT:    [[IV_NEXT]] = add nuw nsw i64 [[IV]], 1
-; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp ult i64 [[IV]], [[EXIT]]
-; CHECK-NEXT:    br i1 [[LOOP_COND]], label %[[LOOP]], label %[[RET_LOOPEXIT]]
+; CHECK-NEXT:    [[IV_NEXT]] = add nuw nsw i64 [[IV1]], 1
+; CHECK-NEXT:    [[LOOP_COND:%.*]] = icmp ult i64 [[IV1]], [[EXIT]]
+; CHECK-NEXT:    br i1 [[LOOP_COND]], label %[[LOOP2]], label %[[RET_LOOPEXIT]], !llvm.loop [[LOOP4:![0-9]+]]
 ; CHECK:       [[RET_LOOPEXIT]]:
 ; CHECK-NEXT:    br label %[[RET]]
 ; CHECK:       [[RET]]:

>From 846650fdbafae4e9f4627b9b64631367add9c628 Mon Sep 17 00:00:00 2001
From: Aleksandr Popov <apopov at azul.com>
Date: Mon, 13 Jul 2026 12:23:57 +0000
Subject: [PATCH 2/2] Update test

---
 llvm/test/Transforms/IndVarSimplify/scev-update-loop-opt.ll | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/test/Transforms/IndVarSimplify/scev-update-loop-opt.ll b/llvm/test/Transforms/IndVarSimplify/scev-update-loop-opt.ll
index f716796745fb7..01060490bb0f3 100644
--- a/llvm/test/Transforms/IndVarSimplify/scev-update-loop-opt.ll
+++ b/llvm/test/Transforms/IndVarSimplify/scev-update-loop-opt.ll
@@ -1,4 +1,4 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals none --version 6
 ; RUN: opt %s -passes="loop(loop-idiom,indvars,loop-deletion,loop-unroll-full)" -S | FileCheck %s
 ; REQUIRES: asserts
 
@@ -10,15 +10,15 @@ define void @loop_limit_test(i32 %conv5, i1 %cmp13, i1 %cmp20, i1 %cmp27, i1 %cm
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    [[TMP0:%.*]] = add i32 [[CONV5]], 1
 ; CHECK-NEXT:    [[TMP1:%.*]] = zext i32 [[TMP0]] to i64
-; CHECK-NEXT:    [[TMP2:%.*]] = zext i32 [[CONV5]] to i64
 ; CHECK-NEXT:    br label %[[FOR_COND:.*]]
 ; CHECK:       [[FOR_COND_LOOPEXIT:.*]]:
 ; CHECK-NEXT:    br label %[[FOR_COND]]
 ; CHECK:       [[FOR_COND]]:
+; CHECK-NEXT:    [[TMP2:%.*]] = zext i32 [[CONV5]] to i64
 ; CHECK-NEXT:    br label %[[FOR_COND2:.*]]
 ; CHECK:       [[FOR_COND2]]:
 ; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[FOR_COND_CLEANUP14:.*]] ], [ 0, %[[FOR_COND]] ]
-; CHECK-NEXT:    [[CMP6:%.*]] = icmp samesign ult i64 [[INDVARS_IV]], [[TMP2]]
+; CHECK-NEXT:    [[CMP6:%.*]] = icmp ne i64 [[INDVARS_IV]], [[TMP2]]
 ; CHECK-NEXT:    br i1 [[CMP6]], label %[[FOR_COND9_PREHEADER:.*]], label %[[FOR_COND_LOOPEXIT]]
 ; CHECK:       [[FOR_COND9_PREHEADER]]:
 ; CHECK-NEXT:    br label %[[FOR_COND9:.*]]



More information about the llvm-commits mailing list