65816-llvm-mos/src/llvm/lib/Target/W65816/W65816PreSpillCrossCall.cpp
Scott Duensing 81694c5971 Checkpoint
2026-05-02 19:17:23 -05:00

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;
}