2108 lines
91 KiB
C++
2108 lines
91 KiB
C++
//===-- W65816StackSlotCleanup.cpp - Remove redundant spill/reload pairs --===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Post-RA cleanup that erases redundant STAfi+LDAfi pairs to the same
|
|
// stack slot when no instruction in between writes A or that slot.
|
|
//
|
|
// The greedy register allocator routinely emits this pattern when
|
|
// materialising a COPY of $a into a vreg that gets allocated back to
|
|
// $a — the spill+reload cycle is a no-op since A already holds the
|
|
// stored value. The standard MachineLateInstrsCleanup pass only
|
|
// detects identical instructions; it doesn't recognise that
|
|
// `LDAfi slot` after `STAfi $a, slot` is a no-op. We do the
|
|
// simple per-block scan here.
|
|
//
|
|
// Conservative: only matches adjacent STAfi+LDAfi pairs (no scan for
|
|
// instructions in between). In practice the greedy-allocator-emitted
|
|
// pattern is always adjacent or near-adjacent, and the scheduler keeps
|
|
// it that way because the LDAfi feeds the next instruction. If
|
|
// future codegen breaks this assumption, generalise to a longer scan
|
|
// with explicit clobber tracking.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "W65816.h"
|
|
#include "W65816InstrInfo.h"
|
|
#include "W65816Subtarget.h"
|
|
#include "llvm/ADT/BitVector.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
#include "llvm/CodeGen/MachineFrameInfo.h"
|
|
#include "llvm/CodeGen/MachineFunction.h"
|
|
#include "llvm/CodeGen/MachineFunctionPass.h"
|
|
#include "llvm/CodeGen/MachineInstr.h"
|
|
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
|
#include "llvm/CodeGen/TargetRegisterInfo.h"
|
|
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "w65816-stack-slot-cleanup"
|
|
|
|
namespace {
|
|
|
|
class W65816StackSlotCleanup : public MachineFunctionPass {
|
|
public:
|
|
static char ID;
|
|
|
|
W65816StackSlotCleanup() : MachineFunctionPass(ID) {}
|
|
|
|
StringRef getPassName() const override {
|
|
return "W65816 redundant stack-slot spill/reload elimination";
|
|
}
|
|
|
|
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
char W65816StackSlotCleanup::ID = 0;
|
|
|
|
INITIALIZE_PASS(W65816StackSlotCleanup, DEBUG_TYPE,
|
|
"W65816 redundant stack-slot spill/reload elimination",
|
|
false, false)
|
|
|
|
FunctionPass *llvm::createW65816StackSlotCleanup() {
|
|
return new W65816StackSlotCleanup();
|
|
}
|
|
|
|
// Returns true if MI references frame index FI as one of its operands.
|
|
// Used to bail dead-store removal when an intervening instruction
|
|
// reads or writes the slot.
|
|
static bool referencesFrameIndex(const MachineInstr &MI, int FI) {
|
|
for (const MachineOperand &MO : MI.operands())
|
|
if (MO.isFI() && MO.getIndex() == FI)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// Sentinel for "no match" returned by matchAccSlotOp. We can't use
|
|
// -1 because FrameIndex numbers for *fixed* (caller-arg) slots are
|
|
// negative — fixed-stack.0 is -1, fixed-stack.1 is -2, etc. Earlier
|
|
// passes that did `if (slot < 0) continue;` were silently bailing on
|
|
// every legitimate fixed-slot LDA/STA, missing many cross-arg-slot
|
|
// optimisation opportunities.
|
|
static constexpr int NO_SLOT_MATCH = INT_MIN;
|
|
|
|
// If MI matches `OP $a, FI, 0` where OP == ExpectedOpc, returns the slot
|
|
// index (which may be negative for fixed-stack args); else NO_SLOT_MATCH.
|
|
// Callers must compare against NO_SLOT_MATCH, NOT against `< 0`.
|
|
static int matchAccSlotOp(const MachineInstr &MI, unsigned ExpectedOpc) {
|
|
if (MI.getOpcode() != ExpectedOpc ||
|
|
MI.getNumOperands() < 3 ||
|
|
!MI.getOperand(0).isReg() || MI.getOperand(0).getReg() != W65816::A ||
|
|
!MI.getOperand(1).isFI() ||
|
|
!MI.getOperand(2).isImm() || MI.getOperand(2).getImm() != 0)
|
|
return NO_SLOT_MATCH;
|
|
return MI.getOperand(1).getIndex();
|
|
}
|
|
|
|
// Returns true if Opc is a commutative *_fi pseudo (the load-fold form
|
|
// where operand 2 is the FI). ADD/AND/OR/EOR / ADCE all qualify; SBC
|
|
// and CMP are non-commutative.
|
|
static bool isCommutativeFiOp(unsigned Opc) {
|
|
return Opc == W65816::ADCfi || Opc == W65816::ADCEfi ||
|
|
Opc == W65816::ANDfi || Opc == W65816::ORAfi ||
|
|
Opc == W65816::EORfi;
|
|
}
|
|
|
|
// If MI is a commutative *_fi op of the canonical shape `OPfi $a (tied), slot, 0`
|
|
// matching slot SlotB, returns true. Used to recognise the OPfi at the
|
|
// end of a *_RR inserter expansion.
|
|
static bool matchCommutativeFiOpOnSlot(const MachineInstr &MI, int SlotB) {
|
|
if (!isCommutativeFiOp(MI.getOpcode()))
|
|
return false;
|
|
if (MI.getNumOperands() < 4 ||
|
|
!MI.getOperand(0).isReg() || MI.getOperand(0).getReg() != W65816::A ||
|
|
!MI.getOperand(2).isFI() || MI.getOperand(2).getIndex() != SlotB ||
|
|
!MI.getOperand(3).isImm() || MI.getOperand(3).getImm() != 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Advance It past debug instructions; returns true if landed on a real
|
|
// instruction in the block. Templated because callers mix iterator and
|
|
// instr_iterator depending on how they got here.
|
|
template <class IterT>
|
|
static bool advancePastDebug(MachineBasicBlock &MBB, IterT &It) {
|
|
while (It != MBB.end() && It->isDebugInstr())
|
|
++It;
|
|
return It != MBB.end();
|
|
}
|
|
|
|
// Match `STAfi reg1, FI, 0; ... ; STAfi reg2, FI, 0` (kill via overwrite)
|
|
// or `STAfi reg, FI, 0; ... ; <return> (no read in between)` (dead store
|
|
// at function exit). Both mean the first STAfi is dead. Conservative:
|
|
// bails on anything that references the slot, calls, inline asm. The
|
|
// slot must be a *local* (non-fixed) FrameIndex — args live across the
|
|
// function so we can't kill stores to fixed slots.
|
|
static bool tryEliminateDeadStore(MachineBasicBlock &MBB,
|
|
MachineInstr &StaMI) {
|
|
if (StaMI.getOpcode() != W65816::STAfi)
|
|
return false;
|
|
if (StaMI.getNumOperands() < 3 ||
|
|
!StaMI.getOperand(1).isFI() ||
|
|
!StaMI.getOperand(2).isImm() || StaMI.getOperand(2).getImm() != 0)
|
|
return false;
|
|
// Never eliminate a volatile store — its observability is the
|
|
// whole point of marking it volatile. Caught by the SJLJ EH path
|
|
// where SjLjEHPrepare emits `store volatile i32 N, fn_ctx.call_site`
|
|
// before each invoke; without this check the call_site never gets
|
|
// written and the personality routine can't pick the landing pad.
|
|
if (StaMI.hasOrderedMemoryRef())
|
|
return false;
|
|
int StoredFI = StaMI.getOperand(1).getIndex();
|
|
|
|
// Don't try to kill a store to a fixed (arg) slot — those are
|
|
// observable to the caller. Locals/spills are fair game.
|
|
const MachineFunction *MF = StaMI.getMF();
|
|
if (MF->getFrameInfo().isFixedObjectIndex(StoredFI))
|
|
return false;
|
|
|
|
auto It = std::next(StaMI.getIterator());
|
|
while (It != MBB.end()) {
|
|
MachineInstr &MI = *It;
|
|
if (MI.isDebugInstr()) {
|
|
++It;
|
|
continue;
|
|
}
|
|
// A subsequent STAfi to the same slot, offset 0, kills our store.
|
|
if (MI.getOpcode() == W65816::STAfi &&
|
|
MI.getNumOperands() >= 3 &&
|
|
MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == StoredFI &&
|
|
MI.getOperand(2).isImm() && MI.getOperand(2).getImm() == 0) {
|
|
// Found the killing store. Erase the first.
|
|
StaMI.eraseFromParent();
|
|
return true;
|
|
}
|
|
// A return that doesn't read the slot kills the store too — the
|
|
// local goes out of scope at function exit.
|
|
if (MI.isReturn() && !referencesFrameIndex(MI, StoredFI)) {
|
|
StaMI.eraseFromParent();
|
|
return true;
|
|
}
|
|
// Anything else that touches the slot (load, ADC d,S, etc.) means
|
|
// the first store IS observed — bail.
|
|
if (referencesFrameIndex(MI, StoredFI))
|
|
return false;
|
|
// Inline asm / branches: too tricky. Calls are OK to walk past —
|
|
// local (non-fixed) slots are addressed at offsets the callee
|
|
// can't reach (callee's S has been shifted down by JSL's
|
|
// 3-byte return frame and any of its own pha/tsc adjustments,
|
|
// so its `(4,s)` reads land above our locals). We've already
|
|
// bailed on fixed slots above, so reaching here means the slot
|
|
// is local and call-safe.
|
|
if (MI.isInlineAsm() || MI.isBranch())
|
|
return false;
|
|
++It;
|
|
}
|
|
// Walked off the end of the BB without seeing a return/use. Bail
|
|
// (could fall through to a successor that reads the slot).
|
|
return false;
|
|
}
|
|
|
|
// Returns true if any MachineInstr in MF (other than those in `ignore`)
|
|
// references frame index `FI`. Used by Pass -4 / Pass -4c safety
|
|
// checks before erasing an init store: a cross-MBB reload would
|
|
// otherwise read stale/uninitialized data. See #107.
|
|
static bool slotHasOtherRefs(const MachineFunction &MF, int FI,
|
|
ArrayRef<const MachineInstr *> ignore) {
|
|
for (const MachineBasicBlock &MBB : MF) {
|
|
for (const MachineInstr &MI : MBB) {
|
|
bool skip = false;
|
|
for (const MachineInstr *I : ignore) {
|
|
if (&MI == I) { skip = true; break; }
|
|
}
|
|
if (skip) continue;
|
|
for (const MachineOperand &MO : MI.operands()) {
|
|
if (MO.isFI() && MO.getIndex() == FI)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Match `STAfi reg, FI, 0; ... ; LDAfi destReg, FI, 0` when reg == destReg
|
|
// and nothing in between clobbers reg or the slot. Erase the LDAfi.
|
|
static bool tryEliminateLoadAfterStore(MachineBasicBlock &MBB,
|
|
MachineInstr &StaMI,
|
|
const TargetRegisterInfo *TRI) {
|
|
if (StaMI.getOpcode() != W65816::STAfi)
|
|
return false;
|
|
if (StaMI.getNumOperands() < 3 ||
|
|
!StaMI.getOperand(0).isReg() ||
|
|
!StaMI.getOperand(1).isFI() ||
|
|
!StaMI.getOperand(2).isImm() || StaMI.getOperand(2).getImm() != 0)
|
|
return false;
|
|
Register StoredReg = StaMI.getOperand(0).getReg();
|
|
int StoredFI = StaMI.getOperand(1).getIndex();
|
|
|
|
// Walk forward looking for the matching LDAfi. Bail on any
|
|
// instruction that could clobber StoredReg or write the slot.
|
|
auto It = std::next(StaMI.getIterator());
|
|
while (It != MBB.end()) {
|
|
MachineInstr &MI = *It;
|
|
if (MI.isDebugInstr()) {
|
|
++It;
|
|
continue;
|
|
}
|
|
if (MI.getOpcode() == W65816::LDAfi &&
|
|
MI.getNumOperands() >= 3 &&
|
|
MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == StoredFI &&
|
|
MI.getOperand(2).isImm() && MI.getOperand(2).getImm() == 0 &&
|
|
MI.getOperand(0).isReg() &&
|
|
MI.getOperand(0).getReg() == StoredReg) {
|
|
// A volatile load is observable — never elide, even if the
|
|
// value is provably the same as the prior store. But STAfi/
|
|
// LDAfi target compiler-managed stack spill slots, which are
|
|
// by construction never volatile — `hasOrderedMemoryRef()`
|
|
// returns true here only because both lack explicit memops
|
|
// (the conservative "no info → treat as ordered" default).
|
|
// Check the actual memops if present; absence is fine.
|
|
auto isReallyVolatile = [](const MachineInstr &I) {
|
|
for (auto *MMO : I.memoperands())
|
|
if (MMO->isVolatile() || MMO->isAtomic())
|
|
return true;
|
|
return false;
|
|
};
|
|
if (isReallyVolatile(MI) || isReallyVolatile(StaMI))
|
|
return false;
|
|
// LDA sets N/Z based on the loaded value. Dropping it would
|
|
// expose stale N/Z from before the STA→LDA pair to the next
|
|
// flag-reading op (e.g. a branch). Only safe to drop if the
|
|
// immediately-following op overwrites N/Z.
|
|
auto opSetsNZ = [](unsigned Op) {
|
|
switch (Op) {
|
|
case W65816::LDAfi:
|
|
case W65816::LDAi16imm:
|
|
case W65816::LDAabs:
|
|
case W65816::ANDi16imm: case W65816::ANDabs: case W65816::ANDfi:
|
|
case W65816::ORAi16imm: case W65816::ORAabs: case W65816::ORAfi:
|
|
case W65816::EORi16imm: case W65816::EORabs: case W65816::EORfi:
|
|
case W65816::ADCi16imm: case W65816::ADCabs: case W65816::ADCfi:
|
|
case W65816::SBCi16imm: case W65816::SBCabs: case W65816::SBCfi:
|
|
case W65816::ADCEi16imm: case W65816::ADCEabs: case W65816::ADCEfi:
|
|
case W65816::SBCEi16imm: case W65816::SBCEabs: case W65816::SBCEfi:
|
|
case W65816::ASLA16: case W65816::LSRA16:
|
|
case W65816::ASLA8: case W65816::LSRA8:
|
|
case W65816::INA: case W65816::DEA:
|
|
case W65816::INA_PSEUDO: case W65816::DEA_PSEUDO:
|
|
case W65816::INA_PSEUDO8: case W65816::DEA_PSEUDO8:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
// Walk past further STAfi pseudos (spill stores) — they preserve
|
|
// A's flags. Also walk past STA_DP (plain `sta $dp` doesn't touch
|
|
// P). Don't walk past STA_DPIndLongY / STA8fi / etc., whose
|
|
// inserter expansions introduce SEP/REP and mutate the M flag
|
|
// (that's what broke printf historically).
|
|
auto NextIt = std::next(MI.getIterator());
|
|
while (NextIt != MBB.end()) {
|
|
if (NextIt->isDebugInstr()) { ++NextIt; continue; }
|
|
if (NextIt->getOpcode() == W65816::STAfi) { ++NextIt; continue; }
|
|
if (NextIt->getOpcode() == W65816::STA_DP) { ++NextIt; continue; }
|
|
break;
|
|
}
|
|
if (NextIt == MBB.end() || NextIt->isBranch() || NextIt->isReturn())
|
|
return false;
|
|
if (!NextIt->definesRegister(W65816::P, TRI) &&
|
|
!opSetsNZ(NextIt->getOpcode()))
|
|
return false;
|
|
// Eliding the LDA leaves $a "dead per verifier" because STAfi's
|
|
// tablegen Defs = [A] is a stale over-approximation (the asm
|
|
// actually preserves A via PHA/lda/sta/PLA bracket for IMG/X/Y
|
|
// sources, and trivially for $a sources — see W65816InstrInfo.td
|
|
// and eliminateFrameIndex). Strip the stale implicit-def $a from
|
|
// walked-past STAfis (any source) and clear any kill flag on the
|
|
// source operand when source is $a, so the verifier sees $a as
|
|
// still alive through the chain. We're post-regalloc so this
|
|
// doesn't affect regalloc's decisions — only the verifier and any
|
|
// downstream passes that read the annotation.
|
|
auto stripStaleAdef = [](MachineInstr &MI2) {
|
|
if (MI2.getOpcode() != W65816::STAfi) return;
|
|
if (MI2.getNumOperands() < 1 || !MI2.getOperand(0).isReg()) return;
|
|
if (MI2.getOperand(0).getReg() == W65816::A) {
|
|
MI2.getOperand(0).setIsKill(false);
|
|
}
|
|
for (unsigned i = MI2.getNumOperands(); i-- > 0;) {
|
|
const MachineOperand &MO = MI2.getOperand(i);
|
|
if (MO.isReg() && MO.isImplicit() && MO.isDef() &&
|
|
MO.getReg() == W65816::A) {
|
|
MI2.removeOperand(i);
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
for (auto FixIt = StaMI.getIterator(); FixIt != MI.getIterator(); ++FixIt) {
|
|
stripStaleAdef(*FixIt);
|
|
}
|
|
MI.eraseFromParent();
|
|
return true;
|
|
}
|
|
// Calls clobber A — be safe.
|
|
if (MI.isCall())
|
|
return false;
|
|
// STAfi has `Defs = [A]` in its tablegen def (a stale over-
|
|
// approximation from before the eliminateFrameIndex PHA-bracket
|
|
// landed for non-A sources). In reality the asm preserves A
|
|
// for every source class — A source is trivial, IMG/X/Y sources
|
|
// go through PHA/lda/sta/PLA which restores A. So a STAfi to
|
|
// a different slot is NOT an A-clobber and shouldn't break the
|
|
// load-after-store redundancy. STAfi to the SAME slot DOES
|
|
// invalidate (slot value changed), handled below.
|
|
bool IsStAFi = (MI.getOpcode() == W65816::STAfi);
|
|
if (!IsStAFi && MI.modifiesRegister(StoredReg, TRI))
|
|
return false;
|
|
if (IsStAFi &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == StoredFI)
|
|
return false;
|
|
++It;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|
const TargetRegisterInfo *TRI = MF.getSubtarget().getRegisterInfo();
|
|
bool Changed = false;
|
|
|
|
// Pass -4: redundant pointer respill. Pattern that the LDAptrOff +
|
|
// STAptrOff inserter pair emits when the same pointer is used for
|
|
// both a load and a store within a loop body:
|
|
//
|
|
// LDAfi slot_c ; reload p from its slot (slot_c = p's home)
|
|
// STAfi slot_A ; spill p to slot_A (for the indirect Y-load)
|
|
// ... LDA (slot_A,Y) ; INC ...
|
|
// LDAfi slot_c ; reload p again (same source!)
|
|
// STAfi slot_B ; spill p to slot_B (for the indirect Y-store)
|
|
// ...; STA (slot_B),Y
|
|
//
|
|
// M[slot_A] and M[slot_B] both hold p — equal. We can redirect any
|
|
// later use of slot_B to slot_A and drop the LDA+STA pair. The
|
|
// saving is 2 insns per affected indirect-pair (4 cycles). Only
|
|
// safe if slot_A wasn't written in between (it isn't — no STAfi to
|
|
// slot_A appears in the loop) and the second LDA reloads from the
|
|
// SAME source slot_c.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Ldas;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::LDAfi)
|
|
Ldas.push_back(&MI);
|
|
SmallPtrSet<MachineInstr *, 8> Erased;
|
|
for (MachineInstr *Lda1 : Ldas) {
|
|
if (Erased.count(Lda1)) continue;
|
|
int SlotC = matchAccSlotOp(*Lda1, W65816::LDAfi);
|
|
if (SlotC == NO_SLOT_MATCH) continue;
|
|
auto It = std::next(Lda1->getIterator());
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
// Step 2: STAfi slotA.
|
|
int SlotA = matchAccSlotOp(*It, W65816::STAfi);
|
|
if (SlotA == NO_SLOT_MATCH || SlotA == SlotC) continue;
|
|
// Walk forward looking for LDAfi slotC again, with no STAfi
|
|
// slotA / slotC in between.
|
|
auto Walker = std::next(It);
|
|
MachineInstr *Lda2 = nullptr;
|
|
while (Walker != MBB.end()) {
|
|
MachineInstr &MI = *Walker;
|
|
if (MI.isDebugInstr()) { ++Walker; continue; }
|
|
if (MI.isCall() || MI.isInlineAsm() || MI.isBranch() ||
|
|
MI.isReturn())
|
|
break;
|
|
// STA to slotA or slotC: M might no longer hold the same value.
|
|
if (MI.getOpcode() == W65816::STAfi &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI()) {
|
|
int Slot = MI.getOperand(1).getIndex();
|
|
if (Slot == SlotA || Slot == SlotC) break;
|
|
}
|
|
// Found another LDA from slotC?
|
|
if (matchAccSlotOp(MI, W65816::LDAfi) == SlotC) {
|
|
Lda2 = &MI;
|
|
break;
|
|
}
|
|
++Walker;
|
|
}
|
|
if (!Lda2) continue;
|
|
auto It2 = std::next(Lda2->getIterator());
|
|
if (!advancePastDebug(MBB, It2)) continue;
|
|
// Step 4: STAfi slotB.
|
|
int SlotB = matchAccSlotOp(*It2, W65816::STAfi);
|
|
if (SlotB == NO_SLOT_MATCH || SlotB == SlotA || SlotB == SlotC) continue;
|
|
MachineInstr &Sta2 = *It2;
|
|
// Walk further to find the indirect use (LDAfi_indY / STAfi_indY)
|
|
// referencing slotB. Bail on STA to slotA before then.
|
|
auto It3 = std::next(Sta2.getIterator());
|
|
MachineInstr *IndYTarget = nullptr;
|
|
while (It3 != MBB.end()) {
|
|
MachineInstr &MI = *It3;
|
|
if (MI.isDebugInstr()) { ++It3; continue; }
|
|
if (MI.isCall() || MI.isBranch() || MI.isReturn() ||
|
|
MI.isInlineAsm())
|
|
break;
|
|
// Slot A or C overwritten — bail.
|
|
if (MI.getOpcode() == W65816::STAfi &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI()) {
|
|
int Slot = MI.getOperand(1).getIndex();
|
|
if (Slot == SlotA || Slot == SlotC) break;
|
|
}
|
|
// Indirect-Y operand: operand 1 (load) or 1 (store) holds
|
|
// the FI pointer slot. Match LDAfi_indY/STAfi_indY using
|
|
// slotB.
|
|
if (MI.getOpcode() == W65816::LDAfi_indY ||
|
|
MI.getOpcode() == W65816::STAfi_indY) {
|
|
for (unsigned i = 0; i < MI.getNumOperands(); ++i) {
|
|
if (MI.getOperand(i).isFI() &&
|
|
MI.getOperand(i).getIndex() == SlotB) {
|
|
IndYTarget = &MI;
|
|
break;
|
|
}
|
|
}
|
|
if (IndYTarget) break;
|
|
}
|
|
++It3;
|
|
}
|
|
if (!IndYTarget) continue;
|
|
// Function-wide safety check (#107): slotB must have no other
|
|
// refs besides Sta2 (which we erase) and IndYTarget (which we
|
|
// rewrite). Cross-MBB reads would otherwise see stale data.
|
|
const MachineInstr *ignoreA[] = {&Sta2, IndYTarget};
|
|
if (slotHasOtherRefs(MF, SlotB, ignoreA))
|
|
continue;
|
|
// Apply rewrite: IndYTarget's slotB → slotA.
|
|
for (unsigned i = 0; i < IndYTarget->getNumOperands(); ++i) {
|
|
if (IndYTarget->getOperand(i).isFI() &&
|
|
IndYTarget->getOperand(i).getIndex() == SlotB) {
|
|
IndYTarget->getOperand(i).setIndex(SlotA);
|
|
break;
|
|
}
|
|
}
|
|
// Mark Lda2 as erased so the outer worklist iteration skips
|
|
// it (it's an LDAfi and was added to Ldas). Sta2 isn't in
|
|
// any worklist so erasing it directly is safe.
|
|
Erased.insert(Lda2);
|
|
Lda2->eraseFromParent();
|
|
Sta2.eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass -4b: redundant pair of consecutive STAfi. Pattern:
|
|
//
|
|
// STAfi $a, slotA, 0
|
|
// STAfi $a, slotB, 0 ; same value, different slot
|
|
// ... use slotB as indirect-Y address ...
|
|
//
|
|
// Both STAs spill $a's current value, so M[slotA] == M[slotB]. We
|
|
// can rewrite later indirect-Y uses of slotB to slotA and drop the
|
|
// second STA. Pattern shows up when an i32 pointer is loaded via
|
|
// two indirect-Y reads (offsets 0 and 2); the inserter spills the
|
|
// pointer twice (once per access).
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Stas;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::STAfi)
|
|
Stas.push_back(&MI);
|
|
SmallPtrSet<MachineInstr *, 8> Erased;
|
|
for (MachineInstr *Sta1 : Stas) {
|
|
if (Erased.count(Sta1)) continue;
|
|
int SlotA = matchAccSlotOp(*Sta1, W65816::STAfi);
|
|
if (SlotA == NO_SLOT_MATCH) continue;
|
|
auto It = std::next(Sta1->getIterator());
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
// Step 2: another STAfi $a, slotB.
|
|
int SlotB = matchAccSlotOp(*It, W65816::STAfi);
|
|
if (SlotB == NO_SLOT_MATCH || SlotB == SlotA) continue;
|
|
MachineInstr &Sta2 = *It;
|
|
// Walk forward redirecting EVERY slotB reference to slotA, until
|
|
// we hit a write to slotA (kills the equivalence) or a slotB write
|
|
// (re-binds slotB to a new value). Bail on calls/branches/asm.
|
|
// Track whether we rewrote anything; if so, drop Sta2.
|
|
auto It2 = std::next(Sta2.getIterator());
|
|
bool Rewrote = false;
|
|
while (It2 != MBB.end()) {
|
|
MachineInstr &MI = *It2;
|
|
if (MI.isDebugInstr()) { ++It2; continue; }
|
|
if (MI.isCall() || MI.isBranch() || MI.isReturn() ||
|
|
MI.isInlineAsm()) break;
|
|
// STA to slotA changes M[slotA]; M[slotA] no longer equals
|
|
// M[slotB] — bail (any further slotB ref reads the unchanged
|
|
// M[slotB], which is now distinct).
|
|
if (MI.getOpcode() == W65816::STAfi &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == SlotA)
|
|
break;
|
|
// STA to slotB rebinds slotB; subsequent reads of slotB read
|
|
// the new value, not slotA. Stop here — the redirects we've
|
|
// done so far are still valid (they read the pre-write value).
|
|
bool StaToB = (MI.getOpcode() == W65816::STAfi ||
|
|
MI.getOpcode() == W65816::STA8fi) &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == SlotB;
|
|
if (StaToB) break;
|
|
// Any *fi op or indirect-Y referencing slotB → redirect.
|
|
if (MI.getOpcode() == W65816::LDAfi_indY ||
|
|
MI.getOpcode() == W65816::STAfi_indY ||
|
|
MI.getOpcode() == W65816::LDAfi ||
|
|
MI.getOpcode() == W65816::ADCfi ||
|
|
MI.getOpcode() == W65816::ADCEfi ||
|
|
MI.getOpcode() == W65816::SBCfi ||
|
|
MI.getOpcode() == W65816::SBCEfi ||
|
|
MI.getOpcode() == W65816::ANDfi ||
|
|
MI.getOpcode() == W65816::ORAfi ||
|
|
MI.getOpcode() == W65816::EORfi ||
|
|
MI.getOpcode() == W65816::CMPfi) {
|
|
for (unsigned i = 0; i < MI.getNumOperands(); ++i) {
|
|
if (MI.getOperand(i).isFI() &&
|
|
MI.getOperand(i).getIndex() == SlotB) {
|
|
MI.getOperand(i).setIndex(SlotA);
|
|
Rewrote = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
++It2;
|
|
}
|
|
// Drop Sta2 only if slotB has no remaining references anywhere
|
|
// in the function — otherwise we'd break a use we couldn't see.
|
|
// (Sta1 stays; SlotA still has the value, and Sta1 is its def.)
|
|
if (Rewrote) {
|
|
bool SlotBStillUsed = false;
|
|
for (MachineBasicBlock &MBBO : MF) {
|
|
for (MachineInstr &MIO : MBBO) {
|
|
if (&MIO == &Sta2) continue;
|
|
for (const MachineOperand &MO : MIO.operands()) {
|
|
if (MO.isFI() && MO.getIndex() == SlotB) {
|
|
SlotBStillUsed = true; break;
|
|
}
|
|
}
|
|
if (SlotBStillUsed) break;
|
|
}
|
|
if (SlotBStillUsed) break;
|
|
}
|
|
if (!SlotBStillUsed) {
|
|
Erased.insert(&Sta2);
|
|
Sta2.eraseFromParent();
|
|
}
|
|
Changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass -4c: redundant single pointer respill. Pattern:
|
|
//
|
|
// LDAfi $a, slotC, 0 ; A = M[slotC] (slotC is "p")
|
|
// STAfi $a, slotB, 0 ; slotB = M[slotC] = "p"
|
|
// ... non-A-clobbering, no STA to slotC ...
|
|
// LDAfi_indY/STAfi_indY ..., slotB, 0
|
|
//
|
|
// M[slotB] just mirrors M[slotC], so the indirect-Y access can read
|
|
// slotC directly. After the rewrite, if slotB has no remaining uses
|
|
// in the MBB, the LDA+STA respill is dead and we erase both. This is
|
|
// the loop-counter / pointer-iteration shape that Pass -4 (the
|
|
// double-respill variant) doesn't catch when only one indirect-Y
|
|
// happens before the pointer increment.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Ldas;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::LDAfi)
|
|
Ldas.push_back(&MI);
|
|
SmallPtrSet<MachineInstr *, 8> Erased;
|
|
for (MachineInstr *Lda : Ldas) {
|
|
if (Erased.count(Lda)) continue;
|
|
int SlotC = matchAccSlotOp(*Lda, W65816::LDAfi);
|
|
if (SlotC == NO_SLOT_MATCH) continue;
|
|
auto It = std::next(Lda->getIterator());
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
int SlotB = matchAccSlotOp(*It, W65816::STAfi);
|
|
if (SlotB == NO_SLOT_MATCH || SlotB == SlotC) continue;
|
|
MachineInstr &Sta = *It;
|
|
// Walk forward through the MBB collecting all indirect-Y uses of
|
|
// slotB (LDAfi_indY / STAfi_indY referencing it as the pointer
|
|
// operand). Bail if we see any *other* reference to slotB (a
|
|
// direct LDAfi/STAfi/etc.) — that means the slot has uses other
|
|
// than as an indirect-Y pointer and we can't safely rewrite all
|
|
// of them. Also bail on STA to slotC (kills the equivalence).
|
|
SmallVector<MachineInstr *, 4> IndYUses;
|
|
bool OtherUse = false;
|
|
auto It2 = std::next(Sta.getIterator());
|
|
while (It2 != MBB.end()) {
|
|
MachineInstr &MI = *It2;
|
|
if (MI.isDebugInstr()) { ++It2; continue; }
|
|
if (MI.isCall() || MI.isBranch() || MI.isReturn() ||
|
|
MI.isInlineAsm()) break;
|
|
if (MI.getOpcode() == W65816::STAfi &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == SlotC)
|
|
break;
|
|
bool IsIndY = (MI.getOpcode() == W65816::LDAfi_indY ||
|
|
MI.getOpcode() == W65816::STAfi_indY);
|
|
bool RefsSlotB = false;
|
|
for (unsigned i = 0; i < MI.getNumOperands(); ++i) {
|
|
if (MI.getOperand(i).isFI() &&
|
|
MI.getOperand(i).getIndex() == SlotB) {
|
|
RefsSlotB = true;
|
|
break;
|
|
}
|
|
}
|
|
if (RefsSlotB) {
|
|
if (IsIndY)
|
|
IndYUses.push_back(&MI);
|
|
else
|
|
{ OtherUse = true; break; }
|
|
}
|
|
++It2;
|
|
}
|
|
if (OtherUse || IndYUses.empty()) continue;
|
|
// After IndYUses, scan rest of MBB for any further reference to
|
|
// slotB; if none, all uses of slotB are in our IndYUses list and
|
|
// we can safely redirect them all + erase the LDA+STA.
|
|
auto LastIt = std::next(IndYUses.back()->getIterator());
|
|
bool LaterUse = false;
|
|
for (auto It3 = LastIt; It3 != MBB.end(); ++It3) {
|
|
for (const MachineOperand &MO : It3->operands()) {
|
|
if (MO.isFI() && MO.getIndex() == SlotB) { LaterUse = true; break; }
|
|
}
|
|
if (LaterUse) break;
|
|
}
|
|
if (LaterUse) continue;
|
|
// Function-wide safety check (#107): slotB must have no other
|
|
// refs besides Sta (erased) and the IndY uses (rewritten).
|
|
// The IndYUses list already collects all in-MBB IndY refs;
|
|
// anything else (including cross-MBB) would expect the init.
|
|
SmallVector<const MachineInstr *, 8> ignore;
|
|
ignore.push_back(&Sta);
|
|
for (MachineInstr *I : IndYUses)
|
|
ignore.push_back(I);
|
|
if (slotHasOtherRefs(MF, SlotB, ignore))
|
|
continue;
|
|
// Apply rewrites: redirect every IndY use of slotB → slotC.
|
|
for (MachineInstr *IndY : IndYUses) {
|
|
for (unsigned i = 0; i < IndY->getNumOperands(); ++i) {
|
|
if (IndY->getOperand(i).isFI() &&
|
|
IndY->getOperand(i).getIndex() == SlotB) {
|
|
IndY->getOperand(i).setIndex(SlotC);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Erased.insert(Lda);
|
|
Lda->eraseFromParent();
|
|
Sta.eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass -3: hoist `LDX #imm` (constant materialisation into the X
|
|
// register) out from between a flag-defining op and the consuming
|
|
// Bxx. LDX physically updates N and Z, but our pseudo lacks
|
|
// `Defs = [P]` so the scheduler can place it in the test window.
|
|
// SAFE because:
|
|
// - LDX writes X; CMP/ORA/etc. read A. Hoisting can't change
|
|
// what the CMP sees.
|
|
// - The LDX's source is an immediate — no operand dependency.
|
|
// - Moving LDX before the CMP just means CMP overwrites the
|
|
// flags LDX set, which is what we want.
|
|
// Only LDX-style — `LDA #imm` is NOT safe because CMP reads A and
|
|
// the hoist would change A's value. Tracked in
|
|
// memory/project_known_issue_lda_flags.md.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 4> Branches;
|
|
for (MachineInstr &MI : MBB) {
|
|
unsigned Opc = MI.getOpcode();
|
|
if (Opc == W65816::BEQ || Opc == W65816::BNE ||
|
|
Opc == W65816::BMI || Opc == W65816::BPL)
|
|
Branches.push_back(&MI);
|
|
}
|
|
for (MachineInstr *Br : Branches) {
|
|
SmallVector<MachineInstr *, 4> ToHoist;
|
|
MachineInstr *Test = nullptr;
|
|
for (auto It = std::prev(Br->getIterator()); ; --It) {
|
|
MachineInstr &MI = *It;
|
|
if (MI.isDebugInstr()) {
|
|
if (It == MBB.begin()) break;
|
|
continue;
|
|
}
|
|
// STA preserves flags (the MC variants STA_StackRel /
|
|
// STA_StackRelIndY only appear post-PEI and are listed here
|
|
// defensively; pre-PEI we see STAfi / STAfi_indY / STA8fi
|
|
// pseudos). STA8fi expands to SEP/STA/REP, which preserves
|
|
// N/Z (only M is touched).
|
|
if (MI.getOpcode() == W65816::STA_StackRel ||
|
|
MI.getOpcode() == W65816::STA_StackRelIndY ||
|
|
MI.getOpcode() == W65816::STAfi ||
|
|
MI.getOpcode() == W65816::STAfi_indY ||
|
|
MI.getOpcode() == W65816::STA8fi) {
|
|
if (It == MBB.begin()) break;
|
|
continue;
|
|
}
|
|
// LDX #imm: candidate to hoist.
|
|
if (MI.getOpcode() == W65816::LDXi16imm &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isImm()) {
|
|
ToHoist.push_back(&MI);
|
|
if (It == MBB.begin()) break;
|
|
continue;
|
|
}
|
|
// First "real" instruction we hit walking back is the flag-
|
|
// defining test (CMP, ORA, etc.) — stop here.
|
|
Test = &MI;
|
|
break;
|
|
}
|
|
if (!Test || ToHoist.empty()) continue;
|
|
for (auto *MI : ToHoist) {
|
|
MI->removeFromParent();
|
|
MBB.insert(Test->getIterator(), MI);
|
|
Changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass -2.5: BR_CC flag-corruption mitigation via PHP/PLP. When a
|
|
// flag-test (CMP/ORA/etc.) is followed by P-corrupting ops (LDA/LDX
|
|
// /AND/etc.) and then a flag-testing branch (Bxx), the branch would
|
|
// test the corrupting op's N/Z instead of the test's. This is a
|
|
// real correctness bug — `while (n > 0)` always exits on first
|
|
// iteration; `eq_test(0)` returns 0; etc. Wrap the corrupting span
|
|
// with PHP (push flags) / PLP (pop flags), preserving the test's
|
|
// flags across the corruption. Costs 2 bytes / 8 cycles per
|
|
// affected pattern, but it's the difference between buggy and
|
|
// correct code. The 4-block SELECT_CC inserter handles its case
|
|
// structurally; this catches the BR_CC paths the inserter can't
|
|
// touch. Only inserts when:
|
|
// - The branch tests N or Z (BEQ/BNE/BMI/BPL); BCC/BCS test C
|
|
// and LDA doesn't touch C, so they're not affected.
|
|
// - There's at least one P-corrupting instruction between the
|
|
// flag-defining test and the Bxx.
|
|
//
|
|
// NOTE: Bxx instructions now declare `Uses = [P]` (W65816InstrInfo.td),
|
|
// so the pre-RA scheduler / regalloc / peephole won't legally insert
|
|
// a P-corrupting op between a CMP and the consuming Bxx. This pass
|
|
// is now mostly defensive — it stays in place to catch any post-RA
|
|
// pass that might still violate the dep, and to wrap the rare cases
|
|
// where the IR-level test is a load (LDA flag side-effect) rather
|
|
// than an explicit CMP.
|
|
// In VLA functions, FI store pseudos (STAfi, STA8fi, STAfi_indY)
|
|
// expand at PEI to a 4-MC sequence ending in `LDY $F8` (Y-restore),
|
|
// which clobbers N/Z. The PHP/PLP wrap pass runs pre-PEI; treating
|
|
// those pseudos as flag-preserving leaves the trailing LDY outside
|
|
// the wrap, so a downstream BEQ/BNE reads the LDY's flags instead of
|
|
// the test's. Treat them as corrupting in VLA functions so the wrap
|
|
// covers the whole expansion.
|
|
// VLAFunc: narrow predicate used by the flag-preserving / lda-like
|
|
// helpers (broadening it to UsesFPRel broke dadd's i64-ABI libcall
|
|
// flow — the STAfi pseudos in non-VLA large-frame functions don't
|
|
// need to be marked corrupting for the wrap-detection walk).
|
|
// UsesFPRel: broader FrameLowering-matching predicate used by the
|
|
// pseudo-bump's offset-routing check (FP-rel ops must NOT be bumped,
|
|
// SP-rel ops MUST be bumped; we replicate eliminateFrameIndex's
|
|
// routing decision below to choose).
|
|
bool VLAFunc = MF.getFrameInfo().hasVarSizedObjects();
|
|
bool UsesFPRel = MF.getFrameInfo().hasVarSizedObjects() ||
|
|
MF.getFrameInfo().estimateStackSize(MF) > 200;
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 4> Branches;
|
|
for (MachineInstr &MI : MBB) {
|
|
unsigned Opc = MI.getOpcode();
|
|
// BCC/BCS/BVC/BVS are collected too, but ONLY for the carry/overflow
|
|
// corrupters (isCarryCorrupting below): an LDA-like op between a CMP
|
|
// and a BCC leaves C alone and needs no wrap, which is why they were
|
|
// never listed here before. ADDframe is different - see its note.
|
|
if (Opc == W65816::BEQ || Opc == W65816::BNE ||
|
|
Opc == W65816::BMI || Opc == W65816::BPL ||
|
|
Opc == W65816::BCC || Opc == W65816::BCS ||
|
|
Opc == W65816::BVC || Opc == W65816::BVS)
|
|
Branches.push_back(&MI);
|
|
}
|
|
auto isFlagPreserving = [VLAFunc](unsigned Opc) {
|
|
if (VLAFunc) {
|
|
// FI store pseudos are flag-corrupting under VLA expansion.
|
|
if (Opc == W65816::STAfi || Opc == W65816::STAfi_indY ||
|
|
Opc == W65816::STA8fi)
|
|
return false;
|
|
}
|
|
return Opc == W65816::STA_StackRel ||
|
|
Opc == W65816::STA_StackRelIndY ||
|
|
Opc == W65816::STAfi ||
|
|
Opc == W65816::STAfi_indY ||
|
|
Opc == W65816::STA8fi ||
|
|
Opc == W65816::STA_DP ||
|
|
Opc == W65816::STA_Abs ||
|
|
Opc == W65816::STA_Long ||
|
|
Opc == W65816::STX_DP ||
|
|
Opc == W65816::STX_Abs ||
|
|
Opc == W65816::STY_DP ||
|
|
Opc == W65816::STY_Abs;
|
|
};
|
|
auto isFlagDefining = [](const MachineInstr &MI) {
|
|
// Anything that physically writes A, X, Y, or P updates N/Z (or
|
|
// P-bits for CMP). We treat any non-store, non-stack-mgmt op
|
|
// that's not a branch as flag-defining. STA family preserves;
|
|
// PHA/PLY don't touch flags either; everything else might.
|
|
unsigned Opc = MI.getOpcode();
|
|
switch (Opc) {
|
|
case W65816::PHA: case W65816::PHX: case W65816::PHY:
|
|
case W65816::PHP: case W65816::PHB: case W65816::PHD:
|
|
case W65816::PHK:
|
|
case W65816::TCS: case W65816::TXS:
|
|
case W65816::TCD:
|
|
case W65816::JSLpseudo: case W65816::JSLpseudo32:
|
|
case W65816::JSL_Long:
|
|
case W65816::JSR_Abs:
|
|
case W65816::JMP_Abs:
|
|
case W65816::BRA:
|
|
case W65816::RTL: case W65816::RTS:
|
|
case W65816::REP: case W65816::SEP:
|
|
case W65816::CLC: case W65816::SEC:
|
|
case W65816::CLV: case W65816::CLI: case W65816::SEI:
|
|
case W65816::CLD: case W65816::SED:
|
|
return false;
|
|
default:
|
|
return !MI.isBranch() && !MI.isReturn();
|
|
}
|
|
};
|
|
auto isLdaLike = [VLAFunc](unsigned Opc) {
|
|
if (VLAFunc) {
|
|
// STAfi-family: see isFlagPreserving comment. They expand to a
|
|
// sequence whose final LDY $F8 corrupts N/Z; treat as corrupting.
|
|
if (Opc == W65816::STAfi || Opc == W65816::STAfi_indY ||
|
|
Opc == W65816::STA8fi)
|
|
return true;
|
|
}
|
|
// COPY between physregs: lowers in AsmPrinter to one of TXA/TYA/
|
|
// LDA $D? (for IMG↔A bridges) etc. — all of which set N/Z based
|
|
// on the loaded value. Treating COPY as flag-defining caused the
|
|
// wrap pass to identify a PHI-elim COPY as the "Test" and wrap
|
|
// too narrow a range, so the cb-test LDA's flags were trampled
|
|
// by intervening A-loads before reaching the BEQ. Including
|
|
// COPY in the corrupting set forces the pass to walk past these
|
|
// PHI-elim copies to find the real test (a CMP).
|
|
if (Opc == TargetOpcode::COPY) return true;
|
|
// ADDframe (the LEA of a stack slot) expands at PEI to `TSC; CLC;
|
|
// ADC #disp`, which rewrites N/Z (TSC, ADC), C (CLC, ADC) and V
|
|
// (ADC). It has no Defs=[P] (deliberately, like the loads above)
|
|
// and isReMaterializable, so post-RA passes are free to place it
|
|
// between a CMP and its Bxx - which Machine Copy Propagation did
|
|
// in RetroNet's rnClient.c: `cmp #3; tsc; clc; adc #0xc1; bne`
|
|
// made an `act.kind == RN_WA_SUBMIT` test branch on (SP+0xC1)!=0,
|
|
// i.e. never true, so the form was never submitted on the IIgs.
|
|
// Before this line the walk below treated ADDframe as the flag-
|
|
// DEFINING test (isFlagDefining's default) and declined to wrap.
|
|
if (Opc == W65816::ADDframe) return true;
|
|
// Pure load / register-transfer instructions: only side effect on
|
|
// flags is N/Z from the loaded/transferred value. Never a "test"
|
|
// — they just move data. Treated as corruption when between the
|
|
// real test and a flag-using branch.
|
|
return Opc == W65816::LDAi16imm ||
|
|
Opc == W65816::LDAi8imm ||
|
|
// LDAi16imm_bank lowers to `lda $BE` (LDA_DP of the program-
|
|
// bank byte). Under GNO/ME the program bank is non-zero, so
|
|
// this load sets N/Z != the CMP's result. Between a CMP and a
|
|
// BEQ/BNE (e.g. the bank half of an i32 `select`/`if(!p)`
|
|
// pointer pick), it corrupts the test and the wrong half is
|
|
// selected -- the `%s`-of-stack-string garbling bug. Plain
|
|
// LDAi16imm was already listed; its _bank sibling must be too.
|
|
Opc == W65816::LDAi16imm_bank ||
|
|
Opc == W65816::LDXi16imm ||
|
|
Opc == W65816::LDA_StackRel ||
|
|
Opc == W65816::LDA_StackRelIndY ||
|
|
Opc == W65816::LDA_DP ||
|
|
Opc == W65816::LDA_Abs ||
|
|
Opc == W65816::LDA_Long ||
|
|
Opc == W65816::LDA_Imm16 || Opc == W65816::LDA_Imm8 ||
|
|
Opc == W65816::LDX_Imm16 || Opc == W65816::LDX_Imm8 ||
|
|
Opc == W65816::LDX_DP || Opc == W65816::LDX_Abs ||
|
|
Opc == W65816::LDY_Imm16 || Opc == W65816::LDY_Imm8 ||
|
|
Opc == W65816::LDY_DP || Opc == W65816::LDY_Abs ||
|
|
// Pseudo wrappers that lower to LDA #imm.
|
|
Opc == W65816::LDAfi ||
|
|
Opc == W65816::LDAfi_indY ||
|
|
// Pure-logic NZ-setters: AND / ORA / EOR against an immediate
|
|
// or absolute operand. These physically set N/Z but
|
|
// deliberately do NOT model Defs=[P] (see W65816InstrInfo.td:
|
|
// the bitwise ops "don't read/write the carry flag"), so a
|
|
// conditional branch can NEVER take its modeled $p from one of
|
|
// them — at this pass's point every branch's flags come from a
|
|
// real CMP (Defs=[P]); the CMP-vs-AND/ORA fold that drops the
|
|
// `cmp #0` runs later. So when one of these is scheduled
|
|
// BETWEEN a loop-guard CMP and its branch (e.g. a loop-
|
|
// invariant signed-compare bias `contactY ^ 0x8000` hoisted
|
|
// into a zero-trip loop's preheader), it silently tramples the
|
|
// CMP's Z: the guard then branches on the wrong value, enters a
|
|
// should-be-skipped loop, and spins ~65536 iterations reading
|
|
// out-of-bounds data (the reported course_support_piece hang).
|
|
// Treating them as corruption walks the wrap past them to the
|
|
// real CMP. NOTE: the *fi variants (ANDfi/ORAfi/EORfi) and
|
|
// ADC/SBC DO model Defs=[P] and can legitimately be the branch's
|
|
// flag source, so they are handled by $p liveness and are NOT
|
|
// listed here; same for INA/DEA and the shifts, which the DEC/
|
|
// INC and shift-carry idioms may read directly.
|
|
Opc == W65816::EORi16imm || Opc == W65816::EORabs ||
|
|
Opc == W65816::ANDi16imm || Opc == W65816::ANDabs ||
|
|
Opc == W65816::ORAi16imm || Opc == W65816::ORAabs ||
|
|
// Register transfers — TAX/TXA/TAY/TYA/TXY/TYX update N/Z
|
|
// based on the transferred value. They're "data movement"
|
|
// not "comparison"; treat as corruption so the wrap pass
|
|
// walks past them to the real test. Without this, a loop
|
|
// like `for (i...) { ...; t = X; ... }` ends up testing
|
|
// (t != 0) instead of (i != 0) and runs forever.
|
|
Opc == W65816::TAX || Opc == W65816::TXA ||
|
|
Opc == W65816::TAY || Opc == W65816::TYA ||
|
|
Opc == W65816::TXY || Opc == W65816::TYX;
|
|
};
|
|
auto isStackRel = [](unsigned Opc) {
|
|
// Stack-relative ops read/write at S+disp. PHP decrements S by 1,
|
|
// so any STA/LDA d,S between PHP and PLP would land at the wrong
|
|
// address (off by 1). We must keep these OUTSIDE the wrap.
|
|
// Includes both the post-lowered MC opcodes (LDA_StackRel etc.)
|
|
// AND the pseudo *fi opcodes (LDAfi etc.) — eliminateFrameIndex
|
|
// hasn't run yet when the wrap pass executes, so it's the pseudos
|
|
// that are actually in the IR.
|
|
return Opc == W65816::STA_StackRel ||
|
|
Opc == W65816::STA_StackRelIndY ||
|
|
Opc == W65816::LDA_StackRel ||
|
|
Opc == W65816::LDA_StackRelIndY ||
|
|
Opc == W65816::ADC_StackRel ||
|
|
Opc == W65816::SBC_StackRel ||
|
|
Opc == W65816::AND_StackRel ||
|
|
Opc == W65816::ORA_StackRel ||
|
|
Opc == W65816::EOR_StackRel ||
|
|
Opc == W65816::CMP_StackRel ||
|
|
Opc == W65816::LDAfi ||
|
|
Opc == W65816::LDAfi_indY ||
|
|
Opc == W65816::STAfi ||
|
|
Opc == W65816::STAfi_indY ||
|
|
Opc == W65816::STA8fi ||
|
|
Opc == W65816::ADCfi ||
|
|
Opc == W65816::ADCEfi ||
|
|
Opc == W65816::SBCfi ||
|
|
Opc == W65816::SBCEfi ||
|
|
Opc == W65816::ANDfi ||
|
|
Opc == W65816::ORAfi ||
|
|
Opc == W65816::EORfi ||
|
|
Opc == W65816::CMPfi ||
|
|
Opc == W65816::ADDframe;
|
|
};
|
|
// The subset of corrupters that also rewrite C/V, and so must be
|
|
// wrapped even for a BCC/BCS/BVC/BVS. Only ADDframe today: every
|
|
// other entry in isLdaLike touches N/Z alone.
|
|
auto isCarryCorrupting = [](unsigned Opc) {
|
|
return Opc == W65816::ADDframe;
|
|
};
|
|
for (MachineInstr *Br : Branches) {
|
|
unsigned BrOpc = Br->getOpcode();
|
|
bool TestsNZ = BrOpc == W65816::BEQ || BrOpc == W65816::BNE ||
|
|
BrOpc == W65816::BMI || BrOpc == W65816::BPL;
|
|
// Walk back from Br looking for the pattern:
|
|
// <test>; (mix of preserving + corrupting ops); Br
|
|
// where <test> is a flag-defining op (CMP/ORA/AND/ADC/...) and
|
|
// there's at least one corrupting (LDA-like / TXA-like) op between
|
|
// <test> and Br. Wrap the corrupting region with PHP/PLP so Br
|
|
// sees <test>'s flags.
|
|
//
|
|
// Wrap boundaries:
|
|
// PHP goes just before the FIRST corrupting op (not just after
|
|
// Test) so any preserving stack-rel STAs before the first
|
|
// corruption stay outside the wrap and use the un-decremented S.
|
|
// PLP goes just after the LAST corrupting op for the same
|
|
// reason — preserving stack-rel STAs that follow stay outside.
|
|
// This is critical: PHP changes S by 1, so a `sta 1,s` inside
|
|
// the wrap writes at the same address PHP just saved P to,
|
|
// corrupting the saved flags. Caught by an iterative fib loop
|
|
// that ran forever because PLP loaded a corrupt P value.
|
|
MachineInstr *Test = nullptr;
|
|
MachineInstr *FirstCorrupt = nullptr;
|
|
MachineInstr *LastCorrupt = nullptr;
|
|
for (auto It = std::prev(Br->getIterator()); ; --It) {
|
|
MachineInstr &MI = *It;
|
|
if (!MI.isDebugInstr()) {
|
|
if (isFlagPreserving(MI.getOpcode())) {
|
|
// skip
|
|
} else if (isCarryCorrupting(MI.getOpcode()) ||
|
|
(TestsNZ && isLdaLike(MI.getOpcode()))) {
|
|
if (!LastCorrupt) LastCorrupt = &MI;
|
|
FirstCorrupt = &MI;
|
|
} else if (isLdaLike(MI.getOpcode())) {
|
|
// An N/Z-only corrupter under a C/V branch: harmless, walk on.
|
|
} else if (isFlagDefining(MI)) {
|
|
Test = &MI;
|
|
break;
|
|
} else {
|
|
// Opaque (call, unrelated terminator) — stop.
|
|
break;
|
|
}
|
|
}
|
|
if (It == MBB.begin()) break;
|
|
}
|
|
if (!Test || !FirstCorrupt) continue;
|
|
// Stack-relative ops inside the wrap need their displacements
|
|
// bumped by +1 to compensate for PHP's S decrement. Without
|
|
// this, `lda 5,s` between PHP and PLP reads at (orig_S-1)+5
|
|
// = orig_S+4, one byte too low. The pseudo *fi ops carry an
|
|
// ImmOffset operand that gets folded into the final disp by
|
|
// eliminateFrameIndex; bumping ImmOffset by 1 produces the
|
|
// right post-lowered disp. For already-lowered MC ops
|
|
// (LDA_StackRel etc), bump the disp operand directly.
|
|
//
|
|
// CAVEAT for FP-relative functions (see UsesFPRel declaration above):
|
|
// FI accesses go through FP-relative addressing (eliminateFrameIndex
|
|
// routes through expandFarFI when FrameLowering captured FP). FP
|
|
// was captured BEFORE PHP, so (FP),Y reads aren't affected by PHP's
|
|
// S decrement. Don't bump pseudo *fi ImmOffsets in that case
|
|
// (already-lowered MC StackRel ops still need the bump — those are
|
|
// SP-rel).
|
|
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
|
|
DebugLoc DL = Test->getDebugLoc();
|
|
BuildMI(MBB, FirstCorrupt->getIterator(), DL, TII->get(W65816::PHP));
|
|
for (auto It = FirstCorrupt->getIterator();
|
|
It != std::next(LastCorrupt->getIterator()); ++It) {
|
|
if (It->isDebugInstr() || !isStackRel(It->getOpcode())) continue;
|
|
// Pseudo *fi ops: operand layout is (def, FI, ImmOffset, ...).
|
|
// Bump the Imm at index 2. MC StackRel ops: operand 0 is the
|
|
// disp Imm (set by eliminateFrameIndex); bump that.
|
|
unsigned Opc = It->getOpcode();
|
|
bool IsPseudo = Opc == W65816::LDAfi || Opc == W65816::LDAfi_indY ||
|
|
Opc == W65816::STAfi || Opc == W65816::STAfi_indY ||
|
|
Opc == W65816::STA8fi ||
|
|
Opc == W65816::ADCfi || Opc == W65816::ADCEfi ||
|
|
Opc == W65816::SBCfi || Opc == W65816::SBCEfi ||
|
|
Opc == W65816::ANDfi || Opc == W65816::ORAfi ||
|
|
Opc == W65816::EORfi || Opc == W65816::CMPfi ||
|
|
Opc == W65816::ADDframe;
|
|
// For pseudo *fi ops in FP-rel functions: only SOME will end up
|
|
// SP-rel after PEI (offsets in [0,255]); the rest go through
|
|
// expandFarFI → `[$F6],Y`. FP-rel access is unaffected by PHP's
|
|
// S decrement and must NOT be bumped; SP-rel access IS affected
|
|
// and MUST be bumped. Replicate eliminateFrameIndex's offset
|
|
// calculation here to decide. Without this, large-frame
|
|
// functions that mix both addressing modes (e.g. sha256-style
|
|
// i32-libcall loops) get their FP-rel pseudos bumped, which
|
|
// shifts reads/writes by one byte and corrupts state at
|
|
// iteration N proportional to the i32-libcall count.
|
|
// ADDframe has no FP-relative expansion (eliminateFrameIndex always
|
|
// emits TSC + ADC #disp), so it is SP-relative in EVERY function and
|
|
// always needs the bump - exempting it here would leave its address
|
|
// one byte low inside the wrap in a large-frame function.
|
|
if (IsPseudo && UsesFPRel && Opc != W65816::ADDframe) {
|
|
const MachineFrameInfo &MFI = MF.getFrameInfo();
|
|
if (It->getOperand(1).isFI()) {
|
|
int FI = It->getOperand(1).getIndex();
|
|
int FrameOffset = MFI.getObjectOffset(FI);
|
|
int ImmOffset = It->getOperand(2).isImm()
|
|
? (int)It->getOperand(2).getImm() : 0;
|
|
int LoweredOff = FrameOffset + ImmOffset +
|
|
(int)MFI.getStackSize();
|
|
if (FrameOffset < 0) LoweredOff += 1;
|
|
// Out-of-range or VLA → FP-rel → no bump.
|
|
if (LoweredOff < 0 || LoweredOff > 0xFF ||
|
|
MFI.hasVarSizedObjects())
|
|
continue;
|
|
// Else SP-rel: fall through and bump ImmOffset.
|
|
}
|
|
}
|
|
unsigned ImmIdx = IsPseudo ? 2 : 0;
|
|
if (ImmIdx < It->getNumOperands() && It->getOperand(ImmIdx).isImm()) {
|
|
int64_t v = It->getOperand(ImmIdx).getImm();
|
|
It->getOperand(ImmIdx).setImm(v + 1);
|
|
}
|
|
}
|
|
BuildMI(MBB, std::next(LastCorrupt->getIterator()), DL,
|
|
TII->get(W65816::PLP));
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass -2c: relaxed mem-to-mem copy elimination across arbitrary
|
|
// instructions. Pattern:
|
|
//
|
|
// LDAfi $a, slotA, 0 ; A = M[slotA]
|
|
// STAfi $a, slotB, 0 ; M[slotB] = M[slotA]
|
|
// ... arbitrary instructions, possibly including JSL, ALU, etc.,
|
|
// as long as nothing writes slotA or slotB ...
|
|
// OPfi $a, slotB, 0 ; reads M[slotB]
|
|
//
|
|
// Rewrite OPfi to read slotA and drop the LDA-STA pair if slotB has
|
|
// no other uses anywhere in the function. Catches the "loop-carry"
|
|
// shape in `for (i = 0; i < n; i++) sum += ...` where each iteration
|
|
// re-spills sum to a separate adc-input slot. Pass -2 (the strict
|
|
// adjacent variant) doesn't catch this because of the JSL / ALU
|
|
// ops between Sta and the OPfi.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Ldas;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::LDAfi)
|
|
Ldas.push_back(&MI);
|
|
SmallPtrSet<MachineInstr *, 8> Erased;
|
|
for (MachineInstr *Lda : Ldas) {
|
|
if (Erased.count(Lda)) continue;
|
|
int SlotA = matchAccSlotOp(*Lda, W65816::LDAfi);
|
|
if (SlotA == NO_SLOT_MATCH) continue;
|
|
auto It = std::next(Lda->getIterator());
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
int SlotB = matchAccSlotOp(*It, W65816::STAfi);
|
|
if (SlotB == NO_SLOT_MATCH || SlotB == SlotA) continue;
|
|
MachineInstr &Sta = *It;
|
|
// Walk forward. Find the FIRST *fi op whose pointer-FI operand
|
|
// is slotB and rewrite it. We allow calls in between — local
|
|
// (non-fixed) slots are below-S and not reachable by the callee
|
|
// (the callee's stack-rel offsets are above its own SP). Fixed
|
|
// slots are also unreachable for the same reason. Bail on
|
|
// branches, asm, returns; on STAs writing slotA or slotB.
|
|
auto It2 = std::next(Sta.getIterator());
|
|
MachineInstr *OpfiTarget = nullptr;
|
|
unsigned RewriteIdx = 0;
|
|
while (It2 != MBB.end()) {
|
|
MachineInstr &MI = *It2;
|
|
if (MI.isDebugInstr()) { ++It2; continue; }
|
|
if (MI.isInlineAsm() || MI.isBranch() || MI.isReturn()) break;
|
|
bool StaToA = (MI.getOpcode() == W65816::STAfi ||
|
|
MI.getOpcode() == W65816::STA8fi) &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == SlotA;
|
|
if (StaToA) break;
|
|
bool StaToB = (MI.getOpcode() == W65816::STAfi ||
|
|
MI.getOpcode() == W65816::STA8fi) &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == SlotB;
|
|
if (StaToB) break;
|
|
unsigned Opc = MI.getOpcode();
|
|
bool IsOpFi = (Opc == W65816::ADCfi || Opc == W65816::ADCEfi ||
|
|
Opc == W65816::SBCfi || Opc == W65816::SBCEfi ||
|
|
Opc == W65816::ANDfi || Opc == W65816::ORAfi ||
|
|
Opc == W65816::EORfi || Opc == W65816::CMPfi);
|
|
if (IsOpFi) {
|
|
unsigned FiIdx = (Opc == W65816::CMPfi) ? 1 : 2;
|
|
if (MI.getNumOperands() >= FiIdx + 2 &&
|
|
MI.getOperand(FiIdx).isFI() &&
|
|
MI.getOperand(FiIdx).getIndex() == SlotB &&
|
|
MI.getOperand(FiIdx + 1).isImm() &&
|
|
MI.getOperand(FiIdx + 1).getImm() == 0) {
|
|
OpfiTarget = &MI;
|
|
RewriteIdx = FiIdx;
|
|
break;
|
|
}
|
|
}
|
|
// LDAfi $a, slotB, 0 — final reload before a DPF0 stage. The
|
|
// LDAptr32 inserter emits this shape per pointer half:
|
|
// LDAfi $a, srcSlot ; STAfi $a, freshSlot ; [other STAfi]
|
|
// LDAfi $a, freshSlot ; STA_DP $E0
|
|
// Pass -2c can elide the round-trip by retargeting the final LDAfi
|
|
// to read srcSlot (=SlotA) directly, dropping the LDA+STA pair.
|
|
// The final LDAfi is a pure A-write so dropping the earlier Lda1
|
|
// can't break any intervening A-reader.
|
|
if (Opc == W65816::LDAfi) {
|
|
if (MI.getNumOperands() >= 3 &&
|
|
MI.getOperand(0).isReg() &&
|
|
MI.getOperand(0).getReg() == W65816::A &&
|
|
MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == SlotB &&
|
|
MI.getOperand(2).isImm() &&
|
|
MI.getOperand(2).getImm() == 0) {
|
|
OpfiTarget = &MI;
|
|
RewriteIdx = 1;
|
|
break;
|
|
}
|
|
}
|
|
++It2;
|
|
}
|
|
if (!OpfiTarget) continue;
|
|
// Verify slotB has no OTHER references in this function (besides
|
|
// Sta and OpfiTarget). If it does, we can't safely drop Sta.
|
|
const MachineInstr *ignoreS2c[] = {&Sta, OpfiTarget};
|
|
if (slotHasOtherRefs(MF, SlotB, ignoreS2c))
|
|
continue;
|
|
// Apply rewrite.
|
|
OpfiTarget->getOperand(RewriteIdx).setIndex(SlotA);
|
|
Erased.insert(Lda);
|
|
Lda->eraseFromParent();
|
|
Sta.eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass -2: collapse `LDAfi slotA; STAfi slotB; LDAfi slotC; OPfi slotB`
|
|
// to `LDAfi slotC; OPfi slotA`. This is the "memory-to-memory copy
|
|
// through A" pattern the inserter + regalloc emit when both operands
|
|
// of OR_RR/AND_RR/EOR_RR/CMP_RR are already-spilled vregs. We're not
|
|
// using OP commutativity here — after `STAfi $a, slotB` we have
|
|
// M[slotB] == M[slotA], so reading slotA in place of slotB is a
|
|
// value-identity rewrite that's safe even for non-commutative OPs
|
|
// (CMP, SBC). slotA must not be written between the STAfi we erase
|
|
// and the OPfi we rewrite — the only intervening instruction is the
|
|
// single LDAfi in step 3, which doesn't write any slot.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 4> Worklist;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::LDAfi)
|
|
Worklist.push_back(&MI);
|
|
for (MachineInstr *Lda1 : Worklist) {
|
|
// Step 1: LDAfi $a, slotA.
|
|
int SlotA = matchAccSlotOp(*Lda1, W65816::LDAfi);
|
|
if (SlotA == NO_SLOT_MATCH) continue;
|
|
|
|
auto It = std::next(Lda1->getIterator());
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
|
|
// Step 2: STAfi $a, slotB.
|
|
int SlotB = matchAccSlotOp(*It, W65816::STAfi);
|
|
if (SlotB == NO_SLOT_MATCH || SlotA == SlotB) continue;
|
|
MachineInstr &Sta = *It;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
|
|
// Step 3: LDAfi $a, slotC (loading the OPfi's tied input).
|
|
int SlotC = matchAccSlotOp(*It, W65816::LDAfi);
|
|
if (SlotC == NO_SLOT_MATCH || SlotC == SlotB) continue;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
|
|
// Step 4: OPfi $a (tied), slotB — accept any *_fi op whose 2nd
|
|
// operand is the FI we want to redirect. Commutative ops always
|
|
// match. CMPfi / SBCfi are non-commutative but the rewrite still
|
|
// preserves the comparison since M[slotA] == M[slotB] here.
|
|
MachineInstr &Op = *It;
|
|
unsigned Opc = Op.getOpcode();
|
|
bool IsFiOp = isCommutativeFiOp(Opc) ||
|
|
Opc == W65816::CMPfi ||
|
|
Opc == W65816::SBCfi ||
|
|
Opc == W65816::SBCEfi;
|
|
if (!IsFiOp) continue;
|
|
// Operand layout: CMPfi has (outs), (ins Acc16:$lhs, memfi:$addr)
|
|
// → operand 0 = $lhs, operand 1+2 = memfi. All other *fi ops
|
|
// are (outs Acc16:$dst), (ins Acc16:$src, memfi) → operand 0 =
|
|
// $dst, 1 = $src, 2+3 = memfi. Pick the right FI operand index.
|
|
unsigned FiIdx = (Opc == W65816::CMPfi) ? 1 : 2;
|
|
if (Op.getNumOperands() < FiIdx + 2 ||
|
|
!Op.getOperand(0).isReg() || Op.getOperand(0).getReg() != W65816::A ||
|
|
!Op.getOperand(FiIdx).isFI() ||
|
|
Op.getOperand(FiIdx).getIndex() != SlotB ||
|
|
!Op.getOperand(FiIdx + 1).isImm() ||
|
|
Op.getOperand(FiIdx + 1).getImm() != 0)
|
|
continue;
|
|
|
|
// Function-wide safety check: slotB must have no other refs
|
|
// besides Sta (which we erase) and Op (which we rewrite to use
|
|
// slotA instead). Without this, deleting Sta orphans any other
|
|
// reader of slotB (e.g. a loop-header LDAfi reading the same
|
|
// copy that this entry-side STA initialises) — surfaces as the
|
|
// qsort #70 miscompile where greedy parks a value into a slot
|
|
// both for the immediate CMPfi and for a downstream loop-body
|
|
// reload, and Pass -2 silently kills the only initialiser.
|
|
const MachineInstr *ignoreS2[] = {&Sta, &Op};
|
|
if (slotHasOtherRefs(MF, SlotB, ignoreS2))
|
|
continue;
|
|
|
|
// Rewrite OP to use slotA, drop Lda1+Sta.
|
|
Op.getOperand(FiIdx).setIndex(SlotA);
|
|
Lda1->eraseFromParent();
|
|
Sta.eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass -1: redundant double-spill in *_RR custom-inserter expansions.
|
|
// The OR_RR / AND_RR / EOR_RR / ADC[E]fi / SBC[E]fi inserter spills
|
|
// its Src2 to a fresh slot so the OPfi can load-fold from there.
|
|
// When Src2 came from $x (an i32-first-arg-in-A:X hi half) and Src1
|
|
// came from $a, the regalloc winds up emitting:
|
|
//
|
|
// STAfi $a, slot_a ; regalloc-allocated spill of $a (Src1)
|
|
// COPY $a = $x ; TXA — reuse $a for Src2
|
|
// STAfi $a, slot_b ; inserter-allocated spill of Src2 (now in $a)
|
|
// LDAfi $a, slot_a ; reload Src1 (the tied input of OPfi)
|
|
// OPfi $a (tied), slot_b
|
|
//
|
|
// Slot_a holds the original Src1 value; slot_b holds Src2's value.
|
|
// OPfi reads slot_b but Src1 is already in $a — so semantically
|
|
// we could use slot_a (which already holds Src1's spilled value)
|
|
// by swapping which operand the OPfi load-folds:
|
|
//
|
|
// STAfi $a, slot_a
|
|
// COPY $a = $x
|
|
// OPfi $a (tied), slot_a ; uses slot_a; OP is commutative
|
|
//
|
|
// Saves: the STAfi to slot_b and the LDAfi from slot_a. Only
|
|
// valid for *commutative* ops (ADD/AND/OR/EOR — and ADCE/ADCfi
|
|
// since carry semantics are the same regardless of operand order).
|
|
// SBC/CMP/SUB are non-commutative; skip them.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 4> Worklist;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::STAfi)
|
|
Worklist.push_back(&MI);
|
|
for (MachineInstr *Sta1 : Worklist) {
|
|
// Step 1: STAfi $a, slot_a (the regalloc-allocated spill of Src1).
|
|
int SlotA = matchAccSlotOp(*Sta1, W65816::STAfi);
|
|
if (SlotA == NO_SLOT_MATCH) continue;
|
|
|
|
auto It = std::next(Sta1->getIterator());
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
|
|
// Step 2: COPY $a = <something> (TXA, etc.).
|
|
MachineInstr &Copy = *It;
|
|
if (!Copy.isCopy() || Copy.getOperand(0).getReg() != W65816::A)
|
|
continue;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
|
|
// Step 3: STAfi $a, slot_b (inserter-allocated spill of Src2).
|
|
int SlotB = matchAccSlotOp(*It, W65816::STAfi);
|
|
if (SlotB == NO_SLOT_MATCH || SlotA == SlotB) continue;
|
|
MachineInstr &Sta2 = *It;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
|
|
// Step 4: LDAfi $a, slot_a (reload Src1).
|
|
int SlotL = matchAccSlotOp(*It, W65816::LDAfi);
|
|
if (SlotL != SlotA) continue;
|
|
MachineInstr &Lda = *It;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
|
|
// Step 5: OPfi $a tied, slot_b — must be commutative.
|
|
MachineInstr &Op = *It;
|
|
if (!matchCommutativeFiOpOnSlot(Op, SlotB)) continue;
|
|
|
|
// Function-wide safety check: slot_b must have no other refs
|
|
// besides Sta2 (which we erase) and Op (which we rewrite).
|
|
// Same class of bug as #70's Pass -2: an inserter-spawned
|
|
// spill slot can get reused as a regalloc spill home, and
|
|
// erasing the only initialiser would orphan downstream reads.
|
|
const MachineInstr *ignoreS1[] = {&Sta2, &Op};
|
|
if (slotHasOtherRefs(MF, SlotB, ignoreS1))
|
|
continue;
|
|
|
|
// Rewrite Op to use slot_a instead of slot_b, erase Sta2 + Lda.
|
|
Op.getOperand(2).setIndex(SlotA);
|
|
Sta2.eraseFromParent();
|
|
Lda.eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass 0: rewrite `LDAi16imm $a, imm` immediately followed by
|
|
// `COPY $x = $a` (with no intervening A clobber) into
|
|
// `LDXi16imm $x, imm`. Run BEFORE the spill/reload cleanups so
|
|
// the disappearing A clobber unblocks subsequent STAfi+LDAfi
|
|
// pair removal.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 4> Worklist;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::LDAi16imm)
|
|
Worklist.push_back(&MI);
|
|
for (MachineInstr *Lda : Worklist) {
|
|
if (Lda->getNumOperands() < 2 || !Lda->getOperand(0).isReg() ||
|
|
Lda->getOperand(0).getReg() != W65816::A)
|
|
continue;
|
|
auto It = std::next(Lda->getIterator());
|
|
while (It != MBB.end() && It->isDebugInstr())
|
|
++It;
|
|
if (It == MBB.end())
|
|
continue;
|
|
MachineInstr &Next = *It;
|
|
if (!Next.isCopy())
|
|
continue;
|
|
Register DstReg = Next.getOperand(0).getReg();
|
|
Register SrcReg = Next.getOperand(1).getReg();
|
|
if (DstReg != W65816::X || SrcReg != W65816::A)
|
|
continue;
|
|
const MachineOperand &ImmMO = Lda->getOperand(1);
|
|
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
|
|
MachineInstrBuilder Mib =
|
|
BuildMI(MBB, Lda->getIterator(), Lda->getDebugLoc(),
|
|
TII->get(W65816::LDXi16imm), W65816::X);
|
|
if (ImmMO.isImm())
|
|
Mib.addImm(ImmMO.getImm());
|
|
else
|
|
Mib.add(ImmMO);
|
|
Lda->eraseFromParent();
|
|
Next.eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass 1: redundant LDAfi after STAfi (load-after-same-store with
|
|
// matching register). Two-pass over Stores worklist to avoid
|
|
// iterator invalidation when we erase the LDAfi mid-walk.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Stores;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::STAfi)
|
|
Stores.push_back(&MI);
|
|
for (MachineInstr *StaMI : Stores)
|
|
if (tryEliminateLoadAfterStore(MBB, *StaMI, TRI))
|
|
Changed = true;
|
|
}
|
|
|
|
// Pass 1b: redundant reload of the same slot. Pattern:
|
|
// LDAfi $a, slotX, 0
|
|
// STAfi $a, slotY, 0 ; STA preserves A and doesn't touch slotX
|
|
// ... (any non-A-defining, non-slotX-storing instructions)
|
|
// LDAfi $a, slotX, 0 ; <-- redundant: A still holds slotX's value
|
|
// Walk forward from each LDAfi looking for a matching second LDAfi
|
|
// with no intervening A-def or slotX-store. Drops the second LDAfi.
|
|
// This catches the fib-loop pattern where the regalloc emits
|
|
// LDA X; STA Y; LDA X; ADC Z (the second LDA is dead).
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Loads;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::LDAfi)
|
|
Loads.push_back(&MI);
|
|
for (MachineInstr *LdaMI : Loads) {
|
|
int SlotX = matchAccSlotOp(*LdaMI, W65816::LDAfi);
|
|
if (SlotX == NO_SLOT_MATCH) continue;
|
|
auto It = std::next(LdaMI->getIterator());
|
|
while (It != MBB.end()) {
|
|
MachineInstr &MI = *It;
|
|
if (MI.isDebugInstr()) { ++It; continue; }
|
|
// Found another LDAfi $a from the same slot. LDA sets N/Z;
|
|
// dropping it could leave a stale N/Z visible to a following
|
|
// branch. Only drop if the immediately-following instruction
|
|
// overwrites N/Z (CMP, ADC, AND, ORA, EOR, BIT, etc. — anything
|
|
// that defines P). In practice the second LDA is followed by
|
|
// a CLC+ADC or similar arithmetic, so this almost always fires.
|
|
if (matchAccSlotOp(MI, W65816::LDAfi) == SlotX) {
|
|
auto NextIt = std::next(It);
|
|
while (NextIt != MBB.end() && NextIt->isDebugInstr()) ++NextIt;
|
|
// If we can't see a follower or the follower is a flag-using
|
|
// branch, leave the LDA alone.
|
|
if (NextIt == MBB.end() || NextIt->isBranch())
|
|
break;
|
|
MI.eraseFromParent();
|
|
Changed = true;
|
|
break;
|
|
}
|
|
// Calls clobber A.
|
|
if (MI.isCall()) break;
|
|
// STAfi PRESERVES A in the asm (A source: store-only; non-A
|
|
// source: PHA bracket round-trip). The pseudo declares
|
|
// Defs = [A] as a stale over-approximation, so we explicitly
|
|
// skip STAfi when checking for A-clobber. STAfi to slotX
|
|
// (same slot) DOES change M[slotX] — bail in that case below.
|
|
if (MI.getOpcode() != W65816::STAfi &&
|
|
MI.modifiesRegister(W65816::A, TRI)) break;
|
|
// STAfi to slotX would change M[slotX] — bail.
|
|
if (MI.getOpcode() == W65816::STAfi &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == SlotX)
|
|
break;
|
|
// Inline asm / branch boundaries.
|
|
if (MI.isInlineAsm() || MI.isBranch() || MI.isReturn())
|
|
break;
|
|
++It;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 1d: redundant `LDY_Imm16 #N` (Y already holds N). The
|
|
// LDAptrOff/STAptrOff inserters each emit an `LDY #0` (or `LDY #off`)
|
|
// before their indirect access; back-to-back load-then-store of the
|
|
// same pointer ends up with two `LDY #0` in a row. Drop the second
|
|
// when nothing in between writes Y. Like Pass 1b, bail if the
|
|
// following instruction is a branch (Y's flag side-effects matter
|
|
// for branches that test N/Z).
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Ldys;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::LDY_Imm16)
|
|
Ldys.push_back(&MI);
|
|
SmallPtrSet<MachineInstr *, 8> ErasedY;
|
|
for (MachineInstr *Ldy : Ldys) {
|
|
if (ErasedY.count(Ldy)) continue;
|
|
if (Ldy->getNumOperands() < 1 || !Ldy->getOperand(0).isImm())
|
|
continue;
|
|
int64_t Imm = Ldy->getOperand(0).getImm();
|
|
// Walk forward erasing every subsequent matching LDY_Imm16 #Imm
|
|
// until something invalidates the held Y value (call, Y-def, asm,
|
|
// branch). Multiple LDYs in a row collapse on the first source.
|
|
auto It = std::next(Ldy->getIterator());
|
|
while (It != MBB.end()) {
|
|
MachineInstr &MI = *It;
|
|
if (MI.isDebugInstr()) { ++It; continue; }
|
|
if (MI.getOpcode() == W65816::LDY_Imm16 &&
|
|
MI.getNumOperands() >= 1 && MI.getOperand(0).isImm() &&
|
|
MI.getOperand(0).getImm() == Imm) {
|
|
// Bail on branch follower (flag-sensitive — LDY sets N/Z).
|
|
auto NextIt = std::next(It);
|
|
while (NextIt != MBB.end() && NextIt->isDebugInstr()) ++NextIt;
|
|
if (NextIt == MBB.end() || NextIt->isBranch()) break;
|
|
// Erase and continue walking — there may be more dups.
|
|
auto Erased_It = It;
|
|
++It;
|
|
ErasedY.insert(&*Erased_It);
|
|
Erased_It->eraseFromParent();
|
|
Changed = true;
|
|
continue;
|
|
}
|
|
if (MI.isCall()) break;
|
|
if (MI.modifiesRegister(W65816::Y, TRI)) break;
|
|
// killsRegister: an instruction with `implicit killed $y` USES Y
|
|
// and that's the LAST use — Y is dead after. We must NOT treat
|
|
// a subsequent LDY_Imm16 #N as redundant after a kill, because
|
|
// the held value is conceptually gone. Caught by `addOff(p,i)
|
|
// { p[i-1] += p[i]; }` where LDY -2 ; LDA_indY (kills Y) ; ... ;
|
|
// LDY -2 ; STA_indY needs the second LDY to reinitialize Y.
|
|
if (MI.killsRegister(W65816::Y, TRI)) break;
|
|
if (MI.isInlineAsm() || MI.isBranch() || MI.isReturn()) break;
|
|
++It;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 1c: drop redundant `CMPi16imm $a, 0` that follows an op which
|
|
// already set N/Z based on $a's new value (ORA/AND/EOR/ADC/SBC/LDA/...
|
|
// anything that defines $a). Pattern is emitted by the i32-equals-0
|
|
// path (i32 (lo|hi) == 0): the OR sets Z, then the SETCC compares
|
|
// against 0. The second compare is provably redundant because $a
|
|
// hasn't changed since the previous flag-defining op.
|
|
// Intra-MBB only — cross-MBB recursion into predecessors was tried
|
|
// (catches SETCC merge blocks where each pred ends with `lda #c`)
|
|
// but proved too brittle: predecessors ending with JSLpseudo declare
|
|
// implicit-def $a but the return-value flags aren't reliably set,
|
|
// and other corner cases break smoke.
|
|
auto isATransparent = [](const MachineInstr &MI) {
|
|
// Stores that don't touch A or P-bits-other-than-via-A. (Byte
|
|
// stores that internally SEP/REP wrap toggle the M flag, but that
|
|
// doesn't affect N/Z based on A's current value.) Also call-stack
|
|
// pseudos (ADJCALLSTACKDOWN / UP) which are zero-effect at this
|
|
// point in the pipeline (PEI eliminates UP; DOWN is always nil).
|
|
switch (MI.getOpcode()) {
|
|
case W65816::STAfi:
|
|
case W65816::STAfi_indY:
|
|
case W65816::STA8fi:
|
|
case W65816::STAabs:
|
|
case W65816::STA8abs:
|
|
case W65816::STAptr:
|
|
case W65816::STBptr:
|
|
case W65816::STAptrOff:
|
|
case W65816::STBptrOff:
|
|
case W65816::ADJCALLSTACKDOWN:
|
|
// DOWN expands to nothing (PUSH16 chain already shifted SP).
|
|
// UP is NOT transparent: when PEI doesn't process it, AsmPrinter
|
|
// emits a TSC/CLC/ADC/TCS sequence that clobbers A and flags.
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
// Returns true iff walking back from `Start` (exclusive) finds an
|
|
// A-modifier as the first non-skip op. Skips debug ops and
|
|
// A-transparent stores; stops at the first real op. Templated to
|
|
// accept either iterator or const_iterator (Cmps came from a non-
|
|
// const iteration; predecessors are walked via const_iterator).
|
|
auto walkbackBefore = [&](auto Start, auto Begin) -> bool {
|
|
auto It = Start;
|
|
while (It != Begin) {
|
|
--It;
|
|
if (It->isDebugInstr()) continue;
|
|
if (isATransparent(*It)) continue;
|
|
return It->modifiesRegister(W65816::A, TRI);
|
|
}
|
|
return false;
|
|
};
|
|
// Pass 1c can only eliminate CMPi16imm $a, 0 if the preceding
|
|
// A-modifier reliably sets N/Z to reflect A's final value. LDAfi
|
|
// under FP-rel expansion (`sty $fa ; ldy #imm ; lda [$f6],y ; ldy $fa`)
|
|
// ends with `ldy` that clobbers N/Z based on OLD Y, not loaded A — so
|
|
// in FP-rel functions (VLA / huge frame), the CMP is load-bearing.
|
|
// Skip the whole pass for such functions (saves us from the sum_n
|
|
// VLA regression that the PHP-wrap-aware variant tripped).
|
|
bool ssCleanupSPRelOnly = !UsesFPRel;
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Cmps;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::CMPi16imm)
|
|
Cmps.push_back(&MI);
|
|
for (MachineInstr *Cmp : Cmps) {
|
|
// Shape: CMPi16imm $a, 0.
|
|
if (Cmp->getNumOperands() < 2 ||
|
|
!Cmp->getOperand(0).isReg() ||
|
|
Cmp->getOperand(0).getReg() != W65816::A ||
|
|
!Cmp->getOperand(1).isImm() ||
|
|
Cmp->getOperand(1).getImm() != 0)
|
|
continue;
|
|
bool Found = walkbackBefore(Cmp->getIterator(), MBB.begin());
|
|
if (!Found) continue;
|
|
// Only eliminate if there are NO LdaLike instructions between
|
|
// this CMP and the next Bxx (or end of MBB). Otherwise the
|
|
// CMP is the only flag-setting marker between the test value
|
|
// and the consuming branch — without it, the Bxx ends up
|
|
// testing the latest LdaLike's N/Z (typically a PHI-elim COPY
|
|
// or stack reload that has nothing to do with the original
|
|
// condition). Caused __adddf3's renormalize while-loop to
|
|
// skip its body even though `mr & ~mask` was non-zero.
|
|
bool SafeToErase = true;
|
|
bool insidePHPWrap = false;
|
|
for (auto It = std::next(Cmp->getIterator());
|
|
It != Cmp->getParent()->end(); ++It) {
|
|
if (It->isDebugInstr()) continue;
|
|
if (It->isBranch() || It->isReturn()) break;
|
|
// PHP/PLP-wrap-aware: only safe when LDAfi-expansion sets N/Z
|
|
// reliably (SP-rel functions, not FP-rel).
|
|
if (ssCleanupSPRelOnly && It->getOpcode() == W65816::PHP) {
|
|
// PHP must be IMMEDIATELY after CMP to capture CMP's flags.
|
|
if (&*It != &*std::next(Cmp->getIterator())) {
|
|
SafeToErase = false;
|
|
break;
|
|
}
|
|
insidePHPWrap = true;
|
|
continue;
|
|
}
|
|
if (It->getOpcode() == W65816::PLP) {
|
|
insidePHPWrap = false;
|
|
continue;
|
|
}
|
|
if (insidePHPWrap) continue;
|
|
if (It->getOpcode() == TargetOpcode::COPY) {
|
|
SafeToErase = false;
|
|
break;
|
|
}
|
|
unsigned Opc = It->getOpcode();
|
|
// Conservative: any LDA/LDX/LDY/transfer disqualifies erasure.
|
|
// Stores and stack-mgmt are flag-preserving and OK.
|
|
switch (Opc) {
|
|
case W65816::STAfi: case W65816::STAfi_indY: case W65816::STA8fi:
|
|
case W65816::STA_StackRel: case W65816::STA_StackRelIndY:
|
|
case W65816::STA_DP: case W65816::STA_Abs: case W65816::STA_Long:
|
|
case W65816::STX_DP: case W65816::STX_Abs:
|
|
case W65816::STY_DP: case W65816::STY_Abs:
|
|
case W65816::ADJCALLSTACKDOWN: case W65816::ADJCALLSTACKUP:
|
|
case W65816::PHA: case W65816::PHX: case W65816::PHY:
|
|
continue;
|
|
}
|
|
// Anything else (LDA, transfer, ALU op...): bail.
|
|
SafeToErase = false;
|
|
break;
|
|
}
|
|
if (SafeToErase) {
|
|
Cmp->eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 1e: redundant `ANDi16imm $a, $a, 0xFF`. An i8 value zero-
|
|
// extended to i16 has high byte = 0; subsequent AND #$FF is a no-op
|
|
// and just adds a 3-byte instruction. This pattern is emitted twice
|
|
// by the (zextload-then-spill-twice) shape in *cmp helpers — see
|
|
// memcmp_local in the smoke-tests. Drop the second AND when:
|
|
// - first AND was `ANDi16imm $a, $a, 0xFF`
|
|
// - no A-defining op between them (STAfi, CMP*, etc. are fine)
|
|
// - second AND is also `ANDi16imm $a, $a, 0xFF`
|
|
// Flag-safe: both ANDs set N=0, Z=(A==0); after the first, the second
|
|
// produces identical flags, so dropping it leaves any following Bxx
|
|
// with the same N/Z values.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Ands;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::ANDi16imm &&
|
|
MI.getNumOperands() >= 3 && MI.getOperand(2).isImm() &&
|
|
MI.getOperand(2).getImm() == 0xFF)
|
|
Ands.push_back(&MI);
|
|
SmallPtrSet<MachineInstr *, 8> Erased;
|
|
for (MachineInstr *And : Ands) {
|
|
if (Erased.count(And)) continue;
|
|
auto It = std::next(And->getIterator());
|
|
while (It != MBB.end()) {
|
|
MachineInstr &MI = *It;
|
|
if (MI.isDebugInstr()) { ++It; continue; }
|
|
// Match: another `AND #$FF` with A unchanged.
|
|
if (MI.getOpcode() == W65816::ANDi16imm &&
|
|
MI.getNumOperands() >= 3 && MI.getOperand(2).isImm() &&
|
|
MI.getOperand(2).getImm() == 0xFF) {
|
|
Erased.insert(&MI);
|
|
MI.eraseFromParent();
|
|
Changed = true;
|
|
break;
|
|
}
|
|
if (MI.isCall() || MI.isInlineAsm() || MI.isBranch() ||
|
|
MI.isReturn()) break;
|
|
if (MI.modifiesRegister(W65816::A, TRI)) break;
|
|
++It;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 1g: redundant AND #$FF after reload of a masked slot. Pattern:
|
|
//
|
|
// ANDi16imm $a, $a, 0xFF ; A := A & 0xFF (high byte = 0)
|
|
// STAfi $a, slotN, 0 ; M[slotN] = A — slot's high byte is 0
|
|
// ... ; no STAfi to slotN, no A defs
|
|
// LDAfi $a, slotN, 0 ; A := M[slotN] — high byte still 0
|
|
// ANDi16imm $a, $a, 0xFF ; <-- redundant: A's high byte is 0
|
|
//
|
|
// Drop the second AND. Pass 1e (back-to-back AND #FF) bails on any
|
|
// A-defining op in between, so it can't see across the LDA reload.
|
|
// This pass is the "through-memory" complement. Found in find_byte
|
|
// and other char-iteration loops where the regalloc emits an extra
|
|
// mask-then-spill-then-reload-then-mask cycle around the comparison.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> FirstAnds;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::ANDi16imm &&
|
|
MI.getNumOperands() >= 3 && MI.getOperand(2).isImm() &&
|
|
MI.getOperand(2).getImm() == 0xFF)
|
|
FirstAnds.push_back(&MI);
|
|
SmallPtrSet<MachineInstr *, 8> Erased;
|
|
for (MachineInstr *And1 : FirstAnds) {
|
|
if (Erased.count(And1)) continue;
|
|
auto It = std::next(And1->getIterator());
|
|
if (!advancePastDebug(MBB, It)) continue;
|
|
// Step 2: STAfi $a, slotN.
|
|
int SlotN = matchAccSlotOp(*It, W65816::STAfi);
|
|
if (SlotN == NO_SLOT_MATCH) continue;
|
|
// Step 3: walk forward looking for LDAfi from slotN. We allow
|
|
// arbitrary A modifications in between because the LDAfi reload
|
|
// re-establishes A as the masked value (M[slotN] still has high
|
|
// byte = 0 from the And1+Sta we just saw). We ONLY need slotN
|
|
// itself to be unchanged. Bail on calls (callee can clobber any
|
|
// local slot indirectly), branches/returns/asm.
|
|
auto It2 = std::next(It);
|
|
MachineInstr *Lda = nullptr;
|
|
while (It2 != MBB.end()) {
|
|
MachineInstr &MI = *It2;
|
|
if (MI.isDebugInstr()) { ++It2; continue; }
|
|
if (MI.isCall() || MI.isInlineAsm() || MI.isBranch() ||
|
|
MI.isReturn()) break;
|
|
if (MI.getOpcode() == W65816::STAfi &&
|
|
MI.getNumOperands() >= 2 && MI.getOperand(1).isFI() &&
|
|
MI.getOperand(1).getIndex() == SlotN)
|
|
break;
|
|
if (matchAccSlotOp(MI, W65816::LDAfi) == SlotN) {
|
|
Lda = &MI;
|
|
break;
|
|
}
|
|
++It2;
|
|
}
|
|
if (!Lda) continue;
|
|
// Step 4: must be followed by `ANDi16imm $a, $a, 0xFF`.
|
|
auto It3 = std::next(Lda->getIterator());
|
|
if (!advancePastDebug(MBB, It3)) continue;
|
|
if (It3->getOpcode() != W65816::ANDi16imm ||
|
|
It3->getNumOperands() < 3 || !It3->getOperand(2).isImm() ||
|
|
It3->getOperand(2).getImm() != 0xFF)
|
|
continue;
|
|
MachineInstr &And2 = *It3;
|
|
Erased.insert(&And2);
|
|
And2.eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass 2a: function-wide dead-slot stores. If a *local* (non-fixed)
|
|
// FrameIndex is never read anywhere in the function (no LDAfi from
|
|
// it, no *fi op consuming it, no indirect-Y use of it as a pointer
|
|
// slot), then every STAfi/STA8fi that writes to it is dead. This
|
|
// catches the cross-MBB pattern Pass 2 misses (Pass 2 walks within a
|
|
// single MBB and bails on branches).
|
|
//
|
|
// Conservative: read = any opcode whose listed write-operands don't
|
|
// include this FI. We approximate by treating the operand at the
|
|
// STAfi/STA8fi's "addr" position (op 1, the FI; op 2, the imm offset)
|
|
// as the *only* write. Every other reference is treated as a read.
|
|
{
|
|
MachineFrameInfo &MFI = MF.getFrameInfo();
|
|
DenseMap<int, unsigned> Reads;
|
|
DenseMap<int, unsigned> Writes;
|
|
SmallVector<MachineInstr *, 32> Stores;
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
for (MachineInstr &MI : MBB) {
|
|
bool IsStaFi = (MI.getOpcode() == W65816::STAfi ||
|
|
MI.getOpcode() == W65816::STA8fi);
|
|
for (unsigned i = 0; i < MI.getNumOperands(); ++i) {
|
|
const MachineOperand &MO = MI.getOperand(i);
|
|
if (!MO.isFI()) continue;
|
|
int FI = MO.getIndex();
|
|
if (MFI.isFixedObjectIndex(FI)) continue;
|
|
// For STAfi/STA8fi, the FI operand at i==1 is the *write*
|
|
// target; everything else is a read of this FI.
|
|
if (IsStaFi && i == 1)
|
|
Writes[FI]++;
|
|
else
|
|
Reads[FI]++;
|
|
}
|
|
if (IsStaFi)
|
|
Stores.push_back(&MI);
|
|
}
|
|
}
|
|
for (MachineInstr *Sta : Stores) {
|
|
if (Sta->getNumOperands() < 2 || !Sta->getOperand(1).isFI()) continue;
|
|
// Volatile stores are observable side effects — never elide.
|
|
// Caught by SJLJ EH: SjLjEHPrepare emits `store volatile i32 N,
|
|
// fn_ctx.call_site` before each invoke; the function context's
|
|
// call_site field is "never read" within main but IS read by
|
|
// the runtime via gActive — Pass 2a's local liveness can't see
|
|
// that, so volatile is the right gate.
|
|
if (Sta->hasOrderedMemoryRef()) continue;
|
|
int FI = Sta->getOperand(1).getIndex();
|
|
if (Reads.count(FI) == 0 && Writes[FI] >= 1) {
|
|
Sta->eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 1f: drop adjacent PHP/PLP pairs. Pass -2.5 inserts PHP/PLP
|
|
// around LDA-style ops to protect a CMP's flags from being clobbered
|
|
// by the LDA before the consuming branch. Pass 1 (load-after-store
|
|
// elimination) sometimes deletes the LDA *between* the wrap because
|
|
// it's a redundant reload — the spilled value is already in A. After
|
|
// that deletion, PHP and PLP are back-to-back with nothing between,
|
|
// and the pair is a no-op. Drop both.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Phps;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::PHP)
|
|
Phps.push_back(&MI);
|
|
for (MachineInstr *Php : Phps) {
|
|
auto It = std::next(Php->getIterator());
|
|
while (It != MBB.end() && It->isDebugInstr()) ++It;
|
|
if (It == MBB.end() || It->getOpcode() != W65816::PLP) continue;
|
|
MachineInstr *Plp = &*It;
|
|
Php->eraseFromParent();
|
|
Plp->eraseFromParent();
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass 1h: collapse dead TAX/TXA bridge around DPF0 staging. When
|
|
// SpillToX bridges one of the LDAptr32 inserter's STAfi/LDAfi pairs
|
|
// (saving 6 cyc), then Pass -2c collapses the OTHER pair entirely,
|
|
// the TAX/TXA bridge becomes pure overhead. Pattern:
|
|
//
|
|
// LDAfi $a, slotA, 0 ; (1) load ptr_hi (originally for X-bridge)
|
|
// TAX ; (2) X = ptr_hi
|
|
// LDAfi $a, slotB, 0 ; (3) load ptr_lo (the Pass -2c rewrite target)
|
|
// STA_DP imm1 ; (4) $E0 = ptr_lo
|
|
// TXA ; (5) recover ptr_hi to A
|
|
// STA_DP imm2 ; (6) $E2 = ptr_hi
|
|
//
|
|
// Reorder to drop the bridge:
|
|
//
|
|
// LDAfi $a, slotB, 0 ; (3) load ptr_lo
|
|
// STA_DP imm1 ; (4) $E0 = ptr_lo
|
|
// LDAfi $a, slotA, 0 ; (1) load ptr_hi (moved down)
|
|
// STA_DP imm2 ; (6) $E2 = ptr_hi
|
|
//
|
|
// Saves 2 instructions (~6 cyc) per LDAptr32 expansion. Safe iff X
|
|
// is dead after TXA (no read before redef). Common in tight loops
|
|
// that deref two pointers per iter (memcmp, strcpy, dotProduct).
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Ldas;
|
|
for (MachineInstr &MI : MBB) {
|
|
if (MI.getOpcode() == W65816::LDAfi) {
|
|
Ldas.push_back(&MI);
|
|
}
|
|
}
|
|
SmallPtrSet<MachineInstr *, 8> ErasedH;
|
|
for (MachineInstr *Lda1 : Ldas) {
|
|
if (ErasedH.count(Lda1)) {
|
|
continue;
|
|
}
|
|
int SlotA = matchAccSlotOp(*Lda1, W65816::LDAfi);
|
|
if (SlotA == NO_SLOT_MATCH) {
|
|
continue;
|
|
}
|
|
auto It = std::next(Lda1->getIterator());
|
|
if (!advancePastDebug(MBB, It)) {
|
|
continue;
|
|
}
|
|
// Optional `ADCEfi $a (tied), slot, 0` between Lda1 and TAX.
|
|
// strcpy emits `LDAfi hi_part ; ADCEfi $a, $stack.9 ; TAX` to
|
|
// compute ptr_hi via carry-propagation from a prior ADCfi (lo).
|
|
// The ADCEfi's carry-in comes from an earlier ADCfi; intervening
|
|
// LDA/STA preserve C, so reordering this pair past STA_DP is safe.
|
|
MachineInstr *MaybeAdcE = nullptr;
|
|
if (It->getOpcode() == W65816::ADCEfi &&
|
|
It->getNumOperands() >= 4 &&
|
|
It->getOperand(0).isReg() &&
|
|
It->getOperand(0).getReg() == W65816::A) {
|
|
MaybeAdcE = &*It;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) {
|
|
continue;
|
|
}
|
|
}
|
|
if (It->getOpcode() != W65816::TAX) {
|
|
continue;
|
|
}
|
|
MachineInstr *Tax = &*It;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) {
|
|
continue;
|
|
}
|
|
int SlotB = matchAccSlotOp(*It, W65816::LDAfi);
|
|
if (SlotB == NO_SLOT_MATCH || SlotB == SlotA) {
|
|
continue;
|
|
}
|
|
MachineInstr *Lda2 = &*It;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) {
|
|
continue;
|
|
}
|
|
if (It->getOpcode() != W65816::STA_DP) {
|
|
continue;
|
|
}
|
|
MachineInstr *Sta1 = &*It;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) {
|
|
continue;
|
|
}
|
|
if (It->getOpcode() != W65816::TXA) {
|
|
continue;
|
|
}
|
|
MachineInstr *Txa = &*It;
|
|
++It;
|
|
if (!advancePastDebug(MBB, It)) {
|
|
continue;
|
|
}
|
|
if (It->getOpcode() != W65816::STA_DP) {
|
|
continue;
|
|
}
|
|
MachineInstr *Sta2 = &*It;
|
|
// Verify X is dead after Txa within this MBB. Tighter check
|
|
// than just "until branch/return": if we reach end of MBB or a
|
|
// branch without seeing an X-read or X-def, look at successor
|
|
// MBBs. X is dead iff no successor's livein set includes X.
|
|
// Critical for tight loops (strLen, byte-walk loops) where the
|
|
// loop terminator is a branch back to the same MBB.
|
|
bool XDeadAfter = true;
|
|
bool MustCheckSuccessors = false;
|
|
auto checkIt = std::next(Sta2->getIterator());
|
|
while (checkIt != MBB.end()) {
|
|
if (checkIt->isDebugInstr()) {
|
|
++checkIt;
|
|
continue;
|
|
}
|
|
if (checkIt->readsRegister(W65816::X, TRI)) {
|
|
XDeadAfter = false;
|
|
break;
|
|
}
|
|
if (checkIt->modifiesRegister(W65816::X, TRI)) {
|
|
// X redefined before any read — dead range ended.
|
|
break;
|
|
}
|
|
if (checkIt->isCall() || checkIt->isInlineAsm()) {
|
|
XDeadAfter = false;
|
|
break;
|
|
}
|
|
if (checkIt->isBranch() || checkIt->isReturn()) {
|
|
// Hit the terminator without redef — defer to successor
|
|
// liveins to determine if X is live-out.
|
|
MustCheckSuccessors = true;
|
|
break;
|
|
}
|
|
++checkIt;
|
|
}
|
|
if (checkIt == MBB.end()) {
|
|
MustCheckSuccessors = true;
|
|
}
|
|
if (XDeadAfter && MustCheckSuccessors) {
|
|
// X is dead iff no successor lists it as a livein.
|
|
for (MachineBasicBlock *Succ : MBB.successors()) {
|
|
if (Succ->isLiveIn(W65816::X)) {
|
|
XDeadAfter = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!XDeadAfter) {
|
|
continue;
|
|
}
|
|
// Apply: move Lda1 (and optional ADCEfi) to between Sta1 and
|
|
// Sta2, then erase Tax/Txa. Splicing both to Sta2's iterator
|
|
// preserves their relative order — second splice ends up nearer
|
|
// Sta2, so Lda1 first then MaybeAdcE.
|
|
MBB.splice(Sta2->getIterator(), &MBB, Lda1->getIterator());
|
|
if (MaybeAdcE) {
|
|
MBB.splice(Sta2->getIterator(), &MBB, MaybeAdcE->getIterator());
|
|
}
|
|
Tax->eraseFromParent();
|
|
Txa->eraseFromParent();
|
|
ErasedH.insert(Lda1);
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
// Pass 1z: re-run Pass 1's load-after-store elimination after Pass 1h.
|
|
// Pass 1h's splice may have created newly-adjacent STAfi+LDAfi pairs
|
|
// when the moved instructions exposed a producer (e.g., ADCfi) whose
|
|
// result was already in A at the LDAfi point. Common in strcpy's
|
|
// pointer-arithmetic: `ADCfi $a, lo_off ; STAfi $a, slot ; LDAfi $a,
|
|
// slot ; STA_DP` collapses to `ADCfi ; STAfi ; STA_DP` after the LDA
|
|
// is recognised as redundant.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Stores;
|
|
for (MachineInstr &MI : MBB) {
|
|
if (MI.getOpcode() == W65816::STAfi) {
|
|
Stores.push_back(&MI);
|
|
}
|
|
}
|
|
for (MachineInstr *StaMI : Stores) {
|
|
if (tryEliminateLoadAfterStore(MBB, *StaMI, TRI)) {
|
|
Changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 2: dead stores (STAfi to slot followed by another STAfi to
|
|
// the same slot with no intervening read). This catches the
|
|
// arg0_lo "preserve" spill that the regalloc emits even though the
|
|
// value is consumed by the very next instruction.
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
SmallVector<MachineInstr *, 8> Stores;
|
|
for (MachineInstr &MI : MBB)
|
|
if (MI.getOpcode() == W65816::STAfi)
|
|
Stores.push_back(&MI);
|
|
for (MachineInstr *StaMI : Stores)
|
|
if (tryEliminateDeadStore(MBB, *StaMI))
|
|
Changed = true;
|
|
}
|
|
|
|
// Pass 3: zero-size unused local frame objects so the
|
|
// PrologueEpilogue pass shrinks the prologue PHAs / TSC reservation.
|
|
// Walk the MIR collecting which FIs are still referenced; any
|
|
// *non-fixed* (local) FI with no remaining reference is dead. We
|
|
// can't safely remove it (RemoveStackObject can shift indexes); we
|
|
// just zero-size it via setObjectSize, which is enough for the
|
|
// frame layout pass to skip it.
|
|
MachineFrameInfo &MFI = MF.getFrameInfo();
|
|
if (MFI.getNumObjects() > 0) {
|
|
BitVector Used(MFI.getObjectIndexEnd() - MFI.getObjectIndexBegin());
|
|
auto Mark = [&](int FI) {
|
|
int Idx = FI - MFI.getObjectIndexBegin();
|
|
if (Idx >= 0 && Idx < (int)Used.size())
|
|
Used.set(Idx);
|
|
};
|
|
for (MachineBasicBlock &MBB : MF)
|
|
for (MachineInstr &MI : MBB)
|
|
for (MachineOperand &MO : MI.operands())
|
|
if (MO.isFI())
|
|
Mark(MO.getIndex());
|
|
for (int FI = MFI.getObjectIndexBegin();
|
|
FI < MFI.getObjectIndexEnd(); ++FI) {
|
|
// Skip fixed (arg) slots — those are "owned" by the caller.
|
|
if (MFI.isFixedObjectIndex(FI))
|
|
continue;
|
|
int Idx = FI - MFI.getObjectIndexBegin();
|
|
if (Idx < 0 || Idx >= (int)Used.size() || Used.test(Idx))
|
|
continue;
|
|
// Already zero-sized? Skip.
|
|
if (MFI.getObjectSize(FI) == 0)
|
|
continue;
|
|
// Don't touch dead-stripped objects either.
|
|
if (MFI.isDeadObjectIndex(FI))
|
|
continue;
|
|
MFI.setObjectSize(FI, 0);
|
|
Changed = true;
|
|
}
|
|
}
|
|
|
|
return Changed;
|
|
}
|