[llvm] [X86] Avoid shl->mul lowbit-isolate fold when BMI1/BMI2 are available (PR #217776)

PJ Dailey via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 21 09:06:49 PDT 2026


https://github.com/PjDailey11 updated https://github.com/llvm/llvm-project/pull/217776

>From 1c8c8142e028e7f92e188e12af4b36ccfd86ad13 Mon Sep 17 00:00:00 2001
From: pjdailey <pjdailey13 at gmail.com>
Date: Thu, 20 Aug 2026 17:05:08 -0500
Subject: [PATCH] [X86] Avoid shl->mul lowbit-isolate fold when BMI1/BMI2 are
 available

214517 folds shl X, cttz(Y) into mul(and(neg(Y), Y), X), which is
usually profitable but is worse when BMI1/BMI2 are available: the mul
form lowers to blsi+imul while the shl form lowers to tzcnt+shlx, which
has better latency/throughput despite matching instruction count.
Reverse the fold when those features are available and Y is provably
nonzero. Also test that vector types are correctly rejected by the
type-limit guard.

Fixes #216550 (x86 half only; AArch64 left for a follow-up)
---
 llvm/lib/Target/X86/X86ISelLowering.cpp    | 21 +++++++
 llvm/test/CodeGen/X86/shl-cttz-blsi-mul.ll | 72 ++++++++++++++++++++++
 2 files changed, 93 insertions(+)
 create mode 100644 llvm/test/CodeGen/X86/shl-cttz-blsi-mul.ll

diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index bf435b0c2f849..873b168f1e017 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -50780,6 +50780,7 @@ static SDValue combineIntDivRem(SDNode *N, SelectionDAG &DAG,
 static SDValue combineMul(SDNode *N, SelectionDAG &DAG,
                           TargetLowering::DAGCombinerInfo &DCI,
                           const X86Subtarget &Subtarget) {
+  using namespace SDPatternMatch;
   EVT VT = N->getValueType(0);
   SDLoc DL(N);
 
@@ -50792,6 +50793,26 @@ static SDValue combineMul(SDNode *N, SelectionDAG &DAG,
   if (SDValue V = combineMulToPMADD52(N, DL, DAG, Subtarget))
     return V;
 
+  // InstCombine folds shl X, cttz(Y) into mul(and(neg(Y), Y), X), which is
+  // usually profitable but is worse when BMI1/BMI2 are available: the mul
+  // form lowers to blsi+imul while the shl form lowers to tzcnt+shlx, which
+  // has better latency/throughput despite matching instruction count
+  // (https://github.com/llvm/llvm-project/issues/216550). Reverse the fold
+  // when those features are available. Only valid when Y is provably
+  // nonzero: CTTZ_ZERO_POISON is poison at Y == 0, while the mul form is
+  // well-defined (0) at Y == 0 - so the rewrite would be a miscompile for
+  // Y == 0 without that guard.
+  if (Subtarget.hasBMI() && Subtarget.hasBMI2() && VT.isScalarInteger() &&
+      (VT == MVT::i32 || VT == MVT::i64)) {
+    SDValue X, Y;
+    if (sd_match(N, m_Mul(m_Value(X),
+                          m_OneUse(m_And(m_Neg(m_Value(Y)), m_Deferred(Y))))) &&
+        DAG.isKnownNeverZero(Y)) {
+      SDValue Cttz = DAG.getNode(ISD::CTTZ_ZERO_POISON, DL, VT, Y);
+      return DAG.getNode(ISD::SHL, DL, VT, X, Cttz);
+    }
+  }
+
   if (DCI.isBeforeLegalize() && VT.isVector())
     return reduceVMULWidth(N, DL, DAG, Subtarget);
 
diff --git a/llvm/test/CodeGen/X86/shl-cttz-blsi-mul.ll b/llvm/test/CodeGen/X86/shl-cttz-blsi-mul.ll
new file mode 100644
index 0000000000000..1da46b8f58c5d
--- /dev/null
+++ b/llvm/test/CodeGen/X86/shl-cttz-blsi-mul.ll
@@ -0,0 +1,72 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -mcpu=x86-64-v2 | FileCheck %s --check-prefix=NOBMI
+; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu -mcpu=x86-64-v3 | FileCheck %s --check-prefix=BMI
+
+define i32 @mul_unconstrained(i32 %x, i32 %y){
+; NOBMI-LABEL: mul_unconstrained:
+; NOBMI:       # %bb.0:
+; NOBMI-NEXT:    movl %esi, %eax
+; NOBMI-NEXT:    negl %eax
+; NOBMI-NEXT:    andl %esi, %eax
+; NOBMI-NEXT:    imull %edi, %eax
+; NOBMI-NEXT:    retq
+;
+; BMI-LABEL: mul_unconstrained:
+; BMI:       # %bb.0:
+; BMI-NEXT:    blsil %esi, %eax
+; BMI-NEXT:    imull %edi, %eax
+; BMI-NEXT:    retq
+    %neg = sub i32 0, %y
+    %lsb = and i32 %neg, %y
+    %mul = mul i32 %lsb, %x
+    ret i32 %mul
+}
+
+define i32 @mul_nonzero(i32 %x, i32 %y){
+; NOBMI-LABEL: mul_nonzero:
+; NOBMI:       # %bb.0:
+; NOBMI-NEXT:    orl $2, %esi
+; NOBMI-NEXT:    movl %esi, %eax
+; NOBMI-NEXT:    negl %eax
+; NOBMI-NEXT:    andl %esi, %eax
+; NOBMI-NEXT:    imull %edi, %eax
+; NOBMI-NEXT:    retq
+;
+; BMI-LABEL: mul_nonzero:
+; BMI:       # %bb.0:
+; BMI-NEXT:    orl $2, %esi
+; BMI-NEXT:    tzcntl %esi, %eax
+; BMI-NEXT:    shlxl %eax, %edi, %eax
+; BMI-NEXT:    retq
+    %y2 = or i32 %y, 2
+    %neg = sub i32 0, %y2
+    %lsb = and i32 %neg, %y2
+    %mul = mul i32 %lsb, %x
+    ret i32 %mul
+}
+
+define <4 x i32> @mul_nonzero_vector(<4 x i32> %x, <4 x i32> %y) {
+; NOBMI-LABEL: mul_nonzero_vector:
+; NOBMI:       # %bb.0:
+; NOBMI-NEXT:    por {{\.?LCPI[0-9]+_[0-9]+}}(%rip), %xmm1
+; NOBMI-NEXT:    pxor %xmm2, %xmm2
+; NOBMI-NEXT:    psubd %xmm1, %xmm2
+; NOBMI-NEXT:    pand %xmm1, %xmm2
+; NOBMI-NEXT:    pmulld %xmm2, %xmm0
+; NOBMI-NEXT:    retq
+;
+; BMI-LABEL: mul_nonzero_vector:
+; BMI:       # %bb.0:
+; BMI-NEXT:    vpbroadcastd {{.*#+}} xmm2 = [2,2,2,2]
+; BMI-NEXT:    vpor %xmm2, %xmm1, %xmm1
+; BMI-NEXT:    vpxor %xmm2, %xmm2, %xmm2
+; BMI-NEXT:    vpsubd %xmm1, %xmm2, %xmm2
+; BMI-NEXT:    vpand %xmm1, %xmm2, %xmm1
+; BMI-NEXT:    vpmulld %xmm0, %xmm1, %xmm0
+; BMI-NEXT:    retq
+    %y2 = or <4 x i32> %y, splat(i32 2)
+    %neg = sub <4 x i32> zeroinitializer, %y2
+    %lsb = and <4 x i32> %neg, %y2
+    %mul = mul <4 x i32> %lsb, %x
+    ret <4 x i32> %mul
+}



More information about the llvm-commits mailing list