[llvm] [VectorCombine] Fold bitcast(bitreverse.v8i8(bitcast(IntTy))) into bswap+bitreverse (PR #209037)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Jul 12 11:09:33 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Deepak Shirke (deepakshirkem)
<details>
<summary>Changes</summary>
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 ; GPR → vector crossing
rbit v0.8b, v0.8b
fmov x0, d0 ; vector → GPR crossing
After:
rev x8, x0 ; stays in integer registers
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 (single `vgf2p8affineqb` instruction) are unaffected.
A new call site for `BitCast` instructions is added alongside the existing `Call` instruction call site in the dispatch loop.
Fixes https://github.com/llvm/llvm-project/issues/201760
---
Full diff: https://github.com/llvm/llvm-project/pull/209037.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/Vectorize/VectorCombine.cpp (+42)
- (added) llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v8i8-to-gpr.ll (+60)
``````````diff
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 8648125222e2a..d4387527dd2bc 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6118,6 +6118,45 @@ bool VectorCombine::foldBitcastOfVPLoad(Instruction &I) {
/// bitreverse(bswap(x)) --> bitcast(bitreverse(bitcast(x)))
bool VectorCombine::foldBitOrderReverseAndSwap(Instruction &I) {
Value *X;
+
+ if (match(&I, m_BitCast(m_Intrinsic<Intrinsic::bitreverse>(
+ m_BitCast(m_Value(X)))))) {
+ Type *Ty = X->getType();
+ Type *VecTy = cast<BitCastInst>(&I)->getOperand(0)->getType();
+ 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));
+ // OldCost = bitcast to vec + bitreverse.v8i8 + bitcast to int
+ 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 bitreverse.v8i8 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);
+ Value *BRev =
+ Builder.CreateUnaryIntrinsic(Intrinsic::bitreverse, BSwap);
+ replaceValue(I, *BRev);
+ return true;
+ }
+ }
+ }
+
if (!match(&I, m_BitReverse(m_BSwap(m_Value(X)))) &&
!match(&I, m_BSwap(m_BitReverse(m_Value(X)))))
return false;
@@ -6464,6 +6503,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..4fd34987b2d39
--- /dev/null
+++ b/llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v8i8-to-gpr.ll
@@ -0,0 +1,60 @@
+; 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
+
+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
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/209037
More information about the llvm-commits
mailing list