[llvm] [VectorCombine] Fold bitcast(bitreverse.v8i8(bitcast(IntTy))) into bswap+bitreverse (PR #209037)
Deepak Shirke via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 13 05:13:45 PDT 2026
https://github.com/deepakshirkem updated https://github.com/llvm/llvm-project/pull/209037
>From a02a9ccaa7c23e2a0cfab204ab39044743badf4a 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 | 51 ++++++++++++++-
.../AArch64/bitreverse-v8i8-to-gpr.ll | 64 +++++++++++++++++++
2 files changed, 113 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..09b06f6f36f64 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6114,10 +6114,54 @@ 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 *InnerCall = cast<Instruction>(cast<BitCastInst>(&I)->getOperand(0));
+ InstructionCost OldCost =
+ TTI.getCastInstrCost(Instruction::BitCast, VecTy, Ty,
+ TTI::CastContextHint::None, CostKind) +
+ TTI.getInstructionCost(InnerCall, CostKind) +
+ TTI.getCastInstrCost(Instruction::BitCast, Ty, VecTy,
+ TTI::CastContextHint::None, 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 +6508,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