[llvm] [RISCV] Add AddrRegImm26 addressing mode for Xqcilo large-offset load/store (PR #207363)
Garvit Gupta via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 3 03:00:29 PDT 2026
https://github.com/quic-garvgupt created https://github.com/llvm/llvm-project/pull/207363
Introduce a dedicated SelectAddrRegImm26 ComplexPattern for the Qualcomm
Xqcilo large-offset load/store instructions (qc.e.lw/qc.e.sw), whose offset
field is 26 bits wide, and use it in the QC48LdPat/QC48StPat patterns instead
of the generic AddLike + simm26_nosimm12 match.
SelectAddrRegImm26 handles addresses of the form (base + constant):
- simm12 offsets are left to the standard (shorter/compressible) load/store.
- 26-bit (non-simm12) offsets fold directly into qc.e.lw/qc.e.sw.
- Offsets just outside the 26-bit range are split with a compressible ADDI plus a folded 26-bit remainder.
>From aa721d950a903c5581988a41aba62323fe8430c5 Mon Sep 17 00:00:00 2001
From: Garvit Gupta <garvgupt at qti.qualcomm.com>
Date: Fri, 3 Jul 2026 02:22:46 -0700
Subject: [PATCH 1/2] [RISCV] Add pre-commit test for large-offset load/store
addressing (Xqcilo)
Add a test capturing the current -Os codegen for load/store addresses of the
form (base + large constant offset) under the Qualcomm Xqcilo extension. An
offset just outside the 26-bit range is currently materialized with LUI + ADD,
folding only a 12-bit remainder into a plain load/store. A follow-up adds a
dedicated 26-bit addressing mode that splits off a compressible ADDI and folds
the 26-bit remainder into qc.e.lw/qc.e.sw.
Plain 26-bit-offset folding into qc.e.lw/qc.e.sw is already covered by
xqcilo.ll.
---
.../CodeGen/RISCV/xqcilo-addr-regimm26.ll | 158 ++++++++++++++++++
1 file changed, 158 insertions(+)
create mode 100644 llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
diff --git a/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll b/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
new file mode 100644
index 0000000000000..38970df541c08
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
@@ -0,0 +1,158 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; Codegen for load/store addresses of the form (base + large constant offset)
+; under the Qualcomm Xqcilo/Xqcilia extensions, at -Os (optsize). The IR is in
+; the canonical form produced by the -O2/optsize middle-end (GEP offsets are
+; kept intact and reach the backend as (add base, constant)).
+;
+; Plain 26-bit-offset load/store folding into qc.e.lw/qc.e.sw is covered by
+; xqcilo.ll; here we test the cases just outside the 26-bit range and the
+; combination of a qc.e.addi/qc.e.addai base with a folded qc.e.lw/qc.e.sw.
+; RUN: llc < %s -mtriple=riscv32 -mattr=+xqcilo \
+; RUN: | FileCheck %s --check-prefix=RV32XQCILO
+; RUN: llc < %s -mtriple=riscv32 -mattr=+xqcilo,+xqcilia \
+; RUN: | FileCheck %s --check-prefix=RV32XQCILOA
+
+; Single load at an offset just outside the 26-bit range (33555000). Split off a
+; 12-bit ADDI and fold the 26-bit remainder into a qc.e.lw.
+define i32 @load_band(ptr %p) optsize {
+; RV32XQCILO-LABEL: load_band:
+; RV32XQCILO: # %bb.0:
+; RV32XQCILO-NEXT: lui a1, 8192
+; RV32XQCILO-NEXT: add a0, a0, a1
+; RV32XQCILO-NEXT: lw a0, 568(a0)
+; RV32XQCILO-NEXT: ret
+;
+; RV32XQCILOA-LABEL: load_band:
+; RV32XQCILOA: # %bb.0:
+; RV32XQCILOA-NEXT: lui a1, 8192
+; RV32XQCILOA-NEXT: add a0, a0, a1
+; RV32XQCILOA-NEXT: lw a0, 568(a0)
+; RV32XQCILOA-NEXT: ret
+ %g = getelementptr inbounds nuw i8, ptr %p, i32 33555000
+ %v = load i32, ptr %g, align 4
+ ret i32 %v
+}
+
+; Single store at an offset just outside the 26-bit range.
+define void @store_band(ptr %p, i32 %v) optsize {
+; RV32XQCILO-LABEL: store_band:
+; RV32XQCILO: # %bb.0:
+; RV32XQCILO-NEXT: lui a2, 8192
+; RV32XQCILO-NEXT: add a0, a0, a2
+; RV32XQCILO-NEXT: sw a1, 568(a0)
+; RV32XQCILO-NEXT: ret
+;
+; RV32XQCILOA-LABEL: store_band:
+; RV32XQCILOA: # %bb.0:
+; RV32XQCILOA-NEXT: lui a2, 8192
+; RV32XQCILOA-NEXT: add a0, a0, a2
+; RV32XQCILOA-NEXT: sw a1, 568(a0)
+; RV32XQCILOA-NEXT: ret
+ %g = getelementptr inbounds nuw i8, ptr %p, i32 33555000
+ store i32 %v, ptr %g, align 4
+ ret void
+}
+
+; Base %p reused (also loaded at a small offset). The non-destructive ADDI keeps
+; the base live, avoiding a copy.
+define i32 @load_band_reuse(ptr %p) optsize {
+; RV32XQCILO-LABEL: load_band_reuse:
+; RV32XQCILO: # %bb.0:
+; RV32XQCILO-NEXT: lui a1, 8192
+; RV32XQCILO-NEXT: add a1, a1, a0
+; RV32XQCILO-NEXT: lw a1, 568(a1)
+; RV32XQCILO-NEXT: lw a0, 100(a0)
+; RV32XQCILO-NEXT: add a0, a0, a1
+; RV32XQCILO-NEXT: ret
+;
+; RV32XQCILOA-LABEL: load_band_reuse:
+; RV32XQCILOA: # %bb.0:
+; RV32XQCILOA-NEXT: lui a1, 8192
+; RV32XQCILOA-NEXT: add a1, a1, a0
+; RV32XQCILOA-NEXT: lw a1, 568(a1)
+; RV32XQCILOA-NEXT: lw a0, 100(a0)
+; RV32XQCILOA-NEXT: add a0, a0, a1
+; RV32XQCILOA-NEXT: ret
+ %g0 = getelementptr inbounds nuw i8, ptr %p, i32 33555000
+ %v0 = load i32, ptr %g0, align 4
+ %g1 = getelementptr inbounds nuw i8, ptr %p, i32 100
+ %v1 = load i32, ptr %g1, align 4
+ %r = add i32 %v1, %v0
+ ret i32 %r
+}
+
+; Two loads: the base is advanced to a >32-bit-reachable region with a single
+; qc.e.addai, and a second access a 26-bit offset further folds into a qc.e.lw
+; off that base (qc.e.addai + qc.e.lw).
+define i32 @qcaddai_plus_qcelw(ptr %p) optsize {
+; RV32XQCILO-LABEL: qcaddai_plus_qcelw:
+; RV32XQCILO: # %bb.0:
+; RV32XQCILO-NEXT: lui a1, 17090
+; RV32XQCILO-NEXT: addi a1, a1, -640
+; RV32XQCILO-NEXT: add a0, a0, a1
+; RV32XQCILO-NEXT: lw a1, 0(a0)
+; RV32XQCILO-NEXT: qc.e.lw a0, 40000(a0)
+; RV32XQCILO-NEXT: add a0, a0, a1
+; RV32XQCILO-NEXT: ret
+;
+; RV32XQCILOA-LABEL: qcaddai_plus_qcelw:
+; RV32XQCILOA: # %bb.0:
+; RV32XQCILOA-NEXT: qc.e.addai a0, 70000000
+; RV32XQCILOA-NEXT: lw a1, 0(a0)
+; RV32XQCILOA-NEXT: qc.e.lw a0, 40000(a0)
+; RV32XQCILOA-NEXT: add a0, a0, a1
+; RV32XQCILOA-NEXT: ret
+ %g0 = getelementptr inbounds nuw i8, ptr %p, i32 70000000
+ %v0 = load i32, ptr %g0, align 4
+ %g1 = getelementptr inbounds nuw i8, ptr %p, i32 70040000
+ %v1 = load i32, ptr %g1, align 4
+ %r = add i32 %v0, %v1
+ ret i32 %r
+}
+
+; Store variant: qc.e.addai base + folded qc.e.sw.
+define void @qcaddai_plus_qcesw(ptr %p, i32 %v) optsize {
+; RV32XQCILO-LABEL: qcaddai_plus_qcesw:
+; RV32XQCILO: # %bb.0:
+; RV32XQCILO-NEXT: lui a2, 17090
+; RV32XQCILO-NEXT: addi a2, a2, -640
+; RV32XQCILO-NEXT: add a0, a0, a2
+; RV32XQCILO-NEXT: sw a1, 0(a0)
+; RV32XQCILO-NEXT: qc.e.sw a1, 40000(a0)
+; RV32XQCILO-NEXT: ret
+;
+; RV32XQCILOA-LABEL: qcaddai_plus_qcesw:
+; RV32XQCILOA: # %bb.0:
+; RV32XQCILOA-NEXT: qc.e.addai a0, 70000000
+; RV32XQCILOA-NEXT: sw a1, 0(a0)
+; RV32XQCILOA-NEXT: qc.e.sw a1, 40000(a0)
+; RV32XQCILOA-NEXT: ret
+ %g0 = getelementptr inbounds nuw i8, ptr %p, i32 70000000
+ store i32 %v, ptr %g0, align 4
+ %g1 = getelementptr inbounds nuw i8, ptr %p, i32 70040000
+ store i32 %v, ptr %g1, align 4
+ ret void
+}
+
+; An offset that is > 26-bit and too far outside the range for the ADDI + simm26
+; split (70000000): SelectAddrRegImm26 declines, and the standard lowering
+; materializes the high bits with LUI + ADD and folds only the low 12 bits into
+; a plain lw. The AddrRegImm26 change does not affect this case.
+define i32 @load_too_large(ptr %p) optsize {
+; RV32XQCILO-LABEL: load_too_large:
+; RV32XQCILO: # %bb.0:
+; RV32XQCILO-NEXT: lui a1, 17090
+; RV32XQCILO-NEXT: add a0, a0, a1
+; RV32XQCILO-NEXT: lw a0, -640(a0)
+; RV32XQCILO-NEXT: ret
+;
+; RV32XQCILOA-LABEL: load_too_large:
+; RV32XQCILOA: # %bb.0:
+; RV32XQCILOA-NEXT: lui a1, 17090
+; RV32XQCILOA-NEXT: add a0, a0, a1
+; RV32XQCILOA-NEXT: lw a0, -640(a0)
+; RV32XQCILOA-NEXT: ret
+ %g = getelementptr inbounds nuw i8, ptr %p, i32 70000000
+ %v = load i32, ptr %g, align 4
+ ret i32 %v
+}
>From 3d9db99bfe4f508fc463f1055767a69da33c9150 Mon Sep 17 00:00:00 2001
From: Garvit Gupta <garvgupt at qti.qualcomm.com>
Date: Fri, 3 Jul 2026 02:24:15 -0700
Subject: [PATCH 2/2] [RISCV] Add AddrRegImm26 addressing mode for Xqcilo
large-offset load/store
Introduce a dedicated SelectAddrRegImm26 ComplexPattern for the Qualcomm
Xqcilo large-offset load/store instructions (qc.e.lw/qc.e.sw), whose offset
field is 26 bits wide, and use it in the QC48LdPat/QC48StPat patterns instead
of the generic AddLike + simm26_nosimm12 match.
SelectAddrRegImm26 handles addresses of the form (base + constant):
- simm12 offsets are left to the standard (shorter/compressible) load/store.
- 26-bit (non-simm12) offsets fold directly into qc.e.lw/qc.e.sw.
- Offsets just outside the 26-bit range are split with a non-destructive
(compressible) ADDI plus a folded 26-bit remainder. Unlike a single
destructive qc.e.addai, the ADDI keeps the base register live, avoiding a
copy when the base is reused. This saves an instruction.
Updates xqcilo-addr-regimm26.ll to show the code-size improvement for offsets
just outside the 26-bit range.
---
llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp | 53 +++++++++++++++++++
llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h | 1 +
llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td | 12 +++--
.../CodeGen/RISCV/xqcilo-addr-regimm26.ll | 30 +++++------
4 files changed, 74 insertions(+), 22 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index df0a34ea27685..c8518d264744c 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -3589,6 +3589,59 @@ bool RISCVDAGToDAGISel::SelectAddrRegImm(SDValue Addr, SDValue &Base,
return true;
}
+/// Similar to SelectAddrRegImm, except that the offset is a 26-bit signed
+/// immediate. This is used by the Qualcomm Xqcilo large offset load/store
+/// instructions (qc.e.lw/qc.e.sw), whose offset field is 26 bits wide.
+/// Only matches offsets that do not fit a 12-bit signed immediate, so that
+/// offsets in the simm12 range keep using the shorter (and possibly
+/// compressible) standard load/store instructions.
+bool RISCVDAGToDAGISel::SelectAddrRegImm26(SDValue Addr, SDValue &Base,
+ SDValue &Offset) {
+ SDLoc DL(Addr);
+ MVT VT = Addr.getSimpleValueType();
+
+ if (CurDAG->isBaseWithConstantOffset(Addr)) {
+ int64_t CVal = cast<ConstantSDNode>(Addr.getOperand(1))->getSExtValue();
+ // Fold a 26-bit (but not 12-bit) signed offset directly into the
+ // load/store.
+ if (isInt<26>(CVal) && !isInt<12>(CVal)) {
+ Base = Addr.getOperand(0);
+ if (auto *FIN = dyn_cast<FrameIndexSDNode>(Base))
+ Base = CurDAG->getTargetFrameIndex(FIN->getIndex(), VT);
+ Offset = CurDAG->getSignedTargetConstant(CVal, DL, VT);
+ return true;
+ }
+ }
+
+ // The offset is just outside the 26-bit range. Split off a 12-bit signed
+ // adjustment with a (non-destructive, potentially compressible) ADDI and
+ // fold the remaining 26-bit offset into the load/store. Unlike a single
+ // destructive qc.e.addai, the ADDI writes a fresh register, keeping the
+ // base live for other uses (avoiding an extra copy).
+ // Handle ADD with large immediates.
+ if (Addr.getOpcode() == ISD::ADD &&
+ isa<ConstantSDNode>(Addr.getOperand(1))) {
+ int64_t CVal = cast<ConstantSDNode>(Addr.getOperand(1))->getSExtValue();
+ // Only offsets that don't fit simm26 need splitting; simm26 (but not
+ // simm12) offsets are folded directly above, and simm12 offsets are left
+ // for the standard load/store instructions.
+ if (!isInt<26>(CVal)) {
+ int64_t Adj = CVal < 0 ? -2048 : 2047;
+ if (isInt<26>(CVal - Adj)) {
+ Base = SDValue(CurDAG->getMachineNode(
+ RISCV::ADDI, DL, VT, Addr.getOperand(0),
+ CurDAG->getSignedTargetConstant(Adj, DL, VT)),
+ 0);
+ Offset = CurDAG->getSignedTargetConstant(CVal - Adj, DL, VT);
+ return true;
+ }
+ }
+ }
+
+ // Don't match: let the standard addressing modes handle it.
+ return false;
+}
+
/// Similar to SelectAddrRegImm, except that the offset is restricted to uimm9.
bool RISCVDAGToDAGISel::SelectAddrRegImm9(SDValue Addr, SDValue &Base,
SDValue &Offset) {
diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
index 85bbf31425030..190dc083d014a 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h
@@ -49,6 +49,7 @@ class RISCVDAGToDAGISel : public SelectionDAGISel {
bool SelectAddrFrameIndex(SDValue Addr, SDValue &Base, SDValue &Offset);
bool SelectAddrRegImm(SDValue Addr, SDValue &Base, SDValue &Offset);
+ bool SelectAddrRegImm26(SDValue Addr, SDValue &Base, SDValue &Offset);
bool SelectAddrRegImm9(SDValue Addr, SDValue &Base, SDValue &Offset);
bool SelectAddrRegImmLsb00000(SDValue Addr, SDValue &Base, SDValue &Offset);
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
index f6b7c4ddda6ca..bb7b2bf08644e 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
@@ -207,6 +207,10 @@ def AddLike: PatFrags<(ops node:$A, node:$B),
return CurDAG->isBaseWithConstantOffset(SDValue(N, 0));
}]>;
+// Addressing mode for the Xqcilo large-offset load/store instructions
+// (qc.e.lw/qc.e.sw), which have a 26-bit signed offset field.
+def AddrRegImm26 : ComplexPattern<iPTR, 2, "SelectAddrRegImm26">;
+
def SingleBitSetMaskImm12 : RISCVOp<XLenVT>, ImmLeaf<XLenVT, [{
return isPowerOf2_32(Imm) && isInt<12>(Imm) && Imm != 1;
}], SingleBitSetMaskToIndex>;
@@ -1511,12 +1515,12 @@ class PatGprNoX0GprNoX0<SDPatternOperator OpNode, RVInstR Inst>
(Inst GPRNoX0:$rs1, GPRNoX0:$rs2)>;
class QC48LdPat<PatFrag LoadOp, RVInst48 Inst>
- : Pat<(i32 (LoadOp (AddLike (i32 GPR:$rs1), simm26_nosimm12:$imm26))),
- (Inst GPR:$rs1, simm26_nosimm12:$imm26)>;
+ : Pat<(i32 (LoadOp (AddrRegImm26 (i32 GPR:$rs1), simm26:$imm26))),
+ (Inst GPR:$rs1, simm26:$imm26)>;
class QC48StPat<PatFrag StoreOp, RVInst48 Inst>
- : Pat<(StoreOp (i32 GPR:$rs2), (AddLike (i32 GPR:$rs1), simm26_nosimm12:$imm26)),
- (Inst GPR:$rs2, GPR:$rs1, simm26_nosimm12:$imm26)>;
+ : Pat<(StoreOp (i32 GPR:$rs2), (AddrRegImm26 (i32 GPR:$rs1), simm26:$imm26)),
+ (Inst GPR:$rs2, GPR:$rs1, simm26:$imm26)>;
def AddrRegRegScale7 : AddrRegRegScale<7>;
diff --git a/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll b/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
index 38970df541c08..ee6fe480737ab 100644
--- a/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
+++ b/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
@@ -17,16 +17,14 @@
define i32 @load_band(ptr %p) optsize {
; RV32XQCILO-LABEL: load_band:
; RV32XQCILO: # %bb.0:
-; RV32XQCILO-NEXT: lui a1, 8192
-; RV32XQCILO-NEXT: add a0, a0, a1
-; RV32XQCILO-NEXT: lw a0, 568(a0)
+; RV32XQCILO-NEXT: addi a0, a0, 2047
+; RV32XQCILO-NEXT: qc.e.lw a0, 33552953(a0)
; RV32XQCILO-NEXT: ret
;
; RV32XQCILOA-LABEL: load_band:
; RV32XQCILOA: # %bb.0:
-; RV32XQCILOA-NEXT: lui a1, 8192
-; RV32XQCILOA-NEXT: add a0, a0, a1
-; RV32XQCILOA-NEXT: lw a0, 568(a0)
+; RV32XQCILOA-NEXT: addi a0, a0, 2047
+; RV32XQCILOA-NEXT: qc.e.lw a0, 33552953(a0)
; RV32XQCILOA-NEXT: ret
%g = getelementptr inbounds nuw i8, ptr %p, i32 33555000
%v = load i32, ptr %g, align 4
@@ -37,16 +35,14 @@ define i32 @load_band(ptr %p) optsize {
define void @store_band(ptr %p, i32 %v) optsize {
; RV32XQCILO-LABEL: store_band:
; RV32XQCILO: # %bb.0:
-; RV32XQCILO-NEXT: lui a2, 8192
-; RV32XQCILO-NEXT: add a0, a0, a2
-; RV32XQCILO-NEXT: sw a1, 568(a0)
+; RV32XQCILO-NEXT: addi a0, a0, 2047
+; RV32XQCILO-NEXT: qc.e.sw a1, 33552953(a0)
; RV32XQCILO-NEXT: ret
;
; RV32XQCILOA-LABEL: store_band:
; RV32XQCILOA: # %bb.0:
-; RV32XQCILOA-NEXT: lui a2, 8192
-; RV32XQCILOA-NEXT: add a0, a0, a2
-; RV32XQCILOA-NEXT: sw a1, 568(a0)
+; RV32XQCILOA-NEXT: addi a0, a0, 2047
+; RV32XQCILOA-NEXT: qc.e.sw a1, 33552953(a0)
; RV32XQCILOA-NEXT: ret
%g = getelementptr inbounds nuw i8, ptr %p, i32 33555000
store i32 %v, ptr %g, align 4
@@ -58,19 +54,17 @@ define void @store_band(ptr %p, i32 %v) optsize {
define i32 @load_band_reuse(ptr %p) optsize {
; RV32XQCILO-LABEL: load_band_reuse:
; RV32XQCILO: # %bb.0:
-; RV32XQCILO-NEXT: lui a1, 8192
-; RV32XQCILO-NEXT: add a1, a1, a0
-; RV32XQCILO-NEXT: lw a1, 568(a1)
+; RV32XQCILO-NEXT: addi a1, a0, 2047
; RV32XQCILO-NEXT: lw a0, 100(a0)
+; RV32XQCILO-NEXT: qc.e.lw a1, 33552953(a1)
; RV32XQCILO-NEXT: add a0, a0, a1
; RV32XQCILO-NEXT: ret
;
; RV32XQCILOA-LABEL: load_band_reuse:
; RV32XQCILOA: # %bb.0:
-; RV32XQCILOA-NEXT: lui a1, 8192
-; RV32XQCILOA-NEXT: add a1, a1, a0
-; RV32XQCILOA-NEXT: lw a1, 568(a1)
+; RV32XQCILOA-NEXT: addi a1, a0, 2047
; RV32XQCILOA-NEXT: lw a0, 100(a0)
+; RV32XQCILOA-NEXT: qc.e.lw a1, 33552953(a1)
; RV32XQCILOA-NEXT: add a0, a0, a1
; RV32XQCILOA-NEXT: ret
%g0 = getelementptr inbounds nuw i8, ptr %p, i32 33555000
More information about the llvm-commits
mailing list