[llvm] [VectorCombine] Fold bitcast(bitreverse(bitcast(x))) to fshl+bitreverse (PR #214362)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 5 15:45:55 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-vectorizers
Author: Björn Pettersson (bjope)
<details>
<summary>Changes</summary>
Extend foldBitOrderReverseAndSwap to handle <2 x iN> element bitreverse patterns. Previously the fold only handled <N x i8> vectors using bswap to reorder octets. For 2-element vectors, the element swap is a rotate by half the bitwidth, which can be expressed as fshl(x, x, bitwidth/2).
The transform rewrites:
bitcast(bitreverse(bitcast(x))) -> bitreverse(fshl(x, x, N/2))
when the cost model indicates the scalar form is cheaper.
For <2 x i8> we still prefer to use bswap, and we do not even consider to check if fshl is cheaper than bswap.
---
Full diff: https://github.com/llvm/llvm-project/pull/214362.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/Vectorize/VectorCombine.cpp (+30-9)
- (added) llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v2iN-to-scalar.ll (+55)
``````````diff
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 726f564b1aad9..131ee3ab592b5 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6124,18 +6124,31 @@ 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)))
+/// bswap(bitreverse(x)) --> bitcast(bitreverse(bitcast(x)))
/// bitreverse(bswap(x)) <--> bitcast(bitreverse(bitcast(x)))
/// The direction of the fold is cost-model driven.
+/// Also supports:
+/// bitcast(bitreverse(bitcast(x))) --> bitreverse(fshl(x))
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 = I.getOperand(0)->getType();
- if (Ty->isIntegerTy() && Ty == I.getType() && isa<FixedVectorType>(VecTy) &&
+ // Detect the case when bitreversing every octet in X individually. Then we
+ // can use bswap to reorder the octets before doing a single bitreverse.
+ bool CanUseBswap =
+ Ty->isIntegerTy() && Ty == I.getType() && isa<FixedVectorType>(VecTy) &&
cast<FixedVectorType>(VecTy)->getElementType()->isIntegerTy(8) &&
- Ty->getIntegerBitWidth() % 16 == 0) {
+ Ty->getIntegerBitWidth() % 16 == 0;
+ // Detect the case when bitreversing upper and lower half of X
+ // individually. Then we can use fshl as a rotate operation, to swap the
+ // halves before doing a single bitreverse.
+ bool CanUseFshl =
+ Ty->isIntegerTy() && Ty == I.getType() && isa<FixedVectorType>(VecTy) &&
+ cast<FixedVectorType>(VecTy)->getElementType()->isIntegerTy() &&
+ cast<FixedVectorType>(VecTy)->getNumElements() == 2;
+ if (CanUseBswap || CanUseFshl) {
auto *InnerCall = dyn_cast<Instruction>(I.getOperand(0));
if (!InnerCall)
return false;
@@ -6146,9 +6159,12 @@ bool VectorCombine::foldBitOrderReverseAndSwap(Instruction &I) {
TTI.getInstructionCost(InnerCall, CostKind) +
TTI.getInstructionCost(&I, CostKind);
IntrinsicCostAttributes ICABSwap(Intrinsic::bswap, Ty, {Ty});
+ IntrinsicCostAttributes ICABFshl(Intrinsic::fshl, Ty, {Ty, Ty, Ty});
IntrinsicCostAttributes ICABRev(Intrinsic::bitreverse, Ty, {Ty});
- InstructionCost NewCost = TTI.getIntrinsicInstrCost(ICABSwap, CostKind) +
- TTI.getIntrinsicInstrCost(ICABRev, CostKind);
+ InstructionCost NewCost =
+ TTI.getIntrinsicInstrCost(CanUseBswap ? ICABSwap : ICABFshl,
+ CostKind) +
+ TTI.getIntrinsicInstrCost(ICABRev, CostKind);
if (!InnerCall->hasOneUse())
NewCost += TTI.getInstructionCost(InnerCall, CostKind) +
TTI.getInstructionCost(InnerBitCast, CostKind);
@@ -6159,10 +6175,15 @@ bool VectorCombine::foldBitOrderReverseAndSwap(Instruction &I) {
<< " 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);
+ Value *Swap =
+ CanUseBswap
+ ? Builder.CreateUnaryIntrinsic(Intrinsic::bswap, X)
+ : Builder.CreateIntrinsic(
+ Ty, Intrinsic::fshl,
+ {X, X,
+ ConstantInt::get(Ty, Ty->getIntegerBitWidth() / 2)});
+ Worklist.pushValue(Swap);
+ Value *BRev = Builder.CreateUnaryIntrinsic(Intrinsic::bitreverse, Swap);
replaceValue(I, *BRev);
return true;
}
diff --git a/llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v2iN-to-scalar.ll b/llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v2iN-to-scalar.ll
new file mode 100644
index 0000000000000..d9ce89e05b4d8
--- /dev/null
+++ b/llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v2iN-to-scalar.ll
@@ -0,0 +1,55 @@
+; 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 i6 @fold_v2i3_roundtrip_i6(i6 %x) {
+; CHECK-LABEL: define i6 @fold_v2i3_roundtrip_i6(
+; CHECK-SAME: i6 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i6 @llvm.fshl.i6(i6 [[X]], i6 [[X]], i6 3)
+; CHECK-NEXT: [[TMP2:%.*]] = call i6 @llvm.bitreverse.i6(i6 [[TMP1]])
+; CHECK-NEXT: ret i6 [[TMP2]]
+;
+ %1 = bitcast i6 %x to <2 x i3>
+ %2 = call <2 x i3> @llvm.bitreverse.v2i3(<2 x i3> %1)
+ %3 = bitcast <2 x i3> %2 to i6
+ ret i6 %3
+}
+
+define i16 @fold_v2i8_roundtrip_i16(i16 %x) {
+; CHECK-LABEL: define i16 @fold_v2i8_roundtrip_i16(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i16 @llvm.bswap.i16(i16 [[X]])
+; CHECK-NEXT: [[TMP2:%.*]] = call i16 @llvm.bitreverse.i16(i16 [[TMP1]])
+; CHECK-NEXT: ret i16 [[TMP2]]
+;
+ %1 = bitcast i16 %x to <2 x i8>
+ %2 = call <2 x i8> @llvm.bitreverse.v2i8(<2 x i8> %1)
+ %3 = bitcast <2 x i8> %2 to i16
+ ret i16 %3
+}
+
+define i64 @fold_v2i32_roundtrip_i64(i64 %x) {
+; CHECK-LABEL: define i64 @fold_v2i32_roundtrip_i64(
+; CHECK-SAME: i64 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i64 @llvm.fshl.i64(i64 [[X]], i64 [[X]], i64 32)
+; CHECK-NEXT: [[TMP2:%.*]] = call i64 @llvm.bitreverse.i64(i64 [[TMP1]])
+; CHECK-NEXT: ret i64 [[TMP2]]
+;
+ %1 = bitcast i64 %x to <2 x i32>
+ %2 = call <2 x i32> @llvm.bitreverse.v2i32(<2 x i32> %1)
+ %3 = bitcast <2 x i32> %2 to i64
+ ret i64 %3
+}
+
+define i128 @fold_v2i64_roundtrip_i128(i128 %x) {
+; CHECK-LABEL: define i128 @fold_v2i64_roundtrip_i128(
+; CHECK-SAME: i128 [[X:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = call i128 @llvm.fshl.i128(i128 [[X]], i128 [[X]], i128 64)
+; CHECK-NEXT: [[TMP2:%.*]] = call i128 @llvm.bitreverse.i128(i128 [[TMP1]])
+; CHECK-NEXT: ret i128 [[TMP2]]
+;
+ %1 = bitcast i128 %x to <2 x i64>
+ %2 = call <2 x i64> @llvm.bitreverse.v2i64(<2 x i64> %1)
+ %3 = bitcast <2 x i64> %2 to i128
+ ret i128 %3
+}
+
``````````
</details>
https://github.com/llvm/llvm-project/pull/214362
More information about the llvm-commits
mailing list