-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[llvm][RISCV] Implement Zilsd load/store pair optimization #158640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
4bbf382
5722750
569a86c
ce8fc11
2ff058f
a4cca81
294d83e
14c0463
57c10b5
7c9b744
90f3aa4
ed57a2c
fff9d4b
483104e
66d95f1
c4c4925
7c377e3
fe67c22
696bad8
d79e21c
2a87065
44f5809
9453b78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,9 @@ | |
| // paired instruction, leveraging hardware support for paired memory accesses. | ||
| // Much of the pairing logic is adapted from the AArch64LoadStoreOpt pass. | ||
| // | ||
| // Post-allocation Zilsd decomposition: Fixes invalid LD/SD instructions if | ||
| // register allocation didn't provide suitable consecutive registers. | ||
| // | ||
| // NOTE: The AArch64LoadStoreOpt pass performs additional optimizations such as | ||
| // merging zero store instructions, promoting loads that read directly from a | ||
| // preceding store, and merging base register updates with load/store | ||
|
|
@@ -23,6 +26,7 @@ | |
|
|
||
| #include "RISCV.h" | ||
| #include "RISCVTargetMachine.h" | ||
| #include "llvm/ADT/Statistic.h" | ||
| #include "llvm/Analysis/AliasAnalysis.h" | ||
| #include "llvm/CodeGen/Passes.h" | ||
| #include "llvm/MC/TargetRegistry.h" | ||
|
|
@@ -38,6 +42,8 @@ using namespace llvm; | |
| // pairs. | ||
| static cl::opt<unsigned> LdStLimit("riscv-load-store-scan-limit", cl::init(128), | ||
| cl::Hidden); | ||
| STATISTIC(NumLD2LW, "Number of LD instructions split back to LW"); | ||
| STATISTIC(NumSD2SW, "Number of SD instructions split back to SW"); | ||
|
|
||
| namespace { | ||
|
|
||
|
|
@@ -75,6 +81,13 @@ struct RISCVLoadStoreOpt : public MachineFunctionPass { | |
| mergePairedInsns(MachineBasicBlock::iterator I, | ||
| MachineBasicBlock::iterator Paired, bool MergeForward); | ||
|
|
||
| // Post reg-alloc zilsd part | ||
| bool fixInvalidRegPairOp(MachineBasicBlock &MBB, | ||
| MachineBasicBlock::iterator &MBBI); | ||
| bool isValidZilsdRegPair(Register First, Register Second); | ||
| void splitLdSdIntoTwo(MachineBasicBlock &MBB, | ||
| MachineBasicBlock::iterator &MBBI, bool IsLoad); | ||
|
|
||
| private: | ||
| AliasAnalysis *AA; | ||
| MachineRegisterInfo *MRI; | ||
|
|
@@ -92,8 +105,6 @@ bool RISCVLoadStoreOpt::runOnMachineFunction(MachineFunction &Fn) { | |
| if (skipFunction(Fn.getFunction())) | ||
| return false; | ||
| const RISCVSubtarget &Subtarget = Fn.getSubtarget<RISCVSubtarget>(); | ||
| if (!Subtarget.useMIPSLoadStorePairs()) | ||
| return false; | ||
|
|
||
| bool MadeChange = false; | ||
| TII = Subtarget.getInstrInfo(); | ||
|
|
@@ -103,18 +114,34 @@ bool RISCVLoadStoreOpt::runOnMachineFunction(MachineFunction &Fn) { | |
| ModifiedRegUnits.init(*TRI); | ||
| UsedRegUnits.init(*TRI); | ||
|
|
||
| for (MachineBasicBlock &MBB : Fn) { | ||
| LLVM_DEBUG(dbgs() << "MBB: " << MBB.getName() << "\n"); | ||
| if (Subtarget.useMIPSLoadStorePairs()) { | ||
| for (MachineBasicBlock &MBB : Fn) { | ||
| LLVM_DEBUG(dbgs() << "MBB: " << MBB.getName() << "\n"); | ||
|
|
||
| for (MachineBasicBlock::iterator MBBI = MBB.begin(), E = MBB.end(); | ||
| MBBI != E;) { | ||
| if (TII->isPairableLdStInstOpc(MBBI->getOpcode()) && | ||
| tryToPairLdStInst(MBBI)) | ||
| MadeChange = true; | ||
| else | ||
| ++MBBI; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for (MachineBasicBlock::iterator MBBI = MBB.begin(), E = MBB.end(); | ||
| MBBI != E;) { | ||
| if (TII->isPairableLdStInstOpc(MBBI->getOpcode()) && | ||
| tryToPairLdStInst(MBBI)) | ||
| MadeChange = true; | ||
| else | ||
| ++MBBI; | ||
| if (!Subtarget.is64Bit() && Subtarget.hasStdExtZilsd()) { | ||
| for (auto &MBB : Fn) { | ||
| for (auto MBBI = MBB.begin(), E = MBB.end(); MBBI != E;) { | ||
| if (fixInvalidRegPairOp(MBB, MBBI)) { | ||
| MadeChange = true; | ||
| // Iterator was updated by fixInvalidRegPairOp | ||
| } else { | ||
| ++MBBI; | ||
| } | ||
| } | ||
topperc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| return MadeChange; | ||
| } | ||
|
|
||
|
|
@@ -395,6 +422,157 @@ RISCVLoadStoreOpt::mergePairedInsns(MachineBasicBlock::iterator I, | |
| return NextI; | ||
| } | ||
|
|
||
| //===----------------------------------------------------------------------===// | ||
| // Post reg-alloc zilsd pass implementation | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| bool RISCVLoadStoreOpt::isValidZilsdRegPair(Register First, Register Second) { | ||
| // Special case: First register can not be zero unless both registers are | ||
| // zeros. | ||
| // Spec says: LD instructions with destination x0 are processed as any other | ||
| // load, but the result is discarded entirely and x1 is not written. If using | ||
| // x0 as src of SD, the entire 64-bit operand is zero — i.e., register x1 is | ||
| // not accessed. | ||
| if (First == RISCV::X0 && Second == RISCV::X0) | ||
| return true; | ||
|
||
| if (First == RISCV::X0) | ||
| return false; | ||
|
|
||
| // Check if registers form a valid even/odd pair for Zilsd | ||
| unsigned FirstNum = TRI->getEncodingValue(First); | ||
| unsigned SecondNum = TRI->getEncodingValue(Second); | ||
|
|
||
| // Must be consecutive and first must be even | ||
| return (FirstNum % 2 == 0) && (SecondNum == FirstNum + 1); | ||
4vtomat marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| void RISCVLoadStoreOpt::splitLdSdIntoTwo(MachineBasicBlock &MBB, | ||
| MachineBasicBlock::iterator &MBBI, | ||
| bool IsLoad) { | ||
| MachineInstr *MI = &*MBBI; | ||
| DebugLoc DL = MI->getDebugLoc(); | ||
|
|
||
| const MachineOperand &FirstOp = MI->getOperand(0); | ||
| const MachineOperand &SecondOp = MI->getOperand(1); | ||
| const MachineOperand &BaseOp = MI->getOperand(2); | ||
| Register FirstReg = FirstOp.getReg(); | ||
| Register SecondReg = SecondOp.getReg(); | ||
| Register BaseReg = BaseOp.getReg(); | ||
|
|
||
| // Handle both immediate and symbolic operands for offset | ||
| const MachineOperand &OffsetOp = MI->getOperand(3); | ||
| int BaseOffset; | ||
| if (OffsetOp.isImm()) | ||
| BaseOffset = OffsetOp.getImm(); | ||
| else | ||
| // For symbolic operands, extract the embedded offset | ||
| BaseOffset = OffsetOp.getOffset(); | ||
|
|
||
| unsigned Opc = IsLoad ? RISCV::LW : RISCV::SW; | ||
| MachineInstrBuilder MIB1, MIB2; | ||
|
|
||
| // Create two separate instructions | ||
| if (IsLoad) { | ||
| MIB1 = BuildMI(MBB, MBBI, DL, TII->get(Opc), FirstReg).addReg(BaseReg); | ||
|
|
||
| MIB2 = BuildMI(MBB, MBBI, DL, TII->get(Opc), SecondReg) | ||
| .addReg(BaseReg, BaseOp.isKill() ? RegState::Kill : 0); | ||
topperc marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ++NumLD2LW; | ||
| LLVM_DEBUG(dbgs() << "Split LD back to two LW instructions\n"); | ||
| } else { | ||
| assert( | ||
| FirstReg != SecondReg && | ||
| "First register and second register is impossible to be same register"); | ||
| MIB1 = BuildMI(MBB, MBBI, DL, TII->get(Opc)) | ||
| .addReg(FirstReg, FirstOp.isKill() ? RegState::Kill : 0) | ||
topperc marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .addReg(BaseReg); | ||
|
|
||
| MIB2 = BuildMI(MBB, MBBI, DL, TII->get(Opc)) | ||
| .addReg(SecondReg, SecondOp.isKill() ? RegState::Kill : 0) | ||
| .addReg(BaseReg, BaseOp.isKill() ? RegState::Kill : 0); | ||
topperc marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ++NumSD2SW; | ||
| LLVM_DEBUG(dbgs() << "Split SD back to two SW instructions\n"); | ||
| } | ||
|
|
||
| // Add offset operands - preserve symbolic references | ||
| MIB1.add(OffsetOp); | ||
| if (OffsetOp.isImm()) | ||
| MIB2.addImm(BaseOffset + 4); | ||
| else if (OffsetOp.isGlobal()) | ||
| MIB2.addGlobalAddress(OffsetOp.getGlobal(), BaseOffset + 4, | ||
| OffsetOp.getTargetFlags()); | ||
| else if (OffsetOp.isCPI()) | ||
| MIB2.addConstantPoolIndex(OffsetOp.getIndex(), BaseOffset + 4, | ||
| OffsetOp.getTargetFlags()); | ||
| else if (OffsetOp.isBlockAddress()) | ||
| MIB2.addBlockAddress(OffsetOp.getBlockAddress(), BaseOffset + 4, | ||
| OffsetOp.getTargetFlags()); | ||
|
|
||
| // Copy memory operands if the original instruction had them | ||
| // FIXME: This is overly conservative; the new instruction accesses 4 bytes, | ||
| // not 8. | ||
| MIB1.cloneMemRefs(*MI); | ||
| MIB2.cloneMemRefs(*MI); | ||
topperc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Remove the original paired instruction and update iterator | ||
| MBBI = MBB.erase(MBBI); | ||
| } | ||
|
|
||
| bool RISCVLoadStoreOpt::fixInvalidRegPairOp(MachineBasicBlock &MBB, | ||
| MachineBasicBlock::iterator &MBBI) { | ||
| MachineInstr *MI = &*MBBI; | ||
| unsigned Opcode = MI->getOpcode(); | ||
|
|
||
| // Check if this is a Zilsd pseudo that needs fixing | ||
| if (Opcode != RISCV::PseudoLD_RV32_OPT && Opcode != RISCV::PseudoSD_RV32_OPT) | ||
| return false; | ||
|
|
||
| bool IsLoad = Opcode == RISCV::PseudoLD_RV32_OPT; | ||
|
|
||
| Register FirstReg = MI->getOperand(0).getReg(); | ||
| Register SecondReg = MI->getOperand(1).getReg(); | ||
|
|
||
| if (!isValidZilsdRegPair(FirstReg, SecondReg)) { | ||
| // Need to split back into two instructions | ||
| splitLdSdIntoTwo(MBB, MBBI, IsLoad); | ||
| return true; | ||
| } | ||
|
|
||
| // Registers are valid, convert to real LD/SD instruction | ||
| Register BaseReg = MI->getOperand(2).getReg(); | ||
| DebugLoc DL = MI->getDebugLoc(); | ||
| // Handle both immediate and symbolic operands for offset | ||
| const MachineOperand &OffsetOp = MI->getOperand(3); | ||
|
|
||
| unsigned RealOpc = IsLoad ? RISCV::LD_RV32 : RISCV::SD_RV32; | ||
|
|
||
| // Create register pair from the two individual registers | ||
| unsigned RegPair = TRI->getMatchingSuperReg(FirstReg, RISCV::sub_gpr_even, | ||
| &RISCV::GPRPairRegClass); | ||
| // Create the real LD/SD instruction with register pair | ||
| MachineInstrBuilder MIB = BuildMI(MBB, MBBI, DL, TII->get(RealOpc)); | ||
|
|
||
| if (IsLoad) { | ||
| // For LD, the register pair is the destination | ||
| MIB.addReg(RegPair, RegState::Define); | ||
topperc marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } else { | ||
| // For SD, the register pair is the source | ||
| MIB.addReg(RegPair); | ||
topperc marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| MIB.addReg(BaseReg).add(OffsetOp).cloneMemRefs(*MI); | ||
topperc marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| LLVM_DEBUG(dbgs() << "Converted pseudo to real instruction: " << *MIB | ||
| << "\n"); | ||
|
|
||
| // Remove the pseudo instruction and update iterator | ||
| MBBI = MBB.erase(MBBI); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // Returns an instance of the Load / Store Optimization pass. | ||
| FunctionPass *llvm::createRISCVLoadStoreOptPass() { | ||
| return new RISCVLoadStoreOpt(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -864,6 +864,43 @@ bool RISCVRegisterInfo::getRegAllocationHints( | |
| const MachineRegisterInfo *MRI = &MF.getRegInfo(); | ||
| auto &Subtarget = MF.getSubtarget<RISCVSubtarget>(); | ||
|
|
||
| // Handle RegPairEven/RegPairOdd hints for Zilsd register pairs | ||
| std::pair<unsigned, Register> Hint = MRI->getRegAllocationHint(VirtReg); | ||
| unsigned HintType = Hint.first; | ||
| Register Partner = Hint.second; | ||
|
|
||
| if (HintType == RISCVRI::RegPairEven || HintType == RISCVRI::RegPairOdd) { | ||
| // Check if we want the even or odd register of a consecutive pair | ||
| bool WantOdd = (HintType == RISCVRI::RegPairOdd); | ||
|
|
||
| // First priority: Check if partner is already allocated | ||
| if (Partner.isVirtual() && VRM && VRM->hasPhys(Partner)) { | ||
| MCRegister PartnerPhys = VRM->getPhys(Partner); | ||
| // Calculate the exact register we need for consecutive pairing | ||
| MCRegister TargetReg = PartnerPhys.id() + (WantOdd ? 1 : -1); | ||
|
|
||
| // Verify it's valid and available | ||
| if (RISCV::GPRRegClass.contains(TargetReg) && | ||
| is_contained(Order, TargetReg)) | ||
| Hints.push_back(TargetReg.id()); | ||
topperc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // Second priority: Try to find consecutive register pairs in the allocation | ||
| // order | ||
| for (MCPhysReg PhysReg : Order) { | ||
| if (!PhysReg) | ||
| continue; | ||
|
|
||
| unsigned RegNum = getEncodingValue(PhysReg); | ||
| // Check if this register matches the even/odd requirement | ||
| bool IsOdd = (RegNum % 2 != 0); | ||
|
|
||
| // Verify the pair register exists and is in the same register class | ||
| if ((WantOdd && IsOdd) || (!WantOdd && !IsOdd)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we check reserved registers here like ARM so we don't hint X5 which can never pair with the reserved X4 register. We don't want to hint S1(X9) when S0(X8) is reserved for the frame pointer. And we don't want to hint X1, since X0 and X1 isn't a valid pair. Users can also reserved registers from the command line. We can fix this in a follow up. |
||
| Hints.push_back(PhysReg); | ||
| } | ||
| } | ||
|
|
||
| bool BaseImplRetVal = TargetRegisterInfo::getRegAllocationHints( | ||
| VirtReg, Order, Hints, MF, VRM, Matrix); | ||
|
|
||
|
|
@@ -1005,6 +1042,35 @@ bool RISCVRegisterInfo::getRegAllocationHints( | |
| return BaseImplRetVal; | ||
| } | ||
|
|
||
| void RISCVRegisterInfo::updateRegAllocHint(Register Reg, Register NewReg, | ||
| MachineFunction &MF) const { | ||
| MachineRegisterInfo *MRI = &MF.getRegInfo(); | ||
| std::pair<unsigned, Register> Hint = MRI->getRegAllocationHint(Reg); | ||
|
|
||
| // Handle RegPairEven/RegPairOdd hints for Zilsd register pairs | ||
| if ((Hint.first == RISCVRI::RegPairOdd || | ||
| Hint.first == RISCVRI::RegPairEven) && | ||
| Hint.second.isVirtual()) { | ||
| // If 'Reg' is one of the even/odd register pair and it's now changed | ||
| // (e.g. coalesced) into a different register, the other register of the | ||
| // pair allocation hint must be updated to reflect the relationship change. | ||
| Register Partner = Hint.second; | ||
| std::pair<unsigned, Register> PartnerHint = | ||
| MRI->getRegAllocationHint(Partner); | ||
|
|
||
| // Make sure partner still points to us | ||
| if (PartnerHint.second == Reg) { | ||
| // Update partner to point to NewReg instead of Reg | ||
| MRI->setRegAllocationHint(Partner, PartnerHint.first, NewReg); | ||
|
|
||
| // If NewReg is virtual, set up the reciprocal hint | ||
| // NewReg takes over Reg's role, so it gets the SAME hint type as Reg | ||
| if (NewReg.isVirtual()) | ||
| MRI->setRegAllocationHint(NewReg, Hint.first, Partner); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Register | ||
| RISCVRegisterInfo::findVRegWithEncoding(const TargetRegisterClass &RegClass, | ||
| uint16_t Encoding) const { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.