[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