[llvm] [ValueTracking] Compute known bits of or/and/xor recurrences from start and step (PR #222334)

Konstantin Bogdanov via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 13 16:06:08 PDT 2026


https://github.com/thevar1able updated https://github.com/llvm/llvm-project/pull/222334

>From 58b52d896565184b67ca12a4b4a4a639b6f7f2d3 Mon Sep 17 00:00:00 2001
From: Konstantin Bogdanov <konstantin at clickhouse.com>
Date: Wed, 9 Sep 2026 15:44:15 +0200
Subject: [PATCH] [ValueTracking] Compute known bits of or/and/xor recurrences
 from start and step

---
 llvm/lib/Analysis/ValueTracking.cpp           |  23 +-
 .../ValueTracking/phi-known-nonzero.ll        |  71 +++++++
 .../AMDGPU/GlobalISel/is-safe-to-sink-bug.ll  |  21 +-
 llvm/test/CodeGen/AMDGPU/absdiff.ll           |   5 +-
 .../test/Transforms/InstCombine/recurrence.ll | 198 ++++++++++++++++++
 .../X86/ctlz-loop-or-reduction.ll             | 116 ++++++++++
 6 files changed, 416 insertions(+), 18 deletions(-)
 create mode 100644 llvm/test/Transforms/PhaseOrdering/X86/ctlz-loop-or-reduction.ll

diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index ac49dbc8d76d3..130e900cf837e 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -1895,6 +1895,7 @@ static void computeKnownBitsFromOperator(const Operator *I,
       case Instruction::Sub:
       case Instruction::And:
       case Instruction::Or:
+      case Instruction::Xor:
       case Instruction::Mul: {
         // Change the context instruction to the "edge" that flows into the
         // phi. This is important because that is where the value is actually
@@ -1919,8 +1920,17 @@ static void computeKnownBitsFromOperator(const Operator *I,
         RecQ.CxtI = LatchTerm;
         computeKnownBits(Step, DemandedElts, KnownStep, RecQ, Depth + 1);
 
-        Known.Zero.setLowBits(std::min(KnownStart.countMinTrailingZeros(),
-                                       KnownStep.countMinTrailingZeros()));
+        if (Opcode == Instruction::Or || Opcode == Instruction::Xor) {
+          Known.Zero |= KnownStart.Zero & KnownStep.Zero;
+          if (Opcode == Instruction::Or)
+            Known.One |= KnownStart.One;
+        } else if (Opcode == Instruction::And) {
+          Known.Zero |= KnownStart.Zero;
+          Known.One |= KnownStart.One & KnownStep.One;
+        } else {
+          Known.Zero.setLowBits(std::min(KnownStart.countMinTrailingZeros(),
+                                         KnownStep.countMinTrailingZeros()));
+        }
 
         auto *OverflowOp = dyn_cast<OverflowingBinaryOperator>(BO);
         if (!OverflowOp || !Q.IIQ.hasNoSignedWrap(OverflowOp))
@@ -3152,6 +3162,8 @@ static bool isNonZeroRecurrence(const PHINode *PN) {
   case Instruction::AShr:
   case Instruction::LShr:
     return BO->isExact();
+  case Instruction::Or:
+    return true;
   default:
     return false;
   }
@@ -3525,9 +3537,12 @@ static bool isKnownNonZeroFromOperator(const Operator *I,
     if (Q.IIQ.UseInstrInfo && isNonZeroRecurrence(PN))
       return true;
 
-    // Check if all incoming values are non-zero using recursion.
+    // Check if all incoming values are non-zero using recursion. A phi with a
+    // single incoming value is just a copy, so don't limit the depth for it.
     SimplifyQuery RecQ = Q.getWithoutCondContext();
-    unsigned NewDepth = std::max(Depth, MaxAnalysisRecursionDepth - 1);
+    unsigned NewDepth = PN->getNumIncomingValues() == 1
+                            ? Depth
+                            : std::max(Depth, MaxAnalysisRecursionDepth - 1);
     return llvm::all_of(PN->operands(), [&](const Use &U) {
       if (U.get() == PN)
         return true;
diff --git a/llvm/test/Analysis/ValueTracking/phi-known-nonzero.ll b/llvm/test/Analysis/ValueTracking/phi-known-nonzero.ll
index d80d1704c73d1..378ab3230eb5d 100644
--- a/llvm/test/Analysis/ValueTracking/phi-known-nonzero.ll
+++ b/llvm/test/Analysis/ValueTracking/phi-known-nonzero.ll
@@ -262,3 +262,74 @@ T:
 F:
   br label %T
 }
+
+; An or recurrence with a non-zero start value is never zero.
+define i1 @or_recurrence_nonzero_start(ptr %p, i64 %n) {
+; CHECK-LABEL: @or_recurrence_nonzero_start(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[ACC:%.*]] = phi i32 [ 32768, [[ENTRY]] ], [ [[ACC_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i32, ptr [[P:%.*]], i64 [[IV]]
+; CHECK-NEXT:    [[X:%.*]] = load i32, ptr [[GEP]], align 4
+; CHECK-NEXT:    [[ACC_NEXT]] = or i32 [[X]], [[ACC]]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[EC:%.*]] = icmp eq i64 [[IV_NEXT]], [[N:%.*]]
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i1 false
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %acc = phi i32 [ 32768, %entry ], [ %acc.next, %loop ]
+  %gep = getelementptr i32, ptr %p, i64 %iv
+  %x = load i32, ptr %gep
+  %acc.next = or i32 %x, %acc
+  %iv.next = add i64 %iv, 1
+  %ec = icmp eq i64 %iv.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  %cmp = icmp eq i32 %acc.next, 0
+  ret i1 %cmp
+}
+
+; Negative test: the start value is zero.
+define i1 @or_recurrence_zero_start(ptr %p, i64 %n) {
+; CHECK-LABEL: @or_recurrence_zero_start(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[ACC:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[ACC_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr i32, ptr [[P:%.*]], i64 [[IV]]
+; CHECK-NEXT:    [[X:%.*]] = load i32, ptr [[GEP]], align 4
+; CHECK-NEXT:    [[ACC_NEXT]] = or i32 [[X]], [[ACC]]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[EC:%.*]] = icmp eq i64 [[IV_NEXT]], [[N:%.*]]
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[ACC_NEXT]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %acc = phi i32 [ 0, %entry ], [ %acc.next, %loop ]
+  %gep = getelementptr i32, ptr %p, i64 %iv
+  %x = load i32, ptr %gep
+  %acc.next = or i32 %x, %acc
+  %iv.next = add i64 %iv, 1
+  %ec = icmp eq i64 %iv.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  %cmp = icmp eq i32 %acc.next, 0
+  ret i1 %cmp
+}
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/is-safe-to-sink-bug.ll b/llvm/test/CodeGen/AMDGPU/GlobalISel/is-safe-to-sink-bug.ll
index d039334ab63dc..a1ea35304f113 100644
--- a/llvm/test/CodeGen/AMDGPU/GlobalISel/is-safe-to-sink-bug.ll
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/is-safe-to-sink-bug.ll
@@ -19,11 +19,11 @@ define amdgpu_ps void @_amdgpu_ps_main(i1 %arg) {
 ; CHECK-NEXT:    s_mov_b32 s2, s0
 ; CHECK-NEXT:    s_mov_b32 s3, s0
 ; CHECK-NEXT:    s_addc_u32 s9, s9, 0
-; CHECK-NEXT:    s_buffer_load_dword s1, s[0:3], 0x0
+; CHECK-NEXT:    s_buffer_load_dword s2, s[0:3], 0x0
 ; CHECK-NEXT:    s_mov_b32 s32, 0
 ; CHECK-NEXT:    v_cmp_ne_u32_e32 vcc_lo, 0, v0
 ; CHECK-NEXT:    s_waitcnt lgkmcnt(0)
-; CHECK-NEXT:    s_cmp_ge_i32 s1, 0
+; CHECK-NEXT:    s_cmp_ge_i32 s2, 0
 ; CHECK-NEXT:    s_cbranch_scc0 .LBB0_2
 ; CHECK-NEXT:  .LBB0_1: ; %bb12
 ; CHECK-NEXT:    v_cndmask_b32_e64 v0, 1.0, 0, s0
@@ -35,23 +35,22 @@ define amdgpu_ps void @_amdgpu_ps_main(i1 %arg) {
 ; CHECK-NEXT:    s_mov_b64 s[2:3], s[10:11]
 ; CHECK-NEXT:    s_swappc_b64 s[30:31], 0
 ; CHECK-NEXT:  .LBB0_2: ; %bb2.preheader
-; CHECK-NEXT:    s_mov_b32 s3, 0
 ; CHECK-NEXT:    s_mov_b32 s1, 0
-; CHECK-NEXT:    s_mov_b32 s2, 0
+; CHECK-NEXT:    s_mov_b32 s3, 0
 ; CHECK-NEXT:    s_branch .LBB0_4
 ; CHECK-NEXT:    .p2align 6
 ; CHECK-NEXT:  .LBB0_3: ; %bb6
 ; CHECK-NEXT:    ; in Loop: Header=BB0_4 Depth=1
 ; CHECK-NEXT:    s_or_b32 exec_lo, exec_lo, s0
 ; CHECK-NEXT:    v_cmp_ne_u32_e64 s0, 0, v0
-; CHECK-NEXT:    s_or_b32 s4, s3, 1
-; CHECK-NEXT:    s_and_b32 s0, s0, s2
-; CHECK-NEXT:    s_cmp_lt_i32 s3, 0
+; CHECK-NEXT:    s_or_b32 s4, s2, 1
+; CHECK-NEXT:    s_and_b32 s0, s0, s3
+; CHECK-NEXT:    s_cmp_lt_i32 s2, 0
 ; CHECK-NEXT:    s_cselect_b32 s5, 1, 0
-; CHECK-NEXT:    s_andn2_b32 s2, s2, exec_lo
+; CHECK-NEXT:    s_andn2_b32 s2, s3, exec_lo
 ; CHECK-NEXT:    s_and_b32 s3, exec_lo, s0
-; CHECK-NEXT:    s_or_b32 s2, s2, s3
-; CHECK-NEXT:    s_mov_b32 s3, s4
+; CHECK-NEXT:    s_or_b32 s3, s2, s3
+; CHECK-NEXT:    s_mov_b32 s2, s4
 ; CHECK-NEXT:    s_cmp_lg_u32 s5, 0
 ; CHECK-NEXT:    s_cbranch_scc0 .LBB0_1
 ; CHECK-NEXT:  .LBB0_4: ; %bb2
@@ -71,7 +70,7 @@ bb:
 
 bb2:
   %i3 = phi i1 [ %i9, %bb6 ], [ false, %bb ]
-  %i4 = phi i32 [ %i10, %bb6 ], [ 0, %bb ]
+  %i4 = phi i32 [ %i10, %bb6 ], [ %i, %bb ]
   br i1 %arg, label %bb5, label %bb6
 
 bb5:
diff --git a/llvm/test/CodeGen/AMDGPU/absdiff.ll b/llvm/test/CodeGen/AMDGPU/absdiff.ll
index e206b4341a096..6670459880ad2 100644
--- a/llvm/test/CodeGen/AMDGPU/absdiff.ll
+++ b/llvm/test/CodeGen/AMDGPU/absdiff.ll
@@ -2,10 +2,9 @@
 ; RUN: llc -mtriple=amdgpu9.00-amd-amdpal < %s | FileCheck %s
 
 
-define amdgpu_gs float @absdiff_valu_input_regression() {
+define amdgpu_gs float @absdiff_valu_input_regression(i32 inreg %start) {
 ; CHECK-LABEL: absdiff_valu_input_regression:
 ; CHECK:       ; %bb.0: ; %bb
-; CHECK-NEXT:    s_mov_b32 s0, 0
 ; CHECK-NEXT:  .LBB0_1: ; %bb1
 ; CHECK-NEXT:    ; =>This Inner Loop Header: Depth=1
 ; CHECK-NEXT:    s_mov_b32 s1, s0
@@ -22,7 +21,7 @@ bb:
   br label %bb1
 
 bb1:                                              ; preds = %bb1, %bb
-  %i = phi i32 [ 0, %bb ], [ %i9, %bb1 ]
+  %i = phi i32 [ %start, %bb ], [ %i9, %bb1 ]
   %i2 = phi i32 [ 0, %bb ], [ %i5, %bb1 ]
   %i3 = or i32 %i2, 1
   %i4 = or i32 %i3, 0
diff --git a/llvm/test/Transforms/InstCombine/recurrence.ll b/llvm/test/Transforms/InstCombine/recurrence.ll
index 456b20a187519..51378af5820ab 100644
--- a/llvm/test/Transforms/InstCombine/recurrence.ll
+++ b/llvm/test/Transforms/InstCombine/recurrence.ll
@@ -199,3 +199,201 @@ exit:
 }
 
 declare void @use(i64)
+
+; The high bits of an or recurrence are known zero if they are zero in both
+; the start value and the step.
+define i32 @test_or_known_zero_high_bits(ptr %p, i64 %n) {
+; CHECK-LABEL: @test_or_known_zero_high_bits(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[ACC:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[ACC_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, ptr [[P:%.*]], i64 [[IV]]
+; CHECK-NEXT:    [[X:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT:    [[X_EXT:%.*]] = zext i8 [[X]] to i32
+; CHECK-NEXT:    [[ACC_NEXT]] = or i32 [[ACC]], [[X_EXT]]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[EC:%.*]] = icmp eq i64 [[IV_NEXT]], [[N:%.*]]
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 [[ACC_NEXT]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %acc = phi i32 [ 0, %entry ], [ %acc.next, %loop ]
+  %gep = getelementptr inbounds i8, ptr %p, i64 %iv
+  %x = load i8, ptr %gep
+  %x.ext = zext i8 %x to i32
+  %acc.next = or i32 %acc, %x.ext
+  %iv.next = add i64 %iv, 1
+  %ec = icmp eq i64 %iv.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  %masked = and i32 %acc.next, 255
+  ret i32 %masked
+}
+
+; Negative test: the start value has unknown high bits.
+define i32 @test_or_unknown_start(ptr %p, i64 %n, i32 %start) {
+; CHECK-LABEL: @test_or_unknown_start(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[ACC:%.*]] = phi i32 [ [[START:%.*]], [[ENTRY]] ], [ [[ACC_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, ptr [[P:%.*]], i64 [[IV]]
+; CHECK-NEXT:    [[X:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT:    [[X_EXT:%.*]] = zext i8 [[X]] to i32
+; CHECK-NEXT:    [[ACC_NEXT]] = or i32 [[ACC]], [[X_EXT]]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[EC:%.*]] = icmp eq i64 [[IV_NEXT]], [[N:%.*]]
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    [[MASKED:%.*]] = and i32 [[ACC_NEXT]], 255
+; CHECK-NEXT:    ret i32 [[MASKED]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %acc = phi i32 [ %start, %entry ], [ %acc.next, %loop ]
+  %gep = getelementptr inbounds i8, ptr %p, i64 %iv
+  %x = load i8, ptr %gep
+  %x.ext = zext i8 %x to i32
+  %acc.next = or i32 %acc, %x.ext
+  %iv.next = add i64 %iv, 1
+  %ec = icmp eq i64 %iv.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  %masked = and i32 %acc.next, 255
+  ret i32 %masked
+}
+
+; Bits that are one in the start value stay one in an or recurrence, but bits
+; that are only one in the step do not apply to the first iteration.
+define i64 @test_or_known_one_start(i64 %a) {
+; CHECK-LABEL: @test_or_known_one_start(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    tail call void @use(i64 1)
+; CHECK-NEXT:    br label [[LOOP]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 1, %entry ], [ %iv.next, %loop ]
+  %iv.next = or i64 %iv, %a
+  %bit0 = and i64 %iv, 1
+  tail call void @use(i64 %bit0)
+  br label %loop
+}
+
+; The high bits of an and recurrence are known zero if they are zero in the
+; start value.
+define i32 @test_and_known_zero_start(ptr %p, i64 %n) {
+; CHECK-LABEL: @test_and_known_zero_start(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[ACC:%.*]] = phi i32 [ 255, [[ENTRY]] ], [ [[ACC_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds [4 x i8], ptr [[P:%.*]], i64 [[IV]]
+; CHECK-NEXT:    [[X:%.*]] = load i32, ptr [[GEP]], align 4
+; CHECK-NEXT:    [[ACC_NEXT]] = and i32 [[ACC]], [[X]]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[EC:%.*]] = icmp eq i64 [[IV_NEXT]], [[N:%.*]]
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 [[ACC_NEXT]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %acc = phi i32 [ 255, %entry ], [ %acc.next, %loop ]
+  %gep = getelementptr inbounds i32, ptr %p, i64 %iv
+  %x = load i32, ptr %gep
+  %acc.next = and i32 %acc, %x
+  %iv.next = add i64 %iv, 1
+  %ec = icmp eq i64 %iv.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  %masked = and i32 %acc.next, 255
+  ret i32 %masked
+}
+
+; Negative test: an and recurrence does not inherit the zero bits of the step,
+; because the step does not apply to the first iteration.
+define i32 @test_and_step_zero_bits(i32 %start) {
+; CHECK-LABEL: @test_and_step_zero_bits(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    tail call void @use(i64 0)
+; CHECK-NEXT:    br i1 false, label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    [[MASKED:%.*]] = and i32 [[START:%.*]], 4
+; CHECK-NEXT:    ret i32 [[MASKED]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [ %start, %entry ], [ %iv.next, %loop ]
+  %iv.next = and i32 %iv, 4
+  %masked = and i32 %iv, 4
+  tail call void @use(i64 0)
+  br i1 false, label %loop, label %exit
+
+exit:
+  ret i32 %masked
+}
+
+; The high bits of an xor recurrence are known zero if they are zero in both
+; the start value and the step.
+define i32 @test_xor_known_zero_high_bits(ptr %p, i64 %n) {
+; CHECK-LABEL: @test_xor_known_zero_high_bits(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, [[ENTRY:%.*]] ], [ [[IV_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[ACC:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[ACC_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds i8, ptr [[P:%.*]], i64 [[IV]]
+; CHECK-NEXT:    [[X:%.*]] = load i8, ptr [[GEP]], align 1
+; CHECK-NEXT:    [[X_EXT:%.*]] = zext i8 [[X]] to i32
+; CHECK-NEXT:    [[ACC_NEXT]] = xor i32 [[ACC]], [[X_EXT]]
+; CHECK-NEXT:    [[IV_NEXT]] = add i64 [[IV]], 1
+; CHECK-NEXT:    [[EC:%.*]] = icmp eq i64 [[IV_NEXT]], [[N:%.*]]
+; CHECK-NEXT:    br i1 [[EC]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 [[ACC_NEXT]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+  %acc = phi i32 [ 0, %entry ], [ %acc.next, %loop ]
+  %gep = getelementptr inbounds i8, ptr %p, i64 %iv
+  %x = load i8, ptr %gep
+  %x.ext = zext i8 %x to i32
+  %acc.next = xor i32 %acc, %x.ext
+  %iv.next = add i64 %iv, 1
+  %ec = icmp eq i64 %iv.next, %n
+  br i1 %ec, label %exit, label %loop
+
+exit:
+  %masked = and i32 %acc.next, 255
+  ret i32 %masked
+}
diff --git a/llvm/test/Transforms/PhaseOrdering/X86/ctlz-loop-or-reduction.ll b/llvm/test/Transforms/PhaseOrdering/X86/ctlz-loop-or-reduction.ll
new file mode 100644
index 0000000000000..ec5cca9fa3db2
--- /dev/null
+++ b/llvm/test/Transforms/PhaseOrdering/X86/ctlz-loop-or-reduction.ll
@@ -0,0 +1,116 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -O3 -S < %s | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+; An or-reduction with a non-zero start value feeds a count-leading-zeros loop
+; behind a zero check. Known bits prove the reduction result is never zero, so
+; InstCombine removes the zero check. LoopIdiomRecognize must still form
+; llvm.ctlz for the unguarded loop (reduced from ffmpeg's
+; sbc_calc_scalefactors).
+;
+; int scalefactor(const int *data, int n) {
+;   unsigned x = 1u << 15;
+;   int i = 0;
+;   do {
+;     x |= abs(data[i]);
+;   } while (++i < n);
+;   int cnt = 32;
+;   while (x) {
+;     x >>= 1;
+;     cnt--;
+;   }
+;   return cnt;
+; }
+
+define i32 @scalefactor(ptr %data, i32 %n) {
+;
+; CHECK-LABEL: define range(i32 1, 17) i32 @scalefactor(
+; CHECK-SAME: ptr nofree readonly captures(none) [[DATA:%.*]], i32 [[N:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[SMAX:%.*]] = tail call i32 @llvm.smax.i32(i32 [[N]], i32 1)
+; CHECK-NEXT:    [[WIDE_TRIP_COUNT:%.*]] = zext nneg i32 [[SMAX]] to i64
+; CHECK-NEXT:    [[MIN_ITERS_CHECK:%.*]] = icmp slt i32 [[N]], 8
+; CHECK-NEXT:    br i1 [[MIN_ITERS_CHECK]], label %[[RED_LOOP_PREHEADER:.*]], label %[[VECTOR_PH:.*]]
+; CHECK:       [[VECTOR_PH]]:
+; CHECK-NEXT:    [[N_VEC:%.*]] = and i64 [[WIDE_TRIP_COUNT]], 2147483640
+; CHECK-NEXT:    br label %[[VECTOR_BODY:.*]]
+; CHECK:       [[VECTOR_BODY]]:
+; CHECK-NEXT:    [[INDEX:%.*]] = phi i64 [ 0, %[[VECTOR_PH]] ], [ [[INDEX_NEXT:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT:    [[VEC_PHI:%.*]] = phi <4 x i32> [ <i32 32768, i32 0, i32 0, i32 0>, %[[VECTOR_PH]] ], [ [[TMP4:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT:    [[VEC_PHI2:%.*]] = phi <4 x i32> [ zeroinitializer, %[[VECTOR_PH]] ], [ [[TMP5:%.*]], %[[VECTOR_BODY]] ]
+; CHECK-NEXT:    [[TMP0:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[DATA]], i64 [[INDEX]]
+; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr inbounds nuw i8, ptr [[TMP0]], i64 16
+; CHECK-NEXT:    [[WIDE_LOAD:%.*]] = load <4 x i32>, ptr [[TMP0]], align 4
+; CHECK-NEXT:    [[WIDE_LOAD3:%.*]] = load <4 x i32>, ptr [[TMP1]], align 4
+; CHECK-NEXT:    [[TMP2:%.*]] = tail call <4 x i32> @llvm.abs.v4i32(<4 x i32> [[WIDE_LOAD]], i1 true)
+; CHECK-NEXT:    [[TMP3:%.*]] = tail call <4 x i32> @llvm.abs.v4i32(<4 x i32> [[WIDE_LOAD3]], i1 true)
+; CHECK-NEXT:    [[TMP4]] = or <4 x i32> [[TMP2]], [[VEC_PHI]]
+; CHECK-NEXT:    [[TMP5]] = or <4 x i32> [[TMP3]], [[VEC_PHI2]]
+; CHECK-NEXT:    [[INDEX_NEXT]] = add nuw i64 [[INDEX]], 8
+; CHECK-NEXT:    [[TMP6:%.*]] = icmp eq i64 [[INDEX_NEXT]], [[N_VEC]]
+; CHECK-NEXT:    br i1 [[TMP6]], label %[[MIDDLE_BLOCK:.*]], label %[[VECTOR_BODY]], !llvm.loop [[LOOP0:![0-9]+]]
+; CHECK:       [[MIDDLE_BLOCK]]:
+; CHECK-NEXT:    [[BIN_RDX:%.*]] = or <4 x i32> [[TMP5]], [[TMP4]]
+; CHECK-NEXT:    [[TMP7:%.*]] = tail call i32 @llvm.vector.reduce.or.v4i32(<4 x i32> [[BIN_RDX]])
+; CHECK-NEXT:    [[CMP_N:%.*]] = icmp eq i64 [[N_VEC]], [[WIDE_TRIP_COUNT]]
+; CHECK-NEXT:    br i1 [[CMP_N]], label %[[CLZ_LOOP_PREHEADER:.*]], label %[[RED_LOOP_PREHEADER]]
+; CHECK:       [[RED_LOOP_PREHEADER]]:
+; CHECK-NEXT:    [[INDVARS_IV_PH:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[N_VEC]], %[[MIDDLE_BLOCK]] ]
+; CHECK-NEXT:    [[X_PH:%.*]] = phi i32 [ 32768, %[[ENTRY]] ], [ [[TMP7]], %[[MIDDLE_BLOCK]] ]
+; CHECK-NEXT:    br label %[[RED_LOOP:.*]]
+; CHECK:       [[RED_LOOP]]:
+; CHECK-NEXT:    [[INDVARS_IV:%.*]] = phi i64 [ [[INDVARS_IV_NEXT:%.*]], %[[RED_LOOP]] ], [ [[INDVARS_IV_PH]], %[[RED_LOOP_PREHEADER]] ]
+; CHECK-NEXT:    [[X:%.*]] = phi i32 [ [[X_NEXT:%.*]], %[[RED_LOOP]] ], [ [[X_PH]], %[[RED_LOOP_PREHEADER]] ]
+; CHECK-NEXT:    [[GEP:%.*]] = getelementptr inbounds nuw [4 x i8], ptr [[DATA]], i64 [[INDVARS_IV]]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[GEP]], align 4
+; CHECK-NEXT:    [[ABS:%.*]] = tail call i32 @llvm.abs.i32(i32 [[V]], i1 true)
+; CHECK-NEXT:    [[X_NEXT]] = or i32 [[ABS]], [[X]]
+; CHECK-NEXT:    [[INDVARS_IV_NEXT]] = add nuw nsw i64 [[INDVARS_IV]], 1
+; CHECK-NEXT:    [[EXITCOND_NOT:%.*]] = icmp eq i64 [[INDVARS_IV_NEXT]], [[WIDE_TRIP_COUNT]]
+; CHECK-NEXT:    br i1 [[EXITCOND_NOT]], label %[[CLZ_LOOP_PREHEADER]], label %[[RED_LOOP]], !llvm.loop [[LOOP3:![0-9]+]]
+; CHECK:       [[CLZ_LOOP_PREHEADER]]:
+; CHECK-NEXT:    [[X_NEXT_LCSSA:%.*]] = phi i32 [ [[TMP7]], %[[MIDDLE_BLOCK]] ], [ [[X_NEXT]], %[[RED_LOOP]] ]
+; CHECK-NEXT:    [[TMP8:%.*]] = tail call range(i32 1, 17) i32 @llvm.ctlz.i32(i32 [[X_NEXT_LCSSA]], i1 true)
+; CHECK-NEXT:    ret i32 [[TMP8]]
+;
+entry:
+  br label %red.loop
+
+red.loop:
+  %i = phi i32 [ 0, %entry ], [ %i.next, %red.loop ]
+  %x = phi i32 [ 32768, %entry ], [ %x.next, %red.loop ]
+  %idx = zext i32 %i to i64
+  %gep = getelementptr inbounds i32, ptr %data, i64 %idx
+  %v = load i32, ptr %gep, align 4
+  %abs = call i32 @llvm.abs.i32(i32 %v, i1 true)
+  %x.next = or i32 %x, %abs
+  %i.next = add nuw nsw i32 %i, 1
+  %ec = icmp slt i32 %i.next, %n
+  br i1 %ec, label %red.loop, label %red.exit
+
+red.exit:
+  %zero = icmp eq i32 %x.next, 0
+  br i1 %zero, label %exit, label %clz.loop
+
+clz.loop:
+  %cnt = phi i32 [ 32, %red.exit ], [ %cnt.next, %clz.loop ]
+  %y = phi i32 [ %x.next, %red.exit ], [ %y.next, %clz.loop ]
+  %y.next = lshr i32 %y, 1
+  %cnt.next = add nsw i32 %cnt, -1
+  %done = icmp eq i32 %y.next, 0
+  br i1 %done, label %exit, label %clz.loop
+
+exit:
+  %cnt.lcssa = phi i32 [ 32, %red.exit ], [ %cnt.next, %clz.loop ]
+  ret i32 %cnt.lcssa
+}
+
+declare i32 @llvm.abs.i32(i32, i1)
+;.
+; CHECK: [[LOOP0]] = distinct !{[[LOOP0]], [[META1:![0-9]+]], [[META2:![0-9]+]]}
+; CHECK: [[META1]] = !{!"llvm.loop.isvectorized", i32 1}
+; CHECK: [[META2]] = !{!"llvm.loop.unroll.runtime.disable"}
+; CHECK: [[LOOP3]] = distinct !{[[LOOP3]], [[META2]], [[META1]]}
+;.



More information about the llvm-commits mailing list