[llvm] [RISCV][llvm] Support frame index in zilsd optimizer (PR #174073)
Brandon Wu via llvm-commits
llvm-commits at lists.llvm.org
Thu Jan 8 00:16:15 PST 2026
https://github.com/4vtomat updated https://github.com/llvm/llvm-project/pull/174073
>From 2cf64c8018781fc27d312ac54ce1f1dadbc9f885 Mon Sep 17 00:00:00 2001
From: Brandon Wu <brandon.wu at sifive.com>
Date: Wed, 31 Dec 2025 00:41:35 -0800
Subject: [PATCH 1/5] [RISCV][llvm] Support frame index in zilsd optimizer
Current zilsd optimizer only support base op that is in a register,
however many use cases are essentially stack load/store.
---
.../Target/RISCV/RISCVLoadStoreOptimizer.cpp | 67 +++++++++----
llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp | 57 +++++++----
.../CodeGen/RISCV/zilsd-ldst-opt-postra.mir | 99 +++++++++++++++++++
.../CodeGen/RISCV/zilsd-ldst-opt-prera.mir | 95 ++++++++++++++++++
4 files changed, 281 insertions(+), 37 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVLoadStoreOptimizer.cpp b/llvm/lib/Target/RISCV/RISCVLoadStoreOptimizer.cpp
index ff5894e1a657d..c4b83adef5ee3 100644
--- a/llvm/lib/Target/RISCV/RISCVLoadStoreOptimizer.cpp
+++ b/llvm/lib/Target/RISCV/RISCVLoadStoreOptimizer.cpp
@@ -576,7 +576,12 @@ void RISCVLoadStoreOpt::splitLdSdIntoTwo(MachineBasicBlock &MBB,
const MachineOperand &BaseOp = MI->getOperand(2);
Register FirstReg = FirstOp.getReg();
Register SecondReg = SecondOp.getReg();
- Register BaseReg = BaseOp.getReg();
+ unsigned BaseReg;
+ bool BaseIsReg = BaseOp.isReg();
+ if (BaseIsReg)
+ BaseReg = BaseOp.getReg().id();
+ else
+ BaseReg = BaseOp.getIndex();
// Handle both immediate and symbolic operands for offset
const MachineOperand &OffsetOp = MI->getOperand(3);
@@ -601,26 +606,37 @@ void RISCVLoadStoreOpt::splitLdSdIntoTwo(MachineBasicBlock &MBB,
// we can just switch the order to resolve that:
// X13 = LW X10, 4
// X10 = LW killed X10, 0
- if (FirstReg == BaseReg) {
+ if (BaseIsReg && FirstReg == BaseReg) {
MIB2 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
.addReg(SecondReg,
- RegState::Define | getDeadRegState(SecondOp.isDead()))
- .addReg(BaseReg);
+ RegState::Define | getDeadRegState(SecondOp.isDead()));
MIB1 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
.addReg(FirstReg,
- RegState::Define | getDeadRegState(FirstOp.isDead()))
- .addReg(BaseReg, getKillRegState(BaseOp.isKill()));
+ RegState::Define | getDeadRegState(FirstOp.isDead()));
+
+ if (BaseIsReg) {
+ MIB2 = MIB2.addReg(BaseReg);
+ MIB1 = MIB1.addReg(BaseReg, getKillRegState(BaseOp.isKill()));
+ } else {
+ MIB2 = MIB2.addFrameIndex(BaseReg);
+ MIB1 = MIB1.addFrameIndex(BaseReg);
+ }
} else {
MIB1 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
.addReg(FirstReg,
- RegState::Define | getDeadRegState(FirstOp.isDead()))
- .addReg(BaseReg);
-
+ RegState::Define | getDeadRegState(FirstOp.isDead()));
MIB2 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
.addReg(SecondReg,
- RegState::Define | getDeadRegState(SecondOp.isDead()))
- .addReg(BaseReg, getKillRegState(BaseOp.isKill()));
+ RegState::Define | getDeadRegState(SecondOp.isDead()));
+
+ if (BaseIsReg) {
+ MIB1 = MIB1.addReg(BaseReg);
+ MIB2 = MIB2.addReg(BaseReg, getKillRegState(BaseOp.isKill()));
+ } else {
+ MIB1 = MIB1.addFrameIndex(BaseReg);
+ MIB2 = MIB2.addFrameIndex(BaseReg);
+ }
}
++NumLD2LW;
@@ -630,12 +646,18 @@ void RISCVLoadStoreOpt::splitLdSdIntoTwo(MachineBasicBlock &MBB,
FirstReg != SecondReg &&
"First register and second register is impossible to be same register");
MIB1 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
- .addReg(FirstReg, getKillRegState(FirstOp.isKill()))
- .addReg(BaseReg);
+ .addReg(FirstReg, getKillRegState(FirstOp.isKill()));
MIB2 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
- .addReg(SecondReg, getKillRegState(SecondOp.isKill()))
- .addReg(BaseReg, getKillRegState(BaseOp.isKill()));
+ .addReg(SecondReg, getKillRegState(SecondOp.isKill()));
+
+ if (BaseIsReg) {
+ MIB1 = MIB1.addReg(BaseReg);
+ MIB2 = MIB2.addReg(BaseReg, getKillRegState(BaseOp.isKill()));
+ } else {
+ MIB1 = MIB1.addFrameIndex(BaseReg);
+ MIB2 = MIB2.addFrameIndex(BaseReg);
+ }
++NumSD2SW;
LLVM_DEBUG(dbgs() << "Split SD back to two SW instructions\n");
@@ -689,7 +711,12 @@ bool RISCVLoadStoreOpt::fixInvalidRegPairOp(MachineBasicBlock &MBB,
// Registers are valid, convert to real LD/SD instruction
const MachineOperand &BaseOp = MI->getOperand(2);
- Register BaseReg = BaseOp.getReg();
+ unsigned BaseReg;
+ bool BaseIsReg = BaseOp.isReg();
+ if (BaseIsReg)
+ BaseReg = BaseOp.getReg().id();
+ else
+ BaseReg = BaseOp.getIndex();
DebugLoc DL = MI->getDebugLoc();
// Handle both immediate and symbolic operands for offset
const MachineOperand &OffsetOp = MI->getOperand(3);
@@ -711,9 +738,11 @@ bool RISCVLoadStoreOpt::fixInvalidRegPairOp(MachineBasicBlock &MBB,
MIB.addReg(RegPair, getKillRegState(FirstOp.isKill() && SecondOp.isKill()));
}
- MIB.addReg(BaseReg, getKillRegState(BaseOp.isKill()))
- .add(OffsetOp)
- .cloneMemRefs(*MI);
+ if (BaseIsReg)
+ MIB = MIB.addReg(BaseReg, getKillRegState(BaseOp.isKill()));
+ else
+ MIB = MIB.addFrameIndex(BaseReg);
+ MIB.add(OffsetOp).cloneMemRefs(*MI);
LLVM_DEBUG(dbgs() << "Converted pseudo to real instruction: " << *MIB
<< "\n");
diff --git a/llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp b/llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp
index 3b47903c351bf..2de73e6267dd0 100644
--- a/llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp
+++ b/llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp
@@ -87,7 +87,8 @@ class RISCVPreAllocZilsdOpt : public MachineFunctionPass {
Global = 1,
CPI = 2,
BlockAddr = 3,
- Unknown = 4,
+ FrameIdx = 4,
+ Unknown = 5,
};
using MemOffset = std::pair<MemoryOffsetKind, int>;
using BaseRegInfo = std::pair<unsigned, MemoryOffsetKind>;
@@ -160,12 +161,16 @@ RISCVPreAllocZilsdOpt::getMemoryOpOffset(const MachineInstr &MI) {
switch (MI.getOpcode()) {
case RISCV::LW:
case RISCV::SW: {
- // For LW/SW, the offset is in operand 2
+ // For LW/SW, the base is in operand 1 and offset is in operand 2
+ const MachineOperand &BaseOp = MI.getOperand(1);
const MachineOperand &OffsetOp = MI.getOperand(2);
// Handle immediate offset
- if (OffsetOp.isImm())
+ if (OffsetOp.isImm()) {
+ if (BaseOp.isFI())
+ return std::make_pair(MemoryOffsetKind::FrameIdx, OffsetOp.getImm());
return std::make_pair(MemoryOffsetKind::Imm, OffsetOp.getImm());
+ }
// Handle symbolic operands with MO_LO flag (from MergeBaseOffset)
if (OffsetOp.getTargetFlags() & RISCVII::MO_LO) {
@@ -229,7 +234,7 @@ bool RISCVPreAllocZilsdOpt::isSafeToMove(MachineInstr *MI, MachineInstr *Target,
++Start;
Register DefReg = MI->getOperand(0).getReg();
- Register BaseReg = MI->getOperand(1).getReg();
+ const MachineOperand &BaseOp = MI->getOperand(1);
unsigned ScanCount = 0;
for (auto It = Start; It != End; ++It, ++ScanCount) {
@@ -247,8 +252,8 @@ bool RISCVPreAllocZilsdOpt::isSafeToMove(MachineInstr *MI, MachineInstr *Target,
}
// Check if the base register is modified
- if (It->modifiesRegister(BaseReg, TRI)) {
- LLVM_DEBUG(dbgs() << "Base register " << BaseReg
+ if (BaseOp.isReg() && It->modifiesRegister(BaseOp.getReg(), TRI)) {
+ LLVM_DEBUG(dbgs() << "Base register " << BaseOp.getReg()
<< " modified by: " << *It);
return false;
}
@@ -297,8 +302,16 @@ bool RISCVPreAllocZilsdOpt::rescheduleOps(
Register FirstReg = MI0->getOperand(0).getReg();
Register SecondReg = MI1->getOperand(0).getReg();
- Register BaseReg = MI0->getOperand(1).getReg();
+ const MachineOperand &BaseOp = MI0->getOperand(1);
const MachineOperand &OffsetOp = MI0->getOperand(2);
+ assert((BaseOp.isReg() || BaseOp.isFI()) &&
+ "Base register should be register or frame index");
+ unsigned BaseReg;
+ bool BaseIsReg = BaseOp.isReg();
+ if (BaseIsReg)
+ BaseReg = BaseOp.getReg().id();
+ else
+ BaseReg = BaseOp.getIndex();
// At this point, MI0 and MI1 are:
// 1. both either LW or SW.
@@ -347,21 +360,23 @@ bool RISCVPreAllocZilsdOpt::rescheduleOps(
if (IsLoad) {
MIB = BuildMI(*MBB, InsertPos, DL, TII->get(RISCV::PseudoLD_RV32_OPT))
.addReg(FirstReg, RegState::Define)
- .addReg(SecondReg, RegState::Define)
- .addReg(BaseReg)
- .add(OffsetOp);
+ .addReg(SecondReg, RegState::Define);
++NumLDFormed;
LLVM_DEBUG(dbgs() << "Formed LD: " << *MIB << "\n");
} else {
MIB = BuildMI(*MBB, InsertPos, DL, TII->get(RISCV::PseudoSD_RV32_OPT))
.addReg(FirstReg)
- .addReg(SecondReg)
- .addReg(BaseReg)
- .add(OffsetOp);
+ .addReg(SecondReg);
++NumSDFormed;
LLVM_DEBUG(dbgs() << "Formed SD: " << *MIB << "\n");
}
+ if (BaseIsReg)
+ MIB = MIB.addReg(BaseReg);
+ else
+ MIB = MIB.addFrameIndex(BaseReg);
+ MIB = MIB.add(OffsetOp);
+
// Copy memory operands
MIB.cloneMergedMemRefs({MI0, MI1});
@@ -394,7 +409,7 @@ bool RISCVPreAllocZilsdOpt::isMemoryOp(const MachineInstr &MI) {
if (Opcode != RISCV::LW && Opcode != RISCV::SW)
return false;
- if (!MI.getOperand(1).isReg())
+ if (!MI.getOperand(1).isReg() && !MI.getOperand(1).isFI())
return false;
// When no memory operands are present, conservatively assume unaligned,
@@ -413,7 +428,7 @@ bool RISCVPreAllocZilsdOpt::isMemoryOp(const MachineInstr &MI) {
return false;
// Likewise don't mess with references to undefined addresses.
- if (MI.getOperand(1).isUndef())
+ if (MI.getOperand(1).isReg() && MI.getOperand(1).isUndef())
return false;
return true;
@@ -462,16 +477,22 @@ bool RISCVPreAllocZilsdOpt::rescheduleLoadStoreInstrs(MachineBasicBlock *MBB) {
continue;
bool IsLd = (MI.getOpcode() == RISCV::LW);
- Register Base = MI.getOperand(1).getReg();
+ const MachineOperand &BaseOp = MI.getOperand(1);
+ unsigned BaseReg;
+ bool BaseIsReg = BaseOp.isReg();
+ if (BaseIsReg)
+ BaseReg = BaseOp.getReg().id();
+ else
+ BaseReg = BaseOp.getIndex();
bool StopHere = false;
// Lambda to find or add base register entries
auto FindBases = [&](Base2InstMap &Base2Ops, BaseVec &Bases) {
- auto [BI, Inserted] = Base2Ops.try_emplace({Base.id(), Offset.first});
+ auto [BI, Inserted] = Base2Ops.try_emplace({BaseReg, Offset.first});
if (Inserted) {
// First time seeing this base register
BI->second.push_back(&MI);
- Bases.push_back({Base.id(), Offset.first});
+ Bases.push_back({BaseReg, Offset.first});
return;
}
// Check if we've seen this exact base+offset before
diff --git a/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-postra.mir b/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-postra.mir
index c27cb25366f27..ec7ecf5a534ef 100644
--- a/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-postra.mir
+++ b/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-postra.mir
@@ -10,6 +10,10 @@
ret i32 %5
}
+ define void @expand_pseudold_frame_index_valid(ptr %0) {
+ ret void
+ }
+
define void @expand_pseudosd_valid(ptr %0, i32 %1, i32 %2) {
store i32 %1, ptr %0, align 4
%4 = getelementptr inbounds i32, ptr %0, i32 1
@@ -17,6 +21,10 @@
ret void
}
+ define void @expand_pseudosd_frame_index_valid(ptr %0, i32 %1, i32 %2) {
+ ret void
+ }
+
define i32 @expand_pseudold_invalid_pair(ptr %0) {
%2 = load i32, ptr %0, align 4
%3 = getelementptr inbounds i32, ptr %0, i32 1
@@ -25,6 +33,10 @@
ret i32 %5
}
+ define void @expand_pseudold_frame_index_invalid_pair(ptr %0) {
+ ret void
+ }
+
define void @expand_pseudosd_invalid_pair(ptr %0, i32 %1, i32 %2) {
store i32 %1, ptr %0, align 4
%4 = getelementptr inbounds i32, ptr %0, i32 1
@@ -32,6 +44,10 @@
ret void
}
+ define void @expand_pseudosd_frame_index_invalid_pair(ptr %0, i32 %1, i32 %2) {
+ ret void
+ }
+
define void @store_zero_combine_valid(ptr %0) {
store i32 0, ptr %0, align 8
%2 = getelementptr inbounds i32, ptr %0, i32 1
@@ -75,6 +91,27 @@ body: |
$x10 = ADD killed $x12, killed $x13
PseudoRET implicit $x10
+...
+---
+# Valid consecutive even/odd register pair - should expand to LD_RV32
+name: expand_pseudold_frame_index_valid
+tracksRegLiveness: false
+stack:
+ - { id: 0, offset: 0, size: 8, alignment: 4 }
+body: |
+ bb.0:
+ liveins: $x10
+
+ ; CHECK-LABEL: name: expand_pseudold_frame_index_valid
+ ; CHECK: liveins: $x10
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: $x12_x13 = LD_RV32 %stack.0, 0
+ ; CHECK-NEXT: $x10 = ADD killed $x12, killed $x13
+ ; CHECK-NEXT: PseudoRET implicit $x10
+ $x12, $x13 = PseudoLD_RV32_OPT %stack.0, 0
+ $x10 = ADD killed $x12, killed $x13
+ PseudoRET implicit $x10
+
...
---
# Valid consecutive even/odd register pair - should expand to SD_RV32
@@ -93,6 +130,26 @@ body: |
PseudoSD_RV32_OPT killed $x12, killed $x13, killed $x10, 0
PseudoRET
+...
+---
+# Valid consecutive even/odd register pair - should expand to SD_RV32
+name: expand_pseudosd_frame_index_valid
+tracksRegLiveness: false
+stack:
+ - { id: 0, offset: 0, size: 8, alignment: 4 }
+body: |
+ bb.0:
+ liveins: $x12, $x13
+
+ ; PseudoSD_RV32_OPT with consecutive even/odd registers (x12, x13)
+ ; CHECK-LABEL: name: expand_pseudosd_frame_index_valid
+ ; CHECK: liveins: $x12, $x13
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: SD_RV32 killed $x12_x13, %stack.0, 0
+ ; CHECK-NEXT: PseudoRET
+ PseudoSD_RV32_OPT killed $x12, killed $x13, %stack.0, 0
+ PseudoRET
+
...
---
# Invalid register pair (not consecutive) - should decompose back to LW
@@ -115,6 +172,26 @@ body: |
$x10 = ADD killed $x11, killed $x13
PseudoRET implicit $x10
+...
+---
+# Invalid register pair (not consecutive) - should decompose back to LW
+name: expand_pseudold_frame_index_invalid_pair
+tracksRegLiveness: false
+stack:
+ - { id: 0, offset: 0, size: 8, alignment: 4 }
+body: |
+ bb.0:
+ ; PseudoLD_RV32_OPT with non-consecutive registers (x11, x13)
+ ; Should decompose back to two LW instructions
+ ; CHECK-LABEL: name: expand_pseudold_frame_index_invalid_pair
+ ; CHECK: $x11 = LW %stack.0, 0
+ ; CHECK-NEXT: $x13 = LW %stack.0, 4
+ ; CHECK-NEXT: $x10 = ADD killed $x11, killed $x13
+ ; CHECK-NEXT: PseudoRET implicit $x10
+ $x11, $x13 = PseudoLD_RV32_OPT %stack.0, 0
+ $x10 = ADD killed $x11, killed $x13
+ PseudoRET implicit $x10
+
...
---
# Invalid register pair (not even/odd) - should decompose back to SW
@@ -135,6 +212,28 @@ body: |
PseudoSD_RV32_OPT killed $x11, killed $x14, killed $x10, 0
PseudoRET
+...
+---
+# Invalid register pair (not even/odd) - should decompose back to SW
+name: expand_pseudosd_frame_index_invalid_pair
+tracksRegLiveness: false
+stack:
+ - { id: 0, offset: 0, size: 8, alignment: 4 }
+body: |
+ bb.0:
+ liveins: $x11, $x14
+
+ ; PseudoSD_RV32_OPT with non-consecutive registers (x11, x14)
+ ; Should decompose back to two SW instructions
+ ; CHECK-LABEL: name: expand_pseudosd_frame_index_invalid_pair
+ ; CHECK: liveins: $x11, $x14
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: SW killed $x11, %stack.0, 0
+ ; CHECK-NEXT: SW killed $x14, %stack.0, 4
+ ; CHECK-NEXT: PseudoRET
+ PseudoSD_RV32_OPT killed $x11, killed $x14, %stack.0, 0
+ PseudoRET
+
...
---
# Test store zero combinations - zeros don't need consecutive pairs
diff --git a/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-prera.mir b/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-prera.mir
index dab394d4bc8c4..dcb9c44f8030b 100644
--- a/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-prera.mir
+++ b/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-prera.mir
@@ -238,6 +238,19 @@
define i32 @symbolic_operands_interleave() {
ret i32 0
}
+
+ declare void @external(ptr)
+ define i32 @frame_index() {
+ %arr = alloca [2 x i32], align 4
+ %arrayidx1 = getelementptr inbounds nuw i8, ptr %arr, i32 4
+ ret i32 0
+ }
+
+ define i32 @frame_index_operands_interleave() {
+ %arr = alloca [2 x i32], align 4
+ %arrayidx1 = getelementptr inbounds nuw i8, ptr %arr, i32 4
+ ret i32 0
+ }
---
# Basic case: two consecutive 32-bit loads that can be combined into LD
name: basic_load_combine
@@ -1240,3 +1253,85 @@ body: |
PseudoRET
...
+---
+name: frame_index
+alignment: 4
+tracksRegLiveness: true
+stack:
+ - { id: 0, offset: 0, size: 8, alignment: 4 }
+liveins:
+ - { reg: '$x10', virtual-reg: '%0' }
+ - { reg: '$x11', virtual-reg: '%1' }
+body: |
+ bb.0:
+ liveins: $x10, $x11
+
+ ; CHECK-LABEL: name: frame_index
+ ; CHECK: liveins: $x10, $x11
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
+ ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gpr = COPY $x11
+ ; CHECK-NEXT: SW [[COPY]], %stack.0, 0 :: (store (s32) into %ir.arr)
+ ; CHECK-NEXT: SW [[COPY1]], %stack.0, 4 :: (store (s32) into %ir.arrayidx1)
+ ; CHECK-NEXT: PseudoRET
+ ;
+ ; CHECK-4BYTE-LABEL: name: frame_index
+ ; CHECK-4BYTE: liveins: $x10, $x11
+ ; CHECK-4BYTE-NEXT: {{ $}}
+ ; CHECK-4BYTE-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
+ ; CHECK-4BYTE-NEXT: [[COPY1:%[0-9]+]]:gpr = COPY $x11
+ ; CHECK-4BYTE-NEXT: PseudoSD_RV32_OPT [[COPY]], [[COPY1]], %stack.0, 0 :: (store (s32) into %ir.arr), (store (s32) into %ir.arrayidx1)
+ ; CHECK-4BYTE-NEXT: PseudoRET
+ %0:gpr = COPY $x10
+ %1:gpr = COPY $x11
+ SW %0:gpr, %stack.0, 0 :: (store (s32) into %ir.arr)
+ SW %1:gpr, %stack.0, 4 :: (store (s32) into %ir.arrayidx1)
+ PseudoRET
+...
+---
+name: frame_index_operands_interleave
+alignment: 4
+tracksRegLiveness: true
+stack:
+ - { id: 0, offset: 0, size: 8, alignment: 4 }
+liveins:
+ - { reg: '$x10', virtual-reg: '%0' }
+ - { reg: '$x11', virtual-reg: '%1' }
+body: |
+ bb.0:
+ liveins: $x10, $x11
+
+ ; CHECK-LABEL: name: frame_index_operands_interleave
+ ; CHECK: liveins: $x10, $x11
+ ; CHECK-NEXT: {{ $}}
+ ; CHECK-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
+ ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gpr = COPY $x11
+ ; CHECK-NEXT: [[LW:%[0-9]+]]:gpr = LW %stack.0, 0 :: (load (s32) from %ir.arr)
+ ; CHECK-NEXT: [[LW1:%[0-9]+]]:gpr = LW [[COPY]], 4 :: (load (s32))
+ ; CHECK-NEXT: [[LW2:%[0-9]+]]:gpr = LW [[COPY]], 0 :: (load (s32))
+ ; CHECK-NEXT: [[LW3:%[0-9]+]]:gpr = LW %stack.0, 4 :: (load (s32) from %ir.arrayidx1)
+ ; CHECK-NEXT: [[ADD:%[0-9]+]]:gpr = ADD [[LW]], [[LW2]]
+ ; CHECK-NEXT: [[ADD1:%[0-9]+]]:gpr = ADD [[LW1]], [[LW3]]
+ ; CHECK-NEXT: PseudoRET
+ ;
+ ; CHECK-4BYTE-LABEL: name: frame_index_operands_interleave
+ ; CHECK-4BYTE: liveins: $x10, $x11
+ ; CHECK-4BYTE-NEXT: {{ $}}
+ ; CHECK-4BYTE-NEXT: [[COPY:%[0-9]+]]:gpr = COPY $x10
+ ; CHECK-4BYTE-NEXT: [[COPY1:%[0-9]+]]:gpr = COPY $x11
+ ; CHECK-4BYTE-NEXT: [[PseudoLD_RV32_OPT:%[0-9]+]]:gpr, [[PseudoLD_RV32_OPT1:%[0-9]+]]:gpr = PseudoLD_RV32_OPT %stack.0, 0 :: (load (s32) from %ir.arr), (load (s32) from %ir.arrayidx1)
+ ; CHECK-4BYTE-NEXT: [[PseudoLD_RV32_OPT2:%[0-9]+]]:gpr, [[PseudoLD_RV32_OPT3:%[0-9]+]]:gpr = PseudoLD_RV32_OPT [[COPY]], 0 :: (load (s32))
+ ; CHECK-4BYTE-NEXT: [[ADD:%[0-9]+]]:gpr = ADD [[PseudoLD_RV32_OPT]], [[PseudoLD_RV32_OPT2]]
+ ; CHECK-4BYTE-NEXT: [[ADD1:%[0-9]+]]:gpr = ADD [[PseudoLD_RV32_OPT3]], [[PseudoLD_RV32_OPT1]]
+ ; CHECK-4BYTE-NEXT: PseudoRET
+ %0:gpr = COPY $x10
+ %1:gpr = COPY $x11
+ %2:gpr = LW %stack.0, 0 :: (load (s32) from %ir.arr)
+ %3:gpr = LW %0, 4 :: (load (s32))
+ %4:gpr = LW %0, 0 :: (load (s32))
+ %5:gpr = LW %stack.0, 4 :: (load (s32) from %ir.arrayidx1)
+ %6:gpr = ADD %2, %4
+ %7:gpr = ADD %3, %5
+ PseudoRET
+
+...
>From db33a19c9f3cf9eae5f782967c501a5d09cf1ce2 Mon Sep 17 00:00:00 2001
From: Brandon Wu <brandon.wu at sifive.com>
Date: Wed, 31 Dec 2025 01:49:25 -0800
Subject: [PATCH 2/5] fixup! revert post-ra change
---
.../Target/RISCV/RISCVLoadStoreOptimizer.cpp | 67 ++++---------
.../CodeGen/RISCV/zilsd-ldst-opt-postra.mir | 99 -------------------
2 files changed, 19 insertions(+), 147 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVLoadStoreOptimizer.cpp b/llvm/lib/Target/RISCV/RISCVLoadStoreOptimizer.cpp
index c4b83adef5ee3..ff5894e1a657d 100644
--- a/llvm/lib/Target/RISCV/RISCVLoadStoreOptimizer.cpp
+++ b/llvm/lib/Target/RISCV/RISCVLoadStoreOptimizer.cpp
@@ -576,12 +576,7 @@ void RISCVLoadStoreOpt::splitLdSdIntoTwo(MachineBasicBlock &MBB,
const MachineOperand &BaseOp = MI->getOperand(2);
Register FirstReg = FirstOp.getReg();
Register SecondReg = SecondOp.getReg();
- unsigned BaseReg;
- bool BaseIsReg = BaseOp.isReg();
- if (BaseIsReg)
- BaseReg = BaseOp.getReg().id();
- else
- BaseReg = BaseOp.getIndex();
+ Register BaseReg = BaseOp.getReg();
// Handle both immediate and symbolic operands for offset
const MachineOperand &OffsetOp = MI->getOperand(3);
@@ -606,37 +601,26 @@ void RISCVLoadStoreOpt::splitLdSdIntoTwo(MachineBasicBlock &MBB,
// we can just switch the order to resolve that:
// X13 = LW X10, 4
// X10 = LW killed X10, 0
- if (BaseIsReg && FirstReg == BaseReg) {
+ if (FirstReg == BaseReg) {
MIB2 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
.addReg(SecondReg,
- RegState::Define | getDeadRegState(SecondOp.isDead()));
+ RegState::Define | getDeadRegState(SecondOp.isDead()))
+ .addReg(BaseReg);
MIB1 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
.addReg(FirstReg,
- RegState::Define | getDeadRegState(FirstOp.isDead()));
-
- if (BaseIsReg) {
- MIB2 = MIB2.addReg(BaseReg);
- MIB1 = MIB1.addReg(BaseReg, getKillRegState(BaseOp.isKill()));
- } else {
- MIB2 = MIB2.addFrameIndex(BaseReg);
- MIB1 = MIB1.addFrameIndex(BaseReg);
- }
+ RegState::Define | getDeadRegState(FirstOp.isDead()))
+ .addReg(BaseReg, getKillRegState(BaseOp.isKill()));
} else {
MIB1 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
.addReg(FirstReg,
- RegState::Define | getDeadRegState(FirstOp.isDead()));
+ RegState::Define | getDeadRegState(FirstOp.isDead()))
+ .addReg(BaseReg);
+
MIB2 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
.addReg(SecondReg,
- RegState::Define | getDeadRegState(SecondOp.isDead()));
-
- if (BaseIsReg) {
- MIB1 = MIB1.addReg(BaseReg);
- MIB2 = MIB2.addReg(BaseReg, getKillRegState(BaseOp.isKill()));
- } else {
- MIB1 = MIB1.addFrameIndex(BaseReg);
- MIB2 = MIB2.addFrameIndex(BaseReg);
- }
+ RegState::Define | getDeadRegState(SecondOp.isDead()))
+ .addReg(BaseReg, getKillRegState(BaseOp.isKill()));
}
++NumLD2LW;
@@ -646,18 +630,12 @@ void RISCVLoadStoreOpt::splitLdSdIntoTwo(MachineBasicBlock &MBB,
FirstReg != SecondReg &&
"First register and second register is impossible to be same register");
MIB1 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
- .addReg(FirstReg, getKillRegState(FirstOp.isKill()));
+ .addReg(FirstReg, getKillRegState(FirstOp.isKill()))
+ .addReg(BaseReg);
MIB2 = BuildMI(MBB, MBBI, DL, TII->get(Opc))
- .addReg(SecondReg, getKillRegState(SecondOp.isKill()));
-
- if (BaseIsReg) {
- MIB1 = MIB1.addReg(BaseReg);
- MIB2 = MIB2.addReg(BaseReg, getKillRegState(BaseOp.isKill()));
- } else {
- MIB1 = MIB1.addFrameIndex(BaseReg);
- MIB2 = MIB2.addFrameIndex(BaseReg);
- }
+ .addReg(SecondReg, getKillRegState(SecondOp.isKill()))
+ .addReg(BaseReg, getKillRegState(BaseOp.isKill()));
++NumSD2SW;
LLVM_DEBUG(dbgs() << "Split SD back to two SW instructions\n");
@@ -711,12 +689,7 @@ bool RISCVLoadStoreOpt::fixInvalidRegPairOp(MachineBasicBlock &MBB,
// Registers are valid, convert to real LD/SD instruction
const MachineOperand &BaseOp = MI->getOperand(2);
- unsigned BaseReg;
- bool BaseIsReg = BaseOp.isReg();
- if (BaseIsReg)
- BaseReg = BaseOp.getReg().id();
- else
- BaseReg = BaseOp.getIndex();
+ Register BaseReg = BaseOp.getReg();
DebugLoc DL = MI->getDebugLoc();
// Handle both immediate and symbolic operands for offset
const MachineOperand &OffsetOp = MI->getOperand(3);
@@ -738,11 +711,9 @@ bool RISCVLoadStoreOpt::fixInvalidRegPairOp(MachineBasicBlock &MBB,
MIB.addReg(RegPair, getKillRegState(FirstOp.isKill() && SecondOp.isKill()));
}
- if (BaseIsReg)
- MIB = MIB.addReg(BaseReg, getKillRegState(BaseOp.isKill()));
- else
- MIB = MIB.addFrameIndex(BaseReg);
- MIB.add(OffsetOp).cloneMemRefs(*MI);
+ MIB.addReg(BaseReg, getKillRegState(BaseOp.isKill()))
+ .add(OffsetOp)
+ .cloneMemRefs(*MI);
LLVM_DEBUG(dbgs() << "Converted pseudo to real instruction: " << *MIB
<< "\n");
diff --git a/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-postra.mir b/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-postra.mir
index ec7ecf5a534ef..c27cb25366f27 100644
--- a/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-postra.mir
+++ b/llvm/test/CodeGen/RISCV/zilsd-ldst-opt-postra.mir
@@ -10,10 +10,6 @@
ret i32 %5
}
- define void @expand_pseudold_frame_index_valid(ptr %0) {
- ret void
- }
-
define void @expand_pseudosd_valid(ptr %0, i32 %1, i32 %2) {
store i32 %1, ptr %0, align 4
%4 = getelementptr inbounds i32, ptr %0, i32 1
@@ -21,10 +17,6 @@
ret void
}
- define void @expand_pseudosd_frame_index_valid(ptr %0, i32 %1, i32 %2) {
- ret void
- }
-
define i32 @expand_pseudold_invalid_pair(ptr %0) {
%2 = load i32, ptr %0, align 4
%3 = getelementptr inbounds i32, ptr %0, i32 1
@@ -33,10 +25,6 @@
ret i32 %5
}
- define void @expand_pseudold_frame_index_invalid_pair(ptr %0) {
- ret void
- }
-
define void @expand_pseudosd_invalid_pair(ptr %0, i32 %1, i32 %2) {
store i32 %1, ptr %0, align 4
%4 = getelementptr inbounds i32, ptr %0, i32 1
@@ -44,10 +32,6 @@
ret void
}
- define void @expand_pseudosd_frame_index_invalid_pair(ptr %0, i32 %1, i32 %2) {
- ret void
- }
-
define void @store_zero_combine_valid(ptr %0) {
store i32 0, ptr %0, align 8
%2 = getelementptr inbounds i32, ptr %0, i32 1
@@ -91,27 +75,6 @@ body: |
$x10 = ADD killed $x12, killed $x13
PseudoRET implicit $x10
-...
----
-# Valid consecutive even/odd register pair - should expand to LD_RV32
-name: expand_pseudold_frame_index_valid
-tracksRegLiveness: false
-stack:
- - { id: 0, offset: 0, size: 8, alignment: 4 }
-body: |
- bb.0:
- liveins: $x10
-
- ; CHECK-LABEL: name: expand_pseudold_frame_index_valid
- ; CHECK: liveins: $x10
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: $x12_x13 = LD_RV32 %stack.0, 0
- ; CHECK-NEXT: $x10 = ADD killed $x12, killed $x13
- ; CHECK-NEXT: PseudoRET implicit $x10
- $x12, $x13 = PseudoLD_RV32_OPT %stack.0, 0
- $x10 = ADD killed $x12, killed $x13
- PseudoRET implicit $x10
-
...
---
# Valid consecutive even/odd register pair - should expand to SD_RV32
@@ -130,26 +93,6 @@ body: |
PseudoSD_RV32_OPT killed $x12, killed $x13, killed $x10, 0
PseudoRET
-...
----
-# Valid consecutive even/odd register pair - should expand to SD_RV32
-name: expand_pseudosd_frame_index_valid
-tracksRegLiveness: false
-stack:
- - { id: 0, offset: 0, size: 8, alignment: 4 }
-body: |
- bb.0:
- liveins: $x12, $x13
-
- ; PseudoSD_RV32_OPT with consecutive even/odd registers (x12, x13)
- ; CHECK-LABEL: name: expand_pseudosd_frame_index_valid
- ; CHECK: liveins: $x12, $x13
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: SD_RV32 killed $x12_x13, %stack.0, 0
- ; CHECK-NEXT: PseudoRET
- PseudoSD_RV32_OPT killed $x12, killed $x13, %stack.0, 0
- PseudoRET
-
...
---
# Invalid register pair (not consecutive) - should decompose back to LW
@@ -172,26 +115,6 @@ body: |
$x10 = ADD killed $x11, killed $x13
PseudoRET implicit $x10
-...
----
-# Invalid register pair (not consecutive) - should decompose back to LW
-name: expand_pseudold_frame_index_invalid_pair
-tracksRegLiveness: false
-stack:
- - { id: 0, offset: 0, size: 8, alignment: 4 }
-body: |
- bb.0:
- ; PseudoLD_RV32_OPT with non-consecutive registers (x11, x13)
- ; Should decompose back to two LW instructions
- ; CHECK-LABEL: name: expand_pseudold_frame_index_invalid_pair
- ; CHECK: $x11 = LW %stack.0, 0
- ; CHECK-NEXT: $x13 = LW %stack.0, 4
- ; CHECK-NEXT: $x10 = ADD killed $x11, killed $x13
- ; CHECK-NEXT: PseudoRET implicit $x10
- $x11, $x13 = PseudoLD_RV32_OPT %stack.0, 0
- $x10 = ADD killed $x11, killed $x13
- PseudoRET implicit $x10
-
...
---
# Invalid register pair (not even/odd) - should decompose back to SW
@@ -212,28 +135,6 @@ body: |
PseudoSD_RV32_OPT killed $x11, killed $x14, killed $x10, 0
PseudoRET
-...
----
-# Invalid register pair (not even/odd) - should decompose back to SW
-name: expand_pseudosd_frame_index_invalid_pair
-tracksRegLiveness: false
-stack:
- - { id: 0, offset: 0, size: 8, alignment: 4 }
-body: |
- bb.0:
- liveins: $x11, $x14
-
- ; PseudoSD_RV32_OPT with non-consecutive registers (x11, x14)
- ; Should decompose back to two SW instructions
- ; CHECK-LABEL: name: expand_pseudosd_frame_index_invalid_pair
- ; CHECK: liveins: $x11, $x14
- ; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: SW killed $x11, %stack.0, 0
- ; CHECK-NEXT: SW killed $x14, %stack.0, 4
- ; CHECK-NEXT: PseudoRET
- PseudoSD_RV32_OPT killed $x11, killed $x14, %stack.0, 0
- PseudoRET
-
...
---
# Test store zero combinations - zeros don't need consecutive pairs
>From ad7cd840d9780d44d9840336deff8fa11c55aadd Mon Sep 17 00:00:00 2001
From: Brandon Wu <brandon.wu at sifive.com>
Date: Wed, 7 Jan 2026 03:20:07 -0800
Subject: [PATCH 3/5] fixup! handle edge case
---
llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
index d802d19a0edcb..7b94daec03098 100644
--- a/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp
@@ -547,9 +547,11 @@ bool RISCVRegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
// MIPS Prefetch instructions require the offset to be 9 bits encoded.
MI.getOperand(FIOperandNum + 1).ChangeToImmediate(0);
} else if ((Opc == RISCV::PseudoRV32ZdinxLD ||
- Opc == RISCV::PseudoRV32ZdinxSD) &&
+ Opc == RISCV::PseudoRV32ZdinxSD ||
+ Opc == RISCV::PseudoLD_RV32_OPT ||
+ Opc == RISCV::PseudoSD_RV32_OPT) &&
Lo12 >= 2044) {
- // This instruction will be split into 2 instructions. The second
+ // This instruction will/might be split into 2 instructions. The second
// 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);
>From a9aa432a19e2f449e20fcc319799cc743224e71e Mon Sep 17 00:00:00 2001
From: Brandon Wu <brandon.wu at sifive.com>
Date: Wed, 7 Jan 2026 03:28:18 -0800
Subject: [PATCH 4/5] fixup! llvm ir test
---
llvm/test/CodeGen/RISCV/zilsd.ll | 48 ++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/llvm/test/CodeGen/RISCV/zilsd.ll b/llvm/test/CodeGen/RISCV/zilsd.ll
index 27b1ff76f6f05..5fbc7de1365fa 100644
--- a/llvm/test/CodeGen/RISCV/zilsd.ll
+++ b/llvm/test/CodeGen/RISCV/zilsd.ll
@@ -226,3 +226,51 @@ entry:
store i64 %b, ptr %add.ptr, align 8
ret void
}
+
+define void @fold_frame_idx(i32 %val1, i32 %val2, ptr nocapture %p) nounwind {
+; SLOW-LABEL: fold_frame_idx:
+; SLOW: # %bb.0: # %entry
+; SLOW-NEXT: addi sp, sp, -400
+; SLOW-NEXT: sw a0, 40(sp)
+; SLOW-NEXT: sw a1, 44(sp)
+; SLOW-NEXT: addi sp, sp, 400
+; SLOW-NEXT: ret
+;
+; FAST-LABEL: fold_frame_idx:
+; FAST: # %bb.0: # %entry
+; FAST-NEXT: addi sp, sp, -400
+; FAST-NEXT: sd a0, 40(sp)
+; FAST-NEXT: addi sp, sp, 400
+; FAST-NEXT: ret
+;
+; 4BYTEALIGN-LABEL: fold_frame_idx:
+; 4BYTEALIGN: # %bb.0: # %entry
+; 4BYTEALIGN-NEXT: addi sp, sp, -400
+; 4BYTEALIGN-NEXT: sd a0, 40(sp)
+; 4BYTEALIGN-NEXT: addi sp, sp, 400
+; 4BYTEALIGN-NEXT: ret
+entry:
+ %local = alloca [100 x i32]
+ %local.ptr = getelementptr inbounds i32, ptr %local, i64 10
+ %local1.ptr = getelementptr inbounds i32, ptr %local, i64 11
+ store i32 %val1, ptr %local.ptr, align 4
+ store i32 %val2, ptr %local1.ptr, align 4
+ ret void
+}
+
+define void @dont_fold_frame_idx(ptr nocapture %p, i32 %val1, i32 %val2) nounwind {
+; CHECK-LABEL: dont_fold_frame_idx:
+; CHECK: # %bb.0: # %entry
+; CHECK-NEXT: addi sp, sp, -400
+; CHECK-NEXT: sw a1, 40(sp)
+; CHECK-NEXT: sw a2, 44(sp)
+; CHECK-NEXT: addi sp, sp, 400
+; CHECK-NEXT: ret
+entry:
+ %local = alloca [100 x i32]
+ %local.ptr = getelementptr inbounds i32, ptr %local, i64 10
+ %local1.ptr = getelementptr inbounds i32, ptr %local, i64 11
+ store i32 %val1, ptr %local.ptr, align 4
+ store i32 %val2, ptr %local1.ptr, align 4
+ ret void
+}
>From 581bd34dbc2f7b668e4d7b2b4a0ed7eca299d751 Mon Sep 17 00:00:00 2001
From: Brandon Wu <brandon.wu at sifive.com>
Date: Thu, 8 Jan 2026 00:16:00 -0800
Subject: [PATCH 5/5] fixup! address Craig's comments
---
llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp | 25 +++++++------------
1 file changed, 9 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp b/llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp
index 2de73e6267dd0..90149acb1cdc1 100644
--- a/llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp
+++ b/llvm/lib/Target/RISCV/RISCVZilsdOptimizer.cpp
@@ -306,12 +306,6 @@ bool RISCVPreAllocZilsdOpt::rescheduleOps(
const MachineOperand &OffsetOp = MI0->getOperand(2);
assert((BaseOp.isReg() || BaseOp.isFI()) &&
"Base register should be register or frame index");
- unsigned BaseReg;
- bool BaseIsReg = BaseOp.isReg();
- if (BaseIsReg)
- BaseReg = BaseOp.getReg().id();
- else
- BaseReg = BaseOp.getIndex();
// At this point, MI0 and MI1 are:
// 1. both either LW or SW.
@@ -371,10 +365,10 @@ bool RISCVPreAllocZilsdOpt::rescheduleOps(
LLVM_DEBUG(dbgs() << "Formed SD: " << *MIB << "\n");
}
- if (BaseIsReg)
- MIB = MIB.addReg(BaseReg);
+ if (BaseOp.isReg())
+ MIB = MIB.addReg(BaseOp.getReg());
else
- MIB = MIB.addFrameIndex(BaseReg);
+ MIB = MIB.addFrameIndex(BaseOp.getIndex());
MIB = MIB.add(OffsetOp);
// Copy memory operands
@@ -478,21 +472,20 @@ bool RISCVPreAllocZilsdOpt::rescheduleLoadStoreInstrs(MachineBasicBlock *MBB) {
bool IsLd = (MI.getOpcode() == RISCV::LW);
const MachineOperand &BaseOp = MI.getOperand(1);
- unsigned BaseReg;
- bool BaseIsReg = BaseOp.isReg();
- if (BaseIsReg)
- BaseReg = BaseOp.getReg().id();
+ unsigned Base;
+ if (BaseOp.isReg())
+ Base = BaseOp.getReg().id();
else
- BaseReg = BaseOp.getIndex();
+ Base = BaseOp.getIndex();
bool StopHere = false;
// Lambda to find or add base register entries
auto FindBases = [&](Base2InstMap &Base2Ops, BaseVec &Bases) {
- auto [BI, Inserted] = Base2Ops.try_emplace({BaseReg, Offset.first});
+ auto [BI, Inserted] = Base2Ops.try_emplace({Base, Offset.first});
if (Inserted) {
// First time seeing this base register
BI->second.push_back(&MI);
- Bases.push_back({BaseReg, Offset.first});
+ Bases.push_back({Base, Offset.first});
return;
}
// Check if we've seen this exact base+offset before
More information about the llvm-commits
mailing list