[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:44 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-backend-risc-v

Author: vitbur (VittorioBurani)

<details>
<summary>Changes</summary>

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


---

Patch is 56.05 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/205836.diff


8 Files Affected:

- (modified) llvm/include/llvm/IR/IntrinsicsRISCVXCV.td (+127) 
- (modified) llvm/lib/Target/RISCV/RISCVCallingConv.cpp (+9) 
- (modified) llvm/lib/Target/RISCV/RISCVExpandPseudoInsts.cpp (+31) 
- (modified) llvm/lib/Target/RISCV/RISCVISelLowering.cpp (+44-1) 
- (modified) llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td (+301) 
- (added) llvm/test/CodeGen/RISCV/xcvsimd-vector-unsupported.ll (+74) 
- (added) llvm/test/CodeGen/RISCV/xcvsimd-vector.ll (+252) 
- (added) llvm/test/CodeGen/RISCV/xcvsimd.ll (+486) 


``````````diff
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/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/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/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 f3f3d408c43e7..ef3d13443e338 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
@@ -857,3 +857,304 @@ 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)>;
+}
+
+// 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"),
+                  !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<Intrins...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/205836


More information about the llvm-commits mailing list