[llvm] [VectorCombine] Fold bitcast(bitreverse.v8i8(bitcast(IntTy))) into bswap+bitreverse (PR #209037)

Simon Pilgrim via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 14 05:27:12 PDT 2026


https://github.com/RKSimon updated https://github.com/llvm/llvm-project/pull/209037

>From 77e4a5b67d11189fd0d09bb23f1662a031242b31 Mon Sep 17 00:00:00 2001
From: deepakshirkem <deepakshirke509 at gmail.com>
Date: Sun, 12 Jul 2026 23:36:52 +0530
Subject: [PATCH] [VectorCombine] Fold bitcast(bitreverse.v8i8(bitcast(IntTy)))
 into bswap+bitreverse

Add reverse direction to foldBitOrderReverseAndSwap:
  bitcast(bitreverse(<N x i8>)(bitcast(IntTy X))) --> bitreverse(bswap(X))

This avoids GPR<=>vector register crossings when integer ops are cheaper.
On AArch64 for example:

Before:
  fmov    d0, x0
  rbit    v0.8b, v0.8b
  fmov    x0, d0

After:
  rev     x8, x0
  rbit    x0, x8

The fold is cost-model driven and only fires when the integer version
is cheaper, so targets like X86+GFNI that prefer the vector form
are unaffected.

Fixes https://github.com/llvm/llvm-project/issues/201760
---
 .../Transforms/Vectorize/VectorCombine.cpp    | 50 ++++++++++++++-
 .../AArch64/bitreverse-v8i8-to-gpr.ll         | 64 +++++++++++++++++++
 2 files changed, 112 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v8i8-to-gpr.ll

diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 8648125222e2a..28a19f83d1194 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6114,10 +6114,53 @@ bool VectorCombine::foldBitcastOfVPLoad(Instruction &I) {
 
 /// Fold the following cases into a single byte-level bit-reverse operation
 /// and accepts bswap and bitreverse intrinsics:
-///   bswap(bitreverse(x)) --> bitcast(bitreverse(bitcast(x)))
-///   bitreverse(bswap(x)) --> bitcast(bitreverse(bitcast(x)))
+///   bswap(bitreverse(x)) <--> bitcast(bitreverse(bitcast(x)))
+///   bitreverse(bswap(x)) <--> bitcast(bitreverse(bitcast(x)))
+/// The direction of the fold is cost-model driven.
 bool VectorCombine::foldBitOrderReverseAndSwap(Instruction &I) {
   Value *X;
+
+  if (match(&I, m_BitCast(m_BitReverse(m_BitCast(m_Value(X)))))) {
+    Type *Ty = X->getType();
+    Type *VecTy = cast<BitCastInst>(&I)->getOperand(0)->getType();
+    // Only handle integer scalar types with even byte count (bswap requirement)
+    // and fixed-length byte vectors.
+    if (Ty->isIntegerTy() && Ty == I.getType() && isa<FixedVectorType>(VecTy) &&
+        cast<FixedVectorType>(VecTy)->getElementType()->isIntegerTy(8) &&
+        Ty->getIntegerBitWidth() % 16 == 0) {
+      auto *OuterBitCast = cast<BitCastInst>(&I);
+      auto *InnerCall = cast<Instruction>(OuterBitCast->getOperand(0));
+      auto *InnerBitCast = cast<BitCastInst>(InnerCall->getOperand(0));
+      InstructionCost OldCost = TTI.getInstructionCost(InnerBitCast, CostKind) +
+                                TTI.getInstructionCost(InnerCall, CostKind) +
+                                TTI.getInstructionCost(OuterBitCast, CostKind);
+      // NewCost = bswap + bitreverse on integer type.
+      // If the bitreverse vector has other uses we cannot remove it,
+      // so add its cost to NewCost.
+      IntrinsicCostAttributes ICABSwap(Intrinsic::bswap, Ty, {Ty});
+      IntrinsicCostAttributes ICABRev(Intrinsic::bitreverse, Ty, {Ty});
+      InstructionCost NewCost = TTI.getIntrinsicInstrCost(ICABSwap, CostKind) +
+                                TTI.getIntrinsicInstrCost(ICABRev, CostKind);
+      if (!InnerCall->hasOneUse())
+        NewCost += TTI.getInstructionCost(InnerCall, CostKind);
+      LLVM_DEBUG(dbgs() << "Found bitreverse vector roundtrip: " << I
+                        << "\n  OldCost: " << OldCost
+                        << " vs NewCost: " << NewCost << "\n");
+      if (NewCost.isValid() && NewCost < OldCost) {
+        Builder.SetInsertPoint(&I);
+        Value *BSwap = Builder.CreateUnaryIntrinsic(Intrinsic::bswap, X);
+        Worklist.pushValue(BSwap);
+        Value *BRev =
+            Builder.CreateUnaryIntrinsic(Intrinsic::bitreverse, BSwap);
+        replaceValue(I, *BRev);
+        return true;
+      }
+    }
+  }
+
+  // Forward direction: bswap(bitreverse(x)) or bitreverse(bswap(x))
+  // --> bitcast(bitreverse(<N x i8>)(bitcast(x)))
+
   if (!match(&I, m_BitReverse(m_BSwap(m_Value(X)))) &&
       !match(&I, m_BSwap(m_BitReverse(m_Value(X)))))
     return false;
@@ -6464,6 +6507,9 @@ bool VectorCombine::run() {
     if (Opcode == Instruction::Call)
       if (foldBitOrderReverseAndSwap(I))
         return true;
+    if (Opcode == Instruction::BitCast)
+      if (foldBitOrderReverseAndSwap(I))
+        return true;
 
     // Otherwise, try folds that improve codegen but may interfere with
     // early IR canonicalizations.
diff --git a/llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v8i8-to-gpr.ll b/llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v8i8-to-gpr.ll
new file mode 100644
index 0000000000000..1a22a6d1f4feb
--- /dev/null
+++ b/llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v8i8-to-gpr.ll
@@ -0,0 +1,64 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=vector-combine -mtriple=aarch64 < %s | FileCheck %s
+; RUN: opt -S -passes=vector-combine -mtriple=aarch64_be < %s | FileCheck %s
+
+; Reverse direction: bitcast(bitreverse(<N x i8>)(bitcast(IntTy X))) --> bitreverse(bswap(X))
+; Avoids GPR<=>vector register crossings on AArch64 (e.g. fmov instructions).
+
+define i64 @fold_v8i8_roundtrip_i64(i64 %x) {
+; CHECK-LABEL: define i64 @fold_v8i8_roundtrip_i64(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = call i64 @llvm.bswap.i64(i64 [[X]])
+; CHECK-NEXT:    [[TMP2:%.*]] = call i64 @llvm.bitreverse.i64(i64 [[TMP1]])
+; CHECK-NEXT:    ret i64 [[TMP2]]
+;
+  %1 = bitcast i64 %x to <8 x i8>
+  %2 = call <8 x i8> @llvm.bitreverse.v8i8(<8 x i8> %1)
+  %3 = bitcast <8 x i8> %2 to i64
+  ret i64 %3
+}
+
+define i32 @fold_v4i8_roundtrip_i32(i32 %x) {
+; CHECK-LABEL: define i32 @fold_v4i8_roundtrip_i32(
+; CHECK-SAME: i32 [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = call i32 @llvm.bswap.i32(i32 [[X]])
+; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @llvm.bitreverse.i32(i32 [[TMP1]])
+; CHECK-NEXT:    ret i32 [[TMP2]]
+;
+  %1 = bitcast i32 %x to <4 x i8>
+  %2 = call <4 x i8> @llvm.bitreverse.v4i8(<4 x i8> %1)
+  %3 = bitcast <4 x i8> %2 to i32
+  ret i32 %3
+}
+
+; Negative test: i24 has odd byte count, bswap requires even bytes
+define i24 @no_fold_v3i8_roundtrip_i24(i24 %x) {
+; CHECK-LABEL: define i24 @no_fold_v3i8_roundtrip_i24(
+; CHECK-SAME: i24 [[X:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = bitcast i24 [[X]] to <3 x i8>
+; CHECK-NEXT:    [[TMP2:%.*]] = call <3 x i8> @llvm.bitreverse.v3i8(<3 x i8> [[TMP1]])
+; CHECK-NEXT:    [[TMP3:%.*]] = bitcast <3 x i8> [[TMP2]] to i24
+; CHECK-NEXT:    ret i24 [[TMP3]]
+;
+  %1 = bitcast i24 %x to <3 x i8>
+  %2 = call <3 x i8> @llvm.bitreverse.v3i8(<3 x i8> %1)
+  %3 = bitcast <3 x i8> %2 to i24
+  ret i24 %3
+}
+
+; Negative test: multiple uses of bitreverse result should NOT fold
+define i64 @no_fold_multiuse(i64 %x, ptr %p) {
+; CHECK-LABEL: define i64 @no_fold_multiuse(
+; CHECK-SAME: i64 [[X:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = bitcast i64 [[X]] to <8 x i8>
+; CHECK-NEXT:    [[TMP2:%.*]] = call <8 x i8> @llvm.bitreverse.v8i8(<8 x i8> [[TMP1]])
+; CHECK-NEXT:    store <8 x i8> [[TMP2]], ptr [[P]], align 8
+; CHECK-NEXT:    [[TMP3:%.*]] = bitcast <8 x i8> [[TMP2]] to i64
+; CHECK-NEXT:    ret i64 [[TMP3]]
+;
+  %1 = bitcast i64 %x to <8 x i8>
+  %2 = call <8 x i8> @llvm.bitreverse.v8i8(<8 x i8> %1)
+  store <8 x i8> %2, ptr %p
+  %3 = bitcast <8 x i8> %2 to i64
+  ret i64 %3
+}



More information about the llvm-commits mailing list