155 lines
5.9 KiB
C++
155 lines
5.9 KiB
C++
//===-- W65816PreSpillCrossCall.cpp - Pre-spill cross-call Acc16 vregs ---===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Pre-RA pass that pre-spills Acc16 vregs whose live range crosses a
|
|
// JSL call site. Greedy regalloc has only one register in the Acc16
|
|
// class (A), and JSL clobbers A — so any Acc16 vreg live across a
|
|
// call MUST be spilled. Greedy normally figures this out, but for
|
|
// functions with many such vregs (the "ok |= bit" bitmask pattern
|
|
// repeated across N if-blocks each calling a helper) greedy can run
|
|
// out of registers during spill placement, aborting compilation with
|
|
// "ran out of registers".
|
|
//
|
|
// We pre-empt the failure: walk the MBB, find cross-call Acc16
|
|
// vregs, and explicitly STAfi their value after the def + LDAfi at
|
|
// each use. This converts the cross-call live ranges into stack-
|
|
// resident loads, dropping greedy's pressure to the point it can
|
|
// always succeed.
|
|
//
|
|
// Cost: an extra STAfi+LDAfi (~6 cyc each) per cross-call vreg.
|
|
// This is the same cost greedy would emit if it succeeded (a spill
|
|
// + reload), so we're not pessimising — just making the spill
|
|
// explicit BEFORE greedy gets confused.
|
|
//
|
|
// Heuristic: only pre-spill if the function has > 5 call sites OR a
|
|
// cross-call Acc16 vreg with > 2 uses after the call. Below that,
|
|
// let greedy do its thing (it usually picks better placements).
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "W65816.h"
|
|
#include "W65816InstrInfo.h"
|
|
#include "W65816Subtarget.h"
|
|
#include "llvm/CodeGen/MachineFrameInfo.h"
|
|
#include "llvm/CodeGen/MachineFunction.h"
|
|
#include "llvm/CodeGen/MachineFunctionPass.h"
|
|
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
|
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "w65816-pre-spill-cross-call"
|
|
|
|
namespace {
|
|
|
|
class W65816PreSpillCrossCall : public MachineFunctionPass {
|
|
public:
|
|
static char ID;
|
|
W65816PreSpillCrossCall() : MachineFunctionPass(ID) {}
|
|
|
|
StringRef getPassName() const override {
|
|
return "W65816 pre-spill Acc16 vregs across calls";
|
|
}
|
|
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
|
AU.setPreservesCFG();
|
|
MachineFunctionPass::getAnalysisUsage(AU);
|
|
}
|
|
|
|
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
char W65816PreSpillCrossCall::ID = 0;
|
|
|
|
INITIALIZE_PASS(W65816PreSpillCrossCall, DEBUG_TYPE,
|
|
"W65816 pre-spill Acc16 vregs across calls", false, false)
|
|
|
|
FunctionPass *llvm::createW65816PreSpillCrossCall() {
|
|
return new W65816PreSpillCrossCall();
|
|
}
|
|
|
|
bool W65816PreSpillCrossCall::runOnMachineFunction(MachineFunction &MF) {
|
|
if (MF.getFunction().hasOptNone()) return false;
|
|
MachineRegisterInfo &MRI = MF.getRegInfo();
|
|
if (!MRI.getNumVirtRegs()) return false;
|
|
const W65816InstrInfo *TII =
|
|
MF.getSubtarget<W65816Subtarget>().getInstrInfo();
|
|
MachineFrameInfo &MFI = MF.getFrameInfo();
|
|
|
|
// First pass: count call sites in the function. Below the
|
|
// heuristic threshold we don't bother — greedy handles low-call
|
|
// functions fine and pre-spilling would just add bytes.
|
|
unsigned callCount = 0;
|
|
for (MachineBasicBlock &MBB : MF)
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.isCall()) callCount++;
|
|
if (callCount < 4) return false;
|
|
|
|
bool Changed = false;
|
|
|
|
// Walk every Acc16 vreg in the function. For each, find its def
|
|
// (allowing multi-def vregs like SELECT_CC results — pick the
|
|
// first by MachineInstr iteration), then check if any use is
|
|
// separated from the def by a JSL call (in the same MBB). If
|
|
// so, pre-spill via STAfi at def + LDAfi at each post-call use.
|
|
unsigned NumVRegs = MRI.getNumVirtRegs();
|
|
for (unsigned i = 0; i < NumVRegs; ++i) {
|
|
Register VReg = Register::index2VirtReg(i);
|
|
if (MRI.def_empty(VReg)) continue;
|
|
if (MRI.getRegClass(VReg) != &W65816::Acc16RegClass) continue;
|
|
// Find the first def. For PHIs we skip — pre-spilling a PHI
|
|
// result is complex and rarely helpful for the high-pressure
|
|
// pattern we target (which is sequential bitmask updates).
|
|
MachineInstr *DefMI = nullptr;
|
|
for (MachineInstr &D : MRI.def_instructions(VReg)) {
|
|
if (D.isPHI()) { DefMI = nullptr; break; }
|
|
if (!DefMI) DefMI = &D;
|
|
}
|
|
if (!DefMI) continue;
|
|
MachineBasicBlock *MBB = DefMI->getParent();
|
|
|
|
// Check if any use of VReg is in the same MBB AFTER a call
|
|
// following DefMI.
|
|
bool sawCallAfterDef = false;
|
|
SmallVector<MachineInstr *, 4> postCallUses;
|
|
auto Walker = std::next(DefMI->getIterator());
|
|
while (Walker != MBB->end()) {
|
|
MachineInstr &W = *Walker++;
|
|
if (W.isCall()) sawCallAfterDef = true;
|
|
if (sawCallAfterDef && W.readsRegister(VReg, /*TRI=*/nullptr))
|
|
postCallUses.push_back(&W);
|
|
}
|
|
if (postCallUses.empty()) continue;
|
|
|
|
// Pre-spill. Fresh slot per vreg — StackSlotColoring may merge
|
|
// slots later if their lifetimes don't overlap.
|
|
int FI = MFI.CreateStackObject(2, Align(2), /*isSpillSlot=*/true);
|
|
DebugLoc DL = DefMI->getDebugLoc();
|
|
auto AfterDef = std::next(DefMI->getIterator());
|
|
BuildMI(*MBB, AfterDef, DL, TII->get(W65816::STAfi))
|
|
.addReg(VReg).addFrameIndex(FI).addImm(0);
|
|
for (MachineInstr *UseMI : postCallUses) {
|
|
Register Reload = MRI.createVirtualRegister(&W65816::Acc16RegClass);
|
|
BuildMI(*UseMI->getParent(), UseMI->getIterator(), UseMI->getDebugLoc(),
|
|
TII->get(W65816::LDAfi), Reload)
|
|
.addFrameIndex(FI).addImm(0);
|
|
// Rewrite this use's references of VReg to Reload.
|
|
for (auto &MO : UseMI->uses()) {
|
|
if (MO.isReg() && MO.getReg() == VReg) {
|
|
MO.setReg(Reload);
|
|
MO.setIsKill(false);
|
|
}
|
|
}
|
|
}
|
|
Changed = true;
|
|
}
|
|
|
|
return Changed;
|
|
}
|