[llvm] [RISCV] Fold 26-bit frame-index offsets into Xqcilo/Xqcilia load/store/addi (PR #209315)

Garvit Gupta via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 13 14:47:39 PDT 2026


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

>From 0e07fd786619e553cf4774378749ca126c1e94db Mon Sep 17 00:00:00 2001
From: Garvit Gupta <garvgupt at qti.qualcomm.com>
Date: Mon, 13 Jul 2026 13:53:18 -0700
Subject: [PATCH 1/2] [RISCV] Add pre-commit test for Xqcilo/Xqcilia
 frame-index addressing

Baseline codegen for stack (frame-index) accesses under Xqcilo/Xqcilia before
the frame-index folding changes. Large (simm26, not simm12) frame offsets are
materialized with a separate base instead of being folded into the wide
qc.e.lw/qc.e.sw/qc.e.addi instructions.
---
 .../RISCV/xqcilo-xqcilia-frame-index.ll       | 118 ++++++++++++++++++
 1 file changed, 118 insertions(+)
 create mode 100644 llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll

diff --git a/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll b/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll
new file mode 100644
index 0000000000000..9b5f57f11ff1f
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll
@@ -0,0 +1,118 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; Frame-index addressing for the Qualcomm Xqcilo/Xqcilia large-offset
+; instructions (qc.e.lw/qc.e.sw/qc.e.addi). Stack accesses whose resolved
+; sp-relative offset is a 26-bit (but not 12-bit) signed immediate fold the
+; offset directly into the wide instruction instead of materialising a base.
+;
+; RUN: llc < %s -mtriple=riscv32 -mattr=+xqcilo,+xqcilia -riscv-no-aliases \
+; RUN:   | FileCheck %s
+
+; Load variant: A load at a large (simm26, not simm12) offset off a stack slot
+; folds the resolved frame offset into a single qc.e.lw. The offset-0 access
+; keeps the alloca live.
+define i32 @stack_fi_load(i32 %x) nounwind {
+; CHECK-LABEL: stack_fi_load:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    qc.e.addi sp, sp, -4000016
+; CHECK-NEXT:    c.addi4spn a1, sp, 16
+; CHECK-NEXT:    qc.e.lw a1, 3000000(a1)
+; CHECK-NEXT:    c.mv a2, a0
+; CHECK-NEXT:    c.mv a0, a1
+; CHECK-NEXT:    c.swsp a2, 16(sp)
+; CHECK-NEXT:    qc.e.addi sp, sp, 4000016
+; CHECK-NEXT:    c.jr ra
+entry:
+  %arr = alloca [4000000 x i8], align 4
+  %p0 = getelementptr inbounds [4000000 x i8], ptr %arr, i32 0, i32 0
+  store i32 %x, ptr %p0, align 4
+  %p1 = getelementptr inbounds [4000000 x i8], ptr %arr, i32 0, i32 3000000
+  %v = load i32, ptr %p1, align 4
+  ret i32 %v
+}
+
+; Store variant: a store at a large (simm26, not simm12) offset off a stack slot
+; folds into a single qc.e.sw.
+define void @stack_fi_store(i32 %x) nounwind {
+; CHECK-LABEL: stack_fi_store:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    qc.e.addi sp, sp, -4000016
+; CHECK-NEXT:    c.addi4spn a1, sp, 16
+; CHECK-NEXT:    qc.e.sw a0, 3000000(a1)
+; CHECK-NEXT:    c.swsp a0, 16(sp)
+; CHECK-NEXT:    qc.e.addi sp, sp, 4000016
+; CHECK-NEXT:    c.jr ra
+entry:
+  %arr = alloca [4000000 x i8], align 4
+  %p1 = getelementptr inbounds [4000000 x i8], ptr %arr, i32 0, i32 3000000
+  store i32 %x, ptr %p1, align 4
+  %p0 = getelementptr inbounds [4000000 x i8], ptr %arr, i32 0, i32 0
+  store i32 %x, ptr %p0, align 4
+  ret void
+}
+
+; The address of a large-offset stack element escapes via a call, so it must be
+; materialised as a value. The (frameindex + simm26) computation selects
+; QC_E_ADDI and folds the resolved 26-bit frame offset into it.
+declare void @use(ptr)
+define void @fi_addr_escapes(i32 %x) nounwind {
+; CHECK-LABEL: fi_addr_escapes:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    c.addi16sp sp, -256
+; CHECK-NEXT:    c.swsp ra, 252(sp) # 4-byte Folded Spill
+; CHECK-NEXT:    qc.e.addi sp, sp, -3999760
+; CHECK-NEXT:    c.addi4spn a0, sp, 12
+; CHECK-NEXT:    qc.e.addi a0, a0, 3000000
+; CHECK-NEXT:    call use
+; CHECK-NEXT:    c.addi4spn a0, sp, 12
+; CHECK-NEXT:    call use
+; CHECK-NEXT:    qc.e.addi sp, sp, 3999760
+; CHECK-NEXT:    c.lwsp ra, 252(sp) # 4-byte Folded Reload
+; CHECK-NEXT:    c.addi16sp sp, 256
+; CHECK-NEXT:    c.jr ra
+  %arr = alloca [4000000 x i8], align 4
+  %p1 = getelementptr inbounds [4000000 x i8], ptr %arr, i32 0, i32 3000000
+  call void @use(ptr %p1)
+  %p0 = getelementptr inbounds [4000000 x i8], ptr %arr, i32 0, i32 0
+  call void @use(ptr %p0)
+  ret void
+}
+
+; A bare frame index (offset 0) whose slot resolves to a large (simm26, not
+; simm12) sp-relative offset. The small alloca is placed first so the allocator
+; lays it out high in the frame (sp + ~8204).
+define i32 @bare_fi_high_frame() nounwind {
+; CHECK-LABEL: bare_fi_high_frame:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.addi sp, sp, -8208
+; CHECK-NEXT:    c.li a0, 1
+; CHECK-NEXT:    c.swsp a0, 12(sp)
+; CHECK-NEXT:    qc.e.addi a0, sp, 8192
+; CHECK-NEXT:    c.lw a0, 12(a0)
+; CHECK-NEXT:    qc.e.addi sp, sp, 8208
+; CHECK-NEXT:    c.jr ra
+  %small = alloca i32
+  %big = alloca [8192 x i8], align 4
+  %pb = getelementptr [8192 x i8], ptr %big, i32 0, i32 0
+  store volatile i32 1, ptr %pb
+  %v = load i32, ptr %small
+  ret i32 %v
+}
+
+; Store variant of the bare-frame-index case.
+define void @bare_fi_high_frame_store(i32 %x) nounwind {
+; CHECK-LABEL: bare_fi_high_frame_store:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    qc.e.addi sp, sp, -8208
+; CHECK-NEXT:    c.li a1, 1
+; CHECK-NEXT:    c.swsp a1, 12(sp)
+; CHECK-NEXT:    qc.e.addi a1, sp, 8192
+; CHECK-NEXT:    c.sw a0, 12(a1)
+; CHECK-NEXT:    qc.e.addi sp, sp, 8208
+; CHECK-NEXT:    c.jr ra
+  %small = alloca i32
+  %big = alloca [8192 x i8], align 4
+  %pb = getelementptr [8192 x i8], ptr %big, i32 0, i32 0
+  store volatile i32 1, ptr %pb
+  store i32 %x, ptr %small
+  ret void
+}

>From 52182c30d112c47e69d4b57f22a8f05ed96665fa Mon Sep 17 00:00:00 2001
From: Garvit Gupta <garvgupt at qti.qualcomm.com>
Date: Mon, 13 Jul 2026 13:54:33 -0700
Subject: [PATCH 2/2] [RISCV] Fold 26-bit frame-index offsets into
 Xqcilo/Xqcilia load/store/addi

Extend frame-index addressing so that a stack access whose resolved sp-relative
offset is a 26-bit (but not 12-bit) signed immediate folds the offset directly
into the wide Xqcilo/Xqcilia instruction, instead of materializing a separate
base register.
---
 llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp   |  6 +++++
 llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td   |  7 ++++++
 llvm/lib/Target/RISCV/RISCVInstrPredicates.td | 22 +++++++++++++++++++
 llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp   |  6 +++++
 .../RISCV/xqcilo-xqcilia-frame-index.ll       | 15 +++++--------
 5 files changed, 46 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
index 1662833768e6e..3c73b5d1eaa70 100644
--- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
+++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp
@@ -3591,6 +3591,10 @@ bool RISCVDAGToDAGISel::SelectAddrRegImm(SDValue Addr, SDValue &Base,
 /// compressible) standard load/store instructions.
 bool RISCVDAGToDAGISel::SelectAddrRegImm26(SDValue Addr, SDValue &Base,
                                            SDValue &Offset) {
+
+  if (SelectAddrFrameIndex(Addr, Base, Offset))
+    return true;
+
   SDLoc DL(Addr);
   MVT VT = Addr.getSimpleValueType();
 
@@ -3600,6 +3604,8 @@ bool RISCVDAGToDAGISel::SelectAddrRegImm26(SDValue Addr, SDValue &Base,
     // 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;
     }
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
index bb7b2bf08644e..ea3c5927f1da8 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
@@ -1635,6 +1635,13 @@ def : PatGprNoX0Simm26NoSimm12<or, QC_E_ORI>;
 def : PatGprNoX0Simm26NoSimm12<xor, QC_E_XORI>;
 } // Predicates = [HasVendorXqcilia, IsRV32]
 
+/// FrameIndex calculations
+
+let Predicates = [HasVendorXqcilia, IsRV32] in {
+  def : Pat<(riscv_add_like frameindex:$fi, simm26_nosimm12:$offset),
+            (QC_E_ADDI (iPTR (to_tframeindex $fi)), simm26_nosimm12:$offset)>;
+} // Predicates = [HasVendorXqcilia, IsRV32]
+
 /// Load/Store operations
 
 let Predicates = [HasVendorXqcilo, IsRV32], AddedComplexity = 2 in {
diff --git a/llvm/lib/Target/RISCV/RISCVInstrPredicates.td b/llvm/lib/Target/RISCV/RISCVInstrPredicates.td
index 367f91bb9902f..cff834150cfeb 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrPredicates.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrPredicates.td
@@ -242,3 +242,25 @@ def isBaseStore
                        SH,
                        SB,
                      ]>>>;
+
+// Xqcilo 26-bit offset Loads (qc.e.lb, qc.e.lbu, qc.e.lh, qc.e.lhu, qc.e.lw)
+def isBaseQCLoad
+    : TIIPredicate<"isBaseQCLoad",
+                   MCReturnStatement<
+                     CheckOpcode<[
+                       QC_E_LB,
+                       QC_E_LBU,
+                       QC_E_LH,
+                       QC_E_LHU,
+                       QC_E_LW
+                     ]>>>;
+
+// Xqcilo 26-bit offset Stores (qc.e.sb, qc.e.sh, qc.e.sw)
+def isBaseQCStore
+    : TIIPredicate<"isBaseQCStore",
+                   MCReturnStatement<
+                     CheckOpcode<[
+                       QC_E_SB,
+                       QC_E_SH,
+                       QC_E_SW
+                     ]>>>;
diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
index 7dbdc881ea6c0..3311841505685 100644
--- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
@@ -588,6 +588,7 @@ bool RISCVRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
   if (!IsRVVSpill) {
     int64_t Val = Offset.getFixed();
     int64_t Lo12 = SignExtend64<12>(Val);
+    int64_t Lo26 = SignExtend64<26>(Val);
     unsigned Opc = MI.getOpcode();
 
     if (Opc == RISCV::ADDI && !isInt<12>(Val)) {
@@ -614,6 +615,11 @@ bool RISCVRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
       // instruction will add 4 to the immediate. If that would overflow 12
       // bits, we can't fold the offset.
       MI.getOperand(FIOperandNum + 1).ChangeToImmediate(0);
+    } else if (Opc == RISCV::QC_E_ADDI || RISCVInstrInfo::isBaseQCLoad(MI) ||
+               RISCVInstrInfo::isBaseQCStore(MI)) {
+      MI.getOperand(FIOperandNum + 1).ChangeToImmediate(Lo26);
+      Offset = StackOffset::get((uint64_t)Val - (uint64_t)Lo26,
+                                Offset.getScalable());
     } else {
       // We can encode an add with 12 bit signed immediate in the immediate
       // operand of our user instruction.  As a result, the remaining
diff --git a/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll b/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll
index 9b5f57f11ff1f..eaf5b78f30ff2 100644
--- a/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll
+++ b/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll
@@ -14,8 +14,7 @@ define i32 @stack_fi_load(i32 %x) nounwind {
 ; CHECK-LABEL: stack_fi_load:
 ; CHECK:       # %bb.0: # %entry
 ; CHECK-NEXT:    qc.e.addi sp, sp, -4000016
-; CHECK-NEXT:    c.addi4spn a1, sp, 16
-; CHECK-NEXT:    qc.e.lw a1, 3000000(a1)
+; CHECK-NEXT:    qc.e.lw a1, 3000016(sp)
 ; CHECK-NEXT:    c.mv a2, a0
 ; CHECK-NEXT:    c.mv a0, a1
 ; CHECK-NEXT:    c.swsp a2, 16(sp)
@@ -36,8 +35,7 @@ define void @stack_fi_store(i32 %x) nounwind {
 ; CHECK-LABEL: stack_fi_store:
 ; CHECK:       # %bb.0: # %entry
 ; CHECK-NEXT:    qc.e.addi sp, sp, -4000016
-; CHECK-NEXT:    c.addi4spn a1, sp, 16
-; CHECK-NEXT:    qc.e.sw a0, 3000000(a1)
+; CHECK-NEXT:    qc.e.sw a0, 3000016(sp)
 ; CHECK-NEXT:    c.swsp a0, 16(sp)
 ; CHECK-NEXT:    qc.e.addi sp, sp, 4000016
 ; CHECK-NEXT:    c.jr ra
@@ -60,8 +58,7 @@ define void @fi_addr_escapes(i32 %x) nounwind {
 ; CHECK-NEXT:    c.addi16sp sp, -256
 ; CHECK-NEXT:    c.swsp ra, 252(sp) # 4-byte Folded Spill
 ; CHECK-NEXT:    qc.e.addi sp, sp, -3999760
-; CHECK-NEXT:    c.addi4spn a0, sp, 12
-; CHECK-NEXT:    qc.e.addi a0, a0, 3000000
+; CHECK-NEXT:    qc.e.addi a0, sp, 3000012
 ; CHECK-NEXT:    call use
 ; CHECK-NEXT:    c.addi4spn a0, sp, 12
 ; CHECK-NEXT:    call use
@@ -86,8 +83,7 @@ define i32 @bare_fi_high_frame() nounwind {
 ; CHECK-NEXT:    qc.e.addi sp, sp, -8208
 ; CHECK-NEXT:    c.li a0, 1
 ; CHECK-NEXT:    c.swsp a0, 12(sp)
-; CHECK-NEXT:    qc.e.addi a0, sp, 8192
-; CHECK-NEXT:    c.lw a0, 12(a0)
+; CHECK-NEXT:    qc.e.lw a0, 8204(sp)
 ; CHECK-NEXT:    qc.e.addi sp, sp, 8208
 ; CHECK-NEXT:    c.jr ra
   %small = alloca i32
@@ -105,8 +101,7 @@ define void @bare_fi_high_frame_store(i32 %x) nounwind {
 ; CHECK-NEXT:    qc.e.addi sp, sp, -8208
 ; CHECK-NEXT:    c.li a1, 1
 ; CHECK-NEXT:    c.swsp a1, 12(sp)
-; CHECK-NEXT:    qc.e.addi a1, sp, 8192
-; CHECK-NEXT:    c.sw a0, 12(a1)
+; CHECK-NEXT:    qc.e.sw a0, 8204(sp)
 ; CHECK-NEXT:    qc.e.addi sp, sp, 8208
 ; CHECK-NEXT:    c.jr ra
   %small = alloca i32



More information about the llvm-commits mailing list