[llvm] [InstCombine] Fold zext(sub(0, trunc(X))) to and(sub(0, X), mask) (PR #207564)

via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 5 01:55:05 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Ayaan (def3r)

<details>
<summary>Changes</summary>

Problem: vector rotate and funnel shift fails to fold for vectors > 16 bytes on AVX-512. This is because of the `trunc` and `zext` instructions.

Example:
```llvm
define dso_local <8 x i64> @<!-- -->baz(<8 x i64> %0, <8 x i64> %1) local_unnamed_addr {
Entry:
  %2 = trunc <8 x i64> %1 to <8 x i6>
  %3 = sub <8 x i6> zeroinitializer, %2
  %4 = zext <8 x i6> %3 to <8 x i64>
  %5 = shl <8 x i64> %0, %4
  %6 = and <8 x i64> %1, splat (i64 63)
  %7 = lshr <8 x i64> %0, %6
  %8 = or <8 x i64> %5, %7
  ret <8 x i64> %8
}
```   

Solution; Canonicalize the transformation `zext(sub(0, trunc(X))) -> and(sub(0, X), mask)` for scalars and vectors.

Closes #<!-- -->165306

---
Full diff: https://github.com/llvm/llvm-project/pull/207564.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp (+9) 
- (added) llvm/test/Transforms/InstCombine/zext-sub-trunc.ll (+55) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
index 7e747ddb9013e..84c143897c8d4 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCasts.cpp
@@ -1686,6 +1686,15 @@ Instruction *InstCombinerImpl::visitZExt(ZExtInst &Zext) {
     return BinaryOperator::CreateXor(Builder.CreateAnd(X, ZC), ZC);
   }
 
+  // zext(sub(0, trunc(X))) -> and(sub(0, X), mask)
+  if (match(Src, m_Sub(m_Zero(), m_Trunc(m_Value(X)))) &&
+      X->getType() == DestTy) {
+    APInt Mask = APInt::getLowBitsSet(DestTy->getScalarSizeInBits(),
+                                      SrcTy->getScalarSizeInBits());
+    Value *Neg = Builder.CreateSub(ConstantInt::get(DestTy, 0), X);
+    return BinaryOperator::CreateAnd(Neg, ConstantInt::get(DestTy, Mask));
+  }
+
   // If we are truncating, masking, and then zexting back to the original type,
   // that's just a mask. This is not handled by canEvaluateZextd if the
   // intermediate values have extra uses. This could be generalized further for
diff --git a/llvm/test/Transforms/InstCombine/zext-sub-trunc.ll b/llvm/test/Transforms/InstCombine/zext-sub-trunc.ll
new file mode 100644
index 0000000000000..47c31acc800c5
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/zext-sub-trunc.ll
@@ -0,0 +1,55 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i64 @test_scalar_mask(i64 %a) {
+; CHECK-LABEL: define i64 @test_scalar_mask(
+; CHECK-SAME: i64 [[A:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = sub i64 0, [[A]]
+; CHECK-NEXT:    [[ZEXT:%.*]] = and i64 [[TMP1]], 63
+; CHECK-NEXT:    ret i64 [[ZEXT]]
+;
+  %trunc = trunc i64 %a to i6
+  %neg = sub i6 0, %trunc
+  %zext = zext i6 %neg to i64
+  ret i64 %zext
+}
+
+define <8 x i64> @test_vector_mask_v8i64(<8 x i64> %a0) {
+; CHECK-LABEL: define <8 x i64> @test_vector_mask_v8i64(
+; CHECK-SAME: <8 x i64> [[A0:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = sub <8 x i64> zeroinitializer, [[A0]]
+; CHECK-NEXT:    [[ZEXT:%.*]] = and <8 x i64> [[TMP1]], splat (i64 63)
+; CHECK-NEXT:    ret <8 x i64> [[ZEXT]]
+;
+  %trunc = trunc <8 x i64> %a0 to <8 x i6>
+  %neg = sub <8 x i6> zeroinitializer, %trunc
+  %zext = zext <8 x i6> %neg to <8 x i64>
+  ret <8 x i64> %zext
+}
+
+define <8 x i32> @test_vector_mask_v8i32(<8 x i32> %a0) {
+; CHECK-LABEL: define <8 x i32> @test_vector_mask_v8i32(
+; CHECK-SAME: <8 x i32> [[A0:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = sub <8 x i32> zeroinitializer, [[A0]]
+; CHECK-NEXT:    [[ZEXT:%.*]] = and <8 x i32> [[TMP1]], splat (i32 15)
+; CHECK-NEXT:    ret <8 x i32> [[ZEXT]]
+;
+  %trunc = trunc <8 x i32> %a0 to <8 x i4>
+  %neg = sub <8 x i4> zeroinitializer, %trunc
+  %zext = zext <8 x i4> %neg to <8 x i32>
+  ret <8 x i32> %zext
+}
+
+define <8 x i64> @test_negative_vector_mask_v8i32(<8 x i32> %a0) {
+; CHECK-LABEL: define <8 x i64> @test_negative_vector_mask_v8i32(
+; CHECK-SAME: <8 x i32> [[A0:%.*]]) {
+; CHECK-NEXT:    [[TRUNC:%.*]] = trunc <8 x i32> [[A0]] to <8 x i4>
+; CHECK-NEXT:    [[NEG:%.*]] = sub <8 x i4> zeroinitializer, [[TRUNC]]
+; CHECK-NEXT:    [[ZEXT:%.*]] = zext <8 x i4> [[NEG]] to <8 x i64>
+; CHECK-NEXT:    ret <8 x i64> [[ZEXT]]
+;
+  %trunc = trunc <8 x i32> %a0 to <8 x i4>
+  %neg = sub <8 x i4> zeroinitializer, %trunc
+  %zext = zext <8 x i4> %neg to <8 x i64>
+  ret <8 x i64> %zext
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/207564


More information about the llvm-commits mailing list