[llvm] [InstCombine] Simplify zext(sub(0, trunc(x))) -> and(sub(0, x), mask) (Fixes #165306) (PR #167101)

V S Susi Krishna via llvm-commits llvm-commits at lists.llvm.org
Fri Nov 7 22:48:03 PST 2025


https://github.com/Susikrishna created https://github.com/llvm/llvm-project/pull/167101

This patch adds a new InstCombine transformation that simplifies the pattern:

    zext (sub (0, trunc X))
      → and (sub (0, X), (bitwidth - 1))

This canonicalization removes redundant trunc/zext pairs surrounding a negate
operation and replaces them with a masked negate of the original operand.
The transform helps expose rotate idioms in vector code, enabling targets such
as X86 (AVX2/AVX-512) to generate more efficient vpror/vpsllvq/vpsrlvq
instructions.

This change is motivated by issue #165306:
  [AVX-512] Look for vector bit rotates on vectors larger than 16 bytes

#### Implementation details
* Added pattern matching logic to `InstCombineCasts.cpp`.
* Added a dedicated test file `rotate-trunc-zext.ll` covering:
  - Scalar case (i64)
  - Vector cases (<2 x i64>, <4 x i64>, <8 x i64>)

>From 70dc7b74fb79cb7f4b9352c0f3c7195eecf47c09 Mon Sep 17 00:00:00 2001
From: Susikrishna <krishnasusi323 at gmail.com>
Date: Fri, 7 Nov 2025 18:55:07 +0530
Subject: [PATCH 1/2] [LLVM][InstCombine] Simplify zext(sub(0, trunc(x))) ->
 and(sub(0, x), (bitwidth-1))

---
 .../InstCombine/InstCombineCasts.cpp          | 16 ++++++
 .../InstCombine/rotate-trunc-zext.ll          | 55 +++++++++++++++++++
 2 files changed, 71 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/rotate-trunc-zext.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 614c6ebd63be6..01368273a47c0 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1217,6 +1217,22 @@ static bool canEvaluateZExtd(Value *V, Type *Ty, unsigned &BitsToClear,
 }
 
 Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
+  {
+    Value *TruncSrc = nullptr;
+    if (match(&Zext, m_ZExt(m_Sub(m_Zero(), m_Trunc(m_Value(TruncSrc)))))) {
+      IRBuilder<> Builder(&Zext);
+      Type *Ty = TruncSrc->getType();
+      unsigned BitWidth = Ty->getScalarSizeInBits();
+      unsigned MaskVal = BitWidth - 1;
+
+      Value *Zero = ConstantInt::get(Ty, 0);
+      Value *Neg = Builder.CreateSub(Zero, TruncSrc);
+      Value *Mask = ConstantInt::get(Ty, MaskVal);
+      Value *Masked = Builder.CreateAnd(Neg, Mask);
+      return replaceInstUsesWith(Zext, Masked);
+    }
+  }
+
   // If this zero extend is only used by a truncate, let the truncate be
   // eliminated before we try to optimize this zext.
   if (Zext.hasOneUse() && isa<TruncInst>(Zext.user_back()) &&
diff --git a/llvm/test/Transforms/InstCombine/rotate-trunc-zext.ll b/llvm/test/Transforms/InstCombine/rotate-trunc-zext.ll
new file mode 100644
index 0000000000000..31c7ba4a26796
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/rotate-trunc-zext.ll
@@ -0,0 +1,55 @@
+; RUN: opt -passes=instcombine -S %s | FileCheck %s
+
+; ================================================================
+; Test: Simplify zext(sub(0, trunc(x))) -> and(sub(0, x), (bitwidth-1))
+; Purpose: Check that InstCombine detects and simplifies the pattern
+;          seen in rotate idioms, enabling backend rotate lowering.
+; ================================================================
+
+; === Scalar Case (i64) =========================================
+define i64 @neg_trunc_zext(i64 %a) {
+; CHECK-LABEL: @neg_trunc_zext(
+; CHECK-NEXT: %[[NEG:[0-9]+]] = sub i64 0, %a
+; CHECK-NEXT: %[[MASKED:[0-9A-Za-z_]+]] = and i64 %[[NEG]], 63
+; CHECK-NEXT: ret i64 %[[MASKED]]
+  %t = trunc i64 %a to i6
+  %n = sub i6 0, %t
+  %z = zext i6 %n to i64
+  ret i64 %z
+}
+
+; === Vector Case 1: <2 x i64> ==================================
+define <2 x i64> @foo(<2 x i64> %x, <2 x i64> %n) {
+; CHECK-LABEL: @foo(
+; CHECK: %[[NEG:[0-9A-Za-z_]+]] = sub <2 x i64> zeroinitializer, %n
+; CHECK: %[[MASK:[0-9A-Za-z_]+]] = and <2 x i64> %[[NEG]], splat (i64 63)
+; CHECK: ret <2 x i64> %[[MASK]]
+  %t = trunc <2 x i64> %n to <2 x i6>
+  %neg = sub <2 x i6> zeroinitializer, %t
+  %z = zext <2 x i6> %neg to <2 x i64>
+  ret <2 x i64> %z
+}
+
+; === Vector Case 2: <4 x i64> ==================================
+define <4 x i64> @bar(<4 x i64> %x, <4 x i64> %n) {
+; CHECK-LABEL: @bar(
+; CHECK: %[[NEG:[0-9A-Za-z_]+]] = sub <4 x i64> zeroinitializer, %n
+; CHECK: %[[MASK:[0-9A-Za-z_]+]] = and <4 x i64> %[[NEG]], splat (i64 63)
+; CHECK: ret <4 x i64> %[[MASK]]
+  %t = trunc <4 x i64> %n to <4 x i6>
+  %neg = sub <4 x i6> zeroinitializer, %t
+  %z = zext <4 x i6> %neg to <4 x i64>
+  ret <4 x i64> %z
+}
+
+; === Vector Case 3: <8 x i64> ==================================
+define <8 x i64> @baz(<8 x i64> %x, <8 x i64> %n) {
+; CHECK-LABEL: @baz(
+; CHECK: %[[NEG:[0-9A-Za-z_]+]] = sub <8 x i64> zeroinitializer, %n
+; CHECK: %[[MASK:[0-9A-Za-z_]+]] = and <8 x i64> %[[NEG]], splat (i64 63)
+; CHECK: ret <8 x i64> %[[MASK]]
+  %t = trunc <8 x i64> %n to <8 x i6>
+  %neg = sub <8 x i6> zeroinitializer, %t
+  %z = zext <8 x i6> %neg to <8 x i64>
+  ret <8 x i64> %z
+}

>From 27cf5b5427eb8be9563de04b3387d836e89c9b31 Mon Sep 17 00:00:00 2001
From: Susikrishna <krishnasusi323 at gmail.com>
Date: Sat, 8 Nov 2025 12:11:08 +0530
Subject: [PATCH 2/2] [NFC][InstCombine] Move zext(sub 0, trunc X) combine to
 end of function

---
 .../InstCombine/InstCombineCasts.cpp          | 32 +++++++++----------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 01368273a47c0..ebe1b747e6be4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1217,22 +1217,6 @@ static bool canEvaluateZExtd(Value *V, Type *Ty, unsigned &BitsToClear,
 }
 
 Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
-  {
-    Value *TruncSrc = nullptr;
-    if (match(&Zext, m_ZExt(m_Sub(m_Zero(), m_Trunc(m_Value(TruncSrc)))))) {
-      IRBuilder<> Builder(&Zext);
-      Type *Ty = TruncSrc->getType();
-      unsigned BitWidth = Ty->getScalarSizeInBits();
-      unsigned MaskVal = BitWidth - 1;
-
-      Value *Zero = ConstantInt::get(Ty, 0);
-      Value *Neg = Builder.CreateSub(Zero, TruncSrc);
-      Value *Mask = ConstantInt::get(Ty, MaskVal);
-      Value *Masked = Builder.CreateAnd(Neg, Mask);
-      return replaceInstUsesWith(Zext, Masked);
-    }
-  }
-
   // If this zero extend is only used by a truncate, let the truncate be
   // eliminated before we try to optimize this zext.
   if (Zext.hasOneUse() && isa<TruncInst>(Zext.user_back()) &&
@@ -1382,6 +1366,22 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
     }
   }
 
+  {
+    Value *TruncSrc = nullptr;
+    if (match(&Zext, m_ZExt(m_Sub(m_Zero(), m_Trunc(m_Value(TruncSrc)))))) {
+      IRBuilder<> Builder(&Zext);
+      Type *Ty = TruncSrc->getType();
+      unsigned BitWidth = Ty->getScalarSizeInBits();
+      unsigned MaskVal = BitWidth - 1;
+
+      Value *Zero = ConstantInt::get(Ty, 0);
+      Value *Neg = Builder.CreateSub(Zero, TruncSrc);
+      Value *Mask = ConstantInt::get(Ty, MaskVal);
+      Value *Masked = Builder.CreateAnd(Neg, Mask);
+      return replaceInstUsesWith(Zext, Masked);
+    }
+  }
+
   return nullptr;
 }
 



More information about the llvm-commits mailing list