Author: Garvit Gupta Date: 2026-08-04T12:34:04Z New Revision: fa0232af8b06ea5247a2abf49c11bbd558dd12e1
URL: https://github.com/llvm/llvm-project/commit/fa0232af8b06ea5247a2abf49c11bbd558dd12e1 DIFF: https://github.com/llvm/llvm-project/commit/fa0232af8b06ea5247a2abf49c11bbd558dd12e1.diff LOG: [RISCV] Reduce spill/reload pairs when Xqcilo extension is enabled (#212807) [RISCV] Reduce spill/reload pairs when Xqcilo extension is enabled Currently, `SelectAddrRegImm26` calls `SelectAddrFrameIndex` first, causing bare frame-index loads (offset 0) to select 48-bit loads/stores at ISel. Due to `AddedComplexity=2` on the QC48LdPat patterns, the wide opcode won over the standard LW/SW even though the resolved frame offset typically fits simm12. This led to more spills and reloads in functions which are under high register pressure because 48-bit loads and stores are not marked easily rematerializable. Also, simply adding 48-bit loads and stores to `isLoadFromStackSlot/isStoreToStackSlot` doesn't solve the regression for the multi call case and only by making Isel produce the plain 32/64-bit loads and store opcodes as the baseline does RA behave identically. Therefor this PR fixes the issue by: -Remove the `SelectAddrFrameIndex` call from SelectAddrRegImm26. Bare frame indices now fall through to standard LW/SW selection at ISel, where RA recognizes them as rematerializable stack loads. -Add post-RA promotion in `eliminateFrameIndex`: when a plain LW/SW has a resolved frame offset that exceeds simm12, promote the opcode to the corresponding 48-bit load/store opcode and fold the 26-bit offset directly. This preserves the intended large-offset optimization without affecting RA decisions. This solves the code size regression in high register pressure function introduced by PR #209315 Assisted by Claude (cherry picked from commit 8b18aa0b1ec2de76d47748a4d13c4de02b4d8580) Added: Modified: llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll Removed: ################################################################################ diff --git a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp index 31ea5972174ff..5c08e48b9cf01 100644 --- a/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp +++ b/llvm/lib/Target/RISCV/RISCVISelDAGToDAG.cpp @@ -3589,10 +3589,6 @@ 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(); diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp index 3311841505685..fb7ebe468a936 100644 --- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp +++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp @@ -561,6 +561,29 @@ void RISCVRegisterInfo::lowerSegmentSpillReload(MachineBasicBlock::iterator II, II->eraseFromParent(); } +static unsigned getXqciloWideOpcode(unsigned Opc) { + switch (Opc) { + case RISCV::LW: + return RISCV::QC_E_LW; + case RISCV::SW: + return RISCV::QC_E_SW; + case RISCV::LB: + return RISCV::QC_E_LB; + case RISCV::LBU: + return RISCV::QC_E_LBU; + case RISCV::LH: + return RISCV::QC_E_LH; + case RISCV::LHU: + return RISCV::QC_E_LHU; + case RISCV::SB: + return RISCV::QC_E_SB; + case RISCV::SH: + return RISCV::QC_E_SH; + default: + return 0; + } +} + bool RISCVRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II, int SPAdj, unsigned FIOperandNum, RegScavenger *RS) const { @@ -569,7 +592,9 @@ bool RISCVRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II, MachineInstr &MI = *II; MachineFunction &MF = *MI.getParent()->getParent(); MachineRegisterInfo &MRI = MF.getRegInfo(); - bool Is64Bit = MF.getSubtarget<RISCVSubtarget>().is64Bit(); + const RISCVSubtarget &ST = MF.getSubtarget<RISCVSubtarget>(); + const RISCVInstrInfo *TII = ST.getInstrInfo(); + bool Is64Bit = ST.is64Bit(); DebugLoc DL = MI.getDebugLoc(); int FrameIndex = MI.getOperand(FIOperandNum).getIndex(); @@ -615,6 +640,17 @@ 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 (unsigned WideOpc = getXqciloWideOpcode(Opc); + !isInt<12>(Val) && ST.hasVendorXqcilo() && WideOpc) { + // The resolved frame offset exceeds simm12 but the instruction is a + // standard load/store (LW/SW/etc). Promote to the wide Xqcilo equivalent + // so the full 26-bit offset folds directly, avoiding a separate + // base-adjust instruction. This runs post-RA and does not affect + // register allocation decisions. + MI.setDesc(TII->get(WideOpc)); + MI.getOperand(FIOperandNum + 1).ChangeToImmediate(Lo26); + Offset = StackOffset::get((uint64_t)Val - (uint64_t)Lo26, + Offset.getScalable()); } else if (Opc == RISCV::QC_E_ADDI || RISCVInstrInfo::isBaseQCLoad(MI) || RISCVInstrInfo::isBaseQCStore(MI)) { MI.getOperand(FIOperandNum + 1).ChangeToImmediate(Lo26); diff --git a/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll b/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll index eaf5b78f30ff2..681418c16816c 100644 --- a/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll +++ b/llvm/test/CodeGen/RISCV/xqcilo-xqcilia-frame-index.ll @@ -111,3 +111,105 @@ define void @bare_fi_high_frame_store(i32 %x) nounwind { store i32 %x, ptr %small ret void } + +; Register-pressure regression test: bare frame-index loads from fixed stack +; slots (incoming args on stack) must select standard LW at ISel — not QC_E_LW — +; so that the register allocator can rematerialize them. With QC_E_LW selected, +; isLoadFromStackSlot does not recognize it, RA cannot rematerialize, and under +; high pressure it spills excessively. +declare dso_local i32 @sink(i32 noundef) local_unnamed_addr + +define dso_local i32 @regpressure(i32 noundef %a0, i32 noundef %a1, i32 noundef %a2, i32 noundef %a3, i32 noundef %a4, i32 noundef %a5, i32 noundef %a6, i32 noundef %a7, i32 noundef %s0, i32 noundef %s1, i32 noundef %s2, i32 noundef %s3, i32 noundef %s4, i32 noundef %s5, i32 noundef %s6, i32 noundef %s7, i32 noundef %s8, i32 noundef %s9, i32 noundef %s10, i32 noundef %s11) local_unnamed_addr nounwind { +; CHECK-LABEL: regpressure: +; CHECK: # %bb.0: # %entry +; CHECK-NEXT: c.addi16sp sp, -64 +; CHECK-NEXT: c.swsp ra, 60(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s0, 56(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s1, 52(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s2, 48(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s3, 44(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s4, 40(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s5, 36(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s6, 32(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s7, 28(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s8, 24(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s9, 20(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s10, 16(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.swsp s11, 12(sp) # 4-byte Folded Spill +; CHECK-NEXT: c.mv s0, a7 +; CHECK-NEXT: c.mv s1, a6 +; CHECK-NEXT: c.mv s2, a5 +; CHECK-NEXT: c.mv s3, a4 +; CHECK-NEXT: c.mv s4, a3 +; CHECK-NEXT: c.mv s5, a2 +; CHECK-NEXT: c.mv s6, a1 +; CHECK-NEXT: c.lwsp s8, 80(sp) +; CHECK-NEXT: c.lwsp s10, 76(sp) +; CHECK-NEXT: c.lwsp s11, 72(sp) +; CHECK-NEXT: c.lwsp s9, 68(sp) +; CHECK-NEXT: c.lwsp s7, 64(sp) +; CHECK-NEXT: call sink +; CHECK-NEXT: c.add s2, s3 +; CHECK-NEXT: c.add s0, s1 +; CHECK-NEXT: c.add s0, s2 +; CHECK-NEXT: c.add s9, s11 +; CHECK-NEXT: c.add s0, s7 +; CHECK-NEXT: c.add s8, s10 +; CHECK-NEXT: c.add s0, s9 +; CHECK-NEXT: c.lwsp a1, 84(sp) +; CHECK-NEXT: c.add s8, a1 +; CHECK-NEXT: c.add s0, s8 +; CHECK-NEXT: c.lwsp a1, 92(sp) +; CHECK-NEXT: c.lwsp a2, 88(sp) +; CHECK-NEXT: c.add a1, a2 +; CHECK-NEXT: c.lwsp a2, 108(sp) +; CHECK-NEXT: c.lwsp a3, 104(sp) +; CHECK-NEXT: c.add a2, a3 +; CHECK-NEXT: c.lwsp a3, 96(sp) +; CHECK-NEXT: c.add a1, a3 +; CHECK-NEXT: c.add a0, a2 +; CHECK-NEXT: c.lwsp a2, 100(sp) +; CHECK-NEXT: c.add a1, a2 +; CHECK-NEXT: c.add a0, s6 +; CHECK-NEXT: c.add a1, s0 +; CHECK-NEXT: c.add a0, s5 +; CHECK-NEXT: c.add a0, a1 +; CHECK-NEXT: c.add a0, s4 +; CHECK-NEXT: c.lwsp ra, 60(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s0, 56(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s1, 52(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s2, 48(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s3, 44(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s4, 40(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s5, 36(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s6, 32(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s7, 28(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s8, 24(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s9, 20(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s10, 16(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.lwsp s11, 12(sp) # 4-byte Folded Reload +; CHECK-NEXT: c.addi16sp sp, 64 +; CHECK-NEXT: c.jr ra +entry: + %call = tail call i32 @sink(i32 noundef %a0) + %add2 = add i32 %a5, %a4 + %add4 = add i32 %add2, %a6 + %add6 = add i32 %add4, %a7 + %add7 = add i32 %add6, %s0 + %add8 = add i32 %add7, %s1 + %add9 = add i32 %add8, %s2 + %add10 = add i32 %add9, %s3 + %add11 = add i32 %add10, %s4 + %add12 = add i32 %add11, %s5 + %add13 = add i32 %add12, %s6 + %add14 = add i32 %add13, %s7 + %add15 = add i32 %add14, %s8 + %add16 = add i32 %add15, %s9 + %add17 = add i32 %add16, %s10 + %add18 = add i32 %add17, %s11 + %add19 = add i32 %add18, %call + %add20 = add i32 %add19, %a1 + %add21 = add i32 %add20, %a2 + %add22 = add i32 %add21, %a3 + ret i32 %add22 +} _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
