[llvm] [InstCombine/RISCV] Constant-fold bitcast(vmv.v.x) (PR #182630)

Ramkumar Ramachandra via llvm-commits llvm-commits at lists.llvm.org
Mon Mar 9 02:02:53 PDT 2026


https://github.com/artagnon updated https://github.com/llvm/llvm-project/pull/182630

>From cdc27128b641b9f92af9985eed5e232d5068fb27 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Fri, 20 Feb 2026 23:04:41 +0000
Subject: [PATCH 1/9] [InstCombine] Pre-commit test for riscv-vmv-v-x

---
 .../InstCombine/RISCV/riscv-vmv-v-x.ll        | 100 ++++++++++++++++++
 1 file changed, 100 insertions(+)
 create mode 100644 llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll

diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
new file mode 100644
index 0000000000000..914957dfce350
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
@@ -0,0 +1,100 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -p instcombine -mtriple=riscv32 -mattr=+v -S %s | FileCheck %s
+; RUN: opt -p instcombine -mtriple=riscv64 -mattr=+v -S %s | FileCheck %s
+
+define <8 x i8> @fixed() {
+; CHECK-LABEL: define <8 x i8> @fixed(
+; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 85, i64 4)
+; CHECK-NEXT:    ret <8 x i8> [[A]]
+;
+  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 4)
+  ret <8 x i8> %a
+}
+
+define <vscale x 8 x i8> @scalable() {
+; CHECK-LABEL: define <vscale x 8 x i8> @scalable(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 4)
+; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
+;
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
+  ret <vscale x 8 x i8> %a
+}
+
+define <8 x i8> @small_scalar() {
+; CHECK-LABEL: define <8 x i8> @small_scalar(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 3, i64 4)
+; CHECK-NEXT:    ret <8 x i8> [[A]]
+;
+  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 3, i64 4)
+  ret <8 x i8> %a
+}
+
+define <64 x i1> @users_with_bitcast() {
+; CHECK-LABEL: define <64 x i1> @users_with_bitcast(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[VMV_1:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 85, i64 4)
+; CHECK-NEXT:    [[VMV_2:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 -86, i64 4)
+; CHECK-NEXT:    [[RET1:%.*]] = xor <8 x i8> [[VMV_1]], [[VMV_2]]
+; CHECK-NEXT:    [[RET:%.*]] = bitcast <8 x i8> [[RET1]] to <64 x i1>
+; CHECK-NEXT:    ret <64 x i1> [[RET]]
+;
+  %vmv.1 = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 4)
+  %cast.1 = bitcast <8 x i8> %vmv.1 to <64 x i1>
+  %vmv.2 = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 -86, i64 4)
+  %cast.2 = bitcast <8 x i8> %vmv.2 to <64 x i1>
+  %ret = xor <64 x i1> %cast.1, %cast.2
+  ret <64 x i1> %ret
+}
+
+define <8 x i8> @passthru_non_poison(<8 x i8> %x) {
+; CHECK-LABEL: define <8 x i8> @passthru_non_poison(
+; CHECK-SAME: <8 x i8> [[X:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> [[X]], i8 85, i64 4)
+; CHECK-NEXT:    ret <8 x i8> [[A]]
+;
+  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> %x, i8 85, i64 4)
+  ret <8 x i8> %a
+}
+
+define <8 x i8> @scalar_non_constant(i8 %scalar) {
+; CHECK-LABEL: define <8 x i8> @scalar_non_constant(
+; CHECK-SAME: i8 [[SCALAR:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 [[SCALAR]], i64 4)
+; CHECK-NEXT:    ret <8 x i8> [[A]]
+;
+  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 %scalar, i64 4)
+  ret <8 x i8> %a
+}
+
+define <8 x i8> @vl_non_constant(i64 %vl) {
+; CHECK-LABEL: define <8 x i8> @vl_non_constant(
+; CHECK-SAME: i64 [[VL:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 85, i64 [[VL]])
+; CHECK-NEXT:    ret <8 x i8> [[A]]
+;
+  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 %vl)
+  ret <8 x i8> %a
+}
+
+define <1 x i128> @scalar_operand_too_large() {
+; CHECK-LABEL: define <1 x i128> @scalar_operand_too_large(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <1 x i128> @llvm.riscv.vmv.v.x.v1i128.i64(<1 x i128> poison, i128 85, i64 4)
+; CHECK-NEXT:    ret <1 x i128> [[A]]
+;
+  %a = call <1 x i128> @llvm.riscv.vmv.v.x.v8i8(<1 x i128> poison, i128 85, i64 4)
+  ret <1 x i128> %a
+}
+
+define <8 x i8> @vl_too_large() {
+; CHECK-LABEL: define <8 x i8> @vl_too_large(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 85, i64 128)
+; CHECK-NEXT:    ret <8 x i8> [[A]]
+;
+  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 128)
+  ret <8 x i8> %a
+}

>From 1b246beccf126a985f9060df752768b2cacfa63e Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Fri, 20 Feb 2026 23:26:18 +0000
Subject: [PATCH 2/9] [InstCombine] Constant-fold riscv.vmv.v.x

The motivating example is: https://godbolt.org/z/vnb3ETsbc

There is an issue with extra vsetvli instructions due to
RISCVInsertVSETVLI asking for instructions that demand VL, and
optimizing based on that. Due to the non-unit VL in vmv.v.x, we
wastefully insert VL number of vsetvli instructions when all operands
are constant. To avoid this, constant-fold vmv.v.x with a bitcast to
handle the type conversion.

llc run showing vsetvli eliminated: https://godbolt.org/z/KEPTxTPcb
---
 .../InstCombine/InstCombineCalls.cpp          | 41 +++++++++++++++++++
 .../InstCombine/RISCV/riscv-vmv-v-x.ll        | 17 ++++----
 2 files changed, 51 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 8185f009bc9d0..0c455a408af8c 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -48,6 +48,7 @@
 #include "llvm/IR/IntrinsicsAMDGPU.h"
 #include "llvm/IR/IntrinsicsARM.h"
 #include "llvm/IR/IntrinsicsHexagon.h"
+#include "llvm/IR/IntrinsicsRISCV.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/PatternMatch.h"
@@ -4286,6 +4287,46 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
           *II, Builder.CreateZExtOrTrunc(II->getArgOperand(0), II->getType()));
     return nullptr;
   }
+  case Intrinsic::riscv_vmv_v_x: {
+    // If all operands are constant, constant-fold with bitcast. The rationale
+    // for this is to optimize the number of inserted vsetivli instructions, by
+    // RISCVInsertVSETVLI.
+    const APInt *Scalar, *VL;
+    if (!match(II, m_Intrinsic<Intrinsic::riscv_vmv_v_x>(
+                       m_Poison(), m_APInt(Scalar), m_APInt(VL))) ||
+        VL->isOne() || Scalar->getBitWidth() > VL->getBitWidth())
+      return nullptr;
+    auto *VecTy = cast<VectorType>(II->getType());
+    bool IsScalable = VecTy->isScalableTy();
+    ElementCount EC = VecTy->getElementCount();
+    ElementCount ScaleFactor =
+        ElementCount::get(VL->getZExtValue(), IsScalable);
+    auto *EltTy = cast<IntegerType>(VecTy->getScalarType());
+    auto *NewEltTy = IntegerType::get(
+        CI.getContext(), EltTy->getScalarSizeInBits() * VL->getZExtValue());
+    if (!EC.hasKnownScalarFactor(ScaleFactor) ||
+        NewEltTy->getBitWidth() > VL->getBitWidth())
+      return nullptr;
+    ElementCount NewEC =
+        ElementCount::get(EC.getKnownScalarFactor(ScaleFactor), IsScalable);
+    Type *RetTy = VectorType::get(NewEltTy, NewEC);
+    assert(VecTy->canLosslesslyBitCastTo(RetTy) &&
+           "Lossless bitcast between types expected");
+    APInt ScalarExt = Scalar->abs().zext(NewEltTy->getBitWidth());
+    APInt NewScalar(ScalarExt.getBitWidth(), 0);
+    for (unsigned Idx : seq(VL->getZExtValue()))
+      NewScalar |= ScalarExt << Scalar->getBitWidth() * Idx;
+    if (Scalar->isSignBitSet())
+      NewScalar.setSignBit();
+    return replaceInstUsesWith(
+        *II,
+        Builder.CreateBitCast(
+            Builder.CreateIntrinsic(
+                RetTy, Intrinsic::riscv_vmv_v_x,
+                {PoisonValue::get(RetTy), ConstantInt::get(NewEltTy, NewScalar),
+                 ConstantInt::get(II->getOperand(2)->getType(), 1)}),
+            VecTy));
+  }
   default: {
     // Handle target specific intrinsics
     std::optional<Instruction *> V = targetInstCombineIntrinsic(*II);
diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
index 914957dfce350..cc8afb5f4b89e 100644
--- a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
@@ -5,7 +5,8 @@
 define <8 x i8> @fixed() {
 ; CHECK-LABEL: define <8 x i8> @fixed(
 ; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
-; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 85, i64 4)
+; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.vmv.v.x.v2i32.i64(<2 x i32> poison, i32 1431655765, i64 1)
+; CHECK-NEXT:    [[A:%.*]] = bitcast <2 x i32> [[TMP1]] to <8 x i8>
 ; CHECK-NEXT:    ret <8 x i8> [[A]]
 ;
   %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 4)
@@ -15,7 +16,8 @@ define <8 x i8> @fixed() {
 define <vscale x 8 x i8> @scalable() {
 ; CHECK-LABEL: define <vscale x 8 x i8> @scalable(
 ; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 4)
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 1)
+; CHECK-NEXT:    [[A:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 8 x i8>
 ; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
@@ -25,7 +27,8 @@ define <vscale x 8 x i8> @scalable() {
 define <8 x i8> @small_scalar() {
 ; CHECK-LABEL: define <8 x i8> @small_scalar(
 ; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 3, i64 4)
+; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.vmv.v.x.v2i32.i64(<2 x i32> poison, i32 50529027, i64 1)
+; CHECK-NEXT:    [[A:%.*]] = bitcast <2 x i32> [[TMP1]] to <8 x i8>
 ; CHECK-NEXT:    ret <8 x i8> [[A]]
 ;
   %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 3, i64 4)
@@ -35,10 +38,10 @@ define <8 x i8> @small_scalar() {
 define <64 x i1> @users_with_bitcast() {
 ; CHECK-LABEL: define <64 x i1> @users_with_bitcast(
 ; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[VMV_1:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 85, i64 4)
-; CHECK-NEXT:    [[VMV_2:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 -86, i64 4)
-; CHECK-NEXT:    [[RET1:%.*]] = xor <8 x i8> [[VMV_1]], [[VMV_2]]
-; CHECK-NEXT:    [[RET:%.*]] = bitcast <8 x i8> [[RET1]] to <64 x i1>
+; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.vmv.v.x.v2i32.i64(<2 x i32> poison, i32 1431655765, i64 1)
+; CHECK-NEXT:    [[TMP2:%.*]] = call <2 x i32> @llvm.riscv.vmv.v.x.v2i32.i64(<2 x i32> poison, i32 -698984874, i64 1)
+; CHECK-NEXT:    [[RET1:%.*]] = xor <2 x i32> [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    [[RET:%.*]] = bitcast <2 x i32> [[RET1]] to <64 x i1>
 ; CHECK-NEXT:    ret <64 x i1> [[RET]]
 ;
   %vmv.1 = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 4)

>From de445e69e5403b1116d66254e60c7b56ef67c1fb Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Sat, 21 Feb 2026 00:44:42 +0000
Subject: [PATCH 3/9] [InstCombine] Strip degenerate fixed-vector case

---
 .../InstCombine/InstCombineCalls.cpp          |   8 +-
 .../InstCombine/RISCV/riscv-vmv-v-x.ll        | 115 ++++++++----------
 2 files changed, 55 insertions(+), 68 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 0c455a408af8c..89c66e2aea1c7 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -4296,11 +4296,9 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
                        m_Poison(), m_APInt(Scalar), m_APInt(VL))) ||
         VL->isOne() || Scalar->getBitWidth() > VL->getBitWidth())
       return nullptr;
-    auto *VecTy = cast<VectorType>(II->getType());
-    bool IsScalable = VecTy->isScalableTy();
+    auto *VecTy = cast<ScalableVectorType>(II->getType());
     ElementCount EC = VecTy->getElementCount();
-    ElementCount ScaleFactor =
-        ElementCount::get(VL->getZExtValue(), IsScalable);
+    ElementCount ScaleFactor = ElementCount::getScalable(VL->getZExtValue());
     auto *EltTy = cast<IntegerType>(VecTy->getScalarType());
     auto *NewEltTy = IntegerType::get(
         CI.getContext(), EltTy->getScalarSizeInBits() * VL->getZExtValue());
@@ -4308,7 +4306,7 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
         NewEltTy->getBitWidth() > VL->getBitWidth())
       return nullptr;
     ElementCount NewEC =
-        ElementCount::get(EC.getKnownScalarFactor(ScaleFactor), IsScalable);
+        ElementCount::getScalable(EC.getKnownScalarFactor(ScaleFactor));
     Type *RetTy = VectorType::get(NewEltTy, NewEC);
     assert(VecTy->canLosslesslyBitCastTo(RetTy) &&
            "Lossless bitcast between types expected");
diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
index cc8afb5f4b89e..0e27bebade036 100644
--- a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
@@ -2,20 +2,9 @@
 ; RUN: opt -p instcombine -mtriple=riscv32 -mattr=+v -S %s | FileCheck %s
 ; RUN: opt -p instcombine -mtriple=riscv64 -mattr=+v -S %s | FileCheck %s
 
-define <8 x i8> @fixed() {
-; CHECK-LABEL: define <8 x i8> @fixed(
-; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.vmv.v.x.v2i32.i64(<2 x i32> poison, i32 1431655765, i64 1)
-; CHECK-NEXT:    [[A:%.*]] = bitcast <2 x i32> [[TMP1]] to <8 x i8>
-; CHECK-NEXT:    ret <8 x i8> [[A]]
-;
-  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 4)
-  ret <8 x i8> %a
-}
-
 define <vscale x 8 x i8> @scalable() {
 ; CHECK-LABEL: define <vscale x 8 x i8> @scalable(
-; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 1)
 ; CHECK-NEXT:    [[A:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 8 x i8>
 ; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
@@ -24,80 +13,80 @@ define <vscale x 8 x i8> @scalable() {
   ret <vscale x 8 x i8> %a
 }
 
-define <8 x i8> @small_scalar() {
-; CHECK-LABEL: define <8 x i8> @small_scalar(
+define <vscale x 8 x i8> @small_scalar() {
+; CHECK-LABEL: define <vscale x 8 x i8> @small_scalar(
 ; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.vmv.v.x.v2i32.i64(<2 x i32> poison, i32 50529027, i64 1)
-; CHECK-NEXT:    [[A:%.*]] = bitcast <2 x i32> [[TMP1]] to <8 x i8>
-; CHECK-NEXT:    ret <8 x i8> [[A]]
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 50529027, i64 1)
+; CHECK-NEXT:    [[A:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 8 x i8>
+; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
 ;
-  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 3, i64 4)
-  ret <8 x i8> %a
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 3, i64 4)
+  ret <vscale x 8 x i8> %a
 }
 
-define <64 x i1> @users_with_bitcast() {
-; CHECK-LABEL: define <64 x i1> @users_with_bitcast(
+define <vscale x 64 x i1> @users_with_bitcast() {
+; CHECK-LABEL: define <vscale x 64 x i1> @users_with_bitcast(
 ; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[TMP1:%.*]] = call <2 x i32> @llvm.riscv.vmv.v.x.v2i32.i64(<2 x i32> poison, i32 1431655765, i64 1)
-; CHECK-NEXT:    [[TMP2:%.*]] = call <2 x i32> @llvm.riscv.vmv.v.x.v2i32.i64(<2 x i32> poison, i32 -698984874, i64 1)
-; CHECK-NEXT:    [[RET1:%.*]] = xor <2 x i32> [[TMP1]], [[TMP2]]
-; CHECK-NEXT:    [[RET:%.*]] = bitcast <2 x i32> [[RET1]] to <64 x i1>
-; CHECK-NEXT:    ret <64 x i1> [[RET]]
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 1)
+; CHECK-NEXT:    [[TMP2:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 -698984874, i64 1)
+; CHECK-NEXT:    [[RET1:%.*]] = xor <vscale x 2 x i32> [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    [[RET:%.*]] = bitcast <vscale x 2 x i32> [[RET1]] to <vscale x 64 x i1>
+; CHECK-NEXT:    ret <vscale x 64 x i1> [[RET]]
 ;
-  %vmv.1 = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 4)
-  %cast.1 = bitcast <8 x i8> %vmv.1 to <64 x i1>
-  %vmv.2 = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 -86, i64 4)
-  %cast.2 = bitcast <8 x i8> %vmv.2 to <64 x i1>
-  %ret = xor <64 x i1> %cast.1, %cast.2
-  ret <64 x i1> %ret
+  %vmv.1 = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
+  %cast.1 = bitcast <vscale x 8 x i8> %vmv.1 to <vscale x 64 x i1>
+  %vmv.2 = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 -86, i64 4)
+  %cast.2 = bitcast <vscale x 8 x i8> %vmv.2 to <vscale x 64 x i1>
+  %ret = xor <vscale x 64 x i1> %cast.1, %cast.2
+  ret <vscale x 64 x i1> %ret
 }
 
-define <8 x i8> @passthru_non_poison(<8 x i8> %x) {
-; CHECK-LABEL: define <8 x i8> @passthru_non_poison(
-; CHECK-SAME: <8 x i8> [[X:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> [[X]], i8 85, i64 4)
-; CHECK-NEXT:    ret <8 x i8> [[A]]
+define <vscale x 8 x i8> @passthru_non_poison(<vscale x 8 x i8> %x) {
+; CHECK-LABEL: define <vscale x 8 x i8> @passthru_non_poison(
+; CHECK-SAME: <vscale x 8 x i8> [[X:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> [[X]], i8 85, i64 4)
+; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
 ;
-  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> %x, i8 85, i64 4)
-  ret <8 x i8> %a
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> %x, i8 85, i64 4)
+  ret <vscale x 8 x i8> %a
 }
 
-define <8 x i8> @scalar_non_constant(i8 %scalar) {
-; CHECK-LABEL: define <8 x i8> @scalar_non_constant(
+define <vscale x 8 x i8> @scalar_non_constant(i8 %scalar) {
+; CHECK-LABEL: define <vscale x 8 x i8> @scalar_non_constant(
 ; CHECK-SAME: i8 [[SCALAR:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 [[SCALAR]], i64 4)
-; CHECK-NEXT:    ret <8 x i8> [[A]]
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 [[SCALAR]], i64 4)
+; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
 ;
-  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 %scalar, i64 4)
-  ret <8 x i8> %a
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 %scalar, i64 4)
+  ret <vscale x 8 x i8> %a
 }
 
-define <8 x i8> @vl_non_constant(i64 %vl) {
-; CHECK-LABEL: define <8 x i8> @vl_non_constant(
+define <vscale x 8 x i8> @vl_non_constant(i64 %vl) {
+; CHECK-LABEL: define <vscale x 8 x i8> @vl_non_constant(
 ; CHECK-SAME: i64 [[VL:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 85, i64 [[VL]])
-; CHECK-NEXT:    ret <8 x i8> [[A]]
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 [[VL]])
+; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
 ;
-  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 %vl)
-  ret <8 x i8> %a
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 %vl)
+  ret <vscale x 8 x i8> %a
 }
 
-define <1 x i128> @scalar_operand_too_large() {
-; CHECK-LABEL: define <1 x i128> @scalar_operand_too_large(
+define <vscale x 1 x i128> @scalar_operand_too_large() {
+; CHECK-LABEL: define <vscale x 1 x i128> @scalar_operand_too_large(
 ; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <1 x i128> @llvm.riscv.vmv.v.x.v1i128.i64(<1 x i128> poison, i128 85, i64 4)
-; CHECK-NEXT:    ret <1 x i128> [[A]]
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 1 x i128> @llvm.riscv.vmv.v.x.nxv1i128.i64(<vscale x 1 x i128> poison, i128 85, i64 4)
+; CHECK-NEXT:    ret <vscale x 1 x i128> [[A]]
 ;
-  %a = call <1 x i128> @llvm.riscv.vmv.v.x.v8i8(<1 x i128> poison, i128 85, i64 4)
-  ret <1 x i128> %a
+  %a = call <vscale x 1 x i128> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 1 x i128> poison, i128 85, i64 4)
+  ret <vscale x 1 x i128> %a
 }
 
-define <8 x i8> @vl_too_large() {
-; CHECK-LABEL: define <8 x i8> @vl_too_large(
+define <vscale x 8 x i8> @vl_too_large() {
+; CHECK-LABEL: define <vscale x 8 x i8> @vl_too_large(
 ; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8.i64(<8 x i8> poison, i8 85, i64 128)
-; CHECK-NEXT:    ret <8 x i8> [[A]]
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 128)
+; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
 ;
-  %a = call <8 x i8> @llvm.riscv.vmv.v.x.v8i8(<8 x i8> poison, i8 85, i64 128)
-  ret <8 x i8> %a
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 128)
+  ret <vscale x 8 x i8> %a
 }

>From 67ac5f7b15b098d2c54e712066d74c59ce5386e2 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Sat, 21 Feb 2026 10:51:23 +0000
Subject: [PATCH 4/9] [InstCombine/RISCV] Address reviews

---
 .../Target/RISCV/RISCVTargetTransformInfo.cpp |  40 +++++
 .../Target/RISCV/RISCVTargetTransformInfo.h   |   3 +
 .../InstCombine/InstCombineCalls.cpp          |  39 -----
 .../InstCombine/RISCV/riscv-vmv-v-x.ll        | 140 +++++++++++++-----
 4 files changed, 148 insertions(+), 74 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index 5394d5dc0be11..ed6730f8508b7 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -17,6 +17,7 @@
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicsRISCV.h"
 #include "llvm/IR/PatternMatch.h"
+#include "llvm/Transforms/InstCombine/InstCombiner.h"
 #include <cmath>
 #include <optional>
 using namespace llvm;
@@ -3603,3 +3604,42 @@ bool RISCVTTIImpl::shouldCopyAttributeWhenOutliningFrom(
 
   return BaseT::shouldCopyAttributeWhenOutliningFrom(Caller, Attr);
 }
+
+std::optional<Instruction *>
+RISCVTTIImpl::instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const {
+  // If all operands are constant, constant-fold with bitcast: make sure that
+  // all users are bitcasts, to avoid introducing new bitcasts. The rationale
+  // for this is to optimize the number of inserted vsetvli instructions, by
+  // RISCVInsertVSETVLI.
+  const APInt *Scalar;
+  uint64_t VL;
+  if (!match(&II, m_Intrinsic<Intrinsic::riscv_vmv_v_x>(
+                      m_Poison(), m_APInt(Scalar), m_ConstantInt(VL))) ||
+      VL == 1 || Scalar->getBitWidth() > ST->getXLen() ||
+      !all_of(II.users(), match_fn(m_BitCast(m_Value()))))
+    return {};
+  auto *VecTy = cast<ScalableVectorType>(II.getType());
+  ElementCount EC = VecTy->getElementCount();
+  ElementCount ScaleFactor = ElementCount::getScalable(VL);
+  auto *EltTy = cast<IntegerType>(VecTy->getElementType());
+  unsigned NewEltBW = EltTy->getScalarSizeInBits() * VL;
+  if (!EC.hasKnownScalarFactor(ScaleFactor) || NewEltBW > ST->getXLen())
+    return {};
+  auto *NewEltTy = IntegerType::get(II.getContext(), NewEltBW);
+  if (!TLI->isLegalElementTypeForRVV(TLI->getValueType(DL, NewEltTy)))
+    return {};
+  ElementCount NewEC =
+      ElementCount::getScalable(EC.getKnownScalarFactor(ScaleFactor));
+  Type *RetTy = VectorType::get(NewEltTy, NewEC);
+  assert(VecTy->canLosslesslyBitCastTo(RetTy) &&
+         "Lossless bitcast between types expected");
+  APInt NewScalar = APInt::getSplat(NewEltBW, *Scalar);
+  Type *VLTy = II.getOperand(2)->getType();
+  return IC.replaceInstUsesWith(
+      II, IC.Builder.CreateBitCast(
+              IC.Builder.CreateIntrinsic(RetTy, Intrinsic::riscv_vmv_v_x,
+                                         {PoisonValue::get(RetTy),
+                                          ConstantInt::get(NewEltTy, NewScalar),
+                                          ConstantInt::get(VLTy, 1)}),
+              VecTy));
+}
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
index 97a364d741314..ad0248138d5bf 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
@@ -506,6 +506,9 @@ class RISCVTTIImpl final : public BasicTTIImplBase<RISCVTTIImpl> {
   bool
   shouldCopyAttributeWhenOutliningFrom(const Function *Caller,
                                        const Attribute &Attr) const override;
+
+  std::optional<Instruction *>
+  instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const override;
 };
 
 } // end namespace llvm
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index 89c66e2aea1c7..8185f009bc9d0 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -48,7 +48,6 @@
 #include "llvm/IR/IntrinsicsAMDGPU.h"
 #include "llvm/IR/IntrinsicsARM.h"
 #include "llvm/IR/IntrinsicsHexagon.h"
-#include "llvm/IR/IntrinsicsRISCV.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/PatternMatch.h"
@@ -4287,44 +4286,6 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
           *II, Builder.CreateZExtOrTrunc(II->getArgOperand(0), II->getType()));
     return nullptr;
   }
-  case Intrinsic::riscv_vmv_v_x: {
-    // If all operands are constant, constant-fold with bitcast. The rationale
-    // for this is to optimize the number of inserted vsetivli instructions, by
-    // RISCVInsertVSETVLI.
-    const APInt *Scalar, *VL;
-    if (!match(II, m_Intrinsic<Intrinsic::riscv_vmv_v_x>(
-                       m_Poison(), m_APInt(Scalar), m_APInt(VL))) ||
-        VL->isOne() || Scalar->getBitWidth() > VL->getBitWidth())
-      return nullptr;
-    auto *VecTy = cast<ScalableVectorType>(II->getType());
-    ElementCount EC = VecTy->getElementCount();
-    ElementCount ScaleFactor = ElementCount::getScalable(VL->getZExtValue());
-    auto *EltTy = cast<IntegerType>(VecTy->getScalarType());
-    auto *NewEltTy = IntegerType::get(
-        CI.getContext(), EltTy->getScalarSizeInBits() * VL->getZExtValue());
-    if (!EC.hasKnownScalarFactor(ScaleFactor) ||
-        NewEltTy->getBitWidth() > VL->getBitWidth())
-      return nullptr;
-    ElementCount NewEC =
-        ElementCount::getScalable(EC.getKnownScalarFactor(ScaleFactor));
-    Type *RetTy = VectorType::get(NewEltTy, NewEC);
-    assert(VecTy->canLosslesslyBitCastTo(RetTy) &&
-           "Lossless bitcast between types expected");
-    APInt ScalarExt = Scalar->abs().zext(NewEltTy->getBitWidth());
-    APInt NewScalar(ScalarExt.getBitWidth(), 0);
-    for (unsigned Idx : seq(VL->getZExtValue()))
-      NewScalar |= ScalarExt << Scalar->getBitWidth() * Idx;
-    if (Scalar->isSignBitSet())
-      NewScalar.setSignBit();
-    return replaceInstUsesWith(
-        *II,
-        Builder.CreateBitCast(
-            Builder.CreateIntrinsic(
-                RetTy, Intrinsic::riscv_vmv_v_x,
-                {PoisonValue::get(RetTy), ConstantInt::get(NewEltTy, NewScalar),
-                 ConstantInt::get(II->getOperand(2)->getType(), 1)}),
-            VecTy));
-  }
   default: {
     // Handle target specific intrinsics
     std::optional<Instruction *> V = targetInstCombineIntrinsic(*II);
diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
index 0e27bebade036..3af574277d745 100644
--- a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
@@ -1,34 +1,49 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt -p instcombine -mtriple=riscv32 -mattr=+v -S %s | FileCheck %s
-; RUN: opt -p instcombine -mtriple=riscv64 -mattr=+v -S %s | FileCheck %s
+; RUN: opt -p instcombine -mtriple=riscv32 -mattr=+v -S %s | FileCheck %s --check-prefixes=CHECK,RV32
+; RUN: opt -p instcombine -mtriple=riscv64 -mattr=+v -S %s | FileCheck %s --check-prefixes=CHECK,RV64
+; RUN: opt -p instcombine -mtriple=riscv32 -mattr=+zve32x -S %s | FileCheck %s --check-prefixes=CHECK,ZVE32X
 
-define <vscale x 8 x i8> @scalable() {
-; CHECK-LABEL: define <vscale x 8 x i8> @scalable(
+define <vscale x 1 x i64> @scalable() {
+; CHECK-LABEL: define <vscale x 1 x i64> @scalable(
 ; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 1)
-; CHECK-NEXT:    [[A:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 8 x i8>
-; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 1 x i64>
+; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
-  ret <vscale x 8 x i8> %a
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
+  ret <vscale x 1 x i64> %b
 }
 
-define <vscale x 8 x i8> @small_scalar() {
-; CHECK-LABEL: define <vscale x 8 x i8> @small_scalar(
+define <vscale x 1 x i64> @small_scalar() {
+; CHECK-LABEL: define <vscale x 1 x i64> @small_scalar(
 ; CHECK-SAME: ) #[[ATTR0]] {
 ; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 50529027, i64 1)
-; CHECK-NEXT:    [[A:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 8 x i8>
-; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 1 x i64>
+; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 3, i64 4)
-  ret <vscale x 8 x i8> %a
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
+  ret <vscale x 1 x i64> %b
+}
+
+define <vscale x 1 x i64> @negative_scalar() {
+; CHECK-LABEL: define <vscale x 1 x i64> @negative_scalar(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 -50529028, i64 1)
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 1 x i64>
+; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
+;
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 -4, i64 4)
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
+  ret <vscale x 1 x i64> %b
 }
 
-define <vscale x 64 x i1> @users_with_bitcast() {
-; CHECK-LABEL: define <vscale x 64 x i1> @users_with_bitcast(
+define <vscale x 64 x i1> @users() {
+; CHECK-LABEL: define <vscale x 64 x i1> @users(
 ; CHECK-SAME: ) #[[ATTR0]] {
 ; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 1)
-; CHECK-NEXT:    [[TMP2:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 -698984874, i64 1)
+; CHECK-NEXT:    [[TMP2:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 -1431655766, i64 1)
 ; CHECK-NEXT:    [[RET1:%.*]] = xor <vscale x 2 x i32> [[TMP1]], [[TMP2]]
 ; CHECK-NEXT:    [[RET:%.*]] = bitcast <vscale x 2 x i32> [[RET1]] to <vscale x 64 x i1>
 ; CHECK-NEXT:    ret <vscale x 64 x i1> [[RET]]
@@ -41,52 +56,107 @@ define <vscale x 64 x i1> @users_with_bitcast() {
   ret <vscale x 64 x i1> %ret
 }
 
-define <vscale x 8 x i8> @passthru_non_poison(<vscale x 8 x i8> %x) {
-; CHECK-LABEL: define <vscale x 8 x i8> @passthru_non_poison(
+define <vscale x 8 x i8> @no_bitcast() {
+; CHECK-LABEL: define <vscale x 8 x i8> @no_bitcast(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 4)
+; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
+;
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
+  ret <vscale x 8 x i8> %a
+}
+
+define <vscale x 1 x i64> @passthru_non_poison(<vscale x 8 x i8> %x) {
+; CHECK-LABEL: define <vscale x 1 x i64> @passthru_non_poison(
 ; CHECK-SAME: <vscale x 8 x i8> [[X:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> [[X]], i8 85, i64 4)
-; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
+; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> %x, i8 85, i64 4)
-  ret <vscale x 8 x i8> %a
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
+  ret <vscale x 1 x i64> %b
 }
 
-define <vscale x 8 x i8> @scalar_non_constant(i8 %scalar) {
-; CHECK-LABEL: define <vscale x 8 x i8> @scalar_non_constant(
+define <vscale x 1 x i64> @scalar_non_constant(i8 %scalar) {
+; CHECK-LABEL: define <vscale x 1 x i64> @scalar_non_constant(
 ; CHECK-SAME: i8 [[SCALAR:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 [[SCALAR]], i64 4)
-; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
+; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 %scalar, i64 4)
-  ret <vscale x 8 x i8> %a
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
+  ret <vscale x 1 x i64> %b
 }
 
-define <vscale x 8 x i8> @vl_non_constant(i64 %vl) {
-; CHECK-LABEL: define <vscale x 8 x i8> @vl_non_constant(
+define <vscale x 1 x i64> @vl_non_constant(i64 %vl) {
+; CHECK-LABEL: define <vscale x 1 x i64> @vl_non_constant(
 ; CHECK-SAME: i64 [[VL:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 [[VL]])
-; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
+; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 %vl)
-  ret <vscale x 8 x i8> %a
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
+  ret <vscale x 1 x i64> %b
 }
 
-define <vscale x 1 x i128> @scalar_operand_too_large() {
-; CHECK-LABEL: define <vscale x 1 x i128> @scalar_operand_too_large(
+define <vscale x 2 x i64> @vector_elt_type_too_large() {
+; CHECK-LABEL: define <vscale x 2 x i64> @vector_elt_type_too_large(
 ; CHECK-SAME: ) #[[ATTR0]] {
 ; CHECK-NEXT:    [[A:%.*]] = call <vscale x 1 x i128> @llvm.riscv.vmv.v.x.nxv1i128.i64(<vscale x 1 x i128> poison, i128 85, i64 4)
-; CHECK-NEXT:    ret <vscale x 1 x i128> [[A]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 1 x i128> [[A]] to <vscale x 2 x i64>
+; CHECK-NEXT:    ret <vscale x 2 x i64> [[B]]
 ;
   %a = call <vscale x 1 x i128> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 1 x i128> poison, i128 85, i64 4)
-  ret <vscale x 1 x i128> %a
+  %b = bitcast <vscale x 1 x i128> %a to <vscale x 2 x i64>
+  ret <vscale x 2 x i64> %b
 }
 
-define <vscale x 8 x i8> @vl_too_large() {
-; CHECK-LABEL: define <vscale x 8 x i8> @vl_too_large(
+define <vscale x 1 x i64> @vl_too_large() {
+; CHECK-LABEL: define <vscale x 1 x i64> @vl_too_large(
 ; CHECK-SAME: ) #[[ATTR0]] {
 ; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 128)
-; CHECK-NEXT:    ret <vscale x 8 x i8> [[A]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
+; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 128)
-  ret <vscale x 8 x i8> %a
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
+  ret <vscale x 1 x i64> %b
+}
+
+define <vscale x 1 x i32> @vl_not_divisible() {
+; CHECK-LABEL: define <vscale x 1 x i32> @vl_not_divisible(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 4 x i8> @llvm.riscv.vmv.v.x.nxv4i8.i64(<vscale x 4 x i8> poison, i8 85, i64 8)
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 4 x i8> [[A]] to <vscale x 1 x i32>
+; CHECK-NEXT:    ret <vscale x 1 x i32> [[B]]
+;
+  %a = call <vscale x 4 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 4 x i8> poison, i8 85, i64 8)
+  %b = bitcast <vscale x 4 x i8> %a to <vscale x 1 x i32>
+  ret <vscale x 1 x i32> %b
+}
+
+define <vscale x 1 x i64> @vector_elt_type_legality() {
+; RV32-LABEL: define <vscale x 1 x i64> @vector_elt_type_legality(
+; RV32-SAME: ) #[[ATTR0]] {
+; RV32-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 8)
+; RV32-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
+; RV32-NEXT:    ret <vscale x 1 x i64> [[B]]
+;
+; RV64-LABEL: define <vscale x 1 x i64> @vector_elt_type_legality(
+; RV64-SAME: ) #[[ATTR0]] {
+; RV64-NEXT:    [[TMP1:%.*]] = call <vscale x 1 x i64> @llvm.riscv.vmv.v.x.nxv1i64.i64(<vscale x 1 x i64> poison, i64 6148914691236517205, i64 1)
+; RV64-NEXT:    ret <vscale x 1 x i64> [[TMP1]]
+;
+; ZVE32X-LABEL: define <vscale x 1 x i64> @vector_elt_type_legality(
+; ZVE32X-SAME: ) #[[ATTR0]] {
+; ZVE32X-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 8)
+; ZVE32X-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
+; ZVE32X-NEXT:    ret <vscale x 1 x i64> [[B]]
+;
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 8)
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
+  ret <vscale x 1 x i64> %b
 }

>From 274aaeaf48e9d34fc254ac5b41cc4113c3bf8587 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Sat, 21 Feb 2026 11:29:40 +0000
Subject: [PATCH 5/9] [InstCombine/RISCV] Also test zve32x on RV64

---
 llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
index 3af574277d745..48f73733012b3 100644
--- a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
@@ -2,6 +2,7 @@
 ; RUN: opt -p instcombine -mtriple=riscv32 -mattr=+v -S %s | FileCheck %s --check-prefixes=CHECK,RV32
 ; RUN: opt -p instcombine -mtriple=riscv64 -mattr=+v -S %s | FileCheck %s --check-prefixes=CHECK,RV64
 ; RUN: opt -p instcombine -mtriple=riscv32 -mattr=+zve32x -S %s | FileCheck %s --check-prefixes=CHECK,ZVE32X
+; RUN: opt -p instcombine -mtriple=riscv64 -mattr=+zve32x -S %s | FileCheck %s --check-prefixes=CHECK,ZVE32X
 
 define <vscale x 1 x i64> @scalable() {
 ; CHECK-LABEL: define <vscale x 1 x i64> @scalable(

>From 60b425f0881a259b0f7a7277cc5765634e56b3fc Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Sat, 21 Feb 2026 17:23:08 +0000
Subject: [PATCH 6/9] [InstCombine/RISCV] Strip degenerate case, use DL

---
 llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp   |  8 ++++----
 .../Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll    | 12 ------------
 2 files changed, 4 insertions(+), 16 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index ed6730f8508b7..0fedf4d3f0223 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -3611,19 +3611,19 @@ RISCVTTIImpl::instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const {
   // all users are bitcasts, to avoid introducing new bitcasts. The rationale
   // for this is to optimize the number of inserted vsetvli instructions, by
   // RISCVInsertVSETVLI.
+  const DataLayout &DL = IC.getDataLayout();
   const APInt *Scalar;
   uint64_t VL;
   if (!match(&II, m_Intrinsic<Intrinsic::riscv_vmv_v_x>(
                       m_Poison(), m_APInt(Scalar), m_ConstantInt(VL))) ||
-      VL == 1 || Scalar->getBitWidth() > ST->getXLen() ||
-      !all_of(II.users(), match_fn(m_BitCast(m_Value()))))
+      VL == 1 || !all_of(II.users(), match_fn(m_BitCast(m_Value()))))
     return {};
   auto *VecTy = cast<ScalableVectorType>(II.getType());
   ElementCount EC = VecTy->getElementCount();
   ElementCount ScaleFactor = ElementCount::getScalable(VL);
   auto *EltTy = cast<IntegerType>(VecTy->getElementType());
-  unsigned NewEltBW = EltTy->getScalarSizeInBits() * VL;
-  if (!EC.hasKnownScalarFactor(ScaleFactor) || NewEltBW > ST->getXLen())
+  unsigned NewEltBW = DL.getTypeSizeInBits(EltTy) * VL;
+  if (!EC.hasKnownScalarFactor(ScaleFactor) || !DL.fitsInLegalInteger(NewEltBW))
     return {};
   auto *NewEltTy = IntegerType::get(II.getContext(), NewEltBW);
   if (!TLI->isLegalElementTypeForRVV(TLI->getValueType(DL, NewEltTy)))
diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
index 48f73733012b3..b645e6d9c0baa 100644
--- a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
@@ -103,18 +103,6 @@ define <vscale x 1 x i64> @vl_non_constant(i64 %vl) {
   ret <vscale x 1 x i64> %b
 }
 
-define <vscale x 2 x i64> @vector_elt_type_too_large() {
-; CHECK-LABEL: define <vscale x 2 x i64> @vector_elt_type_too_large(
-; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <vscale x 1 x i128> @llvm.riscv.vmv.v.x.nxv1i128.i64(<vscale x 1 x i128> poison, i128 85, i64 4)
-; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 1 x i128> [[A]] to <vscale x 2 x i64>
-; CHECK-NEXT:    ret <vscale x 2 x i64> [[B]]
-;
-  %a = call <vscale x 1 x i128> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 1 x i128> poison, i128 85, i64 4)
-  %b = bitcast <vscale x 1 x i128> %a to <vscale x 2 x i64>
-  ret <vscale x 2 x i64> %b
-}
-
 define <vscale x 1 x i64> @vl_too_large() {
 ; CHECK-LABEL: define <vscale x 1 x i64> @vl_too_large(
 ; CHECK-SAME: ) #[[ATTR0]] {

>From cd591a5062eef5c71489d1e844a815632f23aeb7 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Wed, 25 Feb 2026 10:18:11 +0000
Subject: [PATCH 7/9] [InstCombine/RISCV] Scale VL to remove bitcast

---
 .../Target/RISCV/RISCVTargetTransformInfo.cpp |  52 ++++---
 .../InstCombine/RISCV/riscv-vmv-v-x.ll        | 144 +++++++++++-------
 2 files changed, 119 insertions(+), 77 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index 0fedf4d3f0223..04160d5997e05 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -3607,39 +3607,51 @@ bool RISCVTTIImpl::shouldCopyAttributeWhenOutliningFrom(
 
 std::optional<Instruction *>
 RISCVTTIImpl::instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const {
-  // If all operands are constant, constant-fold with bitcast: make sure that
-  // all users are bitcasts, to avoid introducing new bitcasts. The rationale
-  // for this is to optimize the number of inserted vsetvli instructions, by
-  // RISCVInsertVSETVLI.
+  // If all operands of a vmv.v.vx are constant, fold a bitcast(vmv.v.vx) to
+  // scale the vmv.v.vx, enabling removal of the bitcast. The transform helps
+  // avoid creating redundant masks.
   const DataLayout &DL = IC.getDataLayout();
+  auto *TargetVecTy = dyn_cast<ScalableVectorType>(II.user_back()->getType());
+  if (!TargetVecTy)
+    return {};
   const APInt *Scalar;
   uint64_t VL;
   if (!match(&II, m_Intrinsic<Intrinsic::riscv_vmv_v_x>(
                       m_Poison(), m_APInt(Scalar), m_ConstantInt(VL))) ||
-      VL == 1 || !all_of(II.users(), match_fn(m_BitCast(m_Value()))))
+      !all_of(II.users(), [TargetVecTy](User *U) {
+        return U->getType() == TargetVecTy && match(U, m_BitCast(m_Value()));
+      }))
+    return {};
+  auto *SourceVecTy = cast<ScalableVectorType>(II.getType());
+  unsigned TargetEltBW = DL.getTypeSizeInBits(TargetVecTy->getElementType());
+  unsigned SourceEltBW = DL.getTypeSizeInBits(SourceVecTy->getElementType());
+  if (TargetEltBW % SourceEltBW)
     return {};
-  auto *VecTy = cast<ScalableVectorType>(II.getType());
-  ElementCount EC = VecTy->getElementCount();
-  ElementCount ScaleFactor = ElementCount::getScalable(VL);
-  auto *EltTy = cast<IntegerType>(VecTy->getElementType());
-  unsigned NewEltBW = DL.getTypeSizeInBits(EltTy) * VL;
-  if (!EC.hasKnownScalarFactor(ScaleFactor) || !DL.fitsInLegalInteger(NewEltBW))
+  unsigned TargetScale = TargetEltBW / SourceEltBW;
+  if (VL % TargetScale)
+    return {};
+  Type *VLTy = II.getOperand(2)->getType();
+  ElementCount ScaleEC = ElementCount::getScalable(TargetScale);
+  ElementCount SourceEC = SourceVecTy->getElementCount();
+  unsigned NewEltBW = SourceEltBW * TargetScale;
+  if (!SourceEC.hasKnownScalarFactor(ScaleEC) ||
+      !DL.fitsInLegalInteger(NewEltBW))
     return {};
   auto *NewEltTy = IntegerType::get(II.getContext(), NewEltBW);
   if (!TLI->isLegalElementTypeForRVV(TLI->getValueType(DL, NewEltTy)))
     return {};
   ElementCount NewEC =
-      ElementCount::getScalable(EC.getKnownScalarFactor(ScaleFactor));
+      ElementCount::getScalable(SourceEC.getKnownScalarFactor(ScaleEC));
   Type *RetTy = VectorType::get(NewEltTy, NewEC);
-  assert(VecTy->canLosslesslyBitCastTo(RetTy) &&
+  assert(SourceVecTy->canLosslesslyBitCastTo(RetTy) &&
          "Lossless bitcast between types expected");
   APInt NewScalar = APInt::getSplat(NewEltBW, *Scalar);
-  Type *VLTy = II.getOperand(2)->getType();
   return IC.replaceInstUsesWith(
-      II, IC.Builder.CreateBitCast(
-              IC.Builder.CreateIntrinsic(RetTy, Intrinsic::riscv_vmv_v_x,
-                                         {PoisonValue::get(RetTy),
-                                          ConstantInt::get(NewEltTy, NewScalar),
-                                          ConstantInt::get(VLTy, 1)}),
-              VecTy));
+      II,
+      IC.Builder.CreateBitCast(
+          IC.Builder.CreateIntrinsic(
+              RetTy, Intrinsic::riscv_vmv_v_x,
+              {PoisonValue::get(RetTy), ConstantInt::get(NewEltTy, NewScalar),
+               ConstantInt::get(VLTy, VL / TargetScale)}),
+          SourceVecTy));
 }
diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
index b645e6d9c0baa..9f75439a6bd69 100644
--- a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
@@ -4,57 +4,75 @@
 ; RUN: opt -p instcombine -mtriple=riscv32 -mattr=+zve32x -S %s | FileCheck %s --check-prefixes=CHECK,ZVE32X
 ; RUN: opt -p instcombine -mtriple=riscv64 -mattr=+zve32x -S %s | FileCheck %s --check-prefixes=CHECK,ZVE32X
 
-define <vscale x 1 x i64> @scalable() {
-; CHECK-LABEL: define <vscale x 1 x i64> @scalable(
+define <vscale x 2 x i32> @target_vl_one() {
+; CHECK-LABEL: define <vscale x 2 x i32> @target_vl_one(
 ; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 1)
-; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 1 x i64>
-; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[TMP1]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
-  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
-  ret <vscale x 1 x i64> %b
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 2 x i32>
+  ret <vscale x 2 x i32> %b
+}
+
+define <vscale x 4 x i16> @target_vl_two() {
+; CHECK-LABEL: define <vscale x 4 x i16> @target_vl_two(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 4 x i16> @llvm.riscv.vmv.v.x.nxv4i16.i64(<vscale x 4 x i16> poison, i16 21845, i64 2)
+; CHECK-NEXT:    ret <vscale x 4 x i16> [[TMP1]]
+;
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 4 x i16>
+  ret <vscale x 4 x i16> %b
 }
 
-define <vscale x 1 x i64> @small_scalar() {
-; CHECK-LABEL: define <vscale x 1 x i64> @small_scalar(
+define <vscale x 2 x i32> @target_vl_32() {
+; CHECK-LABEL: define <vscale x 2 x i32> @target_vl_32(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 32)
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[TMP1]]
+;
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 128)
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 2 x i32>
+  ret <vscale x 2 x i32> %b
+}
+
+define <vscale x 2 x i32> @small_scalar() {
+; CHECK-LABEL: define <vscale x 2 x i32> @small_scalar(
 ; CHECK-SAME: ) #[[ATTR0]] {
 ; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 50529027, i64 1)
-; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 1 x i64>
-; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[TMP1]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 3, i64 4)
-  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
-  ret <vscale x 1 x i64> %b
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 2 x i32>
+  ret <vscale x 2 x i32> %b
 }
 
-define <vscale x 1 x i64> @negative_scalar() {
-; CHECK-LABEL: define <vscale x 1 x i64> @negative_scalar(
+define <vscale x 2 x i32> @negative_scalar() {
+; CHECK-LABEL: define <vscale x 2 x i32> @negative_scalar(
 ; CHECK-SAME: ) #[[ATTR0]] {
 ; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 -50529028, i64 1)
-; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 2 x i32> [[TMP1]] to <vscale x 1 x i64>
-; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[TMP1]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 -4, i64 4)
-  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
-  ret <vscale x 1 x i64> %b
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 2 x i32>
+  ret <vscale x 2 x i32> %b
 }
 
-define <vscale x 64 x i1> @users() {
-; CHECK-LABEL: define <vscale x 64 x i1> @users(
+define <vscale x 2 x i32> @users() {
+; CHECK-LABEL: define <vscale x 2 x i32> @users(
 ; CHECK-SAME: ) #[[ATTR0]] {
 ; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 1)
 ; CHECK-NEXT:    [[TMP2:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 -1431655766, i64 1)
 ; CHECK-NEXT:    [[RET1:%.*]] = xor <vscale x 2 x i32> [[TMP1]], [[TMP2]]
-; CHECK-NEXT:    [[RET:%.*]] = bitcast <vscale x 2 x i32> [[RET1]] to <vscale x 64 x i1>
-; CHECK-NEXT:    ret <vscale x 64 x i1> [[RET]]
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[RET1]]
 ;
   %vmv.1 = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
-  %cast.1 = bitcast <vscale x 8 x i8> %vmv.1 to <vscale x 64 x i1>
+  %cast.1 = bitcast <vscale x 8 x i8> %vmv.1 to <vscale x 2 x i32>
   %vmv.2 = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 -86, i64 4)
-  %cast.2 = bitcast <vscale x 8 x i8> %vmv.2 to <vscale x 64 x i1>
-  %ret = xor <vscale x 64 x i1> %cast.1, %cast.2
-  ret <vscale x 64 x i1> %ret
+  %cast.2 = bitcast <vscale x 8 x i8> %vmv.2 to <vscale x 2 x i32>
+  %ret = xor <vscale x 2 x i32> %cast.1, %cast.2
+  ret <vscale x 2 x i32> %ret
 }
 
 define <vscale x 8 x i8> @no_bitcast() {
@@ -67,62 +85,74 @@ define <vscale x 8 x i8> @no_bitcast() {
   ret <vscale x 8 x i8> %a
 }
 
-define <vscale x 1 x i64> @passthru_non_poison(<vscale x 8 x i8> %x) {
-; CHECK-LABEL: define <vscale x 1 x i64> @passthru_non_poison(
-; CHECK-SAME: <vscale x 8 x i8> [[X:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> [[X]], i8 85, i64 4)
+define <vscale x 1 x i64> @bitcast_target_elt_type_too_large() {
+; CHECK-LABEL: define <vscale x 1 x i64> @bitcast_target_elt_type_too_large(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 4)
 ; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
 ; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
 ;
-  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> %x, i8 85, i64 4)
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
   %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
   ret <vscale x 1 x i64> %b
 }
 
-define <vscale x 1 x i64> @scalar_non_constant(i8 %scalar) {
-; CHECK-LABEL: define <vscale x 1 x i64> @scalar_non_constant(
+define <vscale x 64 x i1> @bitcast_target_elt_type_too_small() {
+; CHECK-LABEL: define <vscale x 64 x i1> @bitcast_target_elt_type_too_small(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 4)
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 64 x i1>
+; CHECK-NEXT:    ret <vscale x 64 x i1> [[B]]
+;
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 64 x i1>
+  ret <vscale x 64 x i1> %b
+}
+
+define <vscale x 2 x i32> @passthru_non_poison(<vscale x 8 x i8> %x) {
+; CHECK-LABEL: define <vscale x 2 x i32> @passthru_non_poison(
+; CHECK-SAME: <vscale x 8 x i8> [[X:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> [[X]], i8 85, i64 4)
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 2 x i32>
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[B]]
+;
+  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> %x, i8 85, i64 4)
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 2 x i32>
+  ret <vscale x 2 x i32> %b
+}
+
+define <vscale x 2 x i32> @scalar_non_constant(i8 %scalar) {
+; CHECK-LABEL: define <vscale x 2 x i32> @scalar_non_constant(
 ; CHECK-SAME: i8 [[SCALAR:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 [[SCALAR]], i64 4)
-; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
-; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 2 x i32>
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[B]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 %scalar, i64 4)
-  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
-  ret <vscale x 1 x i64> %b
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 2 x i32>
+  ret <vscale x 2 x i32> %b
 }
 
-define <vscale x 1 x i64> @vl_non_constant(i64 %vl) {
-; CHECK-LABEL: define <vscale x 1 x i64> @vl_non_constant(
+define <vscale x 2 x i32> @vl_non_constant(i64 %vl) {
+; CHECK-LABEL: define <vscale x 2 x i32> @vl_non_constant(
 ; CHECK-SAME: i64 [[VL:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 [[VL]])
-; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
-; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
+; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 2 x i32>
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[B]]
 ;
   %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 %vl)
-  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
-  ret <vscale x 1 x i64> %b
-}
-
-define <vscale x 1 x i64> @vl_too_large() {
-; CHECK-LABEL: define <vscale x 1 x i64> @vl_too_large(
-; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 128)
-; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 8 x i8> [[A]] to <vscale x 1 x i64>
-; CHECK-NEXT:    ret <vscale x 1 x i64> [[B]]
-;
-  %a = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 128)
-  %b = bitcast <vscale x 8 x i8> %a to <vscale x 1 x i64>
-  ret <vscale x 1 x i64> %b
+  %b = bitcast <vscale x 8 x i8> %a to <vscale x 2 x i32>
+  ret <vscale x 2 x i32> %b
 }
 
 define <vscale x 1 x i32> @vl_not_divisible() {
 ; CHECK-LABEL: define <vscale x 1 x i32> @vl_not_divisible(
 ; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[A:%.*]] = call <vscale x 4 x i8> @llvm.riscv.vmv.v.x.nxv4i8.i64(<vscale x 4 x i8> poison, i8 85, i64 8)
+; CHECK-NEXT:    [[A:%.*]] = call <vscale x 4 x i8> @llvm.riscv.vmv.v.x.nxv4i8.i64(<vscale x 4 x i8> poison, i8 85, i64 7)
 ; CHECK-NEXT:    [[B:%.*]] = bitcast <vscale x 4 x i8> [[A]] to <vscale x 1 x i32>
 ; CHECK-NEXT:    ret <vscale x 1 x i32> [[B]]
 ;
-  %a = call <vscale x 4 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 4 x i8> poison, i8 85, i64 8)
+  %a = call <vscale x 4 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 4 x i8> poison, i8 85, i64 7)
   %b = bitcast <vscale x 4 x i8> %a to <vscale x 1 x i32>
   ret <vscale x 1 x i32> %b
 }

>From 2fac1065e4c2daf6f14f8fce99f19ea3b318cf78 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Wed, 25 Feb 2026 11:10:40 +0000
Subject: [PATCH 8/9] [InstCombine/RISCV] Handle empty-user case; fix crash

---
 llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index 04160d5997e05..5f494f391d3a2 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -3611,6 +3611,8 @@ RISCVTTIImpl::instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const {
   // scale the vmv.v.vx, enabling removal of the bitcast. The transform helps
   // avoid creating redundant masks.
   const DataLayout &DL = IC.getDataLayout();
+  if (II.user_empty())
+    return {};
   auto *TargetVecTy = dyn_cast<ScalableVectorType>(II.user_back()->getType());
   if (!TargetVecTy)
     return {};

>From c129136988ad5da7105a7fc45596225648d8edf9 Mon Sep 17 00:00:00 2001
From: Ramkumar Ramachandra <artagnon at tenstorrent.com>
Date: Thu, 26 Feb 2026 11:26:10 +0000
Subject: [PATCH 9/9] [InstCombine/RISCV] Address final-nits review

---
 .../Target/RISCV/RISCVTargetTransformInfo.cpp | 12 +++---
 .../InstCombine/RISCV/riscv-vmv-v-x.ll        | 37 +++++++++++--------
 2 files changed, 26 insertions(+), 23 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index 5f494f391d3a2..820daf96218fd 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -3607,9 +3607,9 @@ bool RISCVTTIImpl::shouldCopyAttributeWhenOutliningFrom(
 
 std::optional<Instruction *>
 RISCVTTIImpl::instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const {
-  // If all operands of a vmv.v.vx are constant, fold a bitcast(vmv.v.vx) to
-  // scale the vmv.v.vx, enabling removal of the bitcast. The transform helps
-  // avoid creating redundant masks.
+  // If all operands of a vmv.v.x are constant, fold a bitcast(vmv.v.x) to scale
+  // the vmv.v.x, enabling removal of the bitcast. The transform helps avoid
+  // creating redundant masks.
   const DataLayout &DL = IC.getDataLayout();
   if (II.user_empty())
     return {};
@@ -3633,17 +3633,15 @@ RISCVTTIImpl::instCombineIntrinsic(InstCombiner &IC, IntrinsicInst &II) const {
   if (VL % TargetScale)
     return {};
   Type *VLTy = II.getOperand(2)->getType();
-  ElementCount ScaleEC = ElementCount::getScalable(TargetScale);
   ElementCount SourceEC = SourceVecTy->getElementCount();
   unsigned NewEltBW = SourceEltBW * TargetScale;
-  if (!SourceEC.hasKnownScalarFactor(ScaleEC) ||
+  if (!SourceEC.isKnownMultipleOf(TargetScale) ||
       !DL.fitsInLegalInteger(NewEltBW))
     return {};
   auto *NewEltTy = IntegerType::get(II.getContext(), NewEltBW);
   if (!TLI->isLegalElementTypeForRVV(TLI->getValueType(DL, NewEltTy)))
     return {};
-  ElementCount NewEC =
-      ElementCount::getScalable(SourceEC.getKnownScalarFactor(ScaleEC));
+  ElementCount NewEC = SourceEC.divideCoefficientBy(TargetScale);
   Type *RetTy = VectorType::get(NewEltTy, NewEC);
   assert(SourceVecTy->canLosslesslyBitCastTo(RetTy) &&
          "Lossless bitcast between types expected");
diff --git a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
index 9f75439a6bd69..2c07f5494fa2b 100644
--- a/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
+++ b/llvm/test/Transforms/InstCombine/RISCV/riscv-vmv-v-x.ll
@@ -59,22 +59,6 @@ define <vscale x 2 x i32> @negative_scalar() {
   ret <vscale x 2 x i32> %b
 }
 
-define <vscale x 2 x i32> @users() {
-; CHECK-LABEL: define <vscale x 2 x i32> @users(
-; CHECK-SAME: ) #[[ATTR0]] {
-; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 1431655765, i64 1)
-; CHECK-NEXT:    [[TMP2:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vmv.v.x.nxv2i32.i64(<vscale x 2 x i32> poison, i32 -1431655766, i64 1)
-; CHECK-NEXT:    [[RET1:%.*]] = xor <vscale x 2 x i32> [[TMP1]], [[TMP2]]
-; CHECK-NEXT:    ret <vscale x 2 x i32> [[RET1]]
-;
-  %vmv.1 = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
-  %cast.1 = bitcast <vscale x 8 x i8> %vmv.1 to <vscale x 2 x i32>
-  %vmv.2 = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 -86, i64 4)
-  %cast.2 = bitcast <vscale x 8 x i8> %vmv.2 to <vscale x 2 x i32>
-  %ret = xor <vscale x 2 x i32> %cast.1, %cast.2
-  ret <vscale x 2 x i32> %ret
-}
-
 define <vscale x 8 x i8> @no_bitcast() {
 ; CHECK-LABEL: define <vscale x 8 x i8> @no_bitcast(
 ; CHECK-SAME: ) #[[ATTR0]] {
@@ -85,6 +69,27 @@ define <vscale x 8 x i8> @no_bitcast() {
   ret <vscale x 8 x i8> %a
 }
 
+declare void @use.nxv2i32(<vscale x 2 x i32>)
+declare void @use.nxv1i64(<vscale x 1 x i64>)
+
+define void @bitcast_users_type_mismatch() {
+; CHECK-LABEL: define void @bitcast_users_type_mismatch(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:    [[VMV:%.*]] = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8.i64(<vscale x 8 x i8> poison, i8 85, i64 4)
+; CHECK-NEXT:    [[CAST_1:%.*]] = bitcast <vscale x 8 x i8> [[VMV]] to <vscale x 2 x i32>
+; CHECK-NEXT:    [[CAST_2:%.*]] = bitcast <vscale x 8 x i8> [[VMV]] to <vscale x 1 x i64>
+; CHECK-NEXT:    call void @use.nxv2i32(<vscale x 2 x i32> [[CAST_1]])
+; CHECK-NEXT:    call void @use.nxv1i64(<vscale x 1 x i64> [[CAST_2]])
+; CHECK-NEXT:    ret void
+;
+  %vmv = call <vscale x 8 x i8> @llvm.riscv.vmv.v.x.nxv8i8(<vscale x 8 x i8> poison, i8 85, i64 4)
+  %cast.1 = bitcast <vscale x 8 x i8> %vmv to <vscale x 2 x i32>
+  %cast.2 = bitcast <vscale x 8 x i8> %vmv to <vscale x 1 x i64>
+  call void @use.nxv2i32(<vscale x 2 x i32> %cast.1)
+  call void @use.nxv1i64(<vscale x 1 x i64> %cast.2)
+  ret void
+}
+
 define <vscale x 1 x i64> @bitcast_target_elt_type_too_large() {
 ; CHECK-LABEL: define <vscale x 1 x i64> @bitcast_target_elt_type_too_large(
 ; CHECK-SAME: ) #[[ATTR0]] {



More information about the llvm-commits mailing list