[llvm] [RISCV] Add SelectAddrRegImm26 isel complex pattern for Xqcilo 26-bit load/store (PR #207363)

Garvit Gupta via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 10 11:58:21 PDT 2026


https://github.com/quic-garvgupt updated https://github.com/llvm/llvm-project/pull/207363

>From ce2c1b1ea30dbc4c5f524365821ef3c73b671ff1 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     | 480 ++++++++++++++++++
 1 file changed, 480 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..60358d0ccf7c0
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
@@ -0,0 +1,480 @@
+; 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.
+;
+; 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,+xqcilia -riscv-no-aliases \
+; RUN:   | FileCheck %s --check-prefix=CHECK
+
+;===----------------------------------------------------------------------===;
+; Path A: offset in simm26 but not simm12 -> folded directly into qc.e.lw/sw.
+;===----------------------------------------------------------------------===;
+
+define i32 @load_simm26_pos(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_simm26_pos:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.lw a0, 3000000(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 3000000
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+; Positive simm26 boundary (maxIntN(26) = 33554431).
+define i32 @load_simm26_pos_max(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_simm26_pos_max:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.lw a0, 33554431(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 33554431
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+define i32 @load_simm26_neg(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_simm26_neg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.lw a0, -3000000(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -3000000
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+; Negative simm26 boundary (minIntN(26) = -33554432).
+define i32 @load_simm26_neg_max(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_simm26_neg_max:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.lw a0, -33554432(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554432
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+define void @store_simm26_pos(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_simm26_pos:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.sw a1, 3000000(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 3000000
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+define void @store_simm26_neg(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_simm26_neg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.sw a1, -3000000(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -3000000
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+;===----------------------------------------------------------------------===;
+; Path B: offset just outside simm26 where the saturation adjustment fits simm6
+; -> compressible ADDI (c.addi) + qc.e.lw/sw.
+;===----------------------------------------------------------------------===;
+
+; Positive, boundary (maxIntN(26) + 31): +31 adjustment fits simm6.
+define i32 @load_band_pos_boundary(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_band_pos_boundary:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 8192
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    lw a0, 30(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 33554462
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+; Negative, boundary (minIntN(26) - 32): -32 adjustment fits simm6.
+define i32 @load_band_neg_boundary(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_band_neg_boundary:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 1040384
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    lw a0, -32(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554464
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+define void @store_band_pos_boundary(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_band_pos_boundary:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 8192
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    sw a1, 30(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 33554462
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+define void @store_band_neg_boundary(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_band_neg_boundary:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 1040384
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    sw a1, -32(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554464
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+;===----------------------------------------------------------------------===;
+; Path C: the saturation adjustment does not fit simm6, but the residual low-12
+; offset is 4-aligned and in the c.lw range, therefore the standard lui + add +
+; c.lw/c.sw sequence (with a compressible access) is used.
+;===----------------------------------------------------------------------===;
+
+; Positive, low-12 offset 32 (4-aligned, in c.lw range).
+define i32 @load_pathC_pos(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_pathC_pos:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 8192
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    c.lw a0, 32(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 33554464
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+; Negative, low-12 offset 0 (4-aligned, in c.lw range).
+define i32 @load_pathC_neg(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_pathC_neg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 1040383
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    c.lw a0, 0(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -33558528
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+define void @store_pathC_pos(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_pathC_pos:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 8192
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    c.sw a1, 32(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 33554464
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+define void @store_pathC_neg(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_pathC_neg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 1040383
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    c.sw a1, 0(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -33558528
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+;===----------------------------------------------------------------------===;
+; Path D: the saturation adjustment does not fit simm6, the residual low-12
+; offset is not c.lw-compressible, therefore ADDI + qc.e.lw/sw is emitted.
+;===----------------------------------------------------------------------===;
+
+define i32 @load_pathD_pos(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_pathD_pos:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 8192
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    lw a0, 128(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 33554560
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+define i32 @load_pathD_neg(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_pathD_neg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 1040384
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    lw a0, -128(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554560
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+define void @store_pathD_pos(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_pathD_pos:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 8192
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    sw a1, 128(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 33554560
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+define void @store_pathD_neg(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_pathD_neg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 1040384
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    sw a1, -128(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554560
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+;===----------------------------------------------------------------------===;
+; Path E: the offset is so far outside simm26 that even the simm12 split does
+; not land back in simm26 therefore lui + add + lw is used and no qc.e.addai is
+; emitted for a single-use address.
+;===----------------------------------------------------------------------===;
+
+define i32 @load_too_large(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_too_large:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 17090
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    lw a0, -640(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 70000000
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+define i32 @load_too_large_neg(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_too_large_neg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 1031486
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    lw a0, 640(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -70000000
+  %v = load i32, ptr %g, align 4
+  ret i32 %v
+}
+
+define void @store_too_large(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_too_large:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 17090
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    sw a1, -640(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 70000000
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+define void @store_too_large_neg(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: store_too_large_neg:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a2, 1031486
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    sw a1, 640(a0)
+; CHECK-NEXT:    c.jr ra
+  %g = getelementptr inbounds nuw i8, ptr %p, i32 -70000000
+  store i32 %v, ptr %g, align 4
+  ret void
+}
+
+;===----------------------------------------------------------------------===;
+; Path F: Shared-base / reuse cases: multiple accesses off the same pointer whose
+; large offsets are close enough to share one materialized base.
+;===----------------------------------------------------------------------===;
+
+; Base %p reused (also loaded at a small offset), two accesses.
+define i32 @load_band_reuse2(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_band_reuse2:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 8192
+; CHECK-NEXT:    c.add a1, a0
+; CHECK-NEXT:    lw a1, 568(a1)
+; CHECK-NEXT:    c.lw a0, 100(a0)
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    c.jr ra
+  %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
+  %r1 = add i32 %v1, %v0
+  ret i32 %r1
+}
+
+; Base %p reused with a third large-offset access sharing the common base.
+define i32 @load_band_reuse(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_band_reuse:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    c.mv a1, a0
+; CHECK-NEXT:    qc.e.addai a1, 33554462
+; CHECK-NEXT:    c.lw a0, 100(a0)
+; CHECK-NEXT:    lw a2, 538(a1)
+; CHECK-NEXT:    c.lw a1, 0(a1)
+; CHECK-NEXT:    c.add a0, a2
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    c.jr ra
+  %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
+  %g2 = getelementptr inbounds nuw i8, ptr %p, i32 33554462
+  %v2 = load i32, ptr %g2, align 4
+  %r1 = add i32 %v1, %v0
+  %r2 = add i32 %r1, %v2
+  ret i32 %r2
+}
+
+; Four large-offset accesses; the last one's delta from the shared base exceeds
+; simm26 (by 1), so it needs a separate base materialization.
+define i32 @load_band_reuse_4(ptr %p) nounwind optsize {
+; CHECK-LABEL: load_band_reuse_4:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    lui a1, 16384
+; CHECK-NEXT:    c.mv a2, a0
+; CHECK-NEXT:    c.lw a3, 100(a0)
+; CHECK-NEXT:    qc.e.addai a2, 33554462
+; CHECK-NEXT:    lw a4, 538(a2)
+; CHECK-NEXT:    c.lw a5, 0(a2)
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    qc.e.lw a1, 33554431(a2)
+; CHECK-NEXT:    lw a0, 30(a0)
+; CHECK-NEXT:    c.add a3, a4
+; CHECK-NEXT:    c.add a1, a5
+; CHECK-NEXT:    c.add a1, a3
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    c.jr ra
+  %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
+  %g2 = getelementptr inbounds nuw i8, ptr %p, i32 33554462
+  %v2 = load i32, ptr %g2, align 4
+  %g3 = getelementptr inbounds nuw i8, ptr %p, i32 67108893
+  %v3 = load i32, ptr %g3, align 4
+  %g4 = getelementptr inbounds nuw i8, ptr %p, i32 67108894
+  %v4 = load i32, ptr %g4, align 4
+  %r1 = add i32 %v1, %v0
+  %r2 = add i32 %r1, %v2
+  %r3 = add i32 %r2, %v3
+  %r4 = add i32 %r3, %v4
+  ret i32 %r4
+}
+
+; Two loads whose offsets differ by a foldable 26-bit amount: the DAG shares a
+; common base %p + 70000000 (materialized once as qc.e.addai) and folds the
+; second access's residual into a qc.e.lw.
+define i32 @qcaddai_plus_qcelw(ptr %p) nounwind optsize {
+; CHECK-LABEL: qcaddai_plus_qcelw:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.addai a0, 70000000
+; CHECK-NEXT:    c.lw a1, 0(a0)
+; CHECK-NEXT:    qc.e.lw a0, 40000(a0)
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    c.jr ra
+  %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 of the shared-base qc.e.addai case above.
+define void @qcaddai_plus_qcesw(ptr %p, i32 %v) nounwind optsize {
+; CHECK-LABEL: qcaddai_plus_qcesw:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.addai a0, 70000000
+; CHECK-NEXT:    c.sw a1, 0(a0)
+; CHECK-NEXT:    qc.e.sw a1, 40000(a0)
+; CHECK-NEXT:    c.jr ra
+  %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
+}
+
+;===----------------------------------------------------------------------===;
+; Path G: Offset is within compressed immediate range for loads however due to
+; register pressure, register allocator is not able to allocate any register
+; from GPRC register set resulting in lw not being compressed.
+;===----------------------------------------------------------------------===;
+
+define i32 @load_no_clw_reg_pressure(ptr %p) nounwind {
+; CHECK-LABEL: load_no_clw_reg_pressure:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    c.lw a1, 0(a0)
+; CHECK-NEXT:    c.lw a2, 4(a0)
+; CHECK-NEXT:    c.lw a3, 8(a0)
+; CHECK-NEXT:    c.lw a4, 12(a0)
+; CHECK-NEXT:    c.lw a5, 16(a0)
+; CHECK-NEXT:    lw a6, 20(a0)
+; CHECK-NEXT:    lw a7, 24(a0)
+; CHECK-NEXT:    lw t0, 28(a0)
+; CHECK-NEXT:    lw t1, 32(a0)
+; CHECK-NEXT:    lw t2, 36(a0)
+; CHECK-NEXT:    c.add a1, a2
+; CHECK-NEXT:    c.add a3, a4
+; CHECK-NEXT:    c.lw a2, 40(a0)
+; CHECK-NEXT:    c.lw a0, 44(a0)
+; CHECK-NEXT:    c.add a1, a3
+; CHECK-NEXT:    c.add a5, a6
+; CHECK-NEXT:    c.add t0, t1
+; CHECK-NEXT:    c.add a5, a7
+; CHECK-NEXT:    c.add t0, t2
+; CHECK-NEXT:    c.add a1, a5
+; CHECK-NEXT:    c.add a2, t0
+; CHECK-NEXT:    c.add a1, a2
+; CHECK-NEXT:    c.add a0, a1
+; CHECK-NEXT:    c.jr ra
+  %g0  = getelementptr inbounds nuw i8, ptr %p, i32 0
+  %g1  = getelementptr inbounds nuw i8, ptr %p, i32 4
+  %g2  = getelementptr inbounds nuw i8, ptr %p, i32 8
+  %g3  = getelementptr inbounds nuw i8, ptr %p, i32 12
+  %g4  = getelementptr inbounds nuw i8, ptr %p, i32 16
+  %g5  = getelementptr inbounds nuw i8, ptr %p, i32 20
+  %g6  = getelementptr inbounds nuw i8, ptr %p, i32 24
+  %g7  = getelementptr inbounds nuw i8, ptr %p, i32 28
+  %g8  = getelementptr inbounds nuw i8, ptr %p, i32 32
+  %g9  = getelementptr inbounds nuw i8, ptr %p, i32 36
+  %g10 = getelementptr inbounds nuw i8, ptr %p, i32 40
+  %g11 = getelementptr inbounds nuw i8, ptr %p, i32 44
+  %v0  = load i32, ptr %g0
+  %v1  = load i32, ptr %g1
+  %v2  = load i32, ptr %g2
+  %v3  = load i32, ptr %g3
+  %v4  = load i32, ptr %g4
+  %v5  = load i32, ptr %g5
+  %v6  = load i32, ptr %g6
+  %v7  = load i32, ptr %g7
+  %v8  = load i32, ptr %g8
+  %v9  = load i32, ptr %g9
+  %v10 = load i32, ptr %g10
+  %v11 = load i32, ptr %g11
+  %s0  = add i32 %v0,  %v1
+  %s1  = add i32 %s0,  %v2
+  %s2  = add i32 %s1,  %v3
+  %s3  = add i32 %s2,  %v4
+  %s4  = add i32 %s3,  %v5
+  %s5  = add i32 %s4,  %v6
+  %s6  = add i32 %s5,  %v7
+  %s7  = add i32 %s6,  %v8
+  %s8  = add i32 %s7,  %v9
+  %s9  = add i32 %s8,  %v10
+  %s10 = add i32 %s9,  %v11
+  ret i32 %s10
+}

>From 3f0050ae71ed2a65a9e3351821fb92932602541f 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 SelectAddrRegImm26 isel complex pattern for
 Xqcilo 26-bit 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:
 - 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 ADDI plus a
   folded 26-bit remainder.
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   | 67 +++++++++++++++++++
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.h     |  1 +
 llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td   | 12 ++--
 .../CodeGen/RISCV/xqcilo-addr-regimm26.ll     | 46 ++++++-------
 4 files changed, 95 insertions(+), 31 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index df0a34ea27685..30ba9d7290ffe 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -3589,6 +3589,73 @@ 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);
+      Offset = CurDAG->getSignedTargetConstant(CVal, DL, VT);
+      return true;
+    }
+  }
+
+  // The offset is just outside the 26-bit range. Split off a small (simm12)
+  // adjustment with a plain ADDI and fold the remaining 26-bit offset into the
+  // load/store. A plain ADDI is used (rather than the wide
+  // qc.e.addi/qc.e.addai) because the adjustment fits simm12: this keeps it a
+  // short, compressible (c.addi) instruction and is available without Xqcilia.
+  //
+  // Skip the split if the address is used other than as a foldable load/store
+  // base. `isWorthFoldingAdd()` returns true when every user of the add node is
+  // a scalar load/store using it as an address operand. If it return false, it
+  // means that some use consumes the add result as a value (e.g. it feeds
+  // another add, is a stored value, is used in arithmetic) and that use forces
+  // the add to be materialized into a register.
+  if (Addr.getOpcode() == ISD::ADD && isa<ConstantSDNode>(Addr.getOperand(1)) &&
+      isWorthFoldingAdd(Addr)) {
+    int64_t CVal = cast<ConstantSDNode>(Addr.getOperand(1))->getSExtValue();
+    if (!isInt<26>(CVal)) {
+      // check if lw in lui + add + lw combination can be compressed.
+      // The check here purely based on the immediate value and hopes that
+      // register allocator would assign a register from a GPRC set so that the
+      // instruction can get compressed.
+      bool IsLwCompressable = false;
+      if (isShiftedUInt<5, 2>(CVal & ((1 << 12) - 1)))
+        IsLwCompressable = true;
+
+      int64_t Imm26 = CVal < 0 ? minIntN(26) : maxIntN(26);
+      int64_t Adj = CVal - Imm26;
+      // If Adj fits within 6-bits, then both combinations will take 8 bytes
+      // however c.addi + qc.e.lw/sw will take 1 less cycle. Also, if lw is not
+      // compressable then both combination would take 10 bytes but again
+      // addi + qc.e.lw/sw will take 1 less cycle.
+      if (isInt<6>(Adj) || (isInt<12>(Adj) && !IsLwCompressable)) {
+        Base = SDValue(CurDAG->getMachineNode(
+                           RISCV::ADDI, DL, VT, Addr.getOperand(0),
+                           CurDAG->getSignedTargetConstant(Adj, DL, VT)),
+                       0);
+        Offset = CurDAG->getSignedTargetConstant(Imm26, 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 60358d0ccf7c0..e7c3b50b5c7e0 100644
--- a/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
+++ b/llvm/test/CodeGen/RISCV/xqcilo-addr-regimm26.ll
@@ -5,6 +5,7 @@
 ; 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,+xqcilia -riscv-no-aliases \
 ; RUN:   | FileCheck %s --check-prefix=CHECK
 
@@ -83,9 +84,8 @@ define void @store_simm26_neg(ptr %p, i32 %v) nounwind optsize {
 define i32 @load_band_pos_boundary(ptr %p) nounwind optsize {
 ; CHECK-LABEL: load_band_pos_boundary:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a1, 8192
-; CHECK-NEXT:    c.add a0, a1
-; CHECK-NEXT:    lw a0, 30(a0)
+; CHECK-NEXT:    c.addi a0, 31
+; CHECK-NEXT:    qc.e.lw a0, 33554431(a0)
 ; CHECK-NEXT:    c.jr ra
   %g = getelementptr inbounds nuw i8, ptr %p, i32 33554462
   %v = load i32, ptr %g, align 4
@@ -96,9 +96,8 @@ define i32 @load_band_pos_boundary(ptr %p) nounwind optsize {
 define i32 @load_band_neg_boundary(ptr %p) nounwind optsize {
 ; CHECK-LABEL: load_band_neg_boundary:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a1, 1040384
-; CHECK-NEXT:    c.add a0, a1
-; CHECK-NEXT:    lw a0, -32(a0)
+; CHECK-NEXT:    c.addi a0, -32
+; CHECK-NEXT:    qc.e.lw a0, -33554432(a0)
 ; CHECK-NEXT:    c.jr ra
   %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554464
   %v = load i32, ptr %g, align 4
@@ -108,9 +107,8 @@ define i32 @load_band_neg_boundary(ptr %p) nounwind optsize {
 define void @store_band_pos_boundary(ptr %p, i32 %v) nounwind optsize {
 ; CHECK-LABEL: store_band_pos_boundary:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a2, 8192
-; CHECK-NEXT:    c.add a0, a2
-; CHECK-NEXT:    sw a1, 30(a0)
+; CHECK-NEXT:    c.addi a0, 31
+; CHECK-NEXT:    qc.e.sw a1, 33554431(a0)
 ; CHECK-NEXT:    c.jr ra
   %g = getelementptr inbounds nuw i8, ptr %p, i32 33554462
   store i32 %v, ptr %g, align 4
@@ -120,9 +118,8 @@ define void @store_band_pos_boundary(ptr %p, i32 %v) nounwind optsize {
 define void @store_band_neg_boundary(ptr %p, i32 %v) nounwind optsize {
 ; CHECK-LABEL: store_band_neg_boundary:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a2, 1040384
-; CHECK-NEXT:    c.add a0, a2
-; CHECK-NEXT:    sw a1, -32(a0)
+; CHECK-NEXT:    c.addi a0, -32
+; CHECK-NEXT:    qc.e.sw a1, -33554432(a0)
 ; CHECK-NEXT:    c.jr ra
   %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554464
   store i32 %v, ptr %g, align 4
@@ -193,9 +190,8 @@ define void @store_pathC_neg(ptr %p, i32 %v) nounwind optsize {
 define i32 @load_pathD_pos(ptr %p) nounwind optsize {
 ; CHECK-LABEL: load_pathD_pos:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a1, 8192
-; CHECK-NEXT:    c.add a0, a1
-; CHECK-NEXT:    lw a0, 128(a0)
+; CHECK-NEXT:    addi a0, a0, 129
+; CHECK-NEXT:    qc.e.lw a0, 33554431(a0)
 ; CHECK-NEXT:    c.jr ra
   %g = getelementptr inbounds nuw i8, ptr %p, i32 33554560
   %v = load i32, ptr %g, align 4
@@ -205,9 +201,8 @@ define i32 @load_pathD_pos(ptr %p) nounwind optsize {
 define i32 @load_pathD_neg(ptr %p) nounwind optsize {
 ; CHECK-LABEL: load_pathD_neg:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a1, 1040384
-; CHECK-NEXT:    c.add a0, a1
-; CHECK-NEXT:    lw a0, -128(a0)
+; CHECK-NEXT:    addi a0, a0, -128
+; CHECK-NEXT:    qc.e.lw a0, -33554432(a0)
 ; CHECK-NEXT:    c.jr ra
   %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554560
   %v = load i32, ptr %g, align 4
@@ -217,9 +212,8 @@ define i32 @load_pathD_neg(ptr %p) nounwind optsize {
 define void @store_pathD_pos(ptr %p, i32 %v) nounwind optsize {
 ; CHECK-LABEL: store_pathD_pos:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a2, 8192
-; CHECK-NEXT:    c.add a0, a2
-; CHECK-NEXT:    sw a1, 128(a0)
+; CHECK-NEXT:    addi a0, a0, 129
+; CHECK-NEXT:    qc.e.sw a1, 33554431(a0)
 ; CHECK-NEXT:    c.jr ra
   %g = getelementptr inbounds nuw i8, ptr %p, i32 33554560
   store i32 %v, ptr %g, align 4
@@ -229,9 +223,8 @@ define void @store_pathD_pos(ptr %p, i32 %v) nounwind optsize {
 define void @store_pathD_neg(ptr %p, i32 %v) nounwind optsize {
 ; CHECK-LABEL: store_pathD_neg:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a2, 1040384
-; CHECK-NEXT:    c.add a0, a2
-; CHECK-NEXT:    sw a1, -128(a0)
+; CHECK-NEXT:    addi a0, a0, -128
+; CHECK-NEXT:    qc.e.sw a1, -33554432(a0)
 ; CHECK-NEXT:    c.jr ra
   %g = getelementptr inbounds nuw i8, ptr %p, i32 -33554560
   store i32 %v, ptr %g, align 4
@@ -301,10 +294,9 @@ define void @store_too_large_neg(ptr %p, i32 %v) nounwind optsize {
 define i32 @load_band_reuse2(ptr %p) nounwind optsize {
 ; CHECK-LABEL: load_band_reuse2:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    lui a1, 8192
-; CHECK-NEXT:    c.add a1, a0
-; CHECK-NEXT:    lw a1, 568(a1)
+; CHECK-NEXT:    addi a1, a0, 569
 ; CHECK-NEXT:    c.lw a0, 100(a0)
+; CHECK-NEXT:    qc.e.lw a1, 33554431(a1)
 ; CHECK-NEXT:    c.add a0, a1
 ; CHECK-NEXT:    c.jr ra
   %g0 = getelementptr inbounds nuw i8, ptr %p, i32 33555000



More information about the llvm-commits mailing list