65816-llvm-mos/src/llvm/lib/Target/W65816/W65816WidenAcc16.cpp
Scott Duensing 09f7405362 Updates
2026-06-03 16:08:42 -05:00

214 lines
9.4 KiB
C++

//===-- W65816WidenAcc16.cpp - Promote Acc16 vregs to Wide16 ------------===//
//
// 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 promotes Acc16 vregs (constrained to physreg A only)
// to the wider Wide16 class (A + IMG0..IMG7). Greedy regalloc gets
// 9-way pressure relief on the i16 register class; functions that
// previously failed with "ran out of registers" can now spread their
// live i16 values across A and the DP-backed imaginaries.
//
// Cross-class moves between A and IMGn are LDA/STA dp (4 cyc each way,
// 2 bytes), emitted by W65816InstrInfo::copyPhysReg. The constraint
// that arithmetic ops require their source in A propagates back from
// the use sites — regalloc coerces Wide16 vregs to Acc16 (= {A}) at
// those sites and inserts the necessary COPYs.
//
// Calls clobber IMGn (caller-save), so any vreg in IMGn that lives
// across a call gets spilled to stack by regalloc. This pass doesn't
// model that explicitly — it relies on the calling convention's
// regmask to mark IMGn clobbered.
//
//===----------------------------------------------------------------------===//
#include "W65816.h"
#include "W65816InstrInfo.h"
#include "W65816Subtarget.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-widen-acc16"
namespace {
class W65816WidenAcc16 : public MachineFunctionPass {
public:
static char ID;
W65816WidenAcc16() : MachineFunctionPass(ID) {}
StringRef getPassName() const override {
return "W65816 Acc16 → Wide16 promotion";
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesCFG();
MachineFunctionPass::getAnalysisUsage(AU);
}
bool runOnMachineFunction(MachineFunction &MF) override;
};
} // namespace
char W65816WidenAcc16::ID = 0;
INITIALIZE_PASS(W65816WidenAcc16, DEBUG_TYPE,
"W65816 Acc16 → Wide16 promotion", false, false)
FunctionPass *llvm::createW65816WidenAcc16() {
return new W65816WidenAcc16();
}
// Returns true if the vreg has any physreg-COPY use that would conflict
// with Wide16 class assignment. $a is a member of Wide16 (Wide16 = A +
// IMG0..15), so a COPY to $a is fine — the vreg can be Wide16 and
// regalloc will pick $a to coalesce. $x / $y are in Idx16, NOT in
// Wide16, so a COPY to those forces the vreg to NOT be in Wide16
// (verifier would reject).
static bool flowsToIncompatiblePhysReg(Register VReg,
const MachineRegisterInfo &MRI) {
for (auto &U : MRI.use_nodbg_instructions(VReg)) {
if (!U.isCopy()) continue;
const MachineOperand &Dst = U.getOperand(0);
if (!Dst.isReg() || !Dst.getReg().isPhysical()) continue;
Register P = Dst.getReg();
if (P == W65816::A) continue;
if (P >= W65816::IMG0 && P <= W65816::IMG15) continue;
return true;
}
return false;
}
// Returns true if the vreg is used by any PHI. PHI input/result must
// share the same register class (verifier requirement). Rather than
// also widen the PHI's result and recursively all of its uses, we skip
// vregs caught up in PHIs entirely — leaves a few wins on the table
// but avoids cross-MBB analysis here.
static bool usedByPhi(Register VReg, const MachineRegisterInfo &MRI) {
for (auto &U : MRI.use_nodbg_instructions(VReg)) {
if (U.isPHI()) return true;
}
return false;
}
// Returns true if all non-debug, non-COPY uses of VReg are at operands
// whose required register class accepts Wide16 (i.e., Wide16 or a
// superclass). COPY uses are unconstrained — fine. PHI uses already
// filtered earlier. If any use's operand class is strictly narrower
// than Wide16 (i.e., Acc16-only, Idx16-only, etc.), return false: the
// verifier rejects passing a Wide16 vreg to such an operand.
static bool allUsesAcceptWide(Register VReg,
const MachineRegisterInfo &MRI,
const TargetRegisterInfo &TRI,
const TargetInstrInfo &TII) {
for (auto &MO : MRI.use_nodbg_operands(VReg)) {
MachineInstr *UMI = MO.getParent();
if (UMI->isCopy()) continue; // COPY accepts anything
if (UMI->isPHI()) return false; // already filtered, but be safe
unsigned OpIdx = UMI->getOperandNo(&MO);
(void)TRI;
const TargetRegisterClass *Expected =
TII.getRegClass(UMI->getDesc(), OpIdx);
if (!Expected) continue; // no constraint
if (Expected == &W65816::Wide16RegClass) continue;
// Check superclass relationship: Wide16 must be a sub-or-equal of
// Expected for the use to accept Wide16 vregs. A common case:
// Expected is a superclass that includes Wide16. If Expected is
// narrower (e.g., Acc16 only), reject.
if (Expected->hasSubClassEq(&W65816::Wide16RegClass)) continue;
return false;
}
return true;
}
bool W65816WidenAcc16::runOnMachineFunction(MachineFunction &MF) {
if (!MF.getRegInfo().getNumVirtRegs()) return false;
// At -O0 / optnone, register coalescing doesn't run, so the COPY we
// insert to bridge Acc16 → Wide16 doesn't get folded; instead it
// forces wide16 spills through DP-mapped slots that collide and
// produce miscompiles around modify-in-place ops (lda dp; inc a;
// sta dp; lda dp reads pre-inc value). The promotion is purely a
// performance optimization, so skip it for optnone functions.
if (MF.getFunction().hasOptNone()) return false;
MachineRegisterInfo &MRI = MF.getRegInfo();
const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
const W65816InstrInfo *TII = STI.getInstrInfo();
const TargetRegisterInfo *TRI = STI.getRegisterInfo();
bool Changed = false;
// For each Acc16 vreg, insert a COPY to a fresh Wide16 vreg right
// after its def, then redirect all uses to the Wide16 vreg. The
// original Acc16 vreg keeps its tight constraint (= {A}) for the
// def site (which is typically a pseudo whose AsmPrinter expansion
// assumes A); the new Wide16 vreg is free for greedy to allocate
// anywhere in {A, IMG0..IMG7}. When both end up in A, the COPY
// is a no-op the regalloc/coalescer collapses; when the Wide16
// vreg lands on IMGn, the COPY becomes STA dp via copyPhysReg.
SmallVector<Register, 16> Candidates;
for (unsigned i = 0; i < MRI.getNumVirtRegs(); ++i) {
Register VReg = Register::index2VirtReg(i);
if (MRI.def_empty(VReg)) continue;
if (MRI.getRegClass(VReg) != &W65816::Acc16RegClass) continue;
if (flowsToIncompatiblePhysReg(VReg, MRI)) continue;
if (usedByPhi(VReg, MRI)) continue;
if (!MRI.hasOneDef(VReg)) continue; // require single SSA def
if (!allUsesAcceptWide(VReg, MRI, *TRI, *TII)) continue;
Candidates.push_back(VReg);
}
for (Register VReg : Candidates) {
MachineInstr *DefMI = &*MRI.def_instructions(VReg).begin();
MachineBasicBlock *MBB = DefMI->getParent();
DebugLoc DL = DefMI->getDebugLoc();
Register WideReg = MRI.createVirtualRegister(&W65816::Wide16RegClass);
// Insert AFTER the def, but if the def is a PHI, walk past all
// PHIs in the block first — verifier requires all PHIs at MBB
// entry, no non-PHI may sit between them.
auto InsertAt = std::next(MachineBasicBlock::iterator(DefMI));
if (DefMI->isPHI()) {
while (InsertAt != MBB->end() && InsertAt->isPHI()) ++InsertAt;
}
BuildMI(*MBB, InsertAt, DL, TII->get(TargetOpcode::COPY), WideReg)
.addReg(VReg);
// Rewrite all non-debug uses of VReg (other than the COPY we just
// inserted) to WideReg.
SmallVector<MachineOperand *, 8> ToRewrite;
for (auto &U : MRI.use_nodbg_operands(VReg)) {
MachineInstr *UMI = U.getParent();
if (UMI->getOpcode() == TargetOpcode::COPY &&
UMI->getOperand(0).getReg() == WideReg) continue;
ToRewrite.push_back(&U);
}
for (auto *MO : ToRewrite) {
MO->setReg(WideReg);
MO->setIsKill(false);
}
Changed = true;
}
// Phase 2: PHI cycle widening was prototyped here but never landed.
// The prototype body lived in an #if 0 block that was removed once
// we settled on Phase 1 as the only effective half of the pass.
// Why disabled (2026-05-13 attempt):
// - Widening PHI cycles to Wide16 (= A + IMG0..15) is undone by the
// Register Coalescer: it joins our Wide16 vregs with adjacent
// Acc16 vregs via the bridge COPYs we insert, and the resulting
// joint class is `intersect(Wide16, Acc16) = Acc16`. Net effect:
// no IMG, just more code through the coalescer.
// - Switching to Img16 (= IMG0..15, no A) defeats the coalescer
// (intersection with Acc16 is ∅) but forces ALL widened PHIs into
// IMG slots even when A would be better, AND triggers cascading
// copyPhysReg paths that aren't all implemented (e.g., DPF0 → IMG
// for i64 libcall return values), aborting clang on runtime builds.
// - A targeted fix needs either (a) a class that the coalescer
// refuses to join with Acc16 yet that still allows A as a member,
// (b) a post-coalescer pass that re-widens specific high-traffic
// vregs back to Img16, or (c) regalloc cost-model tuning so it
// prefers IMG8..15 over stack for loop-live values.
return Changed;
}