[llvm] [RISCV][XCV] Auto-select XCVmac/XCValu/XCVbitmanip from generic IR (PR #204882)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 19 11:38:10 PDT 2026


https://github.com/VittorioBurani created https://github.com/llvm/llvm-project/pull/204882

Teach instruction selection to emit CORE-V instructions from ordinary IR, without requiring builtins:

- **XCVmac/XCValu** generic SelectionDAG patterns for `cv.mac`/`cv.msu` and `cv.addn`/`subn`/`addun`/`subun` (plus the `*NR` register-shift variants), guarded with new `add_oneuse`/`sub_oneuse` PatFrags (mirroring the existing `mul_oneuse`) so a fused, tied-destination instruction is only formed when the inner operation has a single use.
- **XCVbitmanip** custom ISel matchers in `RISCVISelDAGToDAG.cpp` recognizing the idiomatic C bit-field operations and lowering them to CORE-V: `cv.extractu` from `(x >> off) & mask`, `cv.extract` from signed `((int)(x << C1)) >> C2`, `cv.bclr` from `x & ~(mask << off)`, `cv.bset` from `x | (mask << off)`, `cv.insert` from `(reg & ~(mask << off)) | ((val & mask) << off)`. Each matcher bails out on multi-use sub-DAGs, and overlaps with existing `cv.extbz/exthz/extbs/exths` patterns are explicitly excluded. RV32-only.

**Tests:** `xcv{alu,mac,bitmanip}-generic-patterns.ll` (CHECK-only, generated with `update_llc_test_checks.py`; the multi-use cases that must not fuse are expressed as full instruction-sequence checks).

Draft pending RFC discussion: the approach of emitting vendor `cv.*` from plain IR (vs. only via builtins) is the main open design question.

Part of a CORE-V (XCV) series; see RFC: https://discourse.llvm.org/t/rfc-core-v-xcv-support-for-cv32e40p-clang-builtins-xcvsimd-intrinsics-and-generic-auto-selection/91111


>From 8bd754915de3d06e05e390c46023b08efd89ffe6 Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Thu, 2 Apr 2026 14:32:47 +0200
Subject: [PATCH 01/11] [RISCV][LLVM] Add `cv.mac` and `cv.msu` Generic
 SelectionDAG patterns

---
 llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
index ec93f60366e1e..1aa5681fc7d18 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
@@ -850,3 +850,17 @@ let Predicates = [HasVendorXCVmac] in {
   def : PatCoreVMacGprGprGprUimm5<"macsRN", "MACSRN">;
   def : PatCoreVMacGprGprGprUimm5<"machhsRN", "MACHHSRN">;
 }
+
+//===----------------------------------------------------------------------===//
+// XCVmac Generic SelectionDAG patterns
+//===----------------------------------------------------------------------===//
+
+let Predicates = [HasVendorXCVmac, IsRV32], AddedComplexity = 2 in {
+  // acc + (a * b) → cv.mac
+  def : Pat<(i32 (add GPR:$rd, (i32 (mul GPR:$rs1, GPR:$rs2)))),
+            (CV_MAC GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+
+  // acc - (a * b) → cv.msu
+  def : Pat<(i32 (sub GPR:$rd, (i32 (mul GPR:$rs1, GPR:$rs2)))),
+            (CV_MSU GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+}

>From 32827041497ccc28993447d0e9a9bba34e5fc8ea Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Thu, 2 Apr 2026 14:42:09 +0200
Subject: [PATCH 02/11] [RISCV][LLVM] Add some `xcvalu` Generic SelectionDAG
 patterns

- add Generic SelectionDAG patterns for `cv.addn`, `cv.addun`,
  `cv.subn`, `cv.subun`, `cv.addnr`, `cv.addunr`, `cv.subnr` and
  `cv.subunr`
---
 llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td | 43 ++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
index 1aa5681fc7d18..dd5179e198e88 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
@@ -29,6 +29,7 @@ def CVrr : Operand<i32>,
 
 def cv_tuimm2 : TImmLeaf<XLenVT, [{return isUInt<2>(Imm);}]>;
 def cv_tuimm5 : TImmLeaf<XLenVT, [{return isUInt<5>(Imm);}]>;
+def cv_uimm5  : ImmLeaf<XLenVT, [{return isUInt<5>(Imm);}]>;
 def cv_uimm10 : ImmLeaf<XLenVT, [{return isUInt<10>(Imm);}]>;
 
 def CV_LO5: SDNodeXForm<imm, [{
@@ -864,3 +865,45 @@ let Predicates = [HasVendorXCVmac, IsRV32], AddedComplexity = 2 in {
   def : Pat<(i32 (sub GPR:$rd, (i32 (mul GPR:$rs1, GPR:$rs2)))),
             (CV_MSU GPR:$rd, GPR:$rs1, GPR:$rs2)>;
 }
+
+//===----------------------------------------------------------------------===//
+// XCValu Generic SelectionDAG patterns
+//===----------------------------------------------------------------------===//
+
+// ---- Immediate shift variants (RRI format) ----
+let Predicates = [HasVendorXCValu, IsRV32], AddedComplexity = 3 in {
+  // cv.addn rd, rs1, rs2, imm5  →  rd = (rs1 + rs2) >>> imm5 (arithmetic)
+  def : Pat<(i32 (sra (i32 (add GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
+            (CV_ADDN GPR:$rs1, GPR:$rs2, cv_uimm5:$imm)>;
+
+  // cv.addun rd, rs1, rs2, imm5  →  rd = (rs1 + rs2) >> imm5 (logical)
+  def : Pat<(i32 (srl (i32 (add GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
+            (CV_ADDUN GPR:$rs1, GPR:$rs2, cv_uimm5:$imm)>;
+
+  // cv.subn rd, rs1, rs2, imm5  →  rd = (rs1 - rs2) >>> imm5 (arithmetic)
+  def : Pat<(i32 (sra (i32 (sub GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
+            (CV_SUBN GPR:$rs1, GPR:$rs2, cv_uimm5:$imm)>;
+
+  // cv.subun rd, rs1, rs2, imm5  →  rd = (rs1 - rs2) >> imm5 (logical)
+  def : Pat<(i32 (srl (i32 (sub GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
+            (CV_SUBUN GPR:$rs1, GPR:$rs2, cv_uimm5:$imm)>;
+} // Predicates = [HasVendorXCValu, IsRV32]
+
+// ---- Register shift variants (NR format, tied rd) ----
+let Predicates = [HasVendorXCValu, IsRV32], AddedComplexity = 2 in {
+  // cv.addnr rd, rs1, rs2  →  rd = (rd + rs1) >> rs2 (arithmetic)
+  def : Pat<(i32 (sra (i32 (add GPR:$rd, GPR:$rs1)), GPR:$rs2)),
+            (CV_ADDNR GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+
+  // cv.addunr rd, rs1, rs2  →  rd = (rd + rs1) >> rs2 (logical)
+  def : Pat<(i32 (srl (i32 (add GPR:$rd, GPR:$rs1)), GPR:$rs2)),
+            (CV_ADDUNR GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+
+  // cv.subnr rd, rs1, rs2  →  rd = (rd - rs1) >> rs2 (arithmetic)
+  def : Pat<(i32 (sra (i32 (sub GPR:$rd, GPR:$rs1)), GPR:$rs2)),
+            (CV_SUBNR GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+
+  // cv.subunr rd, rs1, rs2  →  rd = (rd - rs1) >> rs2 (logical)
+  def : Pat<(i32 (srl (i32 (sub GPR:$rd, GPR:$rs1)), GPR:$rs2)),
+            (CV_SUBUNR GPR:$rd, GPR:$rs1, GPR:$rs2)>;
+} // Predicates = [HasVendorXCValu, IsRV32]

>From 3ab29d2223aa35033ce3085db584424a0226ba4f Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Fri, 3 Apr 2026 15:10:37 +0200
Subject: [PATCH 03/11] [RISCV][LLVM] Auto-select `cv.extractu` from C unsigned
 bitfield reads

Context:
- add custom ISel logic to match the common C bitfield extract
  idiom `(x >> offset) & mask` (where mask is (1 << width) - 1) and
  lower it directly to the `CV_EXTRACTU` instruction from the
  `XCVbitmanip` extension
- this allows the compiler to automatically emit cv.extractu from
  plain C code like `(reg >> 4) & 0x7` without requiring explicit
  builtin calls, which is the most common bitfield access pattern
  in embedded firmware
- cases already handled by `cv.extbz` (mask=0xFF) and `cv.exthz`
  (mask=0xFFFF) at offset 0 are explicitly excluded to preserve
  existing behavior

Implementation:
- implement `RISCVDAGToDAGISel::tryCVBitManipExtractU()`
- add `cv.extractu` tests in `xcvbitmanip-generic-patterns.ll`
- call `tryCVBitManipExtractU()` as the first check in the
  `case ISD::AND:` block of `RISCVDAGToDAGISel::Select`
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   |  69 +++++++++++
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h     |   2 +
 .../RISCV/xcvbitmanip-generic-patterns.ll     | 107 ++++++++++++++++++
 3 files changed, 178 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 5611410469384..8870436a4b257 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1097,6 +1097,71 @@ static bool isApplicableToPLIOrPLUI(int Val) {
          Bit15To8 == Bit7To0;
 }
 
+// --------------------------------------------------------------------------
+// cv.extractu: unsigned bitfield extract
+//
+// Matches:
+//   (and (srl X, C_offset), C_mask)  where C_mask = (1 << width) - 1
+//   (and X, C_mask)                  where C_mask = (1 << width) - 1 (offset=0)
+//
+// Emits: CV_EXTRACTU rd, rs1, IS3=width-1, IS2=offset
+//        (note: cv.extractu semantics is Is3+1 bits, so we encode width-1)
+//
+// Excludes: mask=0xFF (already cv.extbz) and mask=0xFFFF (already cv.exthz)
+//           when offset=0, to avoid stealing existing patterns.
+// --------------------------------------------------------------------------
+bool RISCVDAGToDAGISel::tryCVBitManipExtractU(SDNode *Node) {
+  if (!Subtarget->hasVendorXCVbitmanip() || Subtarget->is64Bit())
+    return false;
+
+  assert(Node->getOpcode() == ISD::AND);
+  SDLoc DL(Node);
+
+  SDValue LHS = Node->getOperand(0);
+  SDValue RHS = Node->getOperand(1);
+
+  // Canonicalize: constant on the right
+  auto *MaskC = dyn_cast<ConstantSDNode>(RHS);
+  if (!MaskC) {
+    std::swap(LHS, RHS);
+    MaskC = dyn_cast<ConstantSDNode>(RHS);
+  }
+  if (!MaskC)
+    return false;
+
+  uint32_t Mask = MaskC->getZExtValue();
+
+  // Mask must be a contiguous run of 1s starting from bit 0: (1 << w) - 1
+  if (!isMask_32(Mask))
+    return false;
+
+  unsigned Width = llvm::countr_one(Mask);
+  unsigned Offset = 0;
+  SDValue Base = LHS;
+
+  // Check if LHS is (srl X, C_offset)
+  if (LHS.getOpcode() == ISD::SRL) {
+    if (auto *ShiftC = dyn_cast<ConstantSDNode>(LHS.getOperand(1))) {
+      Offset = ShiftC->getZExtValue();
+      Base = LHS.getOperand(0);
+    }
+  }
+
+  // Skip cases already handled by cv.extbz / cv.exthz
+  if (Offset == 0 && (Mask == 0xFF || Mask == 0xFFFF))
+    return false;
+
+  // Validate: IS3 in [1,32], IS2 in [0,31], width + offset <= 32
+  if (Width == 0 || Width > 32 || Offset > 31 || (Width + Offset) > 32)
+    return false;
+
+  SDValue IS3 = CurDAG->getTargetConstant(Width - 1, DL, MVT::i32);
+  SDValue IS2 = CurDAG->getTargetConstant(Offset, DL, MVT::i32);
+
+  CurDAG->SelectNodeTo(Node, RISCV::CV_EXTRACTU, MVT::i32, Base, IS3, IS2);
+  return true;
+}
+
 void RISCVDAGToDAGISel::Select(SDNode *Node) {
   // If we have a custom node, we have already selected.
   if (Node->isMachineOpcode()) {
@@ -1504,6 +1569,10 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
 
     break;
   case ISD::AND: {
+    // xcvbitmanip: try extractu first
+    if (tryCVBitManipExtractU(Node))
+      return;
+
     auto *N1C = dyn_cast<ConstantSDNode>(Node->getOperand(1));
     if (!N1C)
       break;
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
index 85bbf31425030..c36aa8df6a1d5 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
@@ -173,6 +173,8 @@ class RISCVDAGToDAGISel : public SelectionDAGISel {
 
   void selectSF_VC_X_SE(SDNode *Node);
 
+  bool tryCVBitManipExtractU(SDNode *Node);
+
   // Return the RISC-V condition code that matches the given DAG integer
   // condition code. The CondCode must be one of those supported by the RISC-V
   // ISA (see translateSetCCForBranch).
diff --git a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
new file mode 100644
index 0000000000000..7ac38ae9b24a3
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
@@ -0,0 +1,107 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -mattr=+m,+xcvbitmanip -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s
+
+; This file tests that generic C bitfield idioms are automatically lowered
+; to XCVbitmanip instructions without requiring explicit builtin calls.
+
+;===----------------------------------------------------------------------===;
+; cv.extractu — unsigned bitfield extract: (x >> offset) & mask
+;===----------------------------------------------------------------------===;
+
+; Extract 3 bits starting at bit 4: (x >> 4) & 0x7
+define i32 @test_extractu_3_4(i32 %x) {
+; CHECK-LABEL: test_extractu_3_4:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extractu a0, a0, 2, 4
+; CHECK-NEXT:    ret
+  %shr = lshr i32 %x, 4
+  %res = and i32 %shr, 7
+  ret i32 %res
+}
+
+; Extract 8 bits starting at bit 8: (x >> 8) & 0xFF
+define i32 @test_extractu_8_8(i32 %x) {
+; CHECK-LABEL: test_extractu_8_8:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extractu a0, a0, 7, 8
+; CHECK-NEXT:    ret
+  %shr = lshr i32 %x, 8
+  %res = and i32 %shr, 255
+  ret i32 %res
+}
+
+; Extract 16 bits starting at bit 16: compiler folds (srl >> 16) & 0xFFFF
+; into just srli because the upper 16 bits are already zero after the shift,
+; making the AND mask redundant. The AND node is removed before ISel runs.
+define i32 @test_extractu_16_16(i32 %x) {
+; CHECK-LABEL: test_extractu_16_16:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    srli a0, a0, 16
+; CHECK-NEXT:    ret
+  %shr = lshr i32 %x, 16
+  %res = and i32 %shr, 65535
+  ret i32 %res
+}
+
+; Extract 1 bit at bit 31: compiler folds (srl >> 31) & 1 into just srli
+; because only bit 0 can be set after a 31-bit logical shift, making the
+; AND mask redundant.
+define i32 @test_extractu_1_31(i32 %x) {
+; CHECK-LABEL: test_extractu_1_31:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    srli a0, a0, 31
+; CHECK-NEXT:    ret
+  %shr = lshr i32 %x, 31
+  %res = and i32 %shr, 1
+  ret i32 %res
+}
+
+; Extract 5 bits at offset 0 (no shift): x & 0x1F
+define i32 @test_extractu_5_0(i32 %x) {
+; CHECK-LABEL: test_extractu_5_0:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extractu a0, a0, 4, 0
+; CHECK-NEXT:    ret
+  %res = and i32 %x, 31
+  ret i32 %res
+}
+
+; Excluded: x & 0xFF should NOT become cv.extractu (handled by base ISA zext.b
+; or cv.extbz when xcvalu is enabled)
+define i32 @test_no_extractu_0xff(i32 %x) {
+; CHECK-LABEL: test_no_extractu_0xff:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    zext.b a0, a0
+; CHECK-NEXT:    ret
+  %res = and i32 %x, 255
+  ret i32 %res
+}
+
+; Excluded: x & 0xFFFF should NOT become cv.extractu (handled by base ISA
+; shift pair or cv.exthz when xcvalu is enabled)
+define i32 @test_no_extractu_0xffff(i32 %x) {
+; CHECK-LABEL: test_no_extractu_0xffff:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    slli a0, a0, 16
+; CHECK-NEXT:    srli a0, a0, 16
+; CHECK-NEXT:    ret
+  %res = and i32 %x, 65535
+  ret i32 %res
+}
+
+;===----------------------------------------------------------------------===;
+; Realistic embedded bitfield examples
+;===----------------------------------------------------------------------===;
+
+; Read STATUS register bits [3:5] (3-bit field at offset 3)
+; Equivalent to: (reg >> 3) & 0x7
+define i32 @test_read_status_field(i32 %reg) {
+; CHECK-LABEL: test_read_status_field:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extractu a0, a0, 2, 3
+; CHECK-NEXT:    ret
+  %shr = lshr i32 %reg, 3
+  %field = and i32 %shr, 7
+  ret i32 %field
+}

>From e772da7900eb1b74cc8373e0ea46ad139aafdd62 Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Fri, 3 Apr 2026 15:24:54 +0200
Subject: [PATCH 04/11] [RISCV][LLVM] Auto-select `cv.extract` from C signed
 bitfield reads

Context:
- add custom ISel logic to match the signed bitfield extract idiom
  `((int)(x << C1)) >> C2` (where width = 32 - C2, offset = C2 - C1)
  and lower it directly to the `CV_EXTRACT` instruction from the
  `XCVbitmanip` extension
- this allows the compiler to automatically emit cv.extract from
  plain C code like `((int32_t)(reg << 20)) >> 22` to sign-extend a
  10-bit field, without requiring explicit builtin calls. This
  pattern is common when reading signed ADC values or two's
  complement fields from hardware registers
- cases already handled by `cv.extbs` (width=8, offset=0) and
  `cv.exths` (width=16, offset=0) via `sext_inreg` patterns are
  explicitly excluded to preserve existing behavior

Implementation:
- implement `RISCVDAGToDAGISel::tryCVBitManipExtract()`
- add `cv.extract` tests in `xcvbitmanip-generic-patterns.ll`
- call `tryCVBitManipExtract()` as the first check in the
  `case ISD::SRA:` block of `RISCVDAGToDAGISel::Select`
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   | 61 ++++++++++++
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h     |  1 +
 .../RISCV/xcvbitmanip-generic-patterns.ll     | 92 +++++++++++++++++++
 3 files changed, 154 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 8870436a4b257..9c9b993e54a40 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1162,6 +1162,64 @@ bool RISCVDAGToDAGISel::tryCVBitManipExtractU(SDNode *Node) {
   return true;
 }
 
+// --------------------------------------------------------------------------
+// cv.extract: signed bitfield extract
+//
+// Matches:
+//   (sra (shl X, C1), C2)
+//   where: width = 32 - C2, offset = C2 - C1
+//          C1 = 32 - width - offset, C2 = 32 - width
+//
+// Emits: CV_EXTRACT rd, rs1, IS3=width-1, IS2=offset
+//        (note: cv.extract semantics is Is3+1 bits, so we encode width-1)
+//
+// Excludes: (sra (shl X, 16), 16) → already sext_inreg i16 → cv.exths
+//           (sra (shl X, 24), 24) → already sext_inreg i8  → cv.extbs
+// --------------------------------------------------------------------------
+bool RISCVDAGToDAGISel::tryCVBitManipExtract(SDNode *Node) {
+  if (!Subtarget->hasVendorXCVbitmanip() || Subtarget->is64Bit())
+    return false;
+
+  assert(Node->getOpcode() == ISD::SRA);
+  SDLoc DL(Node);
+
+  // Check: (sra (shl X, C1), C2)
+  SDValue ShlNode = Node->getOperand(0);
+  if (ShlNode.getOpcode() != ISD::SHL)
+    return false;
+
+  auto *C2Node = dyn_cast<ConstantSDNode>(Node->getOperand(1));
+  auto *C1Node = dyn_cast<ConstantSDNode>(ShlNode.getOperand(1));
+  if (!C2Node || !C1Node)
+    return false;
+
+  unsigned C1 = C1Node->getZExtValue();
+  unsigned C2 = C2Node->getZExtValue();
+
+  // C2 >= C1 required (otherwise offset would be negative)
+  if (C2 < C1 || C2 >= 32 || C1 >= 32)
+    return false;
+
+  unsigned Width = 32 - C2;
+  unsigned Offset = C2 - C1;
+
+  // Skip cases already handled by cv.extbs (width=8, offset=0) and
+  // cv.exths (width=16, offset=0) via sext_inreg patterns
+  if (Offset == 0 && (Width == 8 || Width == 16))
+    return false;
+
+  // Validate ranges
+  if (Width == 0 || Width > 32 || Offset > 31 || (Width + Offset) > 32)
+    return false;
+
+  SDValue Base = ShlNode.getOperand(0);
+  SDValue IS3 = CurDAG->getTargetConstant(Width - 1, DL, MVT::i32);
+  SDValue IS2 = CurDAG->getTargetConstant(Offset, DL, MVT::i32);
+
+  CurDAG->SelectNodeTo(Node, RISCV::CV_EXTRACT, MVT::i32, Base, IS3, IS2);
+  return true;
+}
+
 void RISCVDAGToDAGISel::Select(SDNode *Node) {
   // If we have a custom node, we have already selected.
   if (Node->isMachineOpcode()) {
@@ -1489,6 +1547,9 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
     return;
   }
   case ISD::SRA: {
+    if (tryCVBitManipExtract(Node))
+      return;
+
     if (trySignedBitfieldExtract(Node))
       return;
 
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
index c36aa8df6a1d5..6eb61139d74d7 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
@@ -174,6 +174,7 @@ class RISCVDAGToDAGISel : public SelectionDAGISel {
   void selectSF_VC_X_SE(SDNode *Node);
 
   bool tryCVBitManipExtractU(SDNode *Node);
+  bool tryCVBitManipExtract(SDNode *Node);
 
   // Return the RISC-V condition code that matches the given DAG integer
   // condition code. The CondCode must be one of those supported by the RISC-V
diff --git a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
index 7ac38ae9b24a3..571532127a9b4 100644
--- a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
+++ b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
@@ -90,6 +90,86 @@ define i32 @test_no_extractu_0xffff(i32 %x) {
   ret i32 %res
 }
 
+;===----------------------------------------------------------------------===;
+; cv.extract — signed bitfield extract: (sra (shl x, C1), C2)
+;===----------------------------------------------------------------------===;
+
+; Signed extract 4 bits at offset 4: ((int)(x << 24)) >> 28
+; C1=24, C2=28 → width=4, offset=4
+define i32 @test_extract_4_4(i32 %x) {
+; CHECK-LABEL: test_extract_4_4:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extract a0, a0, 3, 4
+; CHECK-NEXT:    ret
+  %shl = shl i32 %x, 24
+  %res = ashr i32 %shl, 28
+  ret i32 %res
+}
+
+; Signed extract 8 bits at offset 8: ((int)(x << 16)) >> 24
+; C1=16, C2=24 → width=8, offset=8
+define i32 @test_extract_8_8(i32 %x) {
+; CHECK-LABEL: test_extract_8_8:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extract a0, a0, 7, 8
+; CHECK-NEXT:    ret
+  %shl = shl i32 %x, 16
+  %res = ashr i32 %shl, 24
+  ret i32 %res
+}
+
+; Signed extract 12 bits at offset 4: ((int)(x << 16)) >> 20
+; C1=16, C2=20 → width=12, offset=4
+define i32 @test_extract_12_4(i32 %x) {
+; CHECK-LABEL: test_extract_12_4:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extract a0, a0, 11, 4
+; CHECK-NEXT:    ret
+  %shl = shl i32 %x, 16
+  %res = ashr i32 %shl, 20
+  ret i32 %res
+}
+
+; Signed extract 1 bit at offset 15 (sign bit of upper halfword):
+; C1=16, C2=31 → width=1, offset=15
+define i32 @test_extract_1_15(i32 %x) {
+; CHECK-LABEL: test_extract_1_15:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extract a0, a0, 0, 15
+; CHECK-NEXT:    ret
+  %shl = shl i32 %x, 16
+  %res = ashr i32 %shl, 31
+  ret i32 %res
+}
+
+; Excluded: signed extract of low 8 bits (width=8, offset=0) should NOT become
+; cv.extract. Handled by sext_inreg i8 → cv.extbs when xcvalu is enabled, or
+; slli+srai pair otherwise.
+define i32 @test_no_extract_extbs(i32 %x) {
+; CHECK-LABEL: test_no_extract_extbs:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    slli a0, a0, 24
+; CHECK-NEXT:    srai a0, a0, 24
+; CHECK-NEXT:    ret
+  %shl = shl i32 %x, 24
+  %res = ashr i32 %shl, 24
+  ret i32 %res
+}
+
+; Excluded: signed extract of low 16 bits (width=16, offset=0) should NOT
+; become cv.extract. Handled by sext_inreg i16 → cv.exths when xcvalu is
+; enabled, or slli+srai pair otherwise.
+define i32 @test_no_extract_exths(i32 %x) {
+; CHECK-LABEL: test_no_extract_exths:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    slli a0, a0, 16
+; CHECK-NEXT:    srai a0, a0, 16
+; CHECK-NEXT:    ret
+  %shl = shl i32 %x, 16
+  %res = ashr i32 %shl, 16
+  ret i32 %res
+}
+
 ;===----------------------------------------------------------------------===;
 ; Realistic embedded bitfield examples
 ;===----------------------------------------------------------------------===;
@@ -105,3 +185,15 @@ define i32 @test_read_status_field(i32 %reg) {
   %field = and i32 %shr, 7
   ret i32 %field
 }
+
+; Signed extract of a 10-bit ADC value at bits [2:11]
+; Equivalent to: ((int)(x << 20)) >> 22
+define i32 @test_signed_adc_extract(i32 %reg) {
+; CHECK-LABEL: test_signed_adc_extract:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extract a0, a0, 9, 2
+; CHECK-NEXT:    ret
+  %shl = shl i32 %reg, 20
+  %res = ashr i32 %shl, 22
+  ret i32 %res
+}

>From 7c7dabc9d71d49426b56fcf823d1476fa3f8f6fb Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Fri, 3 Apr 2026 15:49:26 +0200
Subject: [PATCH 05/11] [RISCV][LLVM] Auto-select `cv.bclr` from C bitfield
 clear operations

Context:
- add custom ISel logic to match the bitfield clear idiom
  `x & ~(mask << offset)` (where mask is a contiguous run of 1s)
  and lower it directly to the `CV_BCLR` instruction from the
  `XCVbitmanip` extension
- this allows the compiler to automatically emit cv.bclr from plain
  C code like `reg & ~(1 << 5)` to clear interrupt flags or
  `reg & 0xFFFF00FF` to zero out a byte lane, without requiring
  explicit builtin calls. This is one of the most common register
  manipulation patterns in embedded firmware
- cases where the `AND` mask is itself a low mask (e.g., 0xFF,
  0xFFFF) are excluded to avoid conflicts with `cv.extbz`,
  `cv.exthz`, and `cv.extractu` patterns. Cases where the left
  operand is a shift are also excluded to let `cv.extractu` handle
  combined shift-and-mask sequences

Implementation:
- implement `RISCVDAGToDAGISel::tryCVBitManipBClr()`
- add `cv.bclr` tests in `xcvbitmanip-generic-patterns.ll`
- call `tryCVBitManipBClr()` as the second check in the
  `case ISD::AND:` block of `RISCVDAGToDAGISel::Select`, right after
  the `cv.extractu` handling
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   | 66 ++++++++++++++++++
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h     |  1 +
 .../RISCV/xcvbitmanip-generic-patterns.ll     | 67 +++++++++++++++++++
 3 files changed, 134 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 9c9b993e54a40..579cc40fa6735 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1220,6 +1220,69 @@ bool RISCVDAGToDAGISel::tryCVBitManipExtract(SDNode *Node) {
   return true;
 }
 
+// --------------------------------------------------------------------------
+// cv.bclr: clear a contiguous range of bits
+//
+// Matches:
+//   (and X, C) where ~C is a shifted mask: ~C = ((1 << width) - 1) << offset
+//
+// Emits: CV_BCLR rd, rs1, IS3=width-1, IS2=offset
+//        (note: cv.bclr semantics is Is3+1 bits, so we encode width-1)
+//
+// Excludes: C = 0xFF (cv.extbz), C = 0xFFFF (cv.exthz), C = 0 (no-op)
+// --------------------------------------------------------------------------
+bool RISCVDAGToDAGISel::tryCVBitManipBClr(SDNode *Node) {
+  if (!Subtarget->hasVendorXCVbitmanip() || Subtarget->is64Bit())
+    return false;
+
+  assert(Node->getOpcode() == ISD::AND);
+  SDLoc DL(Node);
+
+  SDValue LHS = Node->getOperand(0);
+  SDValue RHS = Node->getOperand(1);
+
+  // Canonicalize: constant on the right
+  auto *MaskC = dyn_cast<ConstantSDNode>(RHS);
+  if (!MaskC) {
+    std::swap(LHS, RHS);
+    MaskC = dyn_cast<ConstantSDNode>(RHS);
+  }
+  if (!MaskC)
+    return false;
+
+  uint32_t Mask = MaskC->getZExtValue();
+  uint32_t InvMask = ~Mask;
+
+  // If mask is all-ones (invmask = 0), nothing to clear
+  if (InvMask == 0)
+    return false;
+
+  // Skip cases where the mask itself is a low mask (extractu/extbz/exthz handle those)
+  if (isMask_32(Mask))
+    return false;
+
+  // ~C must be a shifted mask: contiguous 1s at some offset
+  if (!isShiftedMask_32(InvMask))
+    return false;
+
+  unsigned Width = llvm::popcount(InvMask);
+  unsigned Offset = llvm::countr_zero(InvMask);
+
+  // Skip if LHS is itself a shift — let extractu handle (srl X, C) & mask patterns
+  if (LHS.getOpcode() == ISD::SRL)
+    return false;
+
+  // Validate ranges
+  if (Width == 0 || Width > 32 || Offset > 31 || (Width + Offset) > 32)
+    return false;
+
+  SDValue IS3 = CurDAG->getTargetConstant(Width - 1, DL, MVT::i32);
+  SDValue IS2 = CurDAG->getTargetConstant(Offset, DL, MVT::i32);
+
+  CurDAG->SelectNodeTo(Node, RISCV::CV_BCLR, MVT::i32, LHS, IS3, IS2);
+  return true;
+}
+
 void RISCVDAGToDAGISel::Select(SDNode *Node) {
   // If we have a custom node, we have already selected.
   if (Node->isMachineOpcode()) {
@@ -1633,6 +1696,9 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
     // xcvbitmanip: try extractu first
     if (tryCVBitManipExtractU(Node))
       return;
+    // xcvbitmanip: then try bclr
+    if (tryCVBitManipBClr(Node))
+      return;
 
     auto *N1C = dyn_cast<ConstantSDNode>(Node->getOperand(1));
     if (!N1C)
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
index 6eb61139d74d7..bf723e1b798e1 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
@@ -175,6 +175,7 @@ class RISCVDAGToDAGISel : public SelectionDAGISel {
 
   bool tryCVBitManipExtractU(SDNode *Node);
   bool tryCVBitManipExtract(SDNode *Node);
+  bool tryCVBitManipBClr(SDNode *Node);
 
   // Return the RISC-V condition code that matches the given DAG integer
   // condition code. The CondCode must be one of those supported by the RISC-V
diff --git a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
index 571532127a9b4..eadbd96f1b72e 100644
--- a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
+++ b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
@@ -170,6 +170,62 @@ define i32 @test_no_extract_exths(i32 %x) {
   ret i32 %res
 }
 
+;===----------------------------------------------------------------------===;
+; cv.bclr — clear a contiguous range of bits: x & ~(mask << offset)
+;===----------------------------------------------------------------------===;
+
+; Clear bits [4:6] (3 bits at offset 4): x & 0xFFFFFF8F
+define i32 @test_bclr_3_4(i32 %x) {
+; CHECK-LABEL: test_bclr_3_4:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bclr a0, a0, 2, 4
+; CHECK-NEXT:    ret
+  %res = and i32 %x, -113   ; 0xFFFFFF8F = ~(0x7 << 4)
+  ret i32 %res
+}
+
+; Clear bit 15 (1 bit at offset 15): x & 0xFFFF7FFF
+define i32 @test_bclr_1_15(i32 %x) {
+; CHECK-LABEL: test_bclr_1_15:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bclr a0, a0, 0, 15
+; CHECK-NEXT:    ret
+  %res = and i32 %x, -32769   ; 0xFFFF7FFF = ~(1 << 15)
+  ret i32 %res
+}
+
+; Clear bits [8:15] (8 bits at offset 8): x & 0xFFFF00FF
+define i32 @test_bclr_8_8(i32 %x) {
+; CHECK-LABEL: test_bclr_8_8:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bclr a0, a0, 7, 8
+; CHECK-NEXT:    ret
+  %res = and i32 %x, -65281   ; 0xFFFF00FF = ~(0xFF << 8)
+  ret i32 %res
+}
+
+; Clear bits [0:3] (4 bits at offset 0): x & 0xFFFFFFF0
+define i32 @test_bclr_4_0(i32 %x) {
+; CHECK-LABEL: test_bclr_4_0:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bclr a0, a0, 3, 0
+; CHECK-NEXT:    ret
+  %res = and i32 %x, -16   ; 0xFFFFFFF0 = ~(0xF << 0)
+  ret i32 %res
+}
+
+; Excluded: x & 0x0000FFFF is a low mask, should NOT become cv.bclr.
+; Handled by base ISA shift pair or cv.exthz when xcvalu is enabled.
+define i32 @test_no_bclr_exthz(i32 %x) {
+; CHECK-LABEL: test_no_bclr_exthz:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    slli a0, a0, 16
+; CHECK-NEXT:    srli a0, a0, 16
+; CHECK-NEXT:    ret
+  %res = and i32 %x, 65535
+  ret i32 %res
+}
+
 ;===----------------------------------------------------------------------===;
 ; Realistic embedded bitfield examples
 ;===----------------------------------------------------------------------===;
@@ -197,3 +253,14 @@ define i32 @test_signed_adc_extract(i32 %reg) {
   %res = ashr i32 %shl, 22
   ret i32 %res
 }
+
+; Clear an interrupt flag at bit 5
+; Equivalent to: reg & ~(1 << 5)
+define i32 @test_clear_irq_flag(i32 %reg) {
+; CHECK-LABEL: test_clear_irq_flag:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bclr a0, a0, 0, 5
+; CHECK-NEXT:    ret
+  %res = and i32 %reg, -33   ; ~(1 << 5) = 0xFFFFFFDF
+  ret i32 %res
+}

>From 5f0423e144b499e443e4f1ebef0a55a974b80593 Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Fri, 3 Apr 2026 15:54:28 +0200
Subject: [PATCH 06/11] [RISCV][LLVM] Auto-select `cv.bset` from C bitfield set
 operations

Context:
- add custom ISel logic to match the bitfield set idiom
  `x | (mask << offset)` (where mask is a contiguous run of 1s) and
  lower it directly to the `CV_BSET` instruction from the
  `XCVbitmanip` extension
- this allows the compiler to automatically emit cv.bset from plain
  C code like `reg | (0xF << 8)` to set enable bits in a control
  register, without requiring explicit builtin calls
- only constants that are valid shifted masks (contiguous run of
  1s) are matched. Non-contiguous constants like 0x55 correctly
  fall through to the default `ORI/LUI+OR` lowering

Implementation:
- implement `RISCVDAGToDAGISel::tryCVBitManipBSet()`
- add `cv.bset` tests in `xcvbitmanip-generic-patterns.ll`
- call `tryCVBitManipBSet()` as the first check in the
  `case ISD::OR:` block of `RISCVDAGToDAGISel::Select`
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   | 51 +++++++++++++
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h     |  1 +
 .../RISCV/xcvbitmanip-generic-patterns.ll     | 76 +++++++++++++++++++
 3 files changed, 128 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 579cc40fa6735..244b49b73bf48 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1283,6 +1283,54 @@ bool RISCVDAGToDAGISel::tryCVBitManipBClr(SDNode *Node) {
   return true;
 }
 
+// --------------------------------------------------------------------------
+// cv.bset: set a contiguous range of bits to 1
+//
+// Matches:
+//   (or X, C) where C is a shifted mask: C = ((1 << width) - 1) << offset
+//
+// Emits: CV_BSET rd, rs1, IS3=width-1, IS2=offset
+//        (note: cv.bset semantics is Is3+1 bits, so we encode width-1)
+// --------------------------------------------------------------------------
+bool RISCVDAGToDAGISel::tryCVBitManipBSet(SDNode *Node) {
+  if (!Subtarget->hasVendorXCVbitmanip() || Subtarget->is64Bit())
+    return false;
+
+  assert(Node->getOpcode() == ISD::OR);
+  SDLoc DL(Node);
+
+  SDValue LHS = Node->getOperand(0);
+  SDValue RHS = Node->getOperand(1);
+
+  // Canonicalize: constant on the right
+  auto *ValC = dyn_cast<ConstantSDNode>(RHS);
+  if (!ValC) {
+    std::swap(LHS, RHS);
+    ValC = dyn_cast<ConstantSDNode>(RHS);
+  }
+  if (!ValC)
+    return false;
+
+  uint32_t Val = ValC->getZExtValue();
+
+  // Must be a non-zero shifted mask: contiguous 1s at some offset
+  if (Val == 0 || !isShiftedMask_32(Val))
+    return false;
+
+  unsigned Width = llvm::popcount(Val);
+  unsigned Offset = llvm::countr_zero(Val);
+
+  // Validate ranges
+  if (Width == 0 || Width > 32 || Offset > 31 || (Width + Offset) > 32)
+    return false;
+
+  SDValue IS3 = CurDAG->getTargetConstant(Width - 1, DL, MVT::i32);
+  SDValue IS2 = CurDAG->getTargetConstant(Offset, DL, MVT::i32);
+
+  CurDAG->SelectNodeTo(Node, RISCV::CV_BSET, MVT::i32, LHS, IS3, IS2);
+  return true;
+}
+
 void RISCVDAGToDAGISel::Select(SDNode *Node) {
   // If we have a custom node, we have already selected.
   if (Node->isMachineOpcode()) {
@@ -1682,6 +1730,9 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
     return;
   }
   case ISD::OR: {
+    if (tryCVBitManipBSet(Node))
+      return;
+
     if (tryShrinkShlLogicImm(Node))
       return;
 
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
index bf723e1b798e1..0c26f4033c1d7 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
@@ -176,6 +176,7 @@ class RISCVDAGToDAGISel : public SelectionDAGISel {
   bool tryCVBitManipExtractU(SDNode *Node);
   bool tryCVBitManipExtract(SDNode *Node);
   bool tryCVBitManipBClr(SDNode *Node);
+  bool tryCVBitManipBSet(SDNode *Node);
 
   // Return the RISC-V condition code that matches the given DAG integer
   // condition code. The CondCode must be one of those supported by the RISC-V
diff --git a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
index eadbd96f1b72e..ebb4bd5122912 100644
--- a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
+++ b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
@@ -226,6 +226,71 @@ define i32 @test_no_bclr_exthz(i32 %x) {
   ret i32 %res
 }
 
+;===----------------------------------------------------------------------===;
+; cv.bset — set a contiguous range of bits: x | (mask << offset)
+;===----------------------------------------------------------------------===;
+
+; Set bits [4:6] (3 bits at offset 4): x | 0x70
+define i32 @test_bset_3_4(i32 %x) {
+; CHECK-LABEL: test_bset_3_4:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bset a0, a0, 2, 4
+; CHECK-NEXT:    ret
+  %res = or i32 %x, 112   ; 0x70 = 0x7 << 4
+  ret i32 %res
+}
+
+; Set bit 15: x | 0x8000
+define i32 @test_bset_1_15(i32 %x) {
+; CHECK-LABEL: test_bset_1_15:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bset a0, a0, 0, 15
+; CHECK-NEXT:    ret
+  %res = or i32 %x, 32768   ; 0x8000 = 1 << 15
+  ret i32 %res
+}
+
+; Set bits [8:15] (8 bits at offset 8): x | 0xFF00
+define i32 @test_bset_8_8(i32 %x) {
+; CHECK-LABEL: test_bset_8_8:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bset a0, a0, 7, 8
+; CHECK-NEXT:    ret
+  %res = or i32 %x, 65280   ; 0xFF00 = 0xFF << 8
+  ret i32 %res
+}
+
+; Set bits [0:3] (4 bits at offset 0): x | 0xF
+define i32 @test_bset_4_0(i32 %x) {
+; CHECK-LABEL: test_bset_4_0:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bset a0, a0, 3, 0
+; CHECK-NEXT:    ret
+  %res = or i32 %x, 15   ; 0xF = (1 << 4) - 1
+  ret i32 %res
+}
+
+; Set single bit at MSB: x | 0x80000000
+define i32 @test_bset_1_31(i32 %x) {
+; CHECK-LABEL: test_bset_1_31:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bset a0, a0, 0, 31
+; CHECK-NEXT:    ret
+  %res = or i32 %x, -2147483648   ; 0x80000000 = 1 << 31
+  ret i32 %res
+}
+
+; Non-contiguous constant (0x55 = 01010101): should NOT become cv.bset,
+; falls through to standard ORI lowering.
+define i32 @test_no_bset_noncontiguous(i32 %x) {
+; CHECK-LABEL: test_no_bset_noncontiguous:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    ori a0, a0, 85
+; CHECK-NEXT:    ret
+  %res = or i32 %x, 85   ; 0x55 = 01010101, not a shifted mask
+  ret i32 %res
+}
+
 ;===----------------------------------------------------------------------===;
 ; Realistic embedded bitfield examples
 ;===----------------------------------------------------------------------===;
@@ -264,3 +329,14 @@ define i32 @test_clear_irq_flag(i32 %reg) {
   %res = and i32 %reg, -33   ; ~(1 << 5) = 0xFFFFFFDF
   ret i32 %res
 }
+
+; Set enable bits [8:11] in a control register
+; Equivalent to: reg | (0xF << 8)
+define i32 @test_set_enable_bits(i32 %reg) {
+; CHECK-LABEL: test_set_enable_bits:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.bset a0, a0, 3, 8
+; CHECK-NEXT:    ret
+  %res = or i32 %reg, 3840   ; 0xF00 = 0xF << 8
+  ret i32 %res
+}

>From 082b641d28f9de0ad4e5eb4d088733d6edfa56dd Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Sat, 4 Apr 2026 17:08:40 +0200
Subject: [PATCH 07/11] [RISCV][LLVM] Auto-select `cv.insert` from C bitfield
 write operations

Context:
- add custom ISel logic to match the bitfield insert idiom
  `(reg & ~(mask << offset)) | ((val & mask) << offset)` and lower
  it directly to the `CV_INSERT` instruction from the `XCVbitmanip`
  extension
- three DAG forms are handled: mask-before-shift, mask-after-shift,
  and the no-shift case for offset=0. Both `OR` operand orderings
  are matched
- this allows the compiler to automatically emit cv.insert from
  plain C bitfield writes like
  `reg = (reg & ~(0x7 << 3)) | ((val & 0x7) << 3)` without
  requiring explicit builtin calls, which is the standard pattern
  for writing fields in hardware control registers

Implementation:
- implement `RISCVDAGToDAGISel::tryCVBitManipInsert()`
- add `cv.insert` tests in `xcvbitmanip-generic-patterns.ll`
- call `tryCVBitManipInsert()` as the second check in the
  `case ISD::OR:` block of `RISCVDAGToDAGISel::Select`, after the
  `cv.bset` handling
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   | 134 ++++++++++++++++++
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h     |   1 +
 .../RISCV/xcvbitmanip-generic-patterns.ll     | 103 ++++++++++++++
 3 files changed, 238 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 244b49b73bf48..33b842e958812 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1331,6 +1331,137 @@ bool RISCVDAGToDAGISel::tryCVBitManipBSet(SDNode *Node) {
   return true;
 }
 
+// --------------------------------------------------------------------------
+// cv.insert: replace a contiguous bitfield in rd with low bits of rs1
+//
+// C idiom:  reg = (reg & ~(mask << offset)) | ((val & mask) << offset)
+//   where mask = (1 << width) - 1
+//
+// This is a two-input, multi-node DAG shape. The compiler can produce
+// several forms after DAG combining:
+//
+// Form 1 (most common, offset > 0):
+//   (or (and rd, ClearMask), (shl (and rs1, LowMask), Offset))
+//   where ClearMask = ~(LowMask << Offset)
+//
+// Form 2 (offset > 0, mask applied after shift):
+//   (or (and rd, ClearMask), (and (shl rs1, Offset), ShiftedMask))
+//   where ShiftedMask = LowMask << Offset
+//
+// Form 3 (offset = 0, no shift):
+//   (or (and rd, ClearMask), (and rs1, LowMask))
+//
+// Emit: CV_INSERT rd, rs1, IS3=width-1, IS2=offset
+//        (note: cv.insert semantics is Is3+1 bits, so we encode width-1)
+// --------------------------------------------------------------------------
+bool RISCVDAGToDAGISel::tryCVBitManipInsert(SDNode *Node) {
+  if (!Subtarget->hasVendorXCVbitmanip() || Subtarget->is64Bit())
+    return false;
+
+  assert(Node->getOpcode() == ISD::OR);
+  SDLoc DL(Node);
+
+  // Try both orderings of the OR operands since OR is commutative.
+  // One side must be (and rd, ClearMask), the other provides the value.
+  for (unsigned Swap = 0; Swap < 2; ++Swap) {
+    SDValue ClearSide = Node->getOperand(Swap);
+    SDValue ValueSide = Node->getOperand(1 - Swap);
+
+    // ClearSide must be (and rd, ClearMask)
+    if (ClearSide.getOpcode() != ISD::AND)
+      continue;
+
+    // Find the constant in the AND (could be on either side)
+    auto *ClearC = dyn_cast<ConstantSDNode>(ClearSide.getOperand(1));
+    SDValue RD = ClearSide.getOperand(0);
+    if (!ClearC) {
+      ClearC = dyn_cast<ConstantSDNode>(ClearSide.getOperand(0));
+      RD = ClearSide.getOperand(1);
+    }
+    if (!ClearC)
+      continue;
+
+    uint32_t ClearMask = ClearC->getZExtValue();
+    uint32_t FieldMask = ~ClearMask;
+
+    // FieldMask = ~ClearMask must be a shifted mask (the hole being filled)
+    if (FieldMask == 0 || !isShiftedMask_32(FieldMask))
+      continue;
+
+    unsigned Width = llvm::popcount(FieldMask);
+    unsigned Offset = llvm::countr_zero(FieldMask);
+
+    if (Width == 0 || Width > 32 || Offset > 31 || (Width + Offset) > 32)
+      continue;
+
+    uint32_t LowMask = (1u << Width) - 1;  // (1 << width) - 1
+    SDValue RS1;
+
+    // --- Form 1: (shl (and rs1, LowMask), Offset) ---
+    // This is the most common form from: ((val & mask) << offset)
+    if (ValueSide.getOpcode() == ISD::SHL) {
+      auto *ShiftC = dyn_cast<ConstantSDNode>(ValueSide.getOperand(1));
+      if (ShiftC && ShiftC->getZExtValue() == Offset) {
+        SDValue ShiftInput = ValueSide.getOperand(0);
+        if (ShiftInput.getOpcode() == ISD::AND) {
+          auto *MC = dyn_cast<ConstantSDNode>(ShiftInput.getOperand(1));
+          SDValue Base = ShiftInput.getOperand(0);
+          if (!MC) {
+            MC = dyn_cast<ConstantSDNode>(ShiftInput.getOperand(0));
+            Base = ShiftInput.getOperand(1);
+          }
+          if (MC && MC->getZExtValue() == LowMask)
+            RS1 = Base;
+        }
+      }
+    }
+
+    // --- Form 2: (and (shl rs1, Offset), ShiftedMask) ---
+    // This form appears when the compiler applies the mask after the shift.
+    if (!RS1 && ValueSide.getOpcode() == ISD::AND) {
+      auto *MC = dyn_cast<ConstantSDNode>(ValueSide.getOperand(1));
+      SDValue Inner = ValueSide.getOperand(0);
+      if (!MC) {
+        MC = dyn_cast<ConstantSDNode>(ValueSide.getOperand(0));
+        Inner = ValueSide.getOperand(1);
+      }
+      if (MC && MC->getZExtValue() == FieldMask &&
+          Inner.getOpcode() == ISD::SHL) {
+        auto *ShiftC = dyn_cast<ConstantSDNode>(Inner.getOperand(1));
+        if (ShiftC && ShiftC->getZExtValue() == Offset)
+          RS1 = Inner.getOperand(0);
+      }
+    }
+
+    // --- Form 3: (and rs1, LowMask) when Offset == 0 ---
+    // No shift needed: reg = (reg & ~mask) | (val & mask)
+    if (!RS1 && Offset == 0 && ValueSide.getOpcode() == ISD::AND) {
+      auto *MC = dyn_cast<ConstantSDNode>(ValueSide.getOperand(1));
+      SDValue Base = ValueSide.getOperand(0);
+      if (!MC) {
+        MC = dyn_cast<ConstantSDNode>(ValueSide.getOperand(0));
+        Base = ValueSide.getOperand(1);
+      }
+      if (MC && MC->getZExtValue() == LowMask)
+        RS1 = Base;
+    }
+
+    if (!RS1)
+      continue;
+
+    // Emit: CV_INSERT rd, rs1, IS3=width, IS2=offset
+    // rd is both input and output (tied operand)
+    SDValue IS3 = CurDAG->getTargetConstant(Width - 1, DL, MVT::i32);
+    SDValue IS2 = CurDAG->getTargetConstant(Offset, DL, MVT::i32);
+
+    SmallVector<SDValue, 4> Ops = {RD, RS1, IS3, IS2};
+    CurDAG->SelectNodeTo(Node, RISCV::CV_INSERT, MVT::i32, Ops);
+    return true;
+  }
+
+  return false;
+}
+
 void RISCVDAGToDAGISel::Select(SDNode *Node) {
   // If we have a custom node, we have already selected.
   if (Node->isMachineOpcode()) {
@@ -1733,6 +1864,9 @@ void RISCVDAGToDAGISel::Select(SDNode *Node) {
     if (tryCVBitManipBSet(Node))
       return;
 
+    if (tryCVBitManipInsert(Node))
+      return;
+
     if (tryShrinkShlLogicImm(Node))
       return;
 
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
index 0c26f4033c1d7..c63ccfbe81d3a 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
@@ -177,6 +177,7 @@ class RISCVDAGToDAGISel : public SelectionDAGISel {
   bool tryCVBitManipExtract(SDNode *Node);
   bool tryCVBitManipBClr(SDNode *Node);
   bool tryCVBitManipBSet(SDNode *Node);
+  bool tryCVBitManipInsert(SDNode *Node);
 
   // Return the RISC-V condition code that matches the given DAG integer
   // condition code. The CondCode must be one of those supported by the RISC-V
diff --git a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
index ebb4bd5122912..3ddc34ee3e360 100644
--- a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
+++ b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
@@ -291,6 +291,81 @@ define i32 @test_no_bset_noncontiguous(i32 %x) {
   ret i32 %res
 }
 
+;===----------------------------------------------------------------------===;
+; cv.insert — insert low bits of rs1 into a bitfield of rd
+; C idiom: reg = (reg & ~(mask << offset)) | ((val & mask) << offset)
+;===----------------------------------------------------------------------===;
+
+; Insert 3 bits at offset 4: (rd & ~(0x7<<4)) | ((rs1 & 0x7) << 4)
+define i32 @test_insert_3_4(i32 %rd, i32 %rs1) {
+; CHECK-LABEL: test_insert_3_4:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.insert a0, a1, 2, 4
+; CHECK-NEXT:    ret
+  %clear = and i32 %rd, -113         ; 0xFFFFFF8F = ~(0x7 << 4)
+  %masked = and i32 %rs1, 7          ; 0x7 = (1 << 3) - 1
+  %shifted = shl i32 %masked, 4
+  %res = or i32 %clear, %shifted
+  ret i32 %res
+}
+
+; Insert 8 bits at offset 8: (rd & ~(0xFF<<8)) | ((rs1 & 0xFF) << 8)
+define i32 @test_insert_8_8(i32 %rd, i32 %rs1) {
+; CHECK-LABEL: test_insert_8_8:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.insert a0, a1, 7, 8
+; CHECK-NEXT:    ret
+  %clear = and i32 %rd, -65281       ; 0xFFFF00FF = ~(0xFF << 8)
+  %masked = and i32 %rs1, 255        ; 0xFF
+  %shifted = shl i32 %masked, 8
+  %res = or i32 %clear, %shifted
+  ret i32 %res
+}
+
+; Insert 1 bit at offset 15: (rd & ~(1<<15)) | ((rs1 & 1) << 15)
+define i32 @test_insert_1_15(i32 %rd, i32 %rs1) {
+; CHECK-LABEL: test_insert_1_15:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.insert a0, a1, 0, 15
+; CHECK-NEXT:    ret
+  %clear = and i32 %rd, -32769       ; 0xFFFF7FFF = ~(1 << 15)
+  %masked = and i32 %rs1, 1
+  %shifted = shl i32 %masked, 15
+  %res = or i32 %clear, %shifted
+  ret i32 %res
+}
+
+; Insert 4 bits at offset 0 (no shift): (rd & ~0xF) | (rs1 & 0xF)
+define i32 @test_insert_4_0(i32 %rd, i32 %rs1) {
+; CHECK-LABEL: test_insert_4_0:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.insert a0, a1, 3, 0
+; CHECK-NEXT:    ret
+  %clear = and i32 %rd, -16          ; 0xFFFFFFF0 = ~0xF
+  %masked = and i32 %rs1, 15         ; 0xF
+  %res = or i32 %clear, %masked
+  ret i32 %res
+}
+
+; Insert 16 bits at offset 16: the (and rd, 0xFFFF) clear mask gets folded
+; into a shift pair by the DAG combiner before ISel, preventing the insert
+; pattern from matching. This is a known limitation for width+offset=32 cases
+; where the clear mask happens to be a simple low mask.
+define i32 @test_insert_16_16(i32 %rd, i32 %rs1) {
+; CHECK-LABEL: test_insert_16_16:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    slli a0, a0, 16
+; CHECK-NEXT:    srli a0, a0, 16
+; CHECK-NEXT:    slli a1, a1, 16
+; CHECK-NEXT:    or a0, a0, a1
+; CHECK-NEXT:    ret
+  %clear = and i32 %rd, 65535        ; 0x0000FFFF = ~(0xFFFF << 16)
+  %masked = and i32 %rs1, 65535      ; 0xFFFF
+  %shifted = shl i32 %masked, 16
+  %res = or i32 %clear, %shifted
+  ret i32 %res
+}
+
 ;===----------------------------------------------------------------------===;
 ; Realistic embedded bitfield examples
 ;===----------------------------------------------------------------------===;
@@ -340,3 +415,31 @@ define i32 @test_set_enable_bits(i32 %reg) {
   %res = or i32 %reg, 3840   ; 0xF00 = 0xF << 8
   ret i32 %res
 }
+
+; Write a 3-bit priority value into STATUS register bits [3:5]
+; Equivalent to: reg = (reg & ~(0x7 << 3)) | ((val & 0x7) << 3)
+define i32 @test_write_status_field(i32 %reg, i32 %val) {
+; CHECK-LABEL: test_write_status_field:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.insert a0, a1, 2, 3
+; CHECK-NEXT:    ret
+  %clear = and i32 %reg, -57         ; 0xFFFFFFC7 = ~(0x7 << 3)
+  %masked = and i32 %val, 7
+  %shifted = shl i32 %masked, 3
+  %res = or i32 %clear, %shifted
+  ret i32 %res
+}
+
+; Write a byte into a register's second byte lane (bits [8:15])
+; Common pattern for MMIO register writes
+define i32 @test_write_byte_lane(i32 %reg, i32 %byte) {
+; CHECK-LABEL: test_write_byte_lane:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.insert a0, a1, 7, 8
+; CHECK-NEXT:    ret
+  %clear = and i32 %reg, -65281      ; 0xFFFF00FF
+  %masked = and i32 %byte, 255
+  %shifted = shl i32 %masked, 8
+  %res = or i32 %clear, %shifted
+  ret i32 %res
+}

>From 2db2d4834a56647bcac4b1ac7b4be0b75ade0c83 Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Sun, 10 May 2026 16:54:42 +0200
Subject: [PATCH 08/11] [LLVM][RISCV][XCV] Add one-use guards to
 `XCVmac`/`XCValu` patterns

The Generic SelectionDAG patterns for cv.mac/cv.msu (XCVmac) and
cv.addn/cv.subn/cv.addun/cv.subun and their *NR register variants
(XCValu) all use tied-destination operands: the instruction writes
back into rd while consuming it. If the input subtree (the inner
add/sub/mul) has more than one use, folding clobbers a value still
live elsewhere, producing miscompilation.

Introduce `add_oneuse` and `sub_oneuse` PatFrags (matching the
existing `mul_oneuse`) in `RISCVInstrInfo.td` and rewrite the
affected patterns in `RISCVInstrInfoXCV.td` to require single use
on the inner operation.
---
 llvm/lib/Target/RISCV/RISCVInstrInfo.td    |  2 ++
 llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td | 28 ++++++++++++++--------
 2 files changed, 20 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.td b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
index 6c776c91ae73a..5818ac615fdc2 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
@@ -1429,6 +1429,8 @@ class binop_oneuse<SDPatternOperator operator>
 
 def and_oneuse : binop_oneuse<and>;
 def mul_oneuse : binop_oneuse<mul>;
+def add_oneuse : binop_oneuse<add>;
+def sub_oneuse : binop_oneuse<sub>;
 
 def mul_const_oneuse : PatFrag<(ops node:$A, node:$B),
                                (mul node:$A, node:$B), [{
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
index dd5179e198e88..841672ce6c34e 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXCV.td
@@ -858,11 +858,13 @@ let Predicates = [HasVendorXCVmac] in {
 
 let Predicates = [HasVendorXCVmac, IsRV32], AddedComplexity = 2 in {
   // acc + (a * b) → cv.mac
-  def : Pat<(i32 (add GPR:$rd, (i32 (mul GPR:$rs1, GPR:$rs2)))),
+  // mul_oneuse: bail out if the multiplication result is used elsewhere, because cv.mac ties rd as both input and output and would clobber it.
+  def : Pat<(i32 (add GPR:$rd, (i32 (mul_oneuse GPR:$rs1, GPR:$rs2)))),
             (CV_MAC GPR:$rd, GPR:$rs1, GPR:$rs2)>;
 
   // acc - (a * b) → cv.msu
-  def : Pat<(i32 (sub GPR:$rd, (i32 (mul GPR:$rs1, GPR:$rs2)))),
+  // mul_oneuse: bail out if the multiplication result is used elsewhere, because cv.msu ties rd as both input and output and would clobber it.
+  def : Pat<(i32 (sub GPR:$rd, (i32 (mul_oneuse GPR:$rs1, GPR:$rs2)))),
             (CV_MSU GPR:$rd, GPR:$rs1, GPR:$rs2)>;
 }
 
@@ -872,38 +874,44 @@ let Predicates = [HasVendorXCVmac, IsRV32], AddedComplexity = 2 in {
 
 // ---- Immediate shift variants (RRI format) ----
 let Predicates = [HasVendorXCValu, IsRV32], AddedComplexity = 3 in {
+  // add_oneuse: prevent clobbering rs1+rs2 if it is also consumed elsewhere.
+  // sub_oneuse: prevent clobbering rs1-rs2 if it is also consumed elsewhere.
+
   // cv.addn rd, rs1, rs2, imm5  →  rd = (rs1 + rs2) >>> imm5 (arithmetic)
-  def : Pat<(i32 (sra (i32 (add GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
+  def : Pat<(i32 (sra (i32 (add_oneuse GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
             (CV_ADDN GPR:$rs1, GPR:$rs2, cv_uimm5:$imm)>;
 
   // cv.addun rd, rs1, rs2, imm5  →  rd = (rs1 + rs2) >> imm5 (logical)
-  def : Pat<(i32 (srl (i32 (add GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
+  def : Pat<(i32 (srl (i32 (add_oneuse GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
             (CV_ADDUN GPR:$rs1, GPR:$rs2, cv_uimm5:$imm)>;
 
   // cv.subn rd, rs1, rs2, imm5  →  rd = (rs1 - rs2) >>> imm5 (arithmetic)
-  def : Pat<(i32 (sra (i32 (sub GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
+  def : Pat<(i32 (sra (i32 (sub_oneuse GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
             (CV_SUBN GPR:$rs1, GPR:$rs2, cv_uimm5:$imm)>;
 
   // cv.subun rd, rs1, rs2, imm5  →  rd = (rs1 - rs2) >> imm5 (logical)
-  def : Pat<(i32 (srl (i32 (sub GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
+  def : Pat<(i32 (srl (i32 (sub_oneuse GPR:$rs1, GPR:$rs2)), cv_uimm5:$imm)),
             (CV_SUBUN GPR:$rs1, GPR:$rs2, cv_uimm5:$imm)>;
 } // Predicates = [HasVendorXCValu, IsRV32]
 
 // ---- Register shift variants (NR format, tied rd) ----
 let Predicates = [HasVendorXCValu, IsRV32], AddedComplexity = 2 in {
+  // add_oneuse / sub_oneuse: these instructions write back into rd, so the inner add/sub MUST have a single use,
+  // otherwise the previous value of rd is lost while still being needed by another node in the DAG.
+
   // cv.addnr rd, rs1, rs2  →  rd = (rd + rs1) >> rs2 (arithmetic)
-  def : Pat<(i32 (sra (i32 (add GPR:$rd, GPR:$rs1)), GPR:$rs2)),
+  def : Pat<(i32 (sra (i32 (add_oneuse GPR:$rd, GPR:$rs1)), GPR:$rs2)),
             (CV_ADDNR GPR:$rd, GPR:$rs1, GPR:$rs2)>;
 
   // cv.addunr rd, rs1, rs2  →  rd = (rd + rs1) >> rs2 (logical)
-  def : Pat<(i32 (srl (i32 (add GPR:$rd, GPR:$rs1)), GPR:$rs2)),
+  def : Pat<(i32 (srl (i32 (add_oneuse GPR:$rd, GPR:$rs1)), GPR:$rs2)),
             (CV_ADDUNR GPR:$rd, GPR:$rs1, GPR:$rs2)>;
 
   // cv.subnr rd, rs1, rs2  →  rd = (rd - rs1) >> rs2 (arithmetic)
-  def : Pat<(i32 (sra (i32 (sub GPR:$rd, GPR:$rs1)), GPR:$rs2)),
+  def : Pat<(i32 (sra (i32 (sub_oneuse GPR:$rd, GPR:$rs1)), GPR:$rs2)),
             (CV_SUBNR GPR:$rd, GPR:$rs1, GPR:$rs2)>;
 
   // cv.subunr rd, rs1, rs2  →  rd = (rd - rs1) >> rs2 (logical)
-  def : Pat<(i32 (srl (i32 (sub GPR:$rd, GPR:$rs1)), GPR:$rs2)),
+  def : Pat<(i32 (srl (i32 (sub_oneuse GPR:$rd, GPR:$rs1)), GPR:$rs2)),
             (CV_SUBUNR GPR:$rd, GPR:$rs1, GPR:$rs2)>;
 } // Predicates = [HasVendorXCValu, IsRV32]

>From 374493dec4de9fa12add72872c0e64c7dedffc45 Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Sun, 10 May 2026 17:02:06 +0200
Subject: [PATCH 09/11] [LLVM][RISCV][XCV] Guard `XCVbitmanip` C++ matchers
 against multi-use

`cv.extractu`/`cv.extract`/`cv.bclr`/`cv.bset` operate on values
that may still be needed elsewhere in the DAG; `cv.insert`
additionally has a tied-destination operand. Folding without a
one-use check on the matched node clobbered live values and
miscompiled.

Add `Node->hasOneUse()` bail-outs at the beginning of each matcher
in `RISCVISelDAGToDAG.cpp`. For `tryCVBitManipInsert()`, also
require single use on both the clear-side (and rd, ~mask) and
value-side ((val & mask) << offset or its variants) sub-DAGs, since
`cv.insert` with tied rd cannot duplicate either input.
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 34 +++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 33b842e958812..903d3b55647e0 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1117,6 +1117,10 @@ bool RISCVDAGToDAGISel::tryCVBitManipExtractU(SDNode *Node) {
   assert(Node->getOpcode() == ISD::AND);
   SDLoc DL(Node);
 
+  // Bail out if the AND result is consumed by more than one user.
+  if (!Node->hasOneUse())
+    return false;
+
   SDValue LHS = Node->getOperand(0);
   SDValue RHS = Node->getOperand(1);
 
@@ -1129,6 +1133,11 @@ bool RISCVDAGToDAGISel::tryCVBitManipExtractU(SDNode *Node) {
   if (!MaskC)
     return false;
 
+  // If LHS is (srl X, c) and the shift result is used elsewhere, folding into cv.extractu would leave the
+  // other consumer reading a value we never compute again. Bail out and let the generic SRLI + ANDI lower.
+  if (LHS.getOpcode() == ISD::SRL && !LHS.hasOneUse())
+    return false;
+
   uint32_t Mask = MaskC->getZExtValue();
 
   // Mask must be a contiguous run of 1s starting from bit 0: (1 << w) - 1
@@ -1183,11 +1192,19 @@ bool RISCVDAGToDAGISel::tryCVBitManipExtract(SDNode *Node) {
   assert(Node->getOpcode() == ISD::SRA);
   SDLoc DL(Node);
 
+  // Bail out if the SRA result is consumed by more than one user.
+  if (!Node->hasOneUse())
+    return false;
+
   // Check: (sra (shl X, C1), C2)
   SDValue ShlNode = Node->getOperand(0);
   if (ShlNode.getOpcode() != ISD::SHL)
     return false;
 
+  // Do not steal the SHL value from other users.
+  if (!ShlNode.hasOneUse())
+    return false;
+
   auto *C2Node = dyn_cast<ConstantSDNode>(Node->getOperand(1));
   auto *C1Node = dyn_cast<ConstantSDNode>(ShlNode.getOperand(1));
   if (!C2Node || !C1Node)
@@ -1238,6 +1255,10 @@ bool RISCVDAGToDAGISel::tryCVBitManipBClr(SDNode *Node) {
   assert(Node->getOpcode() == ISD::AND);
   SDLoc DL(Node);
 
+  // Bail out if the AND result is consumed by more than one user.
+  if (!Node->hasOneUse())
+    return false;
+
   SDValue LHS = Node->getOperand(0);
   SDValue RHS = Node->getOperand(1);
 
@@ -1299,6 +1320,10 @@ bool RISCVDAGToDAGISel::tryCVBitManipBSet(SDNode *Node) {
   assert(Node->getOpcode() == ISD::OR);
   SDLoc DL(Node);
 
+  // Bail out if the OR result is consumed by more than one user.
+  if (!Node->hasOneUse())
+    return false;
+
   SDValue LHS = Node->getOperand(0);
   SDValue RHS = Node->getOperand(1);
 
@@ -1361,6 +1386,10 @@ bool RISCVDAGToDAGISel::tryCVBitManipInsert(SDNode *Node) {
   assert(Node->getOpcode() == ISD::OR);
   SDLoc DL(Node);
 
+  // Bail out if the OR result is consumed by more than one user.
+  if (!Node->hasOneUse())
+    return false;
+
   // Try both orderings of the OR operands since OR is commutative.
   // One side must be (and rd, ClearMask), the other provides the value.
   for (unsigned Swap = 0; Swap < 2; ++Swap) {
@@ -1449,6 +1478,11 @@ bool RISCVDAGToDAGISel::tryCVBitManipInsert(SDNode *Node) {
     if (!RS1)
       continue;
 
+    // The OR's input subtree (ClearSide and ValueSide) and the AND inside ClearSide must have a single use.
+    // Otherwise CV_INSERT, with its tied rd_wb = rd, would invalidate the previous rd for downstream consumers.
+    if (!ClearSide.hasOneUse() || !ValueSide.hasOneUse())
+      continue;
+
     // Emit: CV_INSERT rd, rs1, IS3=width, IS2=offset
     // rd is both input and output (tied operand)
     SDValue IS3 = CurDAG->getTargetConstant(Width - 1, DL, MVT::i32);

>From 525cfa54ac1a7d28f67d845dca2bcec09885e072 Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Fri, 19 Jun 2026 18:50:50 +0200
Subject: [PATCH 10/11] [RISCV][LLVM] Add codegen tests for XCV generic-pattern
 selection

Add CHECK-only tests (generated with update_llc_test_checks.py) for the
XCVmac/XCValu generic SelectionDAG patterns and the XCVbitmanip C++
ISel matchers, covering both the fused-instruction cases and the
multi-use cases that must not fuse.
---
 .../CodeGen/RISCV/xcvalu-generic-patterns.ll  | 216 ++++++++++++++++++
 .../RISCV/xcvbitmanip-generic-patterns.ll     | 105 +++++++++
 .../CodeGen/RISCV/xcvmac-generic-patterns.ll  | 120 ++++++++++
 3 files changed, 441 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/xcvalu-generic-patterns.ll
 create mode 100644 llvm/test/CodeGen/RISCV/xcvmac-generic-patterns.ll

diff --git a/llvm/test/CodeGen/RISCV/xcvalu-generic-patterns.ll b/llvm/test/CodeGen/RISCV/xcvalu-generic-patterns.ll
new file mode 100644
index 0000000000000..ad7521b771202
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xcvalu-generic-patterns.ll
@@ -0,0 +1,216 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -mattr=+m,+xcvalu -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s
+
+; This file tests that generic C idioms are automatically lowered to XCValu
+; instructions without requiring explicit builtin/intrinsic calls.
+
+;===----------------------------------------------------------------------===;
+; XCValu — add/sub with normalisation shift from plain C
+;===----------------------------------------------------------------------===;
+
+; (a + b) >> 3 (arithmetic right shift, constant)  →  cv.addn
+define i32 @test_addn(i32 %a, i32 %b) {
+; CHECK-LABEL: test_addn:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.addn a0, a0, a1, 3
+; CHECK-NEXT:    ret
+  %add = add i32 %a, %b
+  %res = ashr i32 %add, 3
+  ret i32 %res
+}
+
+; (unsigned)(a + b) >> 4 (logical right shift, constant)  →  cv.addun
+define i32 @test_addun(i32 %a, i32 %b) {
+; CHECK-LABEL: test_addun:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.addun a0, a0, a1, 4
+; CHECK-NEXT:    ret
+  %add = add i32 %a, %b
+  %res = lshr i32 %add, 4
+  ret i32 %res
+}
+
+; (a - b) >> 2 (arithmetic)  →  cv.subn
+define i32 @test_subn(i32 %a, i32 %b) {
+; CHECK-LABEL: test_subn:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.subn a0, a0, a1, 2
+; CHECK-NEXT:    ret
+  %sub = sub i32 %a, %b
+  %res = ashr i32 %sub, 2
+  ret i32 %res
+}
+
+; (unsigned)(a - b) >> 1 (logical)  →  cv.subun
+define i32 @test_subun(i32 %a, i32 %b) {
+; CHECK-LABEL: test_subun:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.subun a0, a0, a1, 1
+; CHECK-NEXT:    ret
+  %sub = sub i32 %a, %b
+  %res = lshr i32 %sub, 1
+  ret i32 %res
+}
+
+; Edge case: shift by 0 is optimized away before ISel, so only a plain add remains
+define i32 @test_addn_shift0(i32 %a, i32 %b) {
+; CHECK-LABEL: test_addn_shift0:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    add a0, a0, a1
+; CHECK-NEXT:    ret
+  %add = add i32 %a, %b
+  %res = ashr i32 %add, 0
+  ret i32 %res
+}
+
+; Edge case: shift by 31 (maximum uimm5)
+define i32 @test_addn_shift31(i32 %a, i32 %b) {
+; CHECK-LABEL: test_addn_shift31:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.addn a0, a0, a1, 31
+; CHECK-NEXT:    ret
+  %add = add i32 %a, %b
+  %res = ashr i32 %add, 31
+  ret i32 %res
+}
+
+; (a + b) >> reg (arithmetic, register shift)  →  cv.addnr
+define i32 @test_addnr(i32 %a, i32 %b, i32 %shift) {
+; CHECK-LABEL: test_addnr:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.addnr a0, a1, a2
+; CHECK-NEXT:    ret
+  %add = add i32 %a, %b
+  %res = ashr i32 %add, %shift
+  ret i32 %res
+}
+
+; (a + b) >> reg (logical, register shift)  →  cv.addunr
+define i32 @test_addunr(i32 %a, i32 %b, i32 %shift) {
+; CHECK-LABEL: test_addunr:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.addunr a0, a1, a2
+; CHECK-NEXT:    ret
+  %add = add i32 %a, %b
+  %res = lshr i32 %add, %shift
+  ret i32 %res
+}
+
+; (a - b) >> reg (arithmetic)  →  cv.subnr
+define i32 @test_subnr(i32 %a, i32 %b, i32 %shift) {
+; CHECK-LABEL: test_subnr:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.subnr a0, a1, a2
+; CHECK-NEXT:    ret
+  %sub = sub i32 %a, %b
+  %res = ashr i32 %sub, %shift
+  ret i32 %res
+}
+
+; (a - b) >> reg (logical)  →  cv.subunr
+define i32 @test_subunr(i32 %a, i32 %b, i32 %shift) {
+; CHECK-LABEL: test_subunr:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.subunr a0, a1, a2
+; CHECK-NEXT:    ret
+  %sub = sub i32 %a, %b
+  %res = lshr i32 %sub, %shift
+  ret i32 %res
+}
+
+;===----------------------------------------------------------------------===;
+; Realistic DSP-style examples
+;===----------------------------------------------------------------------===;
+
+; Fixed-point average: (a + b) >> 1  →  cv.addn
+define i32 @test_fixed_point_avg(i32 %a, i32 %b) {
+; CHECK-LABEL: test_fixed_point_avg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.addn a0, a0, a1, 1
+; CHECK-NEXT:    ret
+  %sum = add i32 %a, %b
+  %avg = ashr i32 %sum, 1
+  ret i32 %avg
+}
+
+;===----------------------------------------------------------------------===;
+; Negative tests: Generic XCValu patterns must NOT fire when the inner
+; add/sub has more than one user, otherwise the fused cv.{add,sub}{,u}n{,r}
+; would clobber the original value of (a+b) / (a-b) which is still needed
+; elsewhere in the DAG.
+;===----------------------------------------------------------------------===;
+
+; (a + b) used twice: once shifted (would be cv.addn) and once raw.
+; The fused pattern must bail out — emit a plain ADD + SRAI instead.
+define i32 @test_addn_no_oneuse(i32 %a, i32 %b, ptr %sink) {
+; CHECK-LABEL: test_addn_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    add a1, a0, a1
+; CHECK-NEXT:    srai a0, a1, 3
+; CHECK-NEXT:    sw a1, 0(a2)
+; CHECK-NEXT:    ret
+  %sum   = add i32 %a, %b
+  %shift = ashr i32 %sum, 3
+  ; Second use of %sum: stored to memory. Forces hasOneUse() to fail.
+  store i32 %sum, ptr %sink, align 4
+  ret i32 %shift
+}
+
+; Same shape with logical shift -> would have been cv.addun.
+define i32 @test_addun_no_oneuse(i32 %a, i32 %b, ptr %sink) {
+; CHECK-LABEL: test_addun_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    add a1, a0, a1
+; CHECK-NEXT:    srli a0, a1, 4
+; CHECK-NEXT:    sw a1, 0(a2)
+; CHECK-NEXT:    ret
+  %sum   = add i32 %a, %b
+  %shift = lshr i32 %sum, 4
+  store i32 %sum, ptr %sink, align 4
+  ret i32 %shift
+}
+
+; (a - b) reused -> cv.subn must NOT fire.
+define i32 @test_subn_no_oneuse(i32 %a, i32 %b, ptr %sink) {
+; CHECK-LABEL: test_subn_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    sub a1, a0, a1
+; CHECK-NEXT:    srai a0, a1, 2
+; CHECK-NEXT:    sw a1, 0(a2)
+; CHECK-NEXT:    ret
+  %diff  = sub i32 %a, %b
+  %shift = ashr i32 %diff, 2
+  store i32 %diff, ptr %sink, align 4
+  ret i32 %shift
+}
+
+; NR-format: (rd + rs1) >> rs2 where (rd + rs1) is reused.
+; cv.addnr ties rd in/out, so reusing the sum elsewhere is incompatible.
+define i32 @test_addnr_no_oneuse(i32 %rd, i32 %rs1, i32 %rs2, ptr %sink) {
+; CHECK-LABEL: test_addnr_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    add a1, a0, a1
+; CHECK-NEXT:    sra a0, a1, a2
+; CHECK-NEXT:    sw a1, 0(a3)
+; CHECK-NEXT:    ret
+  %sum   = add i32 %rd, %rs1
+  %shift = ashr i32 %sum, %rs2
+  store i32 %sum, ptr %sink, align 4
+  ret i32 %shift
+}
+
+;===----------------------------------------------------------------------===;
+; Sanity: when the inner add/sub IS single-use, the fused pattern still
+; fires correctly. This guarantees the hasOneUse guard is not over-aggressive.
+;===----------------------------------------------------------------------===;
+
+define i32 @test_addn_single_use(i32 %a, i32 %b) {
+; CHECK-LABEL: test_addn_single_use:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.addn a0, a0, a1, 3
+; CHECK-NEXT:    ret
+  %sum   = add i32 %a, %b
+  %shift = ashr i32 %sum, 3
+  ret i32 %shift
+}
diff --git a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
index 3ddc34ee3e360..f10ef7b3d9d69 100644
--- a/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
+++ b/llvm/test/CodeGen/RISCV/xcvbitmanip-generic-patterns.ll
@@ -443,3 +443,108 @@ define i32 @test_write_byte_lane(i32 %reg, i32 %byte) {
   %res = or i32 %clear, %shifted
   ret i32 %res
 }
+
+;===----------------------------------------------------------------------===;
+; Negative tests: the C++ matchers tryCVBitManipExtractU / Extract / BClr /
+; BSet / Insert must bail out when the inner shift/and/or has more than one
+; consumer. Otherwise the fused cv.* instruction would compute the right
+; value but the *other* consumer would be left reading a register that has
+; already been overwritten (or, worse, a register the allocator never wrote).
+;===----------------------------------------------------------------------===;
+
+; (x >> 4) used twice: once anded (would be cv.extractu), once stored raw
+; to MMIO. cv.extractu would replace the (and (srl x, 4), 7) node but the
+; SRL node would still be needed for the store — must NOT fold.
+define i32 @test_extractu_no_oneuse(i32 %x, ptr %mmio) {
+; CHECK-LABEL: test_extractu_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    srli a2, a0, 4
+; CHECK-NEXT:    andi a0, a2, 7
+; CHECK-NEXT:    sw a2, 0(a1)
+; CHECK-NEXT:    ret
+  %shr   = lshr i32 %x, 4
+  %field = and i32 %shr, 7
+  ; Second use of %shr: stored to a memory-mapped peripheral register.
+  store volatile i32 %shr, ptr %mmio, align 4
+  ret i32 %field
+}
+
+; (sra (shl x, 24), 24) used twice -> cv.extract must NOT fire.
+define i32 @test_extract_no_oneuse(i32 %x, ptr %sink) {
+; CHECK-LABEL: test_extract_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    slli a2, a0, 24
+; CHECK-NEXT:    srai a0, a2, 24
+; CHECK-NEXT:    sw a2, 0(a1)
+; CHECK-NEXT:    ret
+  %shl  = shl i32 %x, 24
+  %sext = ashr i32 %shl, 24
+  ; Second use of the SHL node forces the matcher to bail out.
+  store i32 %shl, ptr %sink, align 4
+  ret i32 %sext
+}
+
+; (x & ~mask) used in two different downstream computations -> cv.bclr must NOT fire.
+; Clearing bits [4:7] of x while also keeping the unmasked computation alive.
+define i32 @test_bclr_no_oneuse(i32 %x, ptr %sink, i32 %extra) {
+; CHECK-LABEL: test_bclr_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    andi a3, a0, -241
+; CHECK-NEXT:    add a0, a3, a2
+; CHECK-NEXT:    sw a3, 0(a1)
+; CHECK-NEXT:    ret
+  %clear = and i32 %x, -241        ; ~0xF0 = 0xFFFFFF0F
+  store i32 %clear, ptr %sink, align 4
+  ; Second, distinct use of %clear: forces hasOneUse() == false on the AND.
+  %sum = add i32 %clear, %extra
+  ret i32 %sum
+}
+
+; (or x, mask) used in two different downstream computations -> cv.bset must NOT fire.
+define i32 @test_bset_no_oneuse(i32 %x, ptr %sink, i32 %extra) {
+; CHECK-LABEL: test_bset_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    ori a3, a0, 240
+; CHECK-NEXT:    add a0, a3, a2
+; CHECK-NEXT:    sw a3, 0(a1)
+; CHECK-NEXT:    ret
+  %set = or i32 %x, 240            ; bits [4:7] = 1
+  store i32 %set, ptr %sink, align 4
+  %sum = add i32 %set, %extra
+  ret i32 %sum
+}
+
+; cv.insert idiom but the cleared value (reg & ~(mask<<offset)) is also
+; consumed elsewhere — fold must bail out.
+define i32 @test_insert_no_oneuse(i32 %reg, i32 %val, ptr %sink) {
+; CHECK-LABEL: test_insert_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a3, 1048575
+; CHECK-NEXT:    addi a3, a3, 255
+; CHECK-NEXT:    cv.extractu a1, a1, 3, 0
+; CHECK-NEXT:    and a3, a0, a3
+; CHECK-NEXT:    slli a0, a1, 8
+; CHECK-NEXT:    or a0, a3, a0
+; CHECK-NEXT:    sw a3, 0(a2)
+; CHECK-NEXT:    ret
+  %clear  = and i32 %reg, -3841    ; ~(0xF << 8) = 0xFFFFF0FF
+  store i32 %clear, ptr %sink, align 4
+  %low    = and i32 %val, 15
+  %shf    = shl i32 %low, 8
+  %merged = or i32 %clear, %shf
+  ret i32 %merged
+}
+
+;===----------------------------------------------------------------------===;
+; Sanity: single-use cases still match the fused instruction.
+;===----------------------------------------------------------------------===;
+
+define i32 @test_extractu_single_use(i32 %x) {
+; CHECK-LABEL: test_extractu_single_use:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.extractu a0, a0, 2, 4
+; CHECK-NEXT:    ret
+  %shr   = lshr i32 %x, 4
+  %field = and i32 %shr, 7
+  ret i32 %field
+}
diff --git a/llvm/test/CodeGen/RISCV/xcvmac-generic-patterns.ll b/llvm/test/CodeGen/RISCV/xcvmac-generic-patterns.ll
new file mode 100644
index 0000000000000..d5c376f13fcb4
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xcvmac-generic-patterns.ll
@@ -0,0 +1,120 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -mattr=+m,+xcvmac -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s
+
+; This file tests that generic C idioms are automatically lowered to XCVmac
+; instructions without requiring explicit builtin/intrinsic calls.
+
+;===----------------------------------------------------------------------===;
+; XCVmac — multiply-accumulate from plain C
+;===----------------------------------------------------------------------===;
+
+; acc += a * b  →  cv.mac
+define i32 @test_mac(i32 %a, i32 %b, i32 %acc) {
+; CHECK-LABEL: test_mac:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.mac a2, a0, a1
+; CHECK-NEXT:    mv a0, a2
+; CHECK-NEXT:    ret
+  %mul = mul i32 %a, %b
+  %res = add i32 %acc, %mul
+  ret i32 %res
+}
+
+; acc += a * b (commuted add operands — should still match)
+define i32 @test_mac_commuted(i32 %a, i32 %b, i32 %acc) {
+; CHECK-LABEL: test_mac_commuted:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.mac a2, a0, a1
+; CHECK-NEXT:    mv a0, a2
+; CHECK-NEXT:    ret
+  %mul = mul i32 %a, %b
+  %res = add i32 %mul, %acc
+  ret i32 %res
+}
+
+; acc -= a * b  →  cv.msu
+define i32 @test_msu(i32 %a, i32 %b, i32 %acc) {
+; CHECK-LABEL: test_msu:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.msu a2, a0, a1
+; CHECK-NEXT:    mv a0, a2
+; CHECK-NEXT:    ret
+  %mul = mul i32 %a, %b
+  %res = sub i32 %acc, %mul
+  ret i32 %res
+}
+
+; Loop-based MAC: sum += arr[i] * coeffs[i]
+; This tests that the compiler uses cv.mac in a realistic loop
+define i32 @test_mac_loop(ptr %arr, ptr %coeffs, i32 %n) {
+; CHECK-LABEL: test_mac_loop:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    li a3, 0
+; CHECK-NEXT:    blez a2, .LBB3_2
+; CHECK-NEXT:  .LBB3_1: # %loop
+; CHECK-NEXT:    # =>This Inner Loop Header: Depth=1
+; CHECK-NEXT:    lw a4, 0(a0)
+; CHECK-NEXT:    lw a5, 0(a1)
+; CHECK-NEXT:    addi a2, a2, -1
+; CHECK-NEXT:    cv.mac a3, a4, a5
+; CHECK-NEXT:    addi a1, a1, 4
+; CHECK-NEXT:    addi a0, a0, 4
+; CHECK-NEXT:    bnez a2, .LBB3_1
+; CHECK-NEXT:  .LBB3_2: # %exit
+; CHECK-NEXT:    mv a0, a3
+; CHECK-NEXT:    ret
+  %cmp = icmp sgt i32 %n, 0
+  br i1 %cmp, label %loop, label %exit
+
+loop:
+  %i = phi i32 [ 0, %0 ], [ %i.next, %loop ]
+  %sum = phi i32 [ 0, %0 ], [ %sum.next, %loop ]
+  %ptr.a = getelementptr i32, ptr %arr, i32 %i
+  %ptr.c = getelementptr i32, ptr %coeffs, i32 %i
+  %a = load i32, ptr %ptr.a
+  %c = load i32, ptr %ptr.c
+  %mul = mul i32 %a, %c
+  %sum.next = add i32 %sum, %mul
+  %i.next = add i32 %i, 1
+  %done = icmp eq i32 %i.next, %n
+  br i1 %done, label %exit, label %loop
+
+exit:
+  %result = phi i32 [ 0, %0 ], [ %sum.next, %loop ]
+  ret i32 %result
+}
+
+;===----------------------------------------------------------------------===;
+; Realistic DSP-style examples
+;===----------------------------------------------------------------------===;
+
+; FIR filter tap: acc += coeff * sample, then normalise
+; This should use cv.mac for the accumulate
+define i32 @test_fir_tap(i32 %acc, i32 %coeff, i32 %sample) {
+; CHECK-LABEL: test_fir_tap:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    cv.mac a0, a1, a2
+; CHECK-NEXT:    ret
+  %mul = mul i32 %coeff, %sample
+  %sum = add i32 %acc, %mul
+  ret i32 %sum
+}
+
+; mul result has TWO uses: one in the add, one in a separate compare.
+; cv.mac would clobber the multiplication result -> we MUST emit mul + add.
+define i32 @test_mac_no_oneuse(i32 %a, i32 %b, i32 %acc) {
+; CHECK-LABEL: test_mac_no_oneuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    mul a0, a0, a1
+; CHECK-NEXT:    sgtz a1, a0
+; CHECK-NEXT:    neg a1, a1
+; CHECK-NEXT:    and a1, a1, a2
+; CHECK-NEXT:    add a0, a0, a1
+; CHECK-NEXT:    ret
+  %mul = mul i32 %a, %b
+  %sum = add i32 %acc, %mul
+  %ok  = icmp sgt i32 %mul, 0
+  %sel = select i1 %ok, i32 %sum, i32 %mul
+  ret i32 %sel
+}

>From 4552eed400e7368e53bbb38d5f22e4013c67d6f1 Mon Sep 17 00:00:00 2001
From: vitbur <vittorioburani at gmail.com>
Date: Fri, 19 Jun 2026 18:50:50 +0200
Subject: [PATCH 11/11] [RISCV][LLVM] Apply clang-format to XCV
 generic-selection code

---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 903d3b55647e0..4b16b9e41f26d 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -1133,8 +1133,9 @@ bool RISCVDAGToDAGISel::tryCVBitManipExtractU(SDNode *Node) {
   if (!MaskC)
     return false;
 
-  // If LHS is (srl X, c) and the shift result is used elsewhere, folding into cv.extractu would leave the
-  // other consumer reading a value we never compute again. Bail out and let the generic SRLI + ANDI lower.
+  // If LHS is (srl X, c) and the shift result is used elsewhere, folding into
+  // cv.extractu would leave the other consumer reading a value we never compute
+  // again. Bail out and let the generic SRLI + ANDI lower.
   if (LHS.getOpcode() == ISD::SRL && !LHS.hasOneUse())
     return false;
 
@@ -1278,7 +1279,8 @@ bool RISCVDAGToDAGISel::tryCVBitManipBClr(SDNode *Node) {
   if (InvMask == 0)
     return false;
 
-  // Skip cases where the mask itself is a low mask (extractu/extbz/exthz handle those)
+  // Skip cases where the mask itself is a low mask (extractu/extbz/exthz handle
+  // those)
   if (isMask_32(Mask))
     return false;
 
@@ -1289,7 +1291,8 @@ bool RISCVDAGToDAGISel::tryCVBitManipBClr(SDNode *Node) {
   unsigned Width = llvm::popcount(InvMask);
   unsigned Offset = llvm::countr_zero(InvMask);
 
-  // Skip if LHS is itself a shift — let extractu handle (srl X, C) & mask patterns
+  // Skip if LHS is itself a shift — let extractu handle (srl X, C) & mask
+  // patterns
   if (LHS.getOpcode() == ISD::SRL)
     return false;
 
@@ -1423,7 +1426,7 @@ bool RISCVDAGToDAGISel::tryCVBitManipInsert(SDNode *Node) {
     if (Width == 0 || Width > 32 || Offset > 31 || (Width + Offset) > 32)
       continue;
 
-    uint32_t LowMask = (1u << Width) - 1;  // (1 << width) - 1
+    uint32_t LowMask = (1u << Width) - 1; // (1 << width) - 1
     SDValue RS1;
 
     // --- Form 1: (shl (and rs1, LowMask), Offset) ---
@@ -1478,8 +1481,9 @@ bool RISCVDAGToDAGISel::tryCVBitManipInsert(SDNode *Node) {
     if (!RS1)
       continue;
 
-    // The OR's input subtree (ClearSide and ValueSide) and the AND inside ClearSide must have a single use.
-    // Otherwise CV_INSERT, with its tied rd_wb = rd, would invalidate the previous rd for downstream consumers.
+    // The OR's input subtree (ClearSide and ValueSide) and the AND inside
+    // ClearSide must have a single use. Otherwise CV_INSERT, with its tied
+    // rd_wb = rd, would invalidate the previous rd for downstream consumers.
     if (!ClearSide.hasOneUse() || !ValueSide.hasOneUse())
       continue;
 



More information about the llvm-commits mailing list