65816-llvm-mos/src/llvm/lib/Target/W65816/W65816LowerWide32.cpp
2026-09-29 17:22:52 -05:00

459 lines
18 KiB
C++

//===-- W65816LowerWide32.cpp - Wide32 -> 2x i16 pre-RA lowering ---------===//
//
// 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-regalloc pass that decomposes Wide32 register-pair vregs into pairs
// of i16 vregs. Without this, greedy / basic regalloc fails on i64-heavy
// functions (`RegAllocBase` crashes during `allocatePhysRegs`) because
// the i64-via-2-i32-via-Wide32 chain produces too many simultaneously
// live register-pair vregs. After this pass, only i16 vregs remain at
// the regalloc input — Wide32 lives only inside this pass and the new
// LDAptr32S / STAptr32S / STBptr32S pseudos that take 2 i16 ptr operands
// directly.
//
// Walks the MIR and:
// 1. Finds REG_SEQUENCE producing Wide32 / Acc32 / AnyWide32; records
// the (lo, hi) i16 source operands; queues the REG_SEQUENCE for
// erasure.
// 2. Finds COPY whose dest is a Wide32 vreg and whose src is another
// mapped Wide32 vreg; chains the (lo, hi) mapping forward.
// 3. Rewrites EXTRACT_SUBREG of mapped Wide32 vregs by replacing the
// destination vreg with the appropriate half (sub_lo or sub_hi).
// 4. Rewrites LDAptr32 / STAptr32 / STBptr32 with a mapped Wide32 ptr
// to the corresponding LDAptr32S / STAptr32S / STBptr32S pseudo
// with two separate i16 operands.
//
// Bail / safety: any Wide32 vreg whose def we can't decompose is left
// in place — regalloc may still struggle but no miscompile.
//
//===----------------------------------------------------------------------===//
#include "W65816.h"
#include "W65816InstrInfo.h"
#include "W65816Subtarget.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstr.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/MachineRegisterInfo.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/CodeGen/TargetRegisterInfo.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
#define DEBUG_TYPE "w65816-lower-wide32"
namespace {
class W65816LowerWide32 : public MachineFunctionPass {
public:
static char ID;
W65816LowerWide32() : MachineFunctionPass(ID) {}
StringRef getPassName() const override {
return "W65816 Wide32 -> 2x i16 lowering";
}
void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.setPreservesCFG();
MachineFunctionPass::getAnalysisUsage(AU);
}
bool runOnMachineFunction(MachineFunction &MF) override;
};
} // namespace
char W65816LowerWide32::ID = 0;
INITIALIZE_PASS(W65816LowerWide32, DEBUG_TYPE,
"W65816 Wide32 lowering", false, false)
FunctionPass *llvm::createW65816LowerWide32() {
return new W65816LowerWide32();
}
static bool isWide32RC(const TargetRegisterClass *RC) {
return RC == &W65816::Wide32RegClass ||
RC == &W65816::Acc32RegClass ||
RC == &W65816::AnyWide32RegClass;
}
bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
MachineRegisterInfo &MRI = MF.getRegInfo();
const W65816Subtarget &STI = MF.getSubtarget<W65816Subtarget>();
const W65816InstrInfo &TII = *STI.getInstrInfo();
// Map: Wide32 vreg -> (loVreg, hiVreg) of i16 type.
DenseMap<Register, std::pair<Register, Register>> wideMap;
// Pass 1: collect all Wide32 vregs.
SmallVector<Register, 16> wide32Vregs;
for (unsigned i = 0, e = MRI.getNumVirtRegs(); i != e; ++i) {
Register R = Register::index2VirtReg(i);
if (MRI.reg_nodbg_empty(R))
continue;
if (!isWide32RC(MRI.getRegClass(R)))
continue;
wide32Vregs.push_back(R);
}
if (wide32Vregs.empty())
return false;
// Pass 2: process REG_SEQUENCE / chained-COPY / multi-subreg-def
// shapes; build the mapping. Iterate to fixed point because COPY
// chains depend on prior mappings.
SmallVector<MachineInstr *, 16> toErase;
bool changed = true;
while (changed) {
changed = false;
for (Register W : wide32Vregs) {
if (wideMap.count(W))
continue;
MachineInstr *DefMI = MRI.getUniqueVRegDef(W);
if (DefMI && DefMI->getOpcode() == TargetOpcode::REG_SEQUENCE) {
Register Lo, Hi;
bool Bail = false;
for (unsigned op = 1; op + 1 < DefMI->getNumOperands(); op += 2) {
if (!DefMI->getOperand(op).isReg() ||
!DefMI->getOperand(op + 1).isImm())
continue;
unsigned idx = DefMI->getOperand(op + 1).getImm();
Register Src = DefMI->getOperand(op).getReg();
unsigned SrcSub = DefMI->getOperand(op).getSubReg();
// If the source has a sub-register specifier (e.g.
// `%W.sub_lo:wide32` is a slice of a wide32 vreg), the
// effective "half" is the corresponding half of that source.
// Resolve via wideMap when the parent is already mapped;
// otherwise defer until a later iteration picks it up.
if (SrcSub != 0) {
if (!Src.isVirtual() || !wideMap.count(Src)) {
Bail = true;
break;
}
auto [SrcLo, SrcHi] = wideMap[Src];
Src = (SrcSub == llvm::sub_lo) ? SrcLo : SrcHi;
}
if (idx == llvm::sub_lo)
Lo = Src;
else if (idx == llvm::sub_hi)
Hi = Src;
}
if (Bail) continue;
if (Lo && Hi) {
wideMap[W] = {Lo, Hi};
toErase.push_back(DefMI);
changed = true;
continue;
}
}
if (DefMI && DefMI->isCopy()) {
Register Src = DefMI->getOperand(1).getReg();
if (Src.isVirtual() && wideMap.count(Src)) {
// Copy the source mapping into a local before writing wideMap[W]:
// DenseMap may rehash when inserting W, invalidating any reference
// returned by operator[](Src). Naive `wideMap[W] = wideMap[Src]`
// dangles after rehash and writes zeros into the new entry.
auto SrcPair = wideMap[Src];
wideMap[W] = SrcPair;
toErase.push_back(DefMI);
changed = true;
continue;
}
}
// Multi-subreg-def shape: separate sub-reg COPYs build %W:
// undef %W.sub_lo:wide32 = COPY %A:acc16
// %W.sub_hi:wide32 = COPY %B:acc16
// Equivalent to a REG_SEQUENCE %A, sub_lo, %B, sub_hi. softDouble
// at -O2 generates this heavily; without handling it the Wide32
// vreg survives to regalloc, which then asks for a spill/reload
// from a non-pair physreg and trips load/storeRegToStackSlot's
// llvm_unreachable.
Register LoSrc, HiSrc;
MachineInstr *LoDefMI = nullptr;
MachineInstr *HiDefMI = nullptr;
bool ok = true;
bool Bail = false;
for (MachineInstr &MI : MRI.def_instructions(W)) {
if (!MI.isCopy()) { ok = false; break; }
const MachineOperand &Dst = MI.getOperand(0);
const MachineOperand &Src = MI.getOperand(1);
if (!Dst.isReg() || Dst.getReg() != W) { ok = false; break; }
unsigned SubIdx = Dst.getSubReg();
Register S = Src.getReg();
unsigned SrcSub = Src.getSubReg();
// If the source has a sub-register specifier, resolve through
// wideMap[parent]. Symmetric with the REG_SEQUENCE handler
// above — without this, `%W.sub_lo = COPY %V.sub_lo:wide32`
// records the wide32 parent %V instead of %V's i16 sub_lo.
if (SrcSub != 0) {
if (!S.isVirtual() || !wideMap.count(S)) { Bail = true; break; }
auto [SL, SH] = wideMap[S];
S = (SrcSub == llvm::sub_lo) ? SL : SH;
}
if (SubIdx == llvm::sub_lo) {
if (LoDefMI) { ok = false; break; }
LoDefMI = &MI;
LoSrc = S;
} else if (SubIdx == llvm::sub_hi) {
if (HiDefMI) { ok = false; break; }
HiDefMI = &MI;
HiSrc = S;
} else {
ok = false;
break;
}
}
if (Bail) continue;
if (ok && LoSrc && HiSrc) {
wideMap[W] = {LoSrc, HiSrc};
if (LoDefMI) toErase.push_back(LoDefMI);
if (HiDefMI) toErase.push_back(HiDefMI);
changed = true;
}
}
}
// Pass 2b: handle PHIs whose result is a Wide32 vreg by splitting
// into 2 PHIs (one per half). Iterate to fixed point: a PHI becomes
// resolvable only after all its sources have been mapped.
changed = true;
while (changed) {
changed = false;
for (Register W : wide32Vregs) {
if (wideMap.count(W))
continue;
MachineInstr *DefMI = MRI.getUniqueVRegDef(W);
if (!DefMI || !DefMI->isPHI())
continue;
bool AllMapped = true;
for (unsigned op = 1; op + 1 < DefMI->getNumOperands(); op += 2) {
Register Src = DefMI->getOperand(op).getReg();
if (!Src.isVirtual() || !wideMap.count(Src)) {
AllMapped = false;
break;
}
}
if (!AllMapped)
continue;
Register NewLo = MRI.createVirtualRegister(&W65816::Acc16RegClass);
Register NewHi = MRI.createVirtualRegister(&W65816::Acc16RegClass);
MachineBasicBlock *MBB = DefMI->getParent();
DebugLoc DL = DefMI->getDebugLoc();
auto PHILo = BuildMI(*MBB, DefMI, DL, TII.get(TargetOpcode::PHI), NewLo);
auto PHIHi = BuildMI(*MBB, DefMI, DL, TII.get(TargetOpcode::PHI), NewHi);
for (unsigned op = 1; op + 1 < DefMI->getNumOperands(); op += 2) {
Register Src = DefMI->getOperand(op).getReg();
MachineBasicBlock *PredMBB = DefMI->getOperand(op + 1).getMBB();
auto [SrcLo, SrcHi] = wideMap[Src];
PHILo.addReg(SrcLo).addMBB(PredMBB);
PHIHi.addReg(SrcHi).addMBB(PredMBB);
}
wideMap[W] = {NewLo, NewHi};
toErase.push_back(DefMI);
changed = true;
}
}
// Pass 3: rewrite uses.
SmallVector<MachineInstr *, 32> useToErase;
// Hand a half to the users of the vreg projected out of the pair, Dst.
// A user whose operand class does not take the half's own class gets a
// COPY of it into that class right before it, as TwoAddress does for a
// tied operand (so tied uses are left to it). Replacing unconditionally
// gave an Img16 half (an argument's high word arrives in X and lives in a
// DP slot) to CMP, whose register operand is implicitly A: the compare
// read whatever A held. The COPY goes at the user rather than at the
// projection so that A, a single register, is held only across the use.
//
// wideMap was filled in Pass 2 and is not updated by the replacements. A
// pair's half can itself be projected out of another pair (`%h = COPY
// %p.sub_hi`, then `%q = REG_SEQUENCE %h, ...`, as an i32 shift by 16
// builds); once %h is replaced and its COPY erased, handing out %h for
// %q's half would leave uses with no def. So every half read from
// wideMap goes through `current`, which follows the replacements.
const TargetRegisterInfo *TRI = STI.getRegisterInfo();
DenseMap<Register, Register> forwarded;
auto current = [&](Register R) {
for (auto It = forwarded.find(R); It != forwarded.end();
It = forwarded.find(R))
R = It->second;
return R;
};
auto forwardHalf = [&](MachineInstr *MI, Register Dst, Register Half) {
Half = current(Half);
const TargetRegisterClass *HalfRC = MRI.getRegClass(Half);
for (MachineOperand &MO :
make_early_inc_range(MRI.use_nodbg_operands(Dst))) {
MachineInstr *User = MO.getParent();
unsigned OpNo = User->getOperandNo(&MO);
const TargetRegisterClass *Need =
User->getRegClassConstraint(OpNo, &TII, TRI);
if (!Need || Need->hasSubClassEq(HalfRC) ||
User->isRegTiedToDefOperand(OpNo))
continue;
Register Fit = MRI.createVirtualRegister(Need);
BuildMI(*User->getParent(), User, User->getDebugLoc(),
TII.get(TargetOpcode::COPY), Fit)
.addReg(Half);
MO.setReg(Fit);
}
MRI.replaceRegWith(Dst, Half);
forwarded[Dst] = Half;
useToErase.push_back(MI);
};
for (MachineBasicBlock &MBB : MF) {
for (MachineBasicBlock::iterator It = MBB.begin(); It != MBB.end();) {
MachineInstr *MI = &*It++;
// EXTRACT_SUBREG of a mapped Wide32 vreg: replace the dest vreg
// with the appropriate half (sub_lo or sub_hi).
if (MI->getOpcode() == TargetOpcode::EXTRACT_SUBREG) {
Register Src = MI->getOperand(1).getReg();
if (Src.isVirtual() && wideMap.count(Src)) {
unsigned SubIdx = MI->getOperand(2).getImm();
auto [Lo, Hi] = wideMap[Src];
Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi;
forwardHalf(MI, MI->getOperand(0).getReg(), Half);
continue;
}
}
// COPY %V.sub_lo / %V.sub_hi (partial-reg COPY where source has a
// sub-reg specifier and the source vreg is a mapped Wide32).
// LLVM emits this shape instead of EXTRACT_SUBREG when projecting
// a half out of a Wide32 vreg. Only the shape with a full-reg
// destination is handled here — partial-reg destinations would
// imply the dst is itself a Wide32 sub-reg def, which the def-side
// multi-subreg-def handling covers separately.
if (MI->isCopy()) {
const MachineOperand &SrcOp = MI->getOperand(1);
const MachineOperand &DstOp = MI->getOperand(0);
if (SrcOp.isReg() && SrcOp.getReg().isVirtual() &&
wideMap.count(SrcOp.getReg()) && SrcOp.getSubReg() != 0 &&
DstOp.isReg() && DstOp.getSubReg() == 0) {
unsigned SubIdx = SrcOp.getSubReg();
auto [Lo, Hi] = wideMap[SrcOp.getReg()];
Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi;
forwardHalf(MI, DstOp.getReg(), Half);
continue;
}
}
// LDAptr32 / STAptr32 / STBptr32 with a mapped Wide32 ptr:
// rewrite to LDAptr32S / STAptr32S / STBptr32S.
unsigned Opc = MI->getOpcode();
bool isPtrOp = (Opc == W65816::LDAptr32 || Opc == W65816::STAptr32 ||
Opc == W65816::STBptr32);
if (isPtrOp) {
Register Ptr = MI->getOperand(1).getReg();
if (Ptr.isVirtual() && wideMap.count(Ptr)) {
Register Lo = current(wideMap[Ptr].first);
Register Hi = current(wideMap[Ptr].second);
unsigned NewOpc = (Opc == W65816::LDAptr32) ? W65816::LDAptr32S
: (Opc == W65816::STAptr32) ? W65816::STAptr32S
: W65816::STBptr32S;
DebugLoc DL = MI->getDebugLoc();
MachineBasicBlock *ParentMBB = MI->getParent();
if (Opc == W65816::LDAptr32) {
Register Dst = MI->getOperand(0).getReg();
BuildMI(*ParentMBB, MI->getIterator(), DL, TII.get(NewOpc), Dst)
.addReg(Lo)
.addReg(Hi);
} else {
Register Val = MI->getOperand(0).getReg();
BuildMI(*ParentMBB, MI->getIterator(), DL, TII.get(NewOpc))
.addReg(Val)
.addReg(Lo)
.addReg(Hi);
}
useToErase.push_back(MI);
continue;
}
}
}
}
// Erase use-side instructions (EXTRACT_SUBREG, LDAptr32-family) first
// so the Wide32 vreg becomes dead.
//
// DBG_VALUE preservation: MRI.replaceRegWith above already redirected
// any debug operands to the new half-vreg (replaceRegWith walks
// reg_operands which includes debug ops). No further work needed for
// the EXTRACT_SUBREG / partial-COPY paths since the user-visible value
// is preserved in the half vreg. For LDAptr32 rewrites we don't move
// a user-named value so DBG_VALUE attribution is unaffected.
for (auto *MI : useToErase)
MI->eraseFromParent();
// Now check each REG_SEQUENCE / chained-COPY def: only erase if the
// Wide32 vreg has no remaining uses. Any leftover use means the pass
// didn't cover that opcode — leaving the def in place keeps the MIR
// well-formed (at the cost of pair-allocation pressure for that
// specific case).
//
// Iterate to fixed point: a chained-COPY pattern like
// %114:wide32 = REG_SEQUENCE ...
// %74:wide32 = COPY %114
// ... uses of %74 ...
// queues both the REG_SEQUENCE and the COPY for erasure. Pass 3
// rewrites %74's uses, leaving %74 dead. In a single-pass erase,
// %114 still has its COPY use at the time we check, so the REG_-
// SEQUENCE is skipped — but then we erase the COPY, leaving %114
// dead too. Loop until no more erasures.
bool eraseAny = !useToErase.empty();
bool progress = true;
while (progress) {
progress = false;
for (auto *&MI : toErase) {
if (!MI)
continue;
if (MI->getNumOperands() == 0) {
MI = nullptr;
continue;
}
Register Dst = MI->getOperand(0).getReg();
if (!Dst.isVirtual() || MRI.use_nodbg_empty(Dst)) {
// DBG_VALUE preservation: use_nodbg_empty ignores debug uses, so
// erasing here can strand DBG_VALUEs referring to Dst. The
// wide32 vreg has no surviving half mapping that's directly
// usable (a wide32-typed variable is implemented as two i16
// halves but DBG_VALUE expects a single location), so mark
// dependent DBG_VALUEs as undef rather than emit a wrong
// location. A future pass could split the variable across
// DW_OP_piece(lo)+DW_OP_piece(hi); not in scope here.
if (Dst.isVirtual()) {
// Dedup: DBG_VALUE_LIST may appear multiple times in the
// use-list when several of its operands reference Dst.
SmallPtrSet<MachineInstr *, 4> DbgUserSet;
SmallVector<MachineInstr *, 4> DbgUsers;
for (MachineInstr &U : MRI.use_instructions(Dst)) {
if (U.isDebugValue() && DbgUserSet.insert(&U).second)
DbgUsers.push_back(&U);
}
for (MachineInstr *DbgMI : DbgUsers) {
for (MachineOperand &Op : DbgMI->debug_operands()) {
if (Op.isReg() && Op.getReg() == Dst) {
Op.setReg(0);
Op.setSubReg(0);
}
}
}
}
MI->eraseFromParent();
MI = nullptr;
eraseAny = true;
progress = true;
}
}
}
return eraseAny;
}