[llvm] [VectorCombine] Fold bitcast(bitreverse(bitcast(x))) to fshl+bitreverse (PR #214362)

Björn Pettersson via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 10 05:01:13 PDT 2026


https://github.com/bjope updated https://github.com/llvm/llvm-project/pull/214362

>From 95fb1b05ff4207c1833ae970a2a1ee903e454a31 Mon Sep 17 00:00:00 2001
From: Bjorn Pettersson <bjorn.a.pettersson at ericsson.com>
Date: Thu, 6 Aug 2026 00:16:16 +0200
Subject: [PATCH 1/3] [VectorCombine] Fold bitcast(bitreverse(bitcast(x))) to
 fshl+bitreverse

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.
---
 .../Transforms/Vectorize/VectorCombine.cpp    | 39 ++++++++++---
 .../AArch64/bitreverse-v2iN-to-scalar.ll      | 55 +++++++++++++++++++
 2 files changed, 85 insertions(+), 9 deletions(-)
 create mode 100644 llvm/test/Transforms/VectorCombine/AArch64/bitreverse-v2iN-to-scalar.ll

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
+}
+

>From bc3a370f82aba343f6aaa373996005bc4300b5fb Mon Sep 17 00:00:00 2001
From: Bjorn Pettersson <bjorn.a.pettersson at ericsson.com>
Date: Mon, 10 Aug 2026 13:55:12 +0200
Subject: [PATCH 2/3] Fixup: Hint that fshl is a rotate

---
 llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 131ee3ab592b5..bafc47a225c6e 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6155,11 +6155,12 @@ bool VectorCombine::foldBitOrderReverseAndSwap(Instruction &I) {
       auto *InnerBitCast = dyn_cast<BitCastInst>(InnerCall->getOperand(0));
       if (!InnerBitCast)
         return false;
+      Constant *HalfBW = ConstantInt::get(Ty, Ty->getIntegerBitWidth() / 2);
       InstructionCost OldCost = TTI.getInstructionCost(InnerBitCast, CostKind) +
                                 TTI.getInstructionCost(InnerCall, CostKind) +
                                 TTI.getInstructionCost(&I, CostKind);
       IntrinsicCostAttributes ICABSwap(Intrinsic::bswap, Ty, {Ty});
-      IntrinsicCostAttributes ICABFshl(Intrinsic::fshl, Ty, {Ty, Ty, Ty});
+      IntrinsicCostAttributes ICABFshl(Intrinsic::fshl, Ty, {X,X,HalfBW}, {Ty, Ty, Ty});
       IntrinsicCostAttributes ICABRev(Intrinsic::bitreverse, Ty, {Ty});
       InstructionCost NewCost =
           TTI.getIntrinsicInstrCost(CanUseBswap ? ICABSwap : ICABFshl,
@@ -6180,8 +6181,7 @@ bool VectorCombine::foldBitOrderReverseAndSwap(Instruction &I) {
                 ? Builder.CreateUnaryIntrinsic(Intrinsic::bswap, X)
                 : Builder.CreateIntrinsic(
                       Ty, Intrinsic::fshl,
-                      {X, X,
-                       ConstantInt::get(Ty, Ty->getIntegerBitWidth() / 2)});
+                      {X, X, HalfBW});
         Worklist.pushValue(Swap);
         Value *BRev = Builder.CreateUnaryIntrinsic(Intrinsic::bitreverse, Swap);
         replaceValue(I, *BRev);

>From f88e590f6293a592d1c75f3928de1962fd8b1ea8 Mon Sep 17 00:00:00 2001
From: Bjorn Pettersson <bjorn.a.pettersson at ericsson.com>
Date: Mon, 10 Aug 2026 14:00:57 +0200
Subject: [PATCH 3/3] clang-format update

---
 llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index bafc47a225c6e..a435d936bdb2c 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6160,7 +6160,8 @@ bool VectorCombine::foldBitOrderReverseAndSwap(Instruction &I) {
                                 TTI.getInstructionCost(InnerCall, CostKind) +
                                 TTI.getInstructionCost(&I, CostKind);
       IntrinsicCostAttributes ICABSwap(Intrinsic::bswap, Ty, {Ty});
-      IntrinsicCostAttributes ICABFshl(Intrinsic::fshl, Ty, {X,X,HalfBW}, {Ty, Ty, Ty});
+      IntrinsicCostAttributes ICABFshl(Intrinsic::fshl, Ty, {X, X, HalfBW},
+                                       {Ty, Ty, Ty});
       IntrinsicCostAttributes ICABRev(Intrinsic::bitreverse, Ty, {Ty});
       InstructionCost NewCost =
           TTI.getIntrinsicInstrCost(CanUseBswap ? ICABSwap : ICABFshl,
@@ -6179,9 +6180,7 @@ bool VectorCombine::foldBitOrderReverseAndSwap(Instruction &I) {
         Value *Swap =
             CanUseBswap
                 ? Builder.CreateUnaryIntrinsic(Intrinsic::bswap, X)
-                : Builder.CreateIntrinsic(
-                      Ty, Intrinsic::fshl,
-                      {X, X, HalfBW});
+                : Builder.CreateIntrinsic(Ty, Intrinsic::fshl, {X, X, HalfBW});
         Worklist.pushValue(Swap);
         Value *BRev = Builder.CreateUnaryIntrinsic(Intrinsic::bitreverse, Swap);
         replaceValue(I, *BRev);



More information about the llvm-commits mailing list