[llvm] [AggressiveInstCombine] Support alternate deBruijn log2 table that isolates the MSB before the multiply. (PR #208532)

Craig Topper via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 9 11:46:14 PDT 2026


https://github.com/topperc created https://github.com/llvm/llvm-project/pull/208532

The existing deBruijn log2 match expects the multiply input to be a mask that has all bits below the most significant set bit to be 1.

There is an alternate form in some programs where this mask is modified to isolate the MSB by doing (mask - (mask >> 1)). A cttz table is then used instead of a log2 table.

This patch teaches tryToRecognizeTableBasedLog2 to match this form too.

Fixes #207571

Assisted-by: Claude

>From ee2b898ecffe69303d7cad39182d01ca41fc30ae Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Thu, 9 Jul 2026 11:27:53 -0700
Subject: [PATCH] [AggressiveInstCombine] Support alternate deBruijn log2 table
 that isolates the MSB before the multiply.

The existing log2 match expects the multiply input to be a mask
that has all bits below the most significant set bit to be 1.

There is an alternate form in some programs where this mask is
modified to isolate the MSB by doing (mask - (mask >> 1)). A cttz
table is then used intead of a log2 table.

This patch teaches tryToRecognizeTableBasedLog2 to match this form too.

Fixes #207571

Assisted-by: Claude
---
 .../AggressiveInstCombine.cpp                 | 48 +++++++++++++++--
 .../X86/lower-table-based-log2-basics.ll      | 53 +++++++++++++++++++
 2 files changed, 98 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/AggressiveInstCombine/AggressiveInstCombine.cpp b/llvm/lib/Transforms/AggressiveInstCombine/AggressiveInstCombine.cpp
index 0ebecf8bcd260..844ffe42cf99f 100644
--- a/llvm/lib/Transforms/AggressiveInstCombine/AggressiveInstCombine.cpp
+++ b/llvm/lib/Transforms/AggressiveInstCombine/AggressiveInstCombine.cpp
@@ -1104,6 +1104,22 @@ static bool isLog2Table(Constant *Table, const APInt &Mul, const APInt &Shift,
 // %arrayidx = getelementptr inbounds i8, ptr @table, i64 %shr11
 // %0 = load i8, ptr %arrayidx, align 1
 //
+// CASE 3:
+// A variant where the most-significant set bit of the OR-cascade result is
+// isolated via subtraction before the multiply, i.e.
+// table[((v - (v >> 1)) * MulConst) >> ShiftConst], analogous to how the
+// cttz pattern isolates the least-significant set bit via `x & -x`:
+//
+// %shr = lshr i64 %v, 1
+// %or = or i64 %shr, %v
+// ... (rest of the OR-cascade, as above) ...
+// %shr11 = lshr i64 %or10, 1
+// %sub = sub i64 %or10, %shr11
+// %mul = mul i64 %sub, 571347909858961602
+// %shr12 = lshr i64 %mul, 58
+// %arrayidx = getelementptr inbounds i8, ptr @table, i64 %shr12
+// %0 = load i8, ptr %arrayidx, align 1
+//
 // All these can be lowered to @llvm.ctlz.i32/64 intrinsics and a subtract.
 //
 // This shares its initial match (load from a GEP into a constant table with
@@ -1122,6 +1138,18 @@ static bool tryToRecognizeTableBasedLog2(LoadInst *LI, Type *AccessType,
   if (!match(GepIdx, m_CastOrSelf(MatchInner)))
     return false;
 
+  // The multiplied value may instead be the OR-cascade result with its
+  // most-significant set bit isolated first via `v - (v >> 1)`: since every
+  // bit below the MSB of an OR-cascade result is 1, this subtraction leaves
+  // just the MSB, mirroring how tryToRecognizeTableBasedCttz() isolates the
+  // least-significant set bit via `x & -x`.
+  bool IsolatedMSB = false;
+  Value *V;
+  if (match(X, m_Sub(m_Value(V), m_LShr(m_Deferred(V), m_SpecificInt(1))))) {
+    IsolatedMSB = true;
+    X = V;
+  }
+
   unsigned InputBits = X->getType()->getScalarSizeInBits();
   if (InputBits != 16 && InputBits != 32 && InputBits != 64 && InputBits != 128)
     return false;
@@ -1140,11 +1168,25 @@ static bool tryToRecognizeTableBasedLog2(LoadInst *LI, Type *AccessType,
     X = Y;
   }
 
-  if (!GEPScale.isIntN(InputBits) ||
-      !isLog2Table(GVTable->getInitializer(), *MulConst, *ShiftConst,
-                   AccessType, InputBits, GEPScale.zextOrTrunc(InputBits), DL))
+  if (!GEPScale.isIntN(InputBits))
     return false;
 
+  if (IsolatedMSB) {
+    // With the MSB isolated, the multiplicand for an input whose MSB is at bit
+    // Idx is a single set bit rather than a run of low bits, which is exactly
+    // what isCTTZTable() checks for (there is no additional masking here, so
+    // pass an all-ones mask).
+    if (!isCTTZTable(GVTable->getInitializer(), *MulConst, *ShiftConst,
+                     APInt::getAllOnes(InputBits), AccessType, InputBits,
+                     GEPScale.zextOrTrunc(InputBits), DL))
+      return false;
+  } else {
+    if (!isLog2Table(GVTable->getInitializer(), *MulConst, *ShiftConst,
+                     AccessType, InputBits, GEPScale.zextOrTrunc(InputBits),
+                     DL))
+      return false;
+  }
+
   ConstantInt *ZeroTableElem = cast<ConstantInt>(
       ConstantFoldLoadFromConst(GVTable->getInitializer(), AccessType, DL));
 
diff --git a/llvm/test/Transforms/AggressiveInstCombine/X86/lower-table-based-log2-basics.ll b/llvm/test/Transforms/AggressiveInstCombine/X86/lower-table-based-log2-basics.ll
index 2b340faab1300..bf770c6c6ceb3 100644
--- a/llvm/test/Transforms/AggressiveInstCombine/X86/lower-table-based-log2-basics.ll
+++ b/llvm/test/Transforms/AggressiveInstCombine/X86/lower-table-based-log2-basics.ll
@@ -215,6 +215,59 @@ define i32 @log2_128(i128 noundef %0) {
   ret i32 %21
 }
 
+;; int log2_64_isolate_msb(unsigned long long v) {
+;;   static const unsigned char table[] = {
+;;     63,  0, 58,  1, 59, 47, 53,  2, 60, 39, 48, 27, 54, 33, 42,  3,
+;;     61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22,  4,
+;;     62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
+;;     56, 45, 25, 31, 35, 16,  9, 12, 44, 24, 15,  8, 23,  7,  6,  5
+;;   };
+;;
+;;   v |= v >> 1;
+;;   v |= v >> 2;
+;;   v |= v >> 4;
+;;   v |= v >> 8;
+;;   v |= v >> 16;
+;;   v |= v >> 32;
+;;
+;;   return table[((v - (v >> 1)) * 0x07EDD5E59A4E28C2ULL) >> 58];
+;; }
+ at log2_64_isolate_msb.table = internal unnamed_addr constant [64 x i8] c"?\00:\01;/5\02<'0\1B6!*\03=3%(1\12\1C\147\1E\22\0B+\0E\16\04>9.4&\1A )2$\11\13\1D\0A\0D\158-\19\1F#\10\09\0C,\18\0F\08\17\07\06\05", align 1
+
+define i32 @log2_64_isolate_msb(i64 noundef %v) {
+; CHECK-LABEL: @log2_64_isolate_msb(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = call i64 @llvm.ctlz.i64(i64 [[V:%.*]], i1 true)
+; CHECK-NEXT:    [[TMP1:%.*]] = sub i64 63, [[TMP0]]
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i64 [[V]], 0
+; CHECK-NEXT:    [[TMP3:%.*]] = select i1 [[TMP2]], i64 63, i64 [[TMP1]], !prof [[PROF1]]
+; CHECK-NEXT:    [[TMP4:%.*]] = trunc i64 [[TMP3]] to i8
+; CHECK-NEXT:    [[CONV:%.*]] = zext i8 [[TMP4]] to i32
+; CHECK-NEXT:    ret i32 [[CONV]]
+;
+entry:
+  %shr = lshr i64 %v, 1
+  %or = or i64 %shr, %v
+  %shr1 = lshr i64 %or, 2
+  %or2 = or i64 %shr1, %or
+  %shr3 = lshr i64 %or2, 4
+  %or4 = or i64 %shr3, %or2
+  %shr5 = lshr i64 %or4, 8
+  %or6 = or i64 %shr5, %or4
+  %shr7 = lshr i64 %or6, 16
+  %or8 = or i64 %shr7, %or6
+  %shr9 = lshr i64 %or8, 32
+  %or10 = or i64 %shr9, %or8
+  %shr11 = lshr i64 %or10, 1
+  %sub = sub i64 %or10, %shr11
+  %mul = mul i64 %sub, u0x7EDD5E59A4E28C2
+  %shr12 = lshr i64 %mul, 58
+  %arrayidx = getelementptr inbounds nuw i8, ptr @log2_64_isolate_msb.table, i64 %shr12
+  %0 = load i8, ptr %arrayidx, align 1
+  %conv = zext i8 %0 to i32
+  ret i32 %conv
+}
+
 !0 = !{!"function_entry_count", i64 1000}
 ; CHECK: [[PROF0]] = !{!"function_entry_count", i64 1000}
 ; CHECK: [[PROF1]] = !{!"branch_weights", i32 1, i32 1048575}



More information about the llvm-commits mailing list