[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