[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