[llvm] [SCEV] Rework wrap-flag-inference in zext-addrec (PR #217405)
Ramkumar Ramachandra via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 21 11:18:50 PDT 2026
https://github.com/artagnon updated https://github.com/llvm/llvm-project/pull/217405
>From 4371b12898251273749edc59ac5e7edf5d42daa9 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Wed, 19 Aug 2026 18:09:39 +0100
Subject: [PATCH] [SCEV] Rework wrap-flag-inferrence in zext-addrec
Rewrite getZeroExtendExprImpl to avoid the roundabout method of creating
zero-extend expressions to check no-wrap, by computing the no-wrap
information using induction and reading it off the expression directly.
The new code has much better compile-time, while not being exactly
equivalent to the old code.
---
llvm/lib/Analysis/ScalarEvolution.cpp | 132 +++---------------
.../no-wrap-unknown-becount.ll | 2 +-
.../CodeGen/PowerPC/hardware-loops-crash.ll | 9 +-
.../test/Transforms/IndVarSimplify/pr66066.ll | 2 +-
.../PhaseOrdering/scev-custom-dl.ll | 2 +-
5 files changed, 28 insertions(+), 119 deletions(-)
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 6095f59ec1413..f57757b206324 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -1656,117 +1656,36 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
// operands (often constants). This allows analysis of something like
// this: for (unsigned char X = 0; X < 100; ++X) { int Y = X; }
if (match(Op, m_scev_AffineAddRec(m_SCEV(Start), m_SCEV(Step), m_Loop(L)))) {
+ // Redo the AddRec check, computing nuw this time.
const auto *AR = cast<SCEVAddRecExpr>(Op);
- unsigned BitWidth = getTypeSizeInBits(AR->getType());
-
- // The no-unsigned-wrap case is handled before the uniquing lookup above.
-
- // Check whether the backedge-taken count is SCEVCouldNotCompute.
- // Note that this serves two purposes: It filters out loops that are
- // simply not analyzable, and it covers the case where this code is
- // being called from within backedge-taken count analysis, such that
- // attempting to ask for the backedge-taken count would likely result
- // in infinite recursion. In the later case, the analysis code will
- // cope with a conservative value, and it will take care to purge
- // that value once it has finished.
- const SCEV *MaxBECount = getConstantMaxBackedgeTakenCount(L);
- if (!isa<SCEVCouldNotCompute>(MaxBECount)) {
- // Manually compute the final value for AR, checking for overflow.
+ inferNoWrapViaConstantRanges(AR);
+ auto NewFlags = proveNoUnsignedWrapViaInduction(AR);
+ if (!hasFlags(NewFlags, SCEV::FlagNUW) &&
+ proveNoWrapByVaryingStart<SCEVZeroExtendExpr>(Start, Step, L))
+ NewFlags |= SCEV::FlagNUW;
+ setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), NewFlags);
- // Check whether the backedge-taken count can be losslessly casted to
- // the addrec's type. The count is always unsigned.
- const SCEV *CastedMaxBECount =
- getTruncateOrZeroExtend(MaxBECount, Start->getType(), Depth);
- const SCEV *RecastedMaxBECount = getTruncateOrZeroExtend(
- CastedMaxBECount, MaxBECount->getType(), Depth);
- if (MaxBECount == RecastedMaxBECount) {
- Type *WideTy = IntegerType::get(getContext(), BitWidth * 2);
- // Check whether Start+Step*MaxBECount has no unsigned overflow.
- const SCEV *ZMul =
- getMulExpr(CastedMaxBECount, Step, SCEV::FlagAnyWrap, Depth + 1);
- const SCEV *ZAdd = getZeroExtendExpr(
- getAddExpr(Start, ZMul, SCEV::FlagAnyWrap, Depth + 1), WideTy,
- Depth + 1);
- const SCEV *WideStart = getZeroExtendExpr(Start, WideTy, Depth + 1);
- const SCEV *WideMaxBECount =
- getZeroExtendExpr(CastedMaxBECount, WideTy, Depth + 1);
- const SCEV *OperandExtendedAdd =
- getAddExpr(WideStart,
- getMulExpr(WideMaxBECount,
- getZeroExtendExpr(Step, WideTy, Depth + 1),
- SCEV::FlagAnyWrap, Depth + 1),
- SCEV::FlagAnyWrap, Depth + 1);
- if (ZAdd == OperandExtendedAdd) {
- // Cache knowledge of AR NUW, which is propagated to this AddRec.
- setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNUW);
- // Return the expression with the addrec on the outside.
- Start =
- getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
- Step = getZeroExtendExpr(Step, Ty, Depth + 1);
- return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
- }
- // Similar to above, only this time treat the step value as signed.
- // This covers loops that count down.
- OperandExtendedAdd =
- getAddExpr(WideStart,
- getMulExpr(WideMaxBECount,
- getSignExtendExpr(Step, WideTy, Depth + 1),
- SCEV::FlagAnyWrap, Depth + 1),
- SCEV::FlagAnyWrap, Depth + 1);
- if (ZAdd == OperandExtendedAdd) {
- // Cache knowledge of AR NW, which is propagated to this AddRec.
- // Negative step causes unsigned wrap, but it still can't self-wrap.
- setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNW);
- // Return the expression with the addrec on the outside.
- Start =
- getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
- Step = getSignExtendExpr(Step, Ty, Depth + 1);
- return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
- }
- }
+ // If we have nuw, the zero-extend distributes over the recurrence.
+ if (AR->hasNoUnsignedWrap()) {
+ Start = getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
+ Step = getZeroExtendExpr(Step, Ty, Depth + 1);
+ return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
}
- // Normally, in the cases we can prove no-overflow via a
- // backedge guarding condition, we can also compute a backedge
- // taken count for the loop. The exceptions are assumptions and
- // guards present in the loop -- SCEV is not great at exploiting
- // these to compute max backedge taken counts, but can still use
- // these to prove lack of overflow. Use this fact to avoid
- // doing extra work that may not pay off.
- if (!isa<SCEVCouldNotCompute>(MaxBECount) || HasGuards ||
- !AC.assumptions().empty()) {
-
- auto NewFlags = proveNoUnsignedWrapViaInduction(AR);
- setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), NewFlags);
- if (AR->hasNoUnsignedWrap()) {
- // Same as nuw case above - duplicated here to avoid a compile time
- // issue. It's not clear that the order of checks does matter, but
- // it's one of two issue possible causes for a change which was
- // reverted. Be conservative for the moment.
+ // For a negative step, we can sign-extend the step iff doing so only
+ // traverses values in the range sext([0,SMAX]). Note that this does not
+ // imply no-self-wrap.
+ if (isKnownNegative(Step)) {
+ unsigned BitWidth = getTypeSizeInBits(AR->getType());
+ const SCEV *N =
+ getConstant(APInt::getMaxValue(BitWidth) - getSignedRangeMin(Step));
+ if (isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
+ isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N)) {
Start =
getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
- Step = getZeroExtendExpr(Step, Ty, Depth + 1);
+ Step = getSignExtendExpr(Step, Ty, Depth + 1);
return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
}
-
- // For a negative step, we can extend the operands iff doing so only
- // traverses values in the range zext([0,UINT_MAX]).
- if (isKnownNegative(Step)) {
- const SCEV *N =
- getConstant(APInt::getMaxValue(BitWidth) - getSignedRangeMin(Step));
- if (isLoopBackedgeGuardedByCond(L, ICmpInst::ICMP_UGT, AR, N) ||
- isKnownOnEveryIteration(ICmpInst::ICMP_UGT, AR, N)) {
- // Cache knowledge of AR NW, which is propagated to this
- // AddRec. Negative step causes unsigned wrap, but it
- // still can't self-wrap.
- setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNW);
- // Return the expression with the addrec on the outside.
- Start =
- getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
- Step = getSignExtendExpr(Step, Ty, Depth + 1);
- return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
- }
- }
}
// zext({C,+,Step}) --> (zext(D) + zext({C-D,+,Step}))<nuw><nsw>
@@ -1784,13 +1703,6 @@ const SCEV *ScalarEvolution::getZeroExtendExprImpl(SCEVUse Op, Type *Ty,
Depth + 1);
}
}
-
- if (proveNoWrapByVaryingStart<SCEVZeroExtendExpr>(Start, Step, L)) {
- setNoWrapFlags(const_cast<SCEVAddRecExpr *>(AR), SCEV::FlagNUW);
- Start = getExtendAddRecStart<SCEVZeroExtendExpr>(AR, Ty, this, Depth + 1);
- Step = getZeroExtendExpr(Step, Ty, Depth + 1);
- return getAddRecExpr(Start, Step, L, AR->getNoWrapFlags());
- }
}
// zext(A % B) --> zext(A) % zext(B)
diff --git a/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll b/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
index 0aa13c5b06ca2..bbc36da11dda7 100644
--- a/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
+++ b/llvm/test/Analysis/ScalarEvolution/no-wrap-unknown-becount.ll
@@ -251,7 +251,7 @@ define void @u_2(ptr %cond) {
; CHECK-NEXT: %iv.inc = add i32 %iv, -2
; CHECK-NEXT: --> {29998,+,-2}<%loop> U: [0,-1) S: [-2147483648,2147483647) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
; CHECK-NEXT: %iv.zext = zext i32 %iv to i64
-; CHECK-NEXT: --> {30000,+,-2}<nw><%loop> U: [0,-1) S: [-9223372036854775808,9223372036854775807) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
+; CHECK-NEXT: --> {30000,+,-2}<%loop> U: [0,-1) S: [-9223372036854775808,9223372036854775807) Exits: <<Unknown>> LoopDispositions: { %loop: Computable }
; CHECK-NEXT: %c = load volatile i1, ptr %cond, align 1
; CHECK-NEXT: --> %c U: full-set S: full-set Exits: <<Unknown>> LoopDispositions: { %loop: Variant }
; CHECK-NEXT: Determining loop execution counts for: @u_2
diff --git a/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll b/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
index afa0f8c4adc0a..7616847afef80 100644
--- a/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
+++ b/llvm/test/CodeGen/PowerPC/hardware-loops-crash.ll
@@ -24,22 +24,19 @@ define void @test() {
; CHECK-NEXT: call void @llvm.set.loop.iterations.i64(i64 51)
; CHECK-NEXT: br label [[WHILE_COND25:%.*]]
; CHECK: while.cond25:
-; CHECK-NEXT: [[INDVAR:%.*]] = phi i64 [ 0, [[WHILE_COND25_PREHEADER]] ], [ [[INDVAR_NEXT:%.*]], [[LAND_RHS:%.*]] ]
-; CHECK-NEXT: [[INDVARS_IV349:%.*]] = phi i64 [ [[INDVARS_IV_NEXT350:%.*]], [[LAND_RHS]] ], [ [[INDVARS_IV349_PH]], [[WHILE_COND25_PREHEADER]] ]
+; CHECK-NEXT: [[INDVARS_IV349:%.*]] = phi i64 [ [[INDVARS_IV_NEXT350:%.*]], [[LAND_RHS:%.*]] ], [ [[INDVARS_IV349_PH]], [[WHILE_COND25_PREHEADER]] ]
; CHECK-NEXT: [[TMP0:%.*]] = call i1 @llvm.loop.decrement.i64(i64 1)
; CHECK-NEXT: br i1 [[TMP0]], label [[LAND_RHS]], label [[WHILE_END187:%.*]]
; CHECK: land.rhs:
; CHECK-NEXT: [[INDVARS_IV_NEXT350]] = add nsw i64 [[INDVARS_IV349]], -1
; CHECK-NEXT: [[C_1:%.*]] = call i1 @cond()
-; CHECK-NEXT: [[INDVAR_NEXT]] = add i64 [[INDVAR]], 1
; CHECK-NEXT: br i1 [[C_1]], label [[WHILE_COND25]], label [[WHILE_END:%.*]]
; CHECK: while.end:
-; CHECK-NEXT: [[INDVAR_LCSSA1:%.*]] = phi i64 [ [[INDVAR]], [[LAND_RHS]] ]
; CHECK-NEXT: [[C_2:%.*]] = call i1 @cond()
; CHECK-NEXT: br i1 [[C_2]], label [[WHILE_END187]], label [[WHILE_COND35_PREHEADER:%.*]]
; CHECK: while.cond35.preheader:
-; CHECK-NEXT: [[TMP1:%.*]] = mul nsw i64 [[INDVAR_LCSSA1]], -1
-; CHECK-NEXT: [[TMP2:%.*]] = add i64 [[TMP1]], 51
+; CHECK-NEXT: [[TMP1:%.*]] = and i64 [[INDVARS_IV349]], 4294967295
+; CHECK-NEXT: [[TMP2:%.*]] = add nuw nsw i64 [[TMP1]], 1
; CHECK-NEXT: call void @llvm.set.loop.iterations.i64(i64 [[TMP2]])
; CHECK-NEXT: br label [[WHILE_COND35:%.*]]
; CHECK: while.cond35:
diff --git a/llvm/test/Transforms/IndVarSimplify/pr66066.ll b/llvm/test/Transforms/IndVarSimplify/pr66066.ll
index 5bb0d8371b3e3..cfd29876b302d 100644
--- a/llvm/test/Transforms/IndVarSimplify/pr66066.ll
+++ b/llvm/test/Transforms/IndVarSimplify/pr66066.ll
@@ -9,7 +9,7 @@ define void @test() {
; CHECK: loop:
; CHECK-NEXT: [[IV:%.*]] = phi i8 [ 1, [[ENTRY:%.*]] ], [ [[IV_DEC:%.*]], [[LOOP]] ]
; CHECK-NEXT: [[IV_DEC]] = add nsw i8 [[IV]], -1
-; CHECK-NEXT: [[SHL:%.*]] = shl nuw i8 [[IV]], 7
+; CHECK-NEXT: [[SHL:%.*]] = shl i8 [[IV]], 7
; CHECK-NEXT: call void @use(i8 [[SHL]])
; CHECK-NEXT: [[CMP1:%.*]] = icmp eq i8 [[SHL]], 0
; CHECK-NEXT: br i1 [[CMP1]], label [[EXIT:%.*]], label [[LOOP]]
diff --git a/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll b/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll
index bdcaaca9390c5..60f2c30291eaa 100644
--- a/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll
+++ b/llvm/test/Transforms/PhaseOrdering/scev-custom-dl.ll
@@ -141,7 +141,7 @@ define i32 @test_loop_idiom_recogize(i32 %x, i32 %y, ptr %lam, ptr %alp) nounwin
; CHECK-NEXT: Classifying expressions for: @test_loop_idiom_recogize
; CHECK-NEXT: %indvar = phi i32 [ 0, %bb1.thread ], [ %indvar.next, %bb1 ]
; CHECK-NEXT: --> {0,+,1}<nuw><nsw><%bb1> U: [0,256) S: [0,256) Exits: 255 LoopDispositions: { %bb1: Computable }
-; CHECK-NEXT: %i.0.reg2mem.0 = sub nuw nsw i32 255, %indvar
+; CHECK-NEXT: %i.0.reg2mem.0 = sub nsw i32 255, %indvar
; CHECK-NEXT: --> {255,+,-1}<nsw><%bb1> U: [0,256) S: [0,256) Exits: 0 LoopDispositions: { %bb1: Computable }
; CHECK-NEXT: %0 = getelementptr [4 x i8], ptr %alp, i32 %i.0.reg2mem.0
; CHECK-NEXT: --> {(1020 + %alp),+,-4}<nw><%bb1> U: full-set S: full-set Exits: %alp LoopDispositions: { %bb1: Computable }
More information about the llvm-commits
mailing list