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

Ramkumar Ramachandra via llvm-commits llvm-commits at lists.llvm.org
Sat Feb 21 03:34:31 PST 2026


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

>From 2a46639bfab97bb71f671d2c679f92d845bcd82b 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/5] [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 a737342df0dd16365475e6ea6b78b5e1e093fa28 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/5] [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 6476a38b8a545..f7b2ac2ca96c8 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"
@@ -4262,6 +4263,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 4a544a71240c4ff19dd4bcbe6a423a0388fe7434 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/5] [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 f7b2ac2ca96c8..2b99a2057101f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -4272,11 +4272,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());
@@ -4284,7 +4282,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 f90570fe4915544bbd713a25943e28b2aa0bd56b 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/5] [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 a40d77dd734c8..fa09478530fc4 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;
@@ -3558,3 +3559,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 cee41b1422b85..e78200696e9c8 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
@@ -511,6 +511,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 2b99a2057101f..6476a38b8a545 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"
@@ -4263,44 +4262,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 5433ea8ed656297c4a262c9e7180afd280fdefa4 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/5] [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(



More information about the llvm-commits mailing list