[llvm] [ValueTracking] Compute known bits for `umin` and `umax` recurences (PR #222890)

Ömer Sinan Ağacan via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 02:03:28 PDT 2026


https://github.com/osa1 created https://github.com/llvm/llvm-project/pull/222890

None

>From cf770c38a1dfed3cdd489a6dcefec960a9521f35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= <omer at osa1.net>
Date: Tue, 8 Sep 2026 19:14:12 +0100
Subject: [PATCH] [ValueTracking] Compute known bits for `umin` and `umax`
 recurences

---
 llvm/lib/Analysis/ValueTracking.cpp           |  59 ++++++
 .../test/Transforms/InstCombine/recurrence.ll | 189 ++++++++++++++++++
 2 files changed, 248 insertions(+)

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 1580571a01451..ca5dcfde42e43 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -95,6 +95,10 @@ static cl::opt<unsigned> DomConditionsMaxUses("dom-conditions-max-uses",
 /// instruction.
 static constexpr unsigned MaxInstrsToCheckForFree = 32;
 
+template <typename InstTy>
+static bool matchTwoInputRecurrence(const PHINode *PN, InstTy *&Inst,
+                                    Value *&Init, Value *&OtherOp);
+
 /// Returns the bitwidth of the given scalar or pointer type. For vector types,
 /// returns the element type's bitwidth.
 static unsigned getBitWidth(Type *Ty, const DataLayout &DL) {
@@ -1959,6 +1963,61 @@ static void computeKnownBitsFromOperator(const Operator *I,
       default:
         break;
       }
+    } else {
+      IntrinsicInst *II = nullptr;
+      if (matchTwoInputRecurrence<IntrinsicInst>(P, II, Start, Step)) {
+        // %iv      = [<Start>, %entry], [%iv.next, %backedge]
+        //
+        // %iv.next = <II>(%iv, <Step>)
+        // or
+        // %iv.next = <II>(<Step>, %iv)
+
+        switch (II->getIntrinsicID()) {
+        case Intrinsic::umin: {
+          // Limit number of leading zeros by Start's number of leading zeros.
+          SimplifyQuery RecQ = Q.getWithoutCondContext();
+
+          unsigned OpNum = P->getOperand(0) == Start ? 0 : 1;
+          Instruction *StartInst = P->getIncomingBlock(OpNum)->getTerminator();
+
+          KnownBits KnownStart(BitWidth);
+          RecQ.CxtI = StartInst;
+          computeKnownBits(Start, DemandedElts, KnownStart, RecQ, Depth + 1);
+
+          Known.Zero.setHighBits(KnownStart.countMinLeadingZeros());
+          break;
+        }
+
+        case Intrinsic::umax: {
+          // Limit number of leading zeros by the minimum of Start's and Step's
+          // number of leading zeros, and number of leading ones by Start's
+          // number of leading ones.
+          SimplifyQuery RecQ = Q.getWithoutCondContext();
+
+          unsigned OpNum = P->getOperand(0) == Start ? 0 : 1;
+          Instruction *StartInst = P->getIncomingBlock(OpNum)->getTerminator();
+          Instruction *StepInst =
+              P->getIncomingBlock(1 - OpNum)->getTerminator();
+
+          KnownBits KnownStart(BitWidth);
+          RecQ.CxtI = StartInst;
+          computeKnownBits(Start, DemandedElts, KnownStart, RecQ, Depth + 1);
+
+          KnownBits KnownStep(BitWidth);
+          RecQ.CxtI = StepInst;
+          computeKnownBits(Step, DemandedElts, KnownStep, RecQ, Depth + 1);
+
+          Known.Zero.setHighBits(std::min(KnownStart.countMinLeadingZeros(),
+                                          KnownStep.countMinLeadingZeros()));
+          Known.One.setHighBits(KnownStart.countMinLeadingOnes());
+
+          break;
+        }
+
+        default:
+          break;
+        }
+      }
     }
 
     // Unreachable blocks may have zero-operand PHI nodes.
diff --git a/llvm/test/Transforms/InstCombine/recurrence.ll b/llvm/test/Transforms/InstCombine/recurrence.ll
index 456b20a187519..bec76f8a53859 100644
--- a/llvm/test/Transforms/InstCombine/recurrence.ll
+++ b/llvm/test/Transforms/InstCombine/recurrence.ll
@@ -198,4 +198,193 @@ exit:
   ret i1 %result
 }
 
+define i64 @test_umin_leading_zeros(ptr %src, ptr %dst, i32 %start) {
+; CHECK-LABEL: @test_umin_leading_zeros(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[S:%.*]] = zext i32 [[START:%.*]] to i64
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[S]], [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[IDX:%.*]] = phi i64 [ 0, [[ENTRY]] ], [ [[IDX_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[P:%.*]], align 8
+; CHECK-NEXT:    [[IV_NEXT]] = call i64 @llvm.umin.i64(i64 [[IV]], i64 [[V]])
+; CHECK-NEXT:    store i64 [[IV_NEXT]], ptr [[DST:%.*]], align 4
+; CHECK-NEXT:    [[IDX_NEXT]] = add i64 [[IDX]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i64 [[IDX_NEXT]], 10
+; CHECK-NEXT:    br i1 [[CMP]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i64 [[IV_NEXT]]
+;
+entry:
+  %s = zext i32 %start to i64
+  br label %loop
+
+loop:
+  %iv = phi i64 [ %s, %entry ], [ %iv.next, %loop ]
+  %idx = phi i64 [ 0, %entry ], [ %idx.next, %loop ]
+  %v = load i64, ptr %src, align 8
+  %iv.next = call i64 @llvm.umin.i64(i64 %iv, i64 %v)
+  %masked = and i64 %iv.next, u0xFFFFFFFF
+  store i64 %masked, ptr %dst
+  %idx.next = add i64 %idx, 1
+  %cmp = icmp ult i64 %idx.next, 10
+  br i1 %cmp, label %exit, label %loop
+
+exit:
+  ret i64 %iv.next
+}
+
+define i64 @test_umax_leading_zeros(ptr %src, ptr %dst, i32 %start) {
+; CHECK-LABEL: @test_umax_leading_zeros(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[S:%.*]] = zext i32 [[START:%.*]] to i64
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[S]], [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[IDX:%.*]] = phi i64 [ 0, [[ENTRY]] ], [ [[IDX_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[SRC:%.*]], align 4
+; CHECK-NEXT:    [[Z:%.*]] = zext i32 [[V]] to i64
+; CHECK-NEXT:    store i64 0, ptr [[DST:%.*]], align 4
+; CHECK-NEXT:    [[IV_NEXT]] = call i64 @llvm.umax.i64(i64 [[IV]], i64 [[Z]])
+; CHECK-NEXT:    [[IDX_NEXT]] = add i64 [[IDX]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i64 [[IDX_NEXT]], 10
+; CHECK-NEXT:    br i1 [[CMP]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i64 [[IV_NEXT]]
+;
+entry:
+  %s = zext i32 %start to i64
+  br label %loop
+
+loop:
+  %iv = phi i64 [ %s, %entry ], [ %iv.next, %loop ]
+  %idx = phi i64 [ 0, %entry ], [ %idx.next, %loop ]
+  %v = load i32, ptr %src, align 4
+  %z = zext i32 %v to i64
+  %masked = and i64 %iv, u0xFFFFFFFF00000000
+  store i64 %masked, ptr %dst
+  %iv.next = call i64 @llvm.umax.i64(i64 %iv, i64 %z)
+  %idx.next = add i64 %idx, 1
+  %cmp = icmp ult i64 %idx.next, 10
+  br i1 %cmp, label %exit, label %loop
+
+exit:
+  ret i64 %iv.next
+}
+
+define i64 @test_umax_leading_ones(ptr %src, ptr %dst, i64 %start) {
+; CHECK-LABEL: @test_umax_leading_ones(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[S:%.*]] = or i64 [[START:%.*]], -4294967296
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[S]], [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[IDX:%.*]] = phi i64 [ 0, [[ENTRY]] ], [ [[IDX_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[SRC:%.*]], align 8
+; CHECK-NEXT:    store i64 [[IV]], ptr [[DST:%.*]], align 4
+; CHECK-NEXT:    [[IV_NEXT]] = call i64 @llvm.umax.i64(i64 [[IV]], i64 [[V]])
+; CHECK-NEXT:    [[IDX_NEXT]] = add i64 [[IDX]], 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i64 [[IDX_NEXT]], 10
+; CHECK-NEXT:    br i1 [[CMP]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i64 [[IV_NEXT]]
+;
+entry:
+  %s = or i64 %start, u0xFFFFFFFF00000000
+  br label %loop
+
+loop:
+  %iv = phi i64 [ %s, %entry ], [ %iv.next, %loop ]
+  %idx = phi i64 [ 0, %entry ], [ %idx.next, %loop ]
+  %v = load i64, ptr %src, align 8
+  %ones = or i64 %iv, u0xFFFFFFFF00000000
+  store i64 %ones, ptr %dst
+  %iv.next = call i64 @llvm.umax.i64(i64 %iv, i64 %v)
+  %idx.next = add i64 %idx, 1
+  %cmp = icmp ult i64 %idx.next, 10
+  br i1 %cmp, label %exit, label %loop
+
+exit:
+  ret i64 %iv.next
+}
+
+; Check that known high zero bits generate optimization flags (`samesign` and `nuw` below).
+define i64 @test_umin_leading_zeros_flags(ptr %p, ptr %q, i32 %start) {
+; CHECK-LABEL: @test_umin_leading_zeros_flags(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[S:%.*]] = zext i32 [[START:%.*]] to i64
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[S]], [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[P:%.*]], align 8
+; CHECK-NEXT:    [[IV_NEXT]] = call i64 @llvm.umin.i64(i64 [[IV]], i64 [[V]])
+; CHECK-NEXT:    [[W:%.*]] = load i32, ptr [[Q:%.*]], align 4
+; CHECK-NEXT:    [[ZW:%.*]] = zext i32 [[W]] to i64
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[IV_NEXT]], [[ZW]]
+; CHECK-NEXT:    [[INC:%.*]] = add nuw nsw i64 [[IV_NEXT]], 1
+; CHECK-NEXT:    tail call void @use(i64 [[INC]])
+; CHECK-NEXT:    br i1 [[CMP]], label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i64 [[IV_NEXT]]
+;
+entry:
+  %s = zext i32 %start to i64
+  br label %loop
+
+loop:
+  %iv = phi i64 [ %s, %entry ], [ %iv.next, %loop ]
+  %v = load i64, ptr %p, align 8
+  %iv.next = call i64 @llvm.umin.i64(i64 %iv, i64 %v)
+  %w = load i32, ptr %q, align 4
+  %zw = zext i32 %w to i64
+  %cmp = icmp ugt i64 %iv.next, %zw
+  %inc = add i64 %iv.next, 1
+  tail call void @use(i64 %inc)
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret i64 %iv.next
+}
+
+; Similar to the test above, known leading ones in `umax` calls should generate
+; flags in the use sites.
+define i64 @test_umax_leading_zeros_flags(ptr %p, ptr %q, i32 %start) {
+; CHECK-LABEL: @test_umax_leading_zeros_flags(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[S:%.*]] = zext i32 [[START:%.*]] to i64
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ [[S]], [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[P:%.*]], align 4
+; CHECK-NEXT:    [[Z:%.*]] = zext i32 [[V]] to i64
+; CHECK-NEXT:    [[IV_NEXT]] = call i64 @llvm.umax.i64(i64 [[IV]], i64 [[Z]])
+; CHECK-NEXT:    [[W:%.*]] = load i32, ptr [[Q:%.*]], align 4
+; CHECK-NEXT:    [[ZW:%.*]] = zext i32 [[W]] to i64
+; CHECK-NEXT:    [[CMP:%.*]] = icmp samesign ugt i64 [[IV_NEXT]], [[ZW]]
+; CHECK-NEXT:    [[INC:%.*]] = add nuw nsw i64 [[IV_NEXT]], 1
+; CHECK-NEXT:    tail call void @use(i64 [[INC]])
+; CHECK-NEXT:    br i1 [[CMP]], label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i64 [[IV_NEXT]]
+;
+entry:
+  %s = zext i32 %start to i64
+  br label %loop
+
+loop:
+  %iv = phi i64 [ %s, %entry ], [ %iv.next, %loop ]
+  %v = load i32, ptr %p, align 4
+  %z = zext i32 %v to i64
+  %iv.next = call i64 @llvm.umax.i64(i64 %iv, i64 %z)
+  %w = load i32, ptr %q, align 4
+  %zw = zext i32 %w to i64
+  %cmp = icmp ugt i64 %iv.next, %zw
+  %inc = add i64 %iv.next, 1
+  tail call void @use(i64 %inc)
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  ret i64 %iv.next
+}
+
 declare void @use(i64)



More information about the llvm-commits mailing list