[llvm] [RISCV][XCV] Select element-wise XCVsimd ops from packed-vector IR (PR #205836)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 25 07:54:03 PDT 2026
https://github.com/VittorioBurani created https://github.com/llvm/llvm-project/pull/205836
Select the element-wise XCVsimd operations directly from generic
packed-vector IR (`2 x i16` / `4 x i8`) on RV32, so `vector_size` code
and the autovectorizer reach the `cv.*.h` / `cv.*.b` SIMD instructions
without any dedicated intrinsic.
This is the backend codegen split out from #204880 per @lukel97's
suggestion ("the standard codegen patterns seem reasonable enough to
support independent of the builtins and RFC"). It is independent of the
RFC: it only makes the existing SIMD instructions reachable from generic
IR.
### What it does
- Makes `v2i16` / `v4i8` legal GPR-resident types under `XCVsimd`,
reusing the same model the P extension already uses for these types
(`addRegisterClass` + `computeRegisterProperties`).
- Maps `add`/`sub`, `smin`/`umin`/`smax`/`umax`, `abs`, and `and`/`or`/`xor`
(lane-agnostic, via the scalar ops) to the `cv.*` instructions.
- Passes/returns the packed types in a single GPR (calling convention),
with no-op `i32` <-> packed bitcasts.
### Addressing the review on #204880
- **Expand-all-then-enable** (@topperc: the hand-written Expand list "is
guaranteed to become wrong when new ISD nodes are added"). The block now
iterates every opcode marking it `Expand`, then re-enables the supported
ones, matching the P and V setup. Unmapped operations scalarize, so
generic vector code is always handled rather than failing to select.
- **Misaligned load/store** (@topperc: "I couldn't make LOAD/STORE legal
for the P extension. I had to custom lower them and check the
alignment."). Because the packed value lives in a GPR, its access is a
plain word `lw`/`sw`, so I route these types through the *scalar*
misalignment rule in `allowsMisalignedMemoryAccesses` (the same branch P
uses via `hasStdExtP()`). An under-aligned access then splits into
element accesses instead of becoming a single trapping word access;
`LOAD`/`STORE` can stay `Legal`. Covered by `load_align{4,2}` /
`store_align2` in the test. If you would rather see an explicit custom
lowering like the P extension's, I am happy to switch.
### Tests
`llvm/test/CodeGen/RISCV/xcvsimd-vector.ll` (element-wise selection,
by-value calling convention, alignment splitting) and
`xcvsimd-vector-unsupported.ll` (scalarization of unmapped ops).
Shifts, avg and the comparisons are intentionally left unmapped here;
they need vector-shift legalization and follow separately.
Depends on #204880.
Part of the CORE-V (XCV) series; RFC:
https://discourse.llvm.org/t/rfc-core-v-xcv-support-for-cv32e40p-clang-builtins-xcvsimd-intrinsics-and-generic-auto-selection/91111
>From 2dd3741b5481ca9ac95b04804bf392e039577979 Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Wed, 1 Apr 2026 18:45:22 +0200
Subject: [PATCH 1/2] [RISCV][XCV] Add LLVM intrinsics for non-representable
XCVsimd ops
Add IR intrinsics and SelectionDAG patterns for the XCVsimd operations
that have no faithful generic-IR form: dot products (dotup/dotusp/dotsp
and the accumulating sdot variants), complex multiply
(cplxmul/cplxconj), subrotmj, the shuffle/pack family, and lane
extract/insert. Includes the cv.shuffle.sci.b pseudo expanded in
RISCVExpandPseudoInsts.
The element-wise arithmetic/logical/compare/shift operations are left
out here: they map to ordinary packed-vector IR and are handled by
separate codegen patterns (split per review).
---
llvm/include/llvm/IR/IntrinsicsRISCVXCV.td | 127 +++++
.../Target/RISCV/RISCVExpandPseudoInsts.cpp | 31 ++
llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td | 233 +++++++++
llvm/test/CodeGen/RISCV/xcvsimd.ll | 486 ++++++++++++++++++
4 files changed, 877 insertions(+)
create mode 100644 llvm/test/CodeGen/RISCV/xcvsimd.ll
diff --git a/llvm/include/llvm/IR/IntrinsicsRISCVXCV.td b/llvm/include/llvm/IR/IntrinsicsRISCVXCV.td
index 465665c838bae..0fc47dfe2a2fa 100644
--- a/llvm/include/llvm/IR/IntrinsicsRISCVXCV.td
+++ b/llvm/include/llvm/IR/IntrinsicsRISCVXCV.td
@@ -42,6 +42,54 @@ class ScalarCoreVMacGprGprGprImmIntrinsic
: Intrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty],
[IntrNoMem, IntrWillReturn, IntrSpeculatable, ImmArg<ArgIndex<3>>]>;
+// Binary: (i32, i32) -> i32, pure
+class ScalarCoreVSimdGprGprIntrinsic
+ : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty],
+ [IntrNoMem, IntrSpeculatable]>;
+
+// Unary: (i32) -> i32, pure
+class ScalarCoreVSimdGprIntrinsic
+ : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty],
+ [IntrNoMem, IntrSpeculatable]>;
+
+// Ternary: (i32, i32, i32) -> i32, pure (for accumulating ops like sdot)
+class ScalarCoreVSimdGprGprGprIntrinsic
+ : DefaultAttrsIntrinsic<[llvm_i32_ty],
+ [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty],
+ [IntrNoMem, IntrSpeculatable]>;
+
+// Binary with immediate at arg2: (i32, i32, IMM) -> i32
+// Used for: add_h/sub_h (divcode), subrotmj (divcode)
+class ScalarCoreVSimdGprGprImmIntrinsic
+ : DefaultAttrsIntrinsic<[llvm_i32_ty],
+ [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty],
+ [IntrNoMem, IntrSpeculatable,
+ ImmArg<ArgIndex<2>>]>;
+
+// Unary with immediate at arg1: (i32, IMM) -> i32
+// Used for: extract, extractu, shuffle_sci_h, shuffle_sci_b
+class ScalarCoreVSimdGprImmIntrinsic
+ : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty, llvm_i32_ty],
+ [IntrNoMem, IntrSpeculatable,
+ ImmArg<ArgIndex<1>>]>;
+
+// Ternary with immediate at arg2, where arg0 is rd (accumulator):
+// (rd_in, rs1, IMM) -> rd_out
+// Used for: insert_h, insert_b
+class ScalarCoreVSimdGprGprImm2WbIntrinsic
+ : DefaultAttrsIntrinsic<[llvm_i32_ty],
+ [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty],
+ [IntrNoMem, IntrSpeculatable,
+ ImmArg<ArgIndex<2>>]>;
+
+// Quaternary with immediate at arg3: (rs1, rs2, rd_in, IMM) -> rd_out
+// Used for: cplxmul_r, cplxmul_i
+class ScalarCoreVSimdGprGprGprImmIntrinsic
+ : DefaultAttrsIntrinsic<[llvm_i32_ty],
+ [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty, llvm_i32_ty],
+ [IntrNoMem, IntrSpeculatable,
+ ImmArg<ArgIndex<3>>]>;
+
let TargetPrefix = "riscv" in {
def int_riscv_cv_bitmanip_extract : ScalarCoreVBitManipGprGprIntrinsic;
def int_riscv_cv_bitmanip_extractu : ScalarCoreVBitManipGprGprIntrinsic;
@@ -94,4 +142,83 @@ let TargetPrefix = "riscv" in {
def int_riscv_cv_elw_elw
: Intrinsic<[llvm_i32_ty], [llvm_ptr_ty],
[IntrReadMem, IntrArgMemOnly, IntrHasSideEffects]>;
+
+ // ==== XCVsimd — ADD/SUB ====
+ // add_h takes a divcode immediate (0=none, 1=/2, 2=/4, 3=/8)
+
+ // ==== XCVsimd — AVG / AVGU ====
+
+ // ==== XCVsimd — MIN / MINU / MAX / MAXU ====
+
+ // ==== XCVsimd — SRL / SRA / SLL ====
+
+ // ==== XCVsimd — OR / XOR / AND ====
+
+ // ==== XCVsimd — ABS ====
+
+ // ==== XCVsimd — DOT PRODUCTS (non-accumulating) ====
+ def int_riscv_cv_simd_dotup_h : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotup_b : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotup_sc_h : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotup_sc_b : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotusp_h : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotusp_b : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotusp_sc_h : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotusp_sc_b : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotsp_h : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotsp_b : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotsp_sc_h : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_dotsp_sc_b : ScalarCoreVSimdGprGprIntrinsic;
+
+ // ==== XCVsimd — SDOT (accumulating dot products): (rs1, rs2, acc) -> acc ====
+ def int_riscv_cv_simd_sdotup_h : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotup_b : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotup_sc_h : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotup_sc_b : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotusp_h : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotusp_b : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotusp_sc_h : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotusp_sc_b : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotsp_h : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotsp_b : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotsp_sc_h : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_sdotsp_sc_b : ScalarCoreVSimdGprGprGprIntrinsic;
+
+ // ==== XCVsimd — EXTRACT / EXTRACTU: (rs1, IMM) -> rd ====
+ def int_riscv_cv_simd_extract_h : ScalarCoreVSimdGprImmIntrinsic;
+ def int_riscv_cv_simd_extract_b : ScalarCoreVSimdGprImmIntrinsic;
+ def int_riscv_cv_simd_extractu_h : ScalarCoreVSimdGprImmIntrinsic;
+ def int_riscv_cv_simd_extractu_b : ScalarCoreVSimdGprImmIntrinsic;
+
+ // ==== XCVsimd — INSERT: (rd_in, rs1, IMM) -> rd_out ====
+ def int_riscv_cv_simd_insert_h : ScalarCoreVSimdGprGprImm2WbIntrinsic;
+ def int_riscv_cv_simd_insert_b : ScalarCoreVSimdGprGprImm2WbIntrinsic;
+
+ // ==== XCVsimd — SHUFFLE ====
+ def int_riscv_cv_simd_shuffle_h : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_shuffle_b : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_shuffle_sci_h : ScalarCoreVSimdGprImmIntrinsic;
+ def int_riscv_cv_simd_shuffle_sci_b : ScalarCoreVSimdGprImmIntrinsic;
+
+ // ==== XCVsimd — SHUFFLE2: (rs1, rs2, rd_in) -> rd_out ====
+ def int_riscv_cv_simd_shuffle2_h : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_shuffle2_b : ScalarCoreVSimdGprGprGprIntrinsic;
+
+ // ==== XCVsimd — PACK ====
+ // pack / pack.h: (rs1, rs2) -> rd
+ def int_riscv_cv_simd_packlo_h : ScalarCoreVSimdGprGprIntrinsic;
+ def int_riscv_cv_simd_packhi_h : ScalarCoreVSimdGprGprIntrinsic;
+ // packhi.b / packlo.b: (rd_in, rs1, rs2) -> rd_out (read-modify-write)
+ def int_riscv_cv_simd_packhi_b : ScalarCoreVSimdGprGprGprIntrinsic;
+ def int_riscv_cv_simd_packlo_b : ScalarCoreVSimdGprGprGprIntrinsic;
+
+ // ==== XCVsimd — COMPARE ====
+
+ // ==== XCVsimd — COMPLEX ====
+ // cplxmul: (rs1, rs2, rd_in, divcode_IMM) -> rd_out
+ def int_riscv_cv_simd_cplxmul_r : ScalarCoreVSimdGprGprGprImmIntrinsic;
+ def int_riscv_cv_simd_cplxmul_i : ScalarCoreVSimdGprGprGprImmIntrinsic;
+ def int_riscv_cv_simd_cplxconj : ScalarCoreVSimdGprIntrinsic;
+ // subrotmj: (rs1, rs2, divcode_IMM) -> rd
+ def int_riscv_cv_simd_subrotmj : ScalarCoreVSimdGprGprImmIntrinsic;
} // TargetPrefix = "riscv"
diff --git a/llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp b/llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp
index fdd98d03a77f1..d5a13a7e639fe 100644
--- a/llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp
+++ b/llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp
@@ -60,6 +60,8 @@ class RISCVExpandPseudo : public MachineFunctionPass {
MachineBasicBlock::iterator MBBI);
bool expandPseudoReadVLENBViaVSETVLIX0(MachineBasicBlock &MBB,
MachineBasicBlock::iterator MBBI);
+ bool expandVendorXcvsimdShuffle(MachineBasicBlock &MBB,
+ MachineBasicBlock::iterator MBBI);
#ifndef NDEBUG
unsigned getInstSizeInBytes(const MachineFunction &MF) const {
unsigned Size = 0;
@@ -192,6 +194,8 @@ bool RISCVExpandPseudo::expandMI(MachineBasicBlock &MBB,
return expandVMSET_VMCLR(MBB, MBBI, RISCV::VMXNOR_MM);
case RISCV::PseudoReadVLENBViaVSETVLIX0:
return expandPseudoReadVLENBViaVSETVLIX0(MBB, MBBI);
+ case RISCV::CV_SHUFFLE_SCI_B_PSEUDO:
+ return expandVendorXcvsimdShuffle(MBB, MBBI);
}
return false;
@@ -834,6 +838,33 @@ bool RISCVPreRAExpandPseudo::expandLoadTLSDescAddress(
return true;
}
+bool RISCVExpandPseudo::expandVendorXcvsimdShuffle(
+ MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI) {
+ // CV_SHUFFLE_SCI_B_PSEUDO takes an 8-bit immediate.
+ // Bits [7:6] select one of four cv.shuffleIx.sci.b instructions.
+ // Bits [5:0] become the 6-bit signed immediate payload.
+ DebugLoc DL = MBBI->getDebugLoc();
+ Register DstReg = MBBI->getOperand(0).getReg();
+ Register SrcReg = MBBI->getOperand(1).getReg();
+ unsigned Imm = MBBI->getOperand(2).getImm();
+
+ // Select the correct real instruction based on bits [7:6]
+ static const unsigned Opcodes[] = {
+ RISCV::CV_SHUFFLEI0_SCI_B,
+ RISCV::CV_SHUFFLEI1_SCI_B,
+ RISCV::CV_SHUFFLEI2_SCI_B,
+ RISCV::CV_SHUFFLEI3_SCI_B,
+ };
+ unsigned OpcIdx = (Imm >> 6) & 0x3;
+ unsigned Imm6 = Imm & 0x3F;
+
+ const MCInstrDesc &Desc = TII->get(Opcodes[OpcIdx]);
+ BuildMI(MBB, MBBI, DL, Desc, DstReg).addReg(SrcReg).addImm(Imm6);
+
+ MBBI->eraseFromParent();
+ return true;
+}
+
} // end of anonymous namespace
INITIALIZE_PASS(RISCVExpandPseudo, "riscv-expand-pseudo",
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
index f3f3d408c43e7..75915a76e6036 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
@@ -857,3 +857,236 @@ let Predicates = [HasVendorXCVmac, IsRV32] in {
def : PatCoreVMacGprGprGprUimm5<"macsRN", "MACSRN">;
def : PatCoreVMacGprGprGprUimm5<"machhsRN", "MACHHSRN">;
}
+
+//===----------------------------------------------------------------------===//
+// XCVsimd pseudo-instruction for cv.shuffle.sci.b
+//===----------------------------------------------------------------------===//
+// The hardware encodes the 8-bit shuffle immediate by splitting it across
+// four different opcodes: cv.shufflei0/1/2/3.sci.b, where bits [7:6] select
+// the opcode and bits [5:0] are the payload. This pseudo absorbs the full
+// 8-bit immediate; RISCVExpandPseudoInsts splits it into the correct real
+// instruction.
+
+let Predicates = [HasVendorXCVsimd, IsRV32] in {
+ let hasSideEffects = 0, mayLoad = 0, mayStore = 0, isCodeGenOnly = 1 in
+ def CV_SHUFFLE_SCI_B_PSEUDO
+ : Pseudo<(outs GPR:$rd), (ins GPR:$rs1, i32imm:$imm), []>;
+}
+
+//===----------------------------------------------------------------------===//
+// Pattern helper multiclasses for XCVsimd
+//===----------------------------------------------------------------------===//
+
+// Binary signed: RR variants + SCI with simm6
+multiclass PatCoreVSimdSigned<string iname> {
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_h"),
+ !cast<RVInst>("CV_" # NAME # "_H")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_b"),
+ !cast<RVInst>("CV_" # NAME # "_B")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h"),
+ !cast<RVInst>("CV_" # NAME # "_SC_H")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b"),
+ !cast<RVInst>("CV_" # NAME # "_SC_B")>;
+ // SCI: when the scalar fits in simm6, prefer the immediate instruction
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, simm6:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_H") GPR:$rs1, simm6:$imm)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, simm6:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_B") GPR:$rs1, simm6:$imm)>;
+}
+
+// Binary unsigned: RR variants + SCI with uimm6
+multiclass PatCoreVSimdUnsigned<string iname> {
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_h"),
+ !cast<RVInst>("CV_" # NAME # "_H")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_b"),
+ !cast<RVInst>("CV_" # NAME # "_B")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h"),
+ !cast<RVInst>("CV_" # NAME # "_SC_H")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b"),
+ !cast<RVInst>("CV_" # NAME # "_SC_B")>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, uimm6:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_H") GPR:$rs1, uimm6:$imm)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, uimm6:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_B") GPR:$rs1, uimm6:$imm)>;
+}
+
+// Shift: RR variants + SCI with uimm4 for .h, uimm3 for .b
+multiclass PatCoreVSimdShift<string iname> {
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_h"),
+ !cast<RVInst>("CV_" # NAME # "_H")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_b"),
+ !cast<RVInst>("CV_" # NAME # "_B")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h"),
+ !cast<RVInst>("CV_" # NAME # "_SC_H")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b"),
+ !cast<RVInst>("CV_" # NAME # "_SC_B")>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, uimm4:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_H") GPR:$rs1, uimm4:$imm)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, uimm3:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_B") GPR:$rs1, uimm3:$imm)>;
+}
+
+// Accumulating signed (Wb): intrinsic(rs1, rs2, acc) → instr(acc, rs1, rs2)
+// + SCI variant where rs2 (arg1) is simm6
+multiclass PatCoreVSimdSignedWb<string iname> {
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_h")
+ GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_H") GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_b")
+ GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_B") GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_SC_H") GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_SC_B") GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, simm6:$imm, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_H") GPR:$rd, GPR:$rs1, simm6:$imm)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, simm6:$imm, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_B") GPR:$rd, GPR:$rs1, simm6:$imm)>;
+}
+
+// Accumulating unsigned (Wb): same but SCI uses uimm6
+multiclass PatCoreVSimdUnsignedWb<string iname> {
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_h")
+ GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_H") GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_b")
+ GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_B") GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_SC_H") GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_SC_B") GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, uimm6:$imm, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_H") GPR:$rd, GPR:$rs1, uimm6:$imm)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, uimm6:$imm, GPR:$rd),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_B") GPR:$rd, GPR:$rs1, uimm6:$imm)>;
+}
+
+//===----------------------------------------------------------------------===//
+// XCVsimd intrinsic-to-instruction patterns
+//===----------------------------------------------------------------------===//
+
+let Predicates = [HasVendorXCVsimd, IsRV32], AddedComplexity = 1 in {
+
+ // ---- ADD / SUB ----
+ // add_h / sub_h use a divcode immediate to select between 4 instructions
+ // intrinsic: (rs1, rs2, divcode_IMM) -> rd
+
+
+
+
+ // ---- AVG / AVGU / MIN / MINU / MAX / MAXU ----
+
+ // ---- SRL / SRA / SLL ----
+
+ // ---- OR / XOR / AND ----
+
+ // ---- ABS ----
+
+ // ---- DOT PRODUCTS (non-accumulating) ----
+ defm DOTUP : PatCoreVSimdUnsigned<"dotup">;
+ defm DOTUSP : PatCoreVSimdSigned<"dotusp">;
+ defm DOTSP : PatCoreVSimdSigned<"dotsp">;
+
+ // ---- SDOT (accumulating) ----
+ defm SDOTUP : PatCoreVSimdUnsignedWb<"sdotup">;
+ defm SDOTUSP : PatCoreVSimdSignedWb<"sdotusp">;
+ defm SDOTSP : PatCoreVSimdSignedWb<"sdotsp">;
+
+ // ---- EXTRACT / EXTRACTU ----
+ // intrinsic: (rs1, IMM) -> rd
+ def : Pat<(int_riscv_cv_simd_extract_h GPR:$rs1, timm:$imm),
+ (CV_EXTRACT_H GPR:$rs1, timm:$imm)>;
+ def : Pat<(int_riscv_cv_simd_extract_b GPR:$rs1, timm:$imm),
+ (CV_EXTRACT_B GPR:$rs1, timm:$imm)>;
+ def : Pat<(int_riscv_cv_simd_extractu_h GPR:$rs1, timm:$imm),
+ (CV_EXTRACTU_H GPR:$rs1, timm:$imm)>;
+ def : Pat<(int_riscv_cv_simd_extractu_b GPR:$rs1, timm:$imm),
+ (CV_EXTRACTU_B GPR:$rs1, timm:$imm)>;
+
+ // ---- INSERT ----
+ // intrinsic: (rd_in, rs1, IMM) -> rd_out
+ def : Pat<(int_riscv_cv_simd_insert_h GPR:$rd, GPR:$rs1, timm:$imm),
+ (CV_INSERT_H GPR:$rd, GPR:$rs1, timm:$imm)>;
+ def : Pat<(int_riscv_cv_simd_insert_b GPR:$rd, GPR:$rs1, timm:$imm),
+ (CV_INSERT_B GPR:$rd, GPR:$rs1, timm:$imm)>;
+
+ // ---- SHUFFLE ----
+ def : PatGprGpr<int_riscv_cv_simd_shuffle_h, CV_SHUFFLE_H>;
+ def : PatGprGpr<int_riscv_cv_simd_shuffle_b, CV_SHUFFLE_B>;
+ def : Pat<(int_riscv_cv_simd_shuffle_sci_h GPR:$rs1, timm:$imm),
+ (CV_SHUFFLE_SCI_H GPR:$rs1, timm:$imm)>;
+ // shuffle_sci_b goes through the pseudo (8-bit imm split by expander)
+ def : Pat<(int_riscv_cv_simd_shuffle_sci_b GPR:$rs1, timm:$imm),
+ (CV_SHUFFLE_SCI_B_PSEUDO GPR:$rs1, timm:$imm)>;
+
+ // ---- SHUFFLE2: (rs1, rs2, rd_in) -> rd_out ----
+ def : Pat<(int_riscv_cv_simd_shuffle2_h GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (CV_SHUFFLE2_H GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_shuffle2_b GPR:$rs1, GPR:$rs2, GPR:$rd),
+ (CV_SHUFFLE2_B GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+
+ // ---- PACK ----
+ // pack (packlo_h) and pack.h (packhi_h) are plain binary: (rs1, rs2) -> rd
+ def : PatGprGpr<int_riscv_cv_simd_packlo_h, CV_PACK>;
+ def : PatGprGpr<int_riscv_cv_simd_packhi_h, CV_PACK_H>;
+ // packhi.b / packlo.b are accumulating: (rd_in, rs1, rs2) -> rd_out
+ def : Pat<(int_riscv_cv_simd_packhi_b GPR:$rd, GPR:$rs1, GPR:$rs2),
+ (CV_PACKHI_B GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_packlo_b GPR:$rd, GPR:$rs1, GPR:$rs2),
+ (CV_PACKLO_B GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+
+ // ---- COMPARE (signed) ----
+
+ // ---- COMPARE (unsigned) ----
+
+ // ---- COMPLEX: cplxmul ----
+ // intrinsic: (rs1, rs2, rd_in, divcode_IMM) -> rd_out
+ def : Pat<(int_riscv_cv_simd_cplxmul_r GPR:$rs1, GPR:$rs2, GPR:$rd, 0),
+ (CV_CPLXMUL_R GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_cplxmul_r GPR:$rs1, GPR:$rs2, GPR:$rd, 1),
+ (CV_CPLXMUL_R_DIV2 GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_cplxmul_r GPR:$rs1, GPR:$rs2, GPR:$rd, 2),
+ (CV_CPLXMUL_R_DIV4 GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_cplxmul_r GPR:$rs1, GPR:$rs2, GPR:$rd, 3),
+ (CV_CPLXMUL_R_DIV8 GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+
+ def : Pat<(int_riscv_cv_simd_cplxmul_i GPR:$rs1, GPR:$rs2, GPR:$rd, 0),
+ (CV_CPLXMUL_I GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_cplxmul_i GPR:$rs1, GPR:$rs2, GPR:$rd, 1),
+ (CV_CPLXMUL_I_DIV2 GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_cplxmul_i GPR:$rs1, GPR:$rs2, GPR:$rd, 2),
+ (CV_CPLXMUL_I_DIV4 GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_cplxmul_i GPR:$rs1, GPR:$rs2, GPR:$rd, 3),
+ (CV_CPLXMUL_I_DIV8 GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+
+ // ---- COMPLEX: cplxconj ----
+ def : PatGpr<int_riscv_cv_simd_cplxconj, CV_CPLXCONJ>;
+
+ // ---- COMPLEX: subrotmj ----
+ // intrinsic: (rs1, rs2, divcode_IMM) -> rd
+ def : Pat<(int_riscv_cv_simd_subrotmj GPR:$rs1, GPR:$rs2, 0),
+ (CV_SUBROTMJ GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_subrotmj GPR:$rs1, GPR:$rs2, 1),
+ (CV_SUBROTMJ_DIV2 GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_subrotmj GPR:$rs1, GPR:$rs2, 2),
+ (CV_SUBROTMJ_DIV4 GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(int_riscv_cv_simd_subrotmj GPR:$rs1, GPR:$rs2, 3),
+ (CV_SUBROTMJ_DIV8 GPR:$rs1, GPR:$rs2)>;
+
+} // Predicates = [HasVendorXCVsimd, IsRV32], AddedComplexity = 1
diff --git a/llvm/test/CodeGen/RISCV/xcvsimd.ll b/llvm/test/CodeGen/RISCV/xcvsimd.ll
new file mode 100644
index 0000000000000..d27c5aff84813
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xcvsimd.ll
@@ -0,0 +1,486 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -mattr=+m,+xcvsimd -verify-machineinstrs < %s \
+; RUN: | FileCheck %s
+
+; This file tests that all XCVsimd LLVM IR intrinsics are correctly lowered
+; to their corresponding CV32E40P machine instructions.
+
+;===----------------------------------------------------------------------===;
+; ADD / SUB
+;===----------------------------------------------------------------------===;
+
+;===----------------------------------------------------------------------===;
+; AVG / AVGU
+;===----------------------------------------------------------------------===;
+
+;===----------------------------------------------------------------------===;
+; MIN / MINU / MAX / MAXU (representative: _h and _sc_b for each)
+;===----------------------------------------------------------------------===;
+
+;===----------------------------------------------------------------------===;
+; SRL / SRA / SLL
+;===----------------------------------------------------------------------===;
+
+;===----------------------------------------------------------------------===;
+; OR / XOR / AND
+;===----------------------------------------------------------------------===;
+
+;===----------------------------------------------------------------------===;
+; ABS
+;===----------------------------------------------------------------------===;
+
+;===----------------------------------------------------------------------===;
+; DOT PRODUCTS (non-accumulating)
+;===----------------------------------------------------------------------===;
+
+define i32 @test_cv_simd_dotup_h(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_dotup_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.dotup.h a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.dotup.h(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_dotup_b(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_dotup_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.dotup.b a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.dotup.b(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_dotup_sc_h(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_dotup_sc_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.dotup.sc.h a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.dotup.sc.h(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_dotup_sc_b(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_dotup_sc_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.dotup.sc.b a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.dotup.sc.b(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_dotusp_h(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_dotusp_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.dotusp.h a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.dotusp.h(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_dotsp_h(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_dotsp_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.dotsp.h a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.dotsp.h(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+;===----------------------------------------------------------------------===;
+; SDOT (accumulating dot products)
+;===----------------------------------------------------------------------===;
+
+define i32 @test_cv_simd_sdotup_h(i32 %a, i32 %b, i32 %acc) {
+; CHECK-LABEL: test_cv_simd_sdotup_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.sdotup.h a2, a0, a1
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.sdotup.h(i32 %a, i32 %b, i32 %acc)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_sdotup_b(i32 %a, i32 %b, i32 %acc) {
+; CHECK-LABEL: test_cv_simd_sdotup_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.sdotup.b a2, a0, a1
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.sdotup.b(i32 %a, i32 %b, i32 %acc)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_sdotusp_h(i32 %a, i32 %b, i32 %acc) {
+; CHECK-LABEL: test_cv_simd_sdotusp_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.sdotusp.h a2, a0, a1
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.sdotusp.h(i32 %a, i32 %b, i32 %acc)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_sdotsp_h(i32 %a, i32 %b, i32 %acc) {
+; CHECK-LABEL: test_cv_simd_sdotsp_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.sdotsp.h a2, a0, a1
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.sdotsp.h(i32 %a, i32 %b, i32 %acc)
+ ret i32 %res
+}
+
+;===----------------------------------------------------------------------===;
+; EXTRACT / EXTRACTU / INSERT
+;===----------------------------------------------------------------------===;
+
+define i32 @test_cv_simd_extract_h(i32 %a) {
+; CHECK-LABEL: test_cv_simd_extract_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.extract.h a0, a0, 1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.extract.h(i32 %a, i32 1)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_extract_b(i32 %a) {
+; CHECK-LABEL: test_cv_simd_extract_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.extract.b a0, a0, 3
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.extract.b(i32 %a, i32 3)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_extractu_h(i32 %a) {
+; CHECK-LABEL: test_cv_simd_extractu_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.extractu.h a0, a0, 0
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.extractu.h(i32 %a, i32 0)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_extractu_b(i32 %a) {
+; CHECK-LABEL: test_cv_simd_extractu_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.extractu.b a0, a0, 2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.extractu.b(i32 %a, i32 2)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_insert_h(i32 %rd, i32 %rs1) {
+; CHECK-LABEL: test_cv_simd_insert_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.insert.h a0, a1, 1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.insert.h(i32 %rd, i32 %rs1, i32 1)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_insert_b(i32 %rd, i32 %rs1) {
+; CHECK-LABEL: test_cv_simd_insert_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.insert.b a0, a1, 2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.insert.b(i32 %rd, i32 %rs1, i32 2)
+ ret i32 %res
+}
+
+;===----------------------------------------------------------------------===;
+; SHUFFLE / SHUFFLE2
+;===----------------------------------------------------------------------===;
+
+define i32 @test_cv_simd_shuffle_h(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_shuffle_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shuffle.h a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle.h(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_shuffle_b(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_shuffle_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shuffle.b a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle.b(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_shuffle_sci_h(i32 %a) {
+; CHECK-LABEL: test_cv_simd_shuffle_sci_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shuffle.sci.h a0, a0, 2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle.sci.h(i32 %a, i32 2)
+ ret i32 %res
+}
+
+; Test shuffle_sci_b pseudo expansion: imm=0x45 → bits[7:6]=1 → shufflei1
+define i32 @test_cv_simd_shuffle_sci_b_i1(i32 %a) {
+; CHECK-LABEL: test_cv_simd_shuffle_sci_b_i1:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shufflei1.sci.b a0, a0, 5
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle.sci.b(i32 %a, i32 69)
+ ret i32 %res
+}
+
+; Test shuffle_sci_b pseudo expansion: imm=0x03 → bits[7:6]=0 → shufflei0
+define i32 @test_cv_simd_shuffle_sci_b_i0(i32 %a) {
+; CHECK-LABEL: test_cv_simd_shuffle_sci_b_i0:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shufflei0.sci.b a0, a0, 3
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle.sci.b(i32 %a, i32 3)
+ ret i32 %res
+}
+
+; Test shuffle_sci_b pseudo expansion: imm=0x83 → bits[7:6]=2 → shufflei2
+define i32 @test_cv_simd_shuffle_sci_b_i2(i32 %a) {
+; CHECK-LABEL: test_cv_simd_shuffle_sci_b_i2:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shufflei2.sci.b a0, a0, 3
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle.sci.b(i32 %a, i32 131)
+ ret i32 %res
+}
+
+; Test shuffle_sci_b pseudo expansion: imm=0xC0 → bits[7:6]=3 → shufflei3
+define i32 @test_cv_simd_shuffle_sci_b_i3(i32 %a) {
+; CHECK-LABEL: test_cv_simd_shuffle_sci_b_i3:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shufflei3.sci.b a0, a0, 0
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle.sci.b(i32 %a, i32 192)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_shuffle2_h(i32 %rd, i32 %rs1, i32 %rs2) {
+; CHECK-LABEL: test_cv_simd_shuffle2_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shuffle2.h a0, a1, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle2.h(i32 %rs1, i32 %rs2, i32 %rd)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_shuffle2_b(i32 %rd, i32 %rs1, i32 %rs2) {
+; CHECK-LABEL: test_cv_simd_shuffle2_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.shuffle2.b a0, a1, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.shuffle2.b(i32 %rs1, i32 %rs2, i32 %rd)
+ ret i32 %res
+}
+
+;===----------------------------------------------------------------------===;
+; PACK
+;===----------------------------------------------------------------------===;
+
+define i32 @test_cv_simd_packlo_h(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_packlo_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.pack a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.packlo.h(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_packhi_h(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_packhi_h:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.pack.h a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.packhi.h(i32 %a, i32 %b)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_packhi_b(i32 %rd, i32 %rs1, i32 %rs2) {
+; CHECK-LABEL: test_cv_simd_packhi_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.packhi.b a0, a1, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.packhi.b(i32 %rd, i32 %rs1, i32 %rs2)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_packlo_b(i32 %rd, i32 %rs1, i32 %rs2) {
+; CHECK-LABEL: test_cv_simd_packlo_b:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.packlo.b a0, a1, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.packlo.b(i32 %rd, i32 %rs1, i32 %rs2)
+ ret i32 %res
+}
+
+;===----------------------------------------------------------------------===;
+; COMPARE (representative subset)
+;===----------------------------------------------------------------------===;
+
+;===----------------------------------------------------------------------===;
+; COMPLEX: cplxmul / cplxconj / subrotmj
+;===----------------------------------------------------------------------===;
+
+define i32 @test_cv_simd_cplxmul_r(i32 %rs1, i32 %rs2, i32 %rd) {
+; CHECK-LABEL: test_cv_simd_cplxmul_r:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.cplxmul.r a2, a0, a1
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.cplxmul.r(i32 %rs1, i32 %rs2, i32 %rd, i32 0)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_cplxmul_r_div2(i32 %rs1, i32 %rs2, i32 %rd) {
+; CHECK-LABEL: test_cv_simd_cplxmul_r_div2:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.cplxmul.r.div2 a2, a0, a1
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.cplxmul.r(i32 %rs1, i32 %rs2, i32 %rd, i32 1)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_cplxmul_i(i32 %rs1, i32 %rs2, i32 %rd) {
+; CHECK-LABEL: test_cv_simd_cplxmul_i:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.cplxmul.i a2, a0, a1
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.cplxmul.i(i32 %rs1, i32 %rs2, i32 %rd, i32 0)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_cplxmul_i_div4(i32 %rs1, i32 %rs2, i32 %rd) {
+; CHECK-LABEL: test_cv_simd_cplxmul_i_div4:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.cplxmul.i.div4 a2, a0, a1
+; CHECK-NEXT: mv a0, a2
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.cplxmul.i(i32 %rs1, i32 %rs2, i32 %rd, i32 2)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_cplxconj(i32 %a) {
+; CHECK-LABEL: test_cv_simd_cplxconj:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.cplxconj a0, a0
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.cplxconj(i32 %a)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_subrotmj(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_subrotmj:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.subrotmj a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.subrotmj(i32 %a, i32 %b, i32 0)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_subrotmj_div2(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_subrotmj_div2:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.subrotmj.div2 a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.subrotmj(i32 %a, i32 %b, i32 1)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_subrotmj_div4(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_subrotmj_div4:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.subrotmj.div4 a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.subrotmj(i32 %a, i32 %b, i32 2)
+ ret i32 %res
+}
+
+define i32 @test_cv_simd_subrotmj_div8(i32 %a, i32 %b) {
+; CHECK-LABEL: test_cv_simd_subrotmj_div8:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.subrotmj.div8 a0, a0, a1
+; CHECK-NEXT: ret
+ %res = call i32 @llvm.riscv.cv.simd.subrotmj(i32 %a, i32 %b, i32 3)
+ ret i32 %res
+}
+
+;===----------------------------------------------------------------------===;
+; Intrinsic declarations
+;===----------------------------------------------------------------------===;
+
+; ADD/SUB
+
+; AVG/AVGU
+
+; MIN/MINU/MAX/MAXU
+
+; SRL/SRA/SLL
+
+; OR/XOR/AND
+
+; ABS
+
+; DOT (non-accumulating)
+declare i32 @llvm.riscv.cv.simd.dotup.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotup.b(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotup.sc.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotup.sc.b(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotusp.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotusp.b(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotusp.sc.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotusp.sc.b(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotsp.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotsp.b(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotsp.sc.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.dotsp.sc.b(i32, i32)
+
+; SDOT (accumulating)
+declare i32 @llvm.riscv.cv.simd.sdotup.h(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotup.b(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotup.sc.h(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotup.sc.b(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotusp.h(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotusp.b(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotusp.sc.h(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotusp.sc.b(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotsp.h(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotsp.b(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotsp.sc.h(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.sdotsp.sc.b(i32, i32, i32)
+
+; EXTRACT/EXTRACTU/INSERT
+declare i32 @llvm.riscv.cv.simd.extract.h(i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.extract.b(i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.extractu.h(i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.extractu.b(i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.insert.h(i32, i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.insert.b(i32, i32, i32 immarg)
+
+; SHUFFLE
+declare i32 @llvm.riscv.cv.simd.shuffle.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.shuffle.b(i32, i32)
+declare i32 @llvm.riscv.cv.simd.shuffle.sci.h(i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.shuffle.sci.b(i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.shuffle2.h(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.shuffle2.b(i32, i32, i32)
+
+; PACK
+declare i32 @llvm.riscv.cv.simd.packlo.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.packhi.h(i32, i32)
+declare i32 @llvm.riscv.cv.simd.packhi.b(i32, i32, i32)
+declare i32 @llvm.riscv.cv.simd.packlo.b(i32, i32, i32)
+
+; COMPARE
+
+; COMPLEX
+declare i32 @llvm.riscv.cv.simd.cplxmul.r(i32, i32, i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.cplxmul.i(i32, i32, i32, i32 immarg)
+declare i32 @llvm.riscv.cv.simd.cplxconj(i32)
+declare i32 @llvm.riscv.cv.simd.subrotmj(i32, i32, i32 immarg)
>From 04c985f6c5e2895489cc8d924329475a89e57f5a Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Thu, 25 Jun 2026 12:22:01 +0200
Subject: [PATCH 2/2] [RISCV][XCV] Select element-wise XCVsimd ops from
packed-vector IR
Make 2 x i16 / 4 x i8 legal GPR-resident types under XCVsimd (RV32) so
element-wise vector IR selects the cv.*.h / cv.*.b SIMD instructions
directly, without dedicated intrinsics:
add / sub -> cv.add / cv.sub
smin / umin -> cv.min / cv.minu
smax / umax -> cv.max / cv.maxu
abs -> cv.abs
and/or/xor -> scalar AND/OR/XOR (lane-agnostic)
Every other operation on these types is expanded, so generic vector_size
code with no single instruction scalarizes safely instead of failing to
select (expand-all-then-enable, matching the P extension setup).
The packed types share a GPR with i32, so they pass and return in one
register (calling-convention support) and bitcast to/from i32 for free.
Loads and stores are plain word lw/sw; under-aligned accesses follow the
scalar misalignment rule and split into element accesses rather than a
trapping misaligned word access.
Shifts, avg and the comparisons stay unmapped for now: they need vector
shift legalization, added separately.
---
llvm/lib/Target/RISCV/RISCVCallingConv.cpp | 9 +
llvm/lib/Target/RISCV/RISCVISelLowering.cpp | 45 +++-
llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td | 90 ++++++-
.../RISCV/xcvsimd-vector-unsupported.ll | 74 +++++
llvm/test/CodeGen/RISCV/xcvsimd-vector.ll | 252 ++++++++++++++++++
5 files changed, 458 insertions(+), 12 deletions(-)
create mode 100644 llvm/test/CodeGen/RISCV/xcvsimd-vector-unsupported.ll
create mode 100644 llvm/test/CodeGen/RISCV/xcvsimd-vector.ll
diff --git a/llvm/lib/Target/RISCV/RISCVCallingConv.cpp b/llvm/lib/Target/RISCV/RISCVCallingConv.cpp
index 9853644080161..39fbfd9a5ad28 100644
--- a/llvm/lib/Target/RISCV/RISCVCallingConv.cpp
+++ b/llvm/lib/Target/RISCV/RISCVCallingConv.cpp
@@ -377,6 +377,15 @@ static bool CC_RISCV_Impl(unsigned ValNo, MVT ValVT, MVT LocVT,
unsigned XLen = Subtarget.getXLen();
MVT XLenVT = Subtarget.getXLenVT();
+ // XCVsimd packs 2 x i16 / 4 x i8 into a single GPR. Pass and return them
+ // as XLenVT (a bitcast), so they follow the scalar-integer path instead of
+ // the vector path (which assumes RVV containers).
+ if (Subtarget.hasVendorXCVsimd() && !Subtarget.hasStdExtP() &&
+ (LocVT == MVT::v2i16 || LocVT == MVT::v4i8)) {
+ LocVT = XLenVT;
+ LocInfo = CCValAssign::BCvt;
+ }
+
if (ArgFlags.isNest()) {
// Static chain parameter must not be passed in normal argument registers,
// so we assign t2/t3 for it as done in GCC's
diff --git a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
index 7a2b9611683c6..f63d0af3d170f 100644
--- a/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelLowering.cpp
@@ -304,9 +304,46 @@ RISCVTargetLowering::RISCVTargetLowering(const TargetMachine &TM,
}
}
+ // XCVsimd packs 2 x i16 / 4 x i8 into a GPR (RV32 only); make those types
+ // legal so element-wise IR ops select the cv.*.h / cv.*.b SIMD instructions.
+ if (Subtarget.hasVendorXCVsimd() && !Subtarget.hasStdExtP()) {
+ addRegisterClass(MVT::v2i16, &RISCV::GPRRegClass);
+ addRegisterClass(MVT::v4i8, &RISCV::GPRRegClass);
+ }
+
// Compute derived properties from the register classes.
computeRegisterProperties(STI.getRegisterInfo());
+ if (Subtarget.hasVendorXCVsimd() && !Subtarget.hasStdExtP()) {
+ static const MVT XCVVecVTs[] = {MVT::v2i16, MVT::v4i8};
+
+ // Start by expanding every operation on the packed types, then make legal
+ // only the ones that map to a single SIMD instruction. Anything left
+ // expanded scalarizes, so generic vector_size code is still handled
+ // correctly instead of failing to select.
+ for (unsigned Op = 0; Op < ISD::BUILTIN_OP_END; ++Op)
+ setOperationAction(Op, XCVVecVTs, Expand);
+
+ for (MVT VT : XCVVecVTs) {
+ for (MVT OtherVT : MVT::integer_fixedlen_vector_valuetypes()) {
+ setTruncStoreAction(VT, OtherVT, Expand);
+ setLoadExtAction({ISD::EXTLOAD, ISD::SEXTLOAD, ISD::ZEXTLOAD}, VT,
+ OtherVT, Expand);
+ }
+ }
+
+ // The packed types live in a GPR, so they load/store as a plain word and
+ // bitcast to/from i32 for free (selected by no-op patterns). Under-aligned
+ // accesses are split by allowsMisalignedMemoryAccesses.
+ setOperationAction({ISD::LOAD, ISD::STORE}, XCVVecVTs, Legal);
+ setOperationAction(ISD::BITCAST, XCVVecVTs, Legal);
+
+ // Element-wise ops with a one-to-one cv.* SIMD instruction.
+ setOperationAction({ISD::ADD, ISD::SUB, ISD::AND, ISD::OR, ISD::XOR,
+ ISD::SMIN, ISD::SMAX, ISD::UMIN, ISD::UMAX, ISD::ABS},
+ XCVVecVTs, Legal);
+ }
+
setStackPointerRegisterToSaveRestore(RISCV::X2);
setLoadExtAction({ISD::EXTLOAD, ISD::SEXTLOAD, ISD::ZEXTLOAD}, XLenVT,
@@ -26610,7 +26647,13 @@ bool RISCVTargetLowering::isMulAddWithConstProfitable(SDValue AddNode,
bool RISCVTargetLowering::allowsMisalignedMemoryAccesses(
EVT VT, unsigned AddrSpace, Align Alignment, MachineMemOperand::Flags Flags,
unsigned *Fast) const {
- if (!VT.isVector() || Subtarget.hasStdExtP()) {
+ // XCVsimd packs v2i16/v4i8 into a single GPR, so their accesses are plain
+ // word loads/stores and follow the scalar (not vector) misalignment rules:
+ // an under-aligned access must split into element accesses rather than stay
+ // a single (mis)aligned lw/sw.
+ bool IsXCVPacked =
+ Subtarget.hasVendorXCVsimd() && (VT == MVT::v2i16 || VT == MVT::v4i8);
+ if (!VT.isVector() || Subtarget.hasStdExtP() || IsXCVPacked) {
if (Fast)
*Fast = Subtarget.enableUnalignedScalarMem();
return Subtarget.enableUnalignedScalarMem();
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
index 75915a76e6036..ef3d13443e338 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
@@ -914,6 +914,35 @@ multiclass PatCoreVSimdUnsigned<string iname> {
(!cast<RVInst>("CV_" # NAME # "_SCI_B") GPR:$rs1, uimm6:$imm)>;
}
+// Scalar-broadcast-only variants: the full-vector _h/_b forms are expressed as
+// native packed-vector IR ops (2 x i16 / 4 x i8), so only the scalar-operand
+// (sc / sci) forms keep an intrinsic.
+multiclass PatCoreVSimdSignedScOnly<string iname> {
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h"),
+ !cast<RVInst>("CV_" # NAME # "_SC_H")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b"),
+ !cast<RVInst>("CV_" # NAME # "_SC_B")>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, simm6:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_H") GPR:$rs1, simm6:$imm)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, simm6:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_B") GPR:$rs1, simm6:$imm)>;
+}
+
+multiclass PatCoreVSimdUnsignedScOnly<string iname> {
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h"),
+ !cast<RVInst>("CV_" # NAME # "_SC_H")>;
+ def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b"),
+ !cast<RVInst>("CV_" # NAME # "_SC_B")>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_h")
+ GPR:$rs1, uimm6:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_H") GPR:$rs1, uimm6:$imm)>;
+ def : Pat<(!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_sc_b")
+ GPR:$rs1, uimm6:$imm),
+ (!cast<RVInst>("CV_" # NAME # "_SCI_B") GPR:$rs1, uimm6:$imm)>;
+}
+
// Shift: RR variants + SCI with uimm4 for .h, uimm3 for .b
multiclass PatCoreVSimdShift<string iname> {
def : PatGprGpr<!cast<Intrinsic>("int_riscv_cv_simd_" # iname # "_h"),
@@ -987,17 +1016,6 @@ let Predicates = [HasVendorXCVsimd, IsRV32], AddedComplexity = 1 in {
// add_h / sub_h use a divcode immediate to select between 4 instructions
// intrinsic: (rs1, rs2, divcode_IMM) -> rd
-
-
-
- // ---- AVG / AVGU / MIN / MINU / MAX / MAXU ----
-
- // ---- SRL / SRA / SLL ----
-
- // ---- OR / XOR / AND ----
-
- // ---- ABS ----
-
// ---- DOT PRODUCTS (non-accumulating) ----
defm DOTUP : PatCoreVSimdUnsigned<"dotup">;
defm DOTUSP : PatCoreVSimdSigned<"dotusp">;
@@ -1090,3 +1108,53 @@ let Predicates = [HasVendorXCVsimd, IsRV32], AddedComplexity = 1 in {
(CV_SUBROTMJ_DIV8 GPR:$rs1, GPR:$rs2)>;
} // Predicates = [HasVendorXCVsimd, IsRV32], AddedComplexity = 1
+
+//===----------------------------------------------------------------------===//
+// XCVsimd: select the element-wise SIMD instructions from generic
+// packed-vector IR.
+//
+// 2 x i16 / 4 x i8 are legal GPR-resident types for XCVsimd (RV32), so the
+// element-wise vector operations map directly to the SIMD instructions without
+// needing dedicated intrinsics. Bitwise ops are lane-agnostic and reuse the
+// base scalar AND/OR/XOR.
+//===----------------------------------------------------------------------===//
+
+let Predicates = [HasVendorXCVsimd, IsRV32] in {
+ // The packed vectors live in a GPR, so a bitcast to/from i32 is a no-op
+ // (used by the calling convention to pass/return them in a register).
+ def : Pat<(i32 (bitconvert (v2i16 GPR:$x))), (COPY_TO_REGCLASS GPR:$x, GPR)>;
+ def : Pat<(v2i16 (bitconvert (i32 GPR:$x))), (COPY_TO_REGCLASS GPR:$x, GPR)>;
+ def : Pat<(i32 (bitconvert (v4i8 GPR:$x))), (COPY_TO_REGCLASS GPR:$x, GPR)>;
+ def : Pat<(v4i8 (bitconvert (i32 GPR:$x))), (COPY_TO_REGCLASS GPR:$x, GPR)>;
+
+ def : Pat<(v2i16 (add GPR:$rs1, GPR:$rs2)), (CV_ADD_H GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (add GPR:$rs1, GPR:$rs2)), (CV_ADD_B GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v2i16 (sub GPR:$rs1, GPR:$rs2)), (CV_SUB_H GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (sub GPR:$rs1, GPR:$rs2)), (CV_SUB_B GPR:$rs1, GPR:$rs2)>;
+
+ def : Pat<(v2i16 (smin GPR:$rs1, GPR:$rs2)), (CV_MIN_H GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (smin GPR:$rs1, GPR:$rs2)), (CV_MIN_B GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v2i16 (umin GPR:$rs1, GPR:$rs2)), (CV_MINU_H GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (umin GPR:$rs1, GPR:$rs2)), (CV_MINU_B GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v2i16 (smax GPR:$rs1, GPR:$rs2)), (CV_MAX_H GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (smax GPR:$rs1, GPR:$rs2)), (CV_MAX_B GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v2i16 (umax GPR:$rs1, GPR:$rs2)), (CV_MAXU_H GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (umax GPR:$rs1, GPR:$rs2)), (CV_MAXU_B GPR:$rs1, GPR:$rs2)>;
+
+ // Bitwise ops do not depend on lane boundaries: reuse the scalar insns.
+ def : Pat<(v2i16 (and GPR:$rs1, GPR:$rs2)), (AND GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (and GPR:$rs1, GPR:$rs2)), (AND GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v2i16 (or GPR:$rs1, GPR:$rs2)), (OR GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (or GPR:$rs1, GPR:$rs2)), (OR GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v2i16 (xor GPR:$rs1, GPR:$rs2)), (XOR GPR:$rs1, GPR:$rs2)>;
+ def : Pat<(v4i8 (xor GPR:$rs1, GPR:$rs2)), (XOR GPR:$rs1, GPR:$rs2)>;
+
+ // abs: per-lane absolute value.
+ def : Pat<(v2i16 (abs GPR:$rs1)), (CV_ABS_H GPR:$rs1)>;
+ def : Pat<(v4i8 (abs GPR:$rs1)), (CV_ABS_B GPR:$rs1)>;
+
+ def : LdPat<load, LW, v2i16>;
+ def : LdPat<load, LW, v4i8>;
+ def : StPat<store, SW, GPR, v2i16>;
+ def : StPat<store, SW, GPR, v4i8>;
+}
diff --git a/llvm/test/CodeGen/RISCV/xcvsimd-vector-unsupported.ll b/llvm/test/CodeGen/RISCV/xcvsimd-vector-unsupported.ll
new file mode 100644
index 0000000000000..ce3a7b987266e
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xcvsimd-vector-unsupported.ll
@@ -0,0 +1,74 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=riscv32 -mattr=+m,+xcvsimd -verify-machineinstrs < %s \
+; RUN: | FileCheck %s
+
+; Operations on the packed vector types that XCVsimd has no single
+; instruction for are scalarized (Expand) rather than failing to select.
+
+define <2 x i16> @mul_v2i16(<2 x i16> %a, <2 x i16> %b) {
+; CHECK-LABEL: mul_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: addi sp, sp, -16
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: sw a1, 8(sp)
+; CHECK-NEXT: sw a0, 4(sp)
+; CHECK-NEXT: lh a0, 10(sp)
+; CHECK-NEXT: lh a1, 6(sp)
+; CHECK-NEXT: mul a0, a1, a0
+; CHECK-NEXT: sh a0, 14(sp)
+; CHECK-NEXT: lh a0, 8(sp)
+; CHECK-NEXT: lh a1, 4(sp)
+; CHECK-NEXT: mul a0, a1, a0
+; CHECK-NEXT: sh a0, 12(sp)
+; CHECK-NEXT: lw a0, 12(sp)
+; CHECK-NEXT: addi sp, sp, 16
+; CHECK-NEXT: .cfi_def_cfa_offset 0
+; CHECK-NEXT: ret
+ %c = mul <2 x i16> %a, %b
+ ret <2 x i16> %c
+}
+
+define <4 x i8> @udiv_v4i8(<4 x i8> %a, <4 x i8> %b) {
+; CHECK-LABEL: udiv_v4i8:
+; CHECK: # %bb.0:
+; CHECK-NEXT: addi sp, sp, -16
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: sw a1, 8(sp)
+; CHECK-NEXT: sw a0, 4(sp)
+; CHECK-NEXT: lbu a0, 11(sp)
+; CHECK-NEXT: lbu a1, 7(sp)
+; CHECK-NEXT: divu a0, a1, a0
+; CHECK-NEXT: sb a0, 15(sp)
+; CHECK-NEXT: lbu a0, 10(sp)
+; CHECK-NEXT: lbu a1, 6(sp)
+; CHECK-NEXT: divu a0, a1, a0
+; CHECK-NEXT: sb a0, 14(sp)
+; CHECK-NEXT: lbu a0, 9(sp)
+; CHECK-NEXT: lbu a1, 5(sp)
+; CHECK-NEXT: divu a0, a1, a0
+; CHECK-NEXT: sb a0, 13(sp)
+; CHECK-NEXT: lbu a0, 8(sp)
+; CHECK-NEXT: lbu a1, 4(sp)
+; CHECK-NEXT: divu a0, a1, a0
+; CHECK-NEXT: sb a0, 12(sp)
+; CHECK-NEXT: lw a0, 12(sp)
+; CHECK-NEXT: addi sp, sp, 16
+; CHECK-NEXT: .cfi_def_cfa_offset 0
+; CHECK-NEXT: ret
+ %c = udiv <4 x i8> %a, %b
+ ret <4 x i8> %c
+}
+
+define i16 @extract_v2i16(<2 x i16> %a) {
+; CHECK-LABEL: extract_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: addi sp, sp, -16
+; CHECK-NEXT: .cfi_def_cfa_offset 16
+; CHECK-NEXT: sw a0, 12(sp)
+; CHECK-NEXT: lh a0, 14(sp)
+; CHECK-NEXT: addi sp, sp, 16
+; CHECK-NEXT: .cfi_def_cfa_offset 0
+; CHECK-NEXT: ret
+ %c = extractelement <2 x i16> %a, i32 1
+ ret i16 %c
+}
diff --git a/llvm/test/CodeGen/RISCV/xcvsimd-vector.ll b/llvm/test/CodeGen/RISCV/xcvsimd-vector.ll
new file mode 100644
index 0000000000000..0e9a7c3f3b4ae
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xcvsimd-vector.ll
@@ -0,0 +1,252 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=riscv32 -mattr=+xcvsimd -verify-machineinstrs < %s \
+; RUN: | FileCheck %s
+
+; XCVsimd element-wise ops are expressed as plain packed-vector IR
+; (2 x i16 / 4 x i8) and select the cv.*.h / cv.*.b SIMD instructions,
+; without dedicated intrinsics.
+
+define void @add_v2i16(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: add_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.add.h a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <2 x i16>, ptr %p
+ %b = load <2 x i16>, ptr %q
+ %c = add <2 x i16> %a, %b
+ store <2 x i16> %c, ptr %r
+ ret void
+}
+
+define void @add_v4i8(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: add_v4i8:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.add.b a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <4 x i8>, ptr %p
+ %b = load <4 x i8>, ptr %q
+ %c = add <4 x i8> %a, %b
+ store <4 x i8> %c, ptr %r
+ ret void
+}
+
+define void @sub_v2i16(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: sub_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.sub.h a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <2 x i16>, ptr %p
+ %b = load <2 x i16>, ptr %q
+ %c = sub <2 x i16> %a, %b
+ store <2 x i16> %c, ptr %r
+ ret void
+}
+
+define void @sub_v4i8(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: sub_v4i8:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.sub.b a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <4 x i8>, ptr %p
+ %b = load <4 x i8>, ptr %q
+ %c = sub <4 x i8> %a, %b
+ store <4 x i8> %c, ptr %r
+ ret void
+}
+
+define void @and_v2i16(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: and_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: and a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <2 x i16>, ptr %p
+ %b = load <2 x i16>, ptr %q
+ %c = and <2 x i16> %a, %b
+ store <2 x i16> %c, ptr %r
+ ret void
+}
+
+define void @or_v4i8(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: or_v4i8:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: or a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <4 x i8>, ptr %p
+ %b = load <4 x i8>, ptr %q
+ %c = or <4 x i8> %a, %b
+ store <4 x i8> %c, ptr %r
+ ret void
+}
+
+define void @xor_v2i16(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: xor_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: xor a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <2 x i16>, ptr %p
+ %b = load <2 x i16>, ptr %q
+ %c = xor <2 x i16> %a, %b
+ store <2 x i16> %c, ptr %r
+ ret void
+}
+
+define void @smin_v2i16(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: smin_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.min.h a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <2 x i16>, ptr %p
+ %b = load <2 x i16>, ptr %q
+ %c = call <2 x i16> @llvm.smin.v2i16(<2 x i16> %a, <2 x i16> %b)
+ store <2 x i16> %c, ptr %r
+ ret void
+}
+
+define void @umin_v4i8(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: umin_v4i8:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.minu.b a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <4 x i8>, ptr %p
+ %b = load <4 x i8>, ptr %q
+ %c = call <4 x i8> @llvm.umin.v4i8(<4 x i8> %a, <4 x i8> %b)
+ store <4 x i8> %c, ptr %r
+ ret void
+}
+
+define void @smax_v4i8(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: smax_v4i8:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.max.b a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <4 x i8>, ptr %p
+ %b = load <4 x i8>, ptr %q
+ %c = call <4 x i8> @llvm.smax.v4i8(<4 x i8> %a, <4 x i8> %b)
+ store <4 x i8> %c, ptr %r
+ ret void
+}
+
+define void @umax_v2i16(ptr %p, ptr %q, ptr %r) {
+; CHECK-LABEL: umax_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a1, 0(a1)
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.maxu.h a0, a0, a1
+; CHECK-NEXT: sw a0, 0(a2)
+; CHECK-NEXT: ret
+ %a = load <2 x i16>, ptr %p
+ %b = load <2 x i16>, ptr %q
+ %c = call <2 x i16> @llvm.umax.v2i16(<2 x i16> %a, <2 x i16> %b)
+ store <2 x i16> %c, ptr %r
+ ret void
+}
+
+define void @abs_v2i16(ptr %p, ptr %r) {
+; CHECK-LABEL: abs_v2i16:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.abs.h a0, a0
+; CHECK-NEXT: sw a0, 0(a1)
+; CHECK-NEXT: ret
+ %a = load <2 x i16>, ptr %p
+ %c = call <2 x i16> @llvm.abs.v2i16(<2 x i16> %a, i1 false)
+ store <2 x i16> %c, ptr %r
+ ret void
+}
+
+define void @abs_v4i8(ptr %p, ptr %r) {
+; CHECK-LABEL: abs_v4i8:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: cv.abs.b a0, a0
+; CHECK-NEXT: sw a0, 0(a1)
+; CHECK-NEXT: ret
+ %a = load <4 x i8>, ptr %p
+ %c = call <4 x i8> @llvm.abs.v4i8(<4 x i8> %a, i1 false)
+ store <4 x i8> %c, ptr %r
+ ret void
+}
+
+; Packed vectors passed and returned by value go through a GPR (calling
+; convention), not the RVV path.
+define <2 x i16> @add_h_byval(<2 x i16> %a, <2 x i16> %b) {
+; CHECK-LABEL: add_h_byval:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.add.h a0, a0, a1
+; CHECK-NEXT: ret
+ %c = add <2 x i16> %a, %b
+ ret <2 x i16> %c
+}
+
+define <4 x i8> @sub_b_byval(<4 x i8> %a, <4 x i8> %b) {
+; CHECK-LABEL: sub_b_byval:
+; CHECK: # %bb.0:
+; CHECK-NEXT: cv.sub.b a0, a0, a1
+; CHECK-NEXT: ret
+ %c = sub <4 x i8> %a, %b
+ ret <4 x i8> %c
+}
+
+; A naturally-aligned access is a single word load/store; an under-aligned one
+; must split into element accesses rather than a (trapping) misaligned lw/sw.
+define <2 x i16> @load_align4(ptr %p) {
+; CHECK-LABEL: load_align4:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lw a0, 0(a0)
+; CHECK-NEXT: ret
+ %v = load <2 x i16>, ptr %p, align 4
+ ret <2 x i16> %v
+}
+
+define <2 x i16> @load_align2(ptr %p) {
+; CHECK-LABEL: load_align2:
+; CHECK: # %bb.0:
+; CHECK-NEXT: lhu a1, 2(a0)
+; CHECK-NEXT: lhu a0, 0(a0)
+; CHECK-NEXT: slli a1, a1, 16
+; CHECK-NEXT: or a0, a1, a0
+; CHECK-NEXT: ret
+ %v = load <2 x i16>, ptr %p, align 2
+ ret <2 x i16> %v
+}
+
+define void @store_align2(ptr %p, <2 x i16> %v) {
+; CHECK-LABEL: store_align2:
+; CHECK: # %bb.0:
+; CHECK-NEXT: srli a2, a1, 16
+; CHECK-NEXT: sh a1, 0(a0)
+; CHECK-NEXT: sh a2, 2(a0)
+; CHECK-NEXT: ret
+ store <2 x i16> %v, ptr %p, align 2
+ ret void
+}
More information about the llvm-commits
mailing list