More deferred issues resolved.
This commit is contained in:
parent
1a0ef7ee87
commit
d10d3b9fc6
8 changed files with 103 additions and 201 deletions
43
STATUS.md
43
STATUS.md
|
|
@ -347,11 +347,41 @@ which runs correctly under MAME (apple2gs).
|
||||||
The ~6% gap is the per-call defensive `REP #$30` mode-set our
|
The ~6% gap is the per-call defensive `REP #$30` mode-set our
|
||||||
FrameLowering emits at every function entry (Calypsi omits it via a
|
FrameLowering emits at every function entry (Calypsi omits it via a
|
||||||
global M/X=16 invariant) plus a marginally larger stack frame. Both
|
global M/X=16 invariant) plus a marginally larger stack frame. Both
|
||||||
are deliberate correctness choices; eliding the entry REP safely needs
|
are deliberate correctness choices; eliding the entry REP safely by
|
||||||
the deferred per-region REP/SEP scheduling pass (design §3.3) -- a
|
default needs the deferred per-region REP/SEP scheduling pass (design
|
||||||
naive global elision risks the silent i16-immediate-in-M=1 miscompile
|
§3.3) -- a naive global elision risks the silent i16-immediate-in-M=1
|
||||||
documented in W65816FrameLowering.cpp. Speed is the optimization
|
miscompile documented in W65816FrameLowering.cpp. Speed is the
|
||||||
priority, not size.
|
optimization priority, not size.
|
||||||
|
|
||||||
|
An opt-in `-mllvm -w65816-omit-entry-rep` flag (default off, hidden)
|
||||||
|
drops the per-function entry `REP #$30` for whole-program builds that
|
||||||
|
are known to maintain the M=16/X=16 invariant everywhere (crt0, all
|
||||||
|
compiled code, and every hand-written asm / interrupt entry point --
|
||||||
|
our runtime does; libgcc's `__mulhi3`/`__umulhisi3`/etc. already rely
|
||||||
|
on it and carry no entry REP). It has NO per-function safety net -- it
|
||||||
|
trusts the invariant globally, so it is unsound if any linked code
|
||||||
|
(including an interrupt handler) can enter a function in 8-bit mode.
|
||||||
|
Use only for deliberate whole-program builds; the safe-by-default
|
||||||
|
version remains the §3.3 dataflow pass above.
|
||||||
|
|
||||||
|
Measured whole-program win (runtime + demo both built with the flag,
|
||||||
|
final linked `.bin`; `--gc-sections` on so the saving scales with live
|
||||||
|
function count):
|
||||||
|
|
||||||
|
| demo | base | omit-rep | saved | % |
|
||||||
|
|-------------|-------:|---------:|------:|-------:|
|
||||||
|
| helloBeep | 3611 | 3607 | 4 | 0.11% |
|
||||||
|
| helloWindow | 5493 | 5487 | 6 | 0.11% |
|
||||||
|
| frame | 10492 | 10460 | 32 | 0.30% |
|
||||||
|
| minicad | 15311 | 15275 | 36 | 0.24% |
|
||||||
|
| reversi | 19425 | 19371 | 54 | 0.28% |
|
||||||
|
| printfProbe | 36791 | 36771 | 20 | 0.05% |
|
||||||
|
| **9 demos** | 108588 | 108404 | 184 | 0.17% |
|
||||||
|
|
||||||
|
So ~0.17% overall (2 bytes per live function + ~3 cyc/call): a small,
|
||||||
|
uniform win, largest where function count is high (reversi/minicad) and
|
||||||
|
smallest where a few big functions dominate (printfProbe). Confirms
|
||||||
|
the gain does not justify making elision the default.
|
||||||
|
|
||||||
- `compare/` holds three side-by-side C tests with our asm and
|
- `compare/` holds three side-by-side C tests with our asm and
|
||||||
Calypsi's listing for static-size comparison:
|
Calypsi's listing for static-size comparison:
|
||||||
|
|
@ -429,7 +459,8 @@ for the common-case C / minimal-C++ workload. Priority is speed
|
||||||
|
|
||||||
- **Layer 1 ptr32 deref-fold (always on)** — Constant offset on a
|
- **Layer 1 ptr32 deref-fold (always on)** — Constant offset on a
|
||||||
ptr32 deref folds into the `[dp],Y` Y register instead of a CLC/ADC
|
ptr32 deref folds into the `[dp],Y` Y register instead of a CLC/ADC
|
||||||
carry-chain pre-add. Plus consecutive-deref CSE that shares the
|
carry-chain pre-add, for both stores (ST_PTR_OFF / STB_PTR_OFF) and
|
||||||
|
loads (LD_PTR_OFF). Plus consecutive-deref CSE that shares the
|
||||||
`$E0/$E2` staging across `s->a`, `s->b`, ... accesses with the same
|
`$E0/$E2` staging across `s->a`, `s->b`, ... accesses with the same
|
||||||
base. Always on; saves ~3 instructions per struct-field access.
|
base. Always on; saves ~3 instructions per struct-field access.
|
||||||
See `feedback_ptr32_deref_fold_layer1_landed.md`.
|
See `feedback_ptr32_deref_fold_layer1_landed.md`.
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -32,7 +32,6 @@ add_llvm_target(W65816CodeGen
|
||||||
W65816WidenAcc16.cpp
|
W65816WidenAcc16.cpp
|
||||||
W65816SpillToX.cpp
|
W65816SpillToX.cpp
|
||||||
W65816NegYIndY.cpp
|
W65816NegYIndY.cpp
|
||||||
W65816PreSpillCrossCall.cpp
|
|
||||||
W65816SjLjFinalize.cpp
|
W65816SjLjFinalize.cpp
|
||||||
W65816LowerWide32.cpp
|
W65816LowerWide32.cpp
|
||||||
W65816I32IncFold.cpp
|
W65816I32IncFold.cpp
|
||||||
|
|
|
||||||
|
|
@ -104,12 +104,6 @@ FunctionPass *createW65816SpillToX();
|
||||||
// so signed-negative Y crosses bank boundaries. See W65816NegYIndY.cpp.
|
// so signed-negative Y crosses bank boundaries. See W65816NegYIndY.cpp.
|
||||||
FunctionPass *createW65816NegYIndY();
|
FunctionPass *createW65816NegYIndY();
|
||||||
|
|
||||||
// Pre-RA pass: pre-spill Acc16 vregs whose live range crosses a JSL
|
|
||||||
// call site, in functions with > 5 calls. Drops greedy regalloc
|
|
||||||
// pressure for high-call-count functions that would otherwise hit
|
|
||||||
// "ran out of registers". See W65816PreSpillCrossCall.cpp.
|
|
||||||
FunctionPass *createW65816PreSpillCrossCall();
|
|
||||||
|
|
||||||
// IR pass: finishes the SjLjEHPrepare lowering by inserting a setjmp
|
// IR pass: finishes the SjLjEHPrepare lowering by inserting a setjmp
|
||||||
// at function entry and a dispatch block, then erasing the eh.sjlj.*
|
// at function entry and a dispatch block, then erasing the eh.sjlj.*
|
||||||
// intrinsics our backend doesn't lower natively. Runs after
|
// intrinsics our backend doesn't lower natively. Runs after
|
||||||
|
|
@ -208,7 +202,6 @@ void initializeW65816UnLSRPass(PassRegistry &);
|
||||||
void initializeW65816WidenAcc16Pass(PassRegistry &);
|
void initializeW65816WidenAcc16Pass(PassRegistry &);
|
||||||
void initializeW65816SpillToXPass(PassRegistry &);
|
void initializeW65816SpillToXPass(PassRegistry &);
|
||||||
void initializeW65816NegYIndYPass(PassRegistry &);
|
void initializeW65816NegYIndYPass(PassRegistry &);
|
||||||
void initializeW65816PreSpillCrossCallPass(PassRegistry &);
|
|
||||||
void initializeW65816SjLjFinalizePass(PassRegistry &);
|
void initializeW65816SjLjFinalizePass(PassRegistry &);
|
||||||
void initializeW65816LowerWide32Pass(PassRegistry &);
|
void initializeW65816LowerWide32Pass(PassRegistry &);
|
||||||
void initializeW65816ImgCalleeSavePass(PassRegistry &);
|
void initializeW65816ImgCalleeSavePass(PassRegistry &);
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,42 @@
|
||||||
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
||||||
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
||||||
#include "llvm/IR/Function.h"
|
#include "llvm/IR/Function.h"
|
||||||
|
#include "llvm/Support/CommandLine.h"
|
||||||
#include "llvm/Support/ErrorHandling.h"
|
#include "llvm/Support/ErrorHandling.h"
|
||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
|
// Omit the per-function-entry `REP #$30`. Every function is required by the
|
||||||
|
// C ABI to be entered in M=16/X=16, and the whole toolchain already upholds
|
||||||
|
// that invariant: crt0 sets it at startup, compiled code never makes a call
|
||||||
|
// or a return while in 8-bit mode (8-bit ops are short, block-local
|
||||||
|
// SEP/REP-wrapped windows that contain no calls), and the hand-written
|
||||||
|
// runtime relies on it too — libgcc's __mulhi3 / __umulhisi3 / __udivhi3 /
|
||||||
|
// __lshrsi3 carry NO entry REP and would already miscompile if any caller
|
||||||
|
// were in 8-bit mode. Given that, the entry REP is redundant; dropping it
|
||||||
|
// saves 2 bytes + 3 cycles per function. Default OFF: it is only sound if
|
||||||
|
// the ENTIRE linked program (including every hand-written asm entry point and
|
||||||
|
// any interrupt handler) maintains the invariant, so it must be a deliberate
|
||||||
|
// whole-program choice.
|
||||||
|
//
|
||||||
|
// Investigation (2026-06-30): measured gain is ~0.15% .text and ~3 cyc/call
|
||||||
|
// (this is the fib 1.06x-Calypsi case; the win is broad but small since it is
|
||||||
|
// only the per-call entry REP). A whole-toolchain smoke build with this forced
|
||||||
|
// on was blocked only by asm-grep regression guards that assert the REP's
|
||||||
|
// presence as a correctness proxy (e.g. the sgnlt i8-signed-compare guard) --
|
||||||
|
// no runtime miscompile was reached, consistent with the invariant holding.
|
||||||
|
// This flag has NO per-function safety net: it trusts the invariant globally.
|
||||||
|
// Making the elision safe by default needs the design-doc 3.3 REP/SEP dataflow
|
||||||
|
// pass, which would prove per-function that mode is 16-bit at every call and
|
||||||
|
// return and keep the REP for anything it cannot prove -- that is the real fix
|
||||||
|
// and remains deferred (multi-week; the gain does not justify it while the
|
||||||
|
// geomean already beats Calypsi 0.62x).
|
||||||
|
static cl::opt<bool> OmitEntryRep(
|
||||||
|
"w65816-omit-entry-rep",
|
||||||
|
cl::desc("Omit the per-function-entry REP #$30 (relies on the whole-program "
|
||||||
|
"M=16/X=16 ABI invariant; saves 2 bytes / 3 cycles per function)."),
|
||||||
|
cl::init(false), cl::Hidden);
|
||||||
|
|
||||||
// (The pure-i8-detection helpers were removed when the prologue went
|
// (The pure-i8-detection helpers were removed when the prologue went
|
||||||
// to "always 16-bit M". See emitPrologue comment.)
|
// to "always 16-bit M". See emitPrologue comment.)
|
||||||
//
|
//
|
||||||
|
|
@ -97,7 +129,12 @@ void W65816FrameLowering::emitPrologue(MachineFunction &MF,
|
||||||
// Caught by tracing inc_g for `char inc_g(void) { g++; return g; }`.
|
// Caught by tracing inc_g for `char inc_g(void) { g++; return g; }`.
|
||||||
(void)MRI;
|
(void)MRI;
|
||||||
MF.getInfo<W65816MachineFunctionInfo>()->setUsesAcc8(false);
|
MF.getInfo<W65816MachineFunctionInfo>()->setUsesAcc8(false);
|
||||||
BuildMI(MBB, MBBI, DL, TII.get(W65816::REP)).addImm(0x30);
|
// The entry REP guarantees this function runs in 16-bit M/X even if a
|
||||||
|
// caller somehow left 8-bit mode set. Under -w65816-omit-entry-rep the
|
||||||
|
// whole program is asserted to already maintain the M=16/X=16 invariant,
|
||||||
|
// so this becomes redundant (see the OmitEntryRep comment above).
|
||||||
|
if (!OmitEntryRep)
|
||||||
|
BuildMI(MBB, MBBI, DL, TII.get(W65816::REP)).addImm(0x30);
|
||||||
|
|
||||||
// Reserve stack space for locals/spills.
|
// Reserve stack space for locals/spills.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -1188,19 +1188,24 @@ SDValue W65816TargetLowering::LowerLoad(SDValue Op,
|
||||||
// asserts memvt must be supported; i1 isn't.
|
// asserts memvt must be supported; i1 isn't.
|
||||||
if (MemVT == MVT::i1) MemVT = MVT::i8;
|
if (MemVT == MVT::i1) MemVT = MVT::i8;
|
||||||
SDVTList VTs = DAG.getVTList(MVT::i16, MVT::Other);
|
SDVTList VTs = DAG.getVTList(MVT::i16, MVT::Other);
|
||||||
// Try to peel a constant offset from Ptr and route through
|
// Peel a constant offset from Ptr and route through LD_PTR_OFF — folds
|
||||||
// LD_PTR_OFF — folds `(ptr + K)` into the Y-register of `[E0],Y`,
|
// `(ptr + K)` into the Y-register of the `[E0],Y` deref, saving the i32
|
||||||
// saving the i32 ADD's CLC/ADC carry chain. ~3 instr per access.
|
// ADD's CLC/ADC carry chain (~3 instr per access). Mirrors the
|
||||||
|
// LowerStore ST_PTR_OFF / STB_PTR_OFF peel; the `[dp],Y` inserter and
|
||||||
|
// the W65816ldPtrOff tablegen pattern already handle the result.
|
||||||
// See feedback_ptr32_deref_fold_layer1_mi.md.
|
// See feedback_ptr32_deref_fold_layer1_mi.md.
|
||||||
// LD_PTR_OFF: deferred — the peel fires correctly but the resulting
|
SDValue LdNode;
|
||||||
// SDAG breaks the JSON-tokenizer + snprintf smoke tests in ways
|
SDValue Base; uint16_t Off = 0;
|
||||||
// bisection didn't isolate. Stick with LD_PTR (no peel) here; the
|
if (peelPtr32Offset(DAG, DL, Ptr, Base, Off)) {
|
||||||
// LowerStore peel for ST_PTR_OFF / STB_PTR_OFF keeps the store-side
|
SDValue OffN = DAG.getTargetConstant(Off, DL, MVT::i16);
|
||||||
// optimization. Future: route loads through a SDAG combine that
|
SDValue OpsOff[] = { Chain, Base, OffN };
|
||||||
// runs post-LegalizeOps so we see the final REG_SEQUENCE shape.
|
LdNode = DAG.getMemIntrinsicNode(W65816ISD::LD_PTR_OFF, DL, VTs, OpsOff,
|
||||||
SDValue Ops[] = { Chain, Ptr };
|
MemVT, Ld->getMemOperand());
|
||||||
SDValue LdNode = DAG.getMemIntrinsicNode(W65816ISD::LD_PTR, DL, VTs, Ops,
|
} else {
|
||||||
MemVT, Ld->getMemOperand());
|
SDValue Ops[] = { Chain, Ptr };
|
||||||
|
LdNode = DAG.getMemIntrinsicNode(W65816ISD::LD_PTR, DL, VTs, Ops,
|
||||||
|
MemVT, Ld->getMemOperand());
|
||||||
|
}
|
||||||
SDValue Val = LdNode;
|
SDValue Val = LdNode;
|
||||||
// Byte memory access: mask the high byte for zextload, leave anyext.
|
// Byte memory access: mask the high byte for zextload, leave anyext.
|
||||||
// i1 memVT was widened to i8 above; the mask path is the same.
|
// i1 memVT was widened to i8 above; the mask path is the same.
|
||||||
|
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
//===-- W65816PreSpillCrossCall.cpp - Pre-spill cross-call Acc16 vregs ---===//
|
|
||||||
//
|
|
||||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
||||||
// See https://llvm.org/LICENSE.txt for license information.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// Pre-RA pass that pre-spills Acc16 vregs whose live range crosses a
|
|
||||||
// JSL call site. Greedy regalloc has only one register in the Acc16
|
|
||||||
// class (A), and JSL clobbers A — so any Acc16 vreg live across a
|
|
||||||
// call MUST be spilled. Greedy normally figures this out, but for
|
|
||||||
// functions with many such vregs (the "ok |= bit" bitmask pattern
|
|
||||||
// repeated across N if-blocks each calling a helper) greedy can run
|
|
||||||
// out of registers during spill placement, aborting compilation with
|
|
||||||
// "ran out of registers".
|
|
||||||
//
|
|
||||||
// We pre-empt the failure: walk the MBB, find cross-call Acc16
|
|
||||||
// vregs, and explicitly STAfi their value after the def + LDAfi at
|
|
||||||
// each use. This converts the cross-call live ranges into stack-
|
|
||||||
// resident loads, dropping greedy's pressure to the point it can
|
|
||||||
// always succeed.
|
|
||||||
//
|
|
||||||
// Cost: an extra STAfi+LDAfi (~6 cyc each) per cross-call vreg.
|
|
||||||
// This is the same cost greedy would emit if it succeeded (a spill
|
|
||||||
// + reload), so we're not pessimising — just making the spill
|
|
||||||
// explicit BEFORE greedy gets confused.
|
|
||||||
//
|
|
||||||
// Heuristic: only pre-spill if the function has > 5 call sites OR a
|
|
||||||
// cross-call Acc16 vreg with > 2 uses after the call. Below that,
|
|
||||||
// let greedy do its thing (it usually picks better placements).
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
#include "W65816.h"
|
|
||||||
#include "W65816InstrInfo.h"
|
|
||||||
#include "W65816Subtarget.h"
|
|
||||||
#include "llvm/CodeGen/MachineFrameInfo.h"
|
|
||||||
#include "llvm/CodeGen/MachineFunction.h"
|
|
||||||
#include "llvm/CodeGen/MachineFunctionPass.h"
|
|
||||||
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
|
||||||
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
|
||||||
|
|
||||||
using namespace llvm;
|
|
||||||
|
|
||||||
#define DEBUG_TYPE "w65816-pre-spill-cross-call"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class W65816PreSpillCrossCall : public MachineFunctionPass {
|
|
||||||
public:
|
|
||||||
static char ID;
|
|
||||||
W65816PreSpillCrossCall() : MachineFunctionPass(ID) {}
|
|
||||||
|
|
||||||
StringRef getPassName() const override {
|
|
||||||
return "W65816 pre-spill Acc16 vregs across calls";
|
|
||||||
}
|
|
||||||
|
|
||||||
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
|
||||||
AU.setPreservesCFG();
|
|
||||||
MachineFunctionPass::getAnalysisUsage(AU);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
char W65816PreSpillCrossCall::ID = 0;
|
|
||||||
|
|
||||||
INITIALIZE_PASS(W65816PreSpillCrossCall, DEBUG_TYPE,
|
|
||||||
"W65816 pre-spill Acc16 vregs across calls", false, false)
|
|
||||||
|
|
||||||
FunctionPass *llvm::createW65816PreSpillCrossCall() {
|
|
||||||
return new W65816PreSpillCrossCall();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool W65816PreSpillCrossCall::runOnMachineFunction(MachineFunction &MF) {
|
|
||||||
if (MF.getFunction().hasOptNone()) return false;
|
|
||||||
MachineRegisterInfo &MRI = MF.getRegInfo();
|
|
||||||
if (!MRI.getNumVirtRegs()) return false;
|
|
||||||
const W65816InstrInfo *TII =
|
|
||||||
MF.getSubtarget<W65816Subtarget>().getInstrInfo();
|
|
||||||
MachineFrameInfo &MFI = MF.getFrameInfo();
|
|
||||||
|
|
||||||
// First pass: count call sites in the function. Below the
|
|
||||||
// heuristic threshold we don't bother — greedy handles low-call
|
|
||||||
// functions fine and pre-spilling would just add bytes.
|
|
||||||
constexpr unsigned kCallCountThreshold = 4u;
|
|
||||||
unsigned callCount = 0;
|
|
||||||
for (MachineBasicBlock &MBB : MF) {
|
|
||||||
for (MachineInstr &MI : MBB) {
|
|
||||||
if (MI.isCall()) {
|
|
||||||
callCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (callCount < kCallCountThreshold) return false;
|
|
||||||
|
|
||||||
bool Changed = false;
|
|
||||||
|
|
||||||
// Walk every Acc16 vreg in the function. For each, find its def
|
|
||||||
// (allowing multi-def vregs like SELECT_CC results — pick the
|
|
||||||
// first by MachineInstr iteration), then check if any use is
|
|
||||||
// separated from the def by a JSL call (in the same MBB). If
|
|
||||||
// so, pre-spill via STAfi at def + LDAfi at each post-call use.
|
|
||||||
unsigned NumVRegs = MRI.getNumVirtRegs();
|
|
||||||
for (unsigned i = 0; i < NumVRegs; ++i) {
|
|
||||||
Register VReg = Register::index2VirtReg(i);
|
|
||||||
if (MRI.def_empty(VReg)) continue;
|
|
||||||
if (MRI.getRegClass(VReg) != &W65816::Acc16RegClass) continue;
|
|
||||||
// Find the first def. For PHIs we skip — pre-spilling a PHI
|
|
||||||
// result is complex and rarely helpful for the high-pressure
|
|
||||||
// pattern we target (which is sequential bitmask updates).
|
|
||||||
MachineInstr *DefMI = nullptr;
|
|
||||||
for (MachineInstr &D : MRI.def_instructions(VReg)) {
|
|
||||||
if (D.isPHI()) { DefMI = nullptr; break; }
|
|
||||||
if (!DefMI) DefMI = &D;
|
|
||||||
}
|
|
||||||
if (!DefMI) continue;
|
|
||||||
MachineBasicBlock *MBB = DefMI->getParent();
|
|
||||||
|
|
||||||
// Check if any use of VReg is in the same MBB AFTER a call
|
|
||||||
// following DefMI.
|
|
||||||
bool sawCallAfterDef = false;
|
|
||||||
SmallVector<MachineInstr *, 4> postCallUses;
|
|
||||||
auto Walker = std::next(DefMI->getIterator());
|
|
||||||
while (Walker != MBB->end()) {
|
|
||||||
MachineInstr &W = *Walker++;
|
|
||||||
if (W.isCall()) sawCallAfterDef = true;
|
|
||||||
if (sawCallAfterDef && W.readsRegister(VReg, /*TRI=*/nullptr))
|
|
||||||
postCallUses.push_back(&W);
|
|
||||||
}
|
|
||||||
if (postCallUses.empty()) continue;
|
|
||||||
|
|
||||||
// Pre-spill. Fresh slot per vreg — StackSlotColoring may merge
|
|
||||||
// slots later if their lifetimes don't overlap.
|
|
||||||
int FI = MFI.CreateStackObject(2, Align(2), /*isSpillSlot=*/true);
|
|
||||||
DebugLoc DL = DefMI->getDebugLoc();
|
|
||||||
auto AfterDef = std::next(DefMI->getIterator());
|
|
||||||
BuildMI(*MBB, AfterDef, DL, TII->get(W65816::STAfi))
|
|
||||||
.addReg(VReg).addFrameIndex(FI).addImm(0);
|
|
||||||
for (MachineInstr *UseMI : postCallUses) {
|
|
||||||
Register Reload = MRI.createVirtualRegister(&W65816::Acc16RegClass);
|
|
||||||
BuildMI(*UseMI->getParent(), UseMI->getIterator(), UseMI->getDebugLoc(),
|
|
||||||
TII->get(W65816::LDAfi), Reload)
|
|
||||||
.addFrameIndex(FI).addImm(0);
|
|
||||||
// Rewrite this use's references of VReg to Reload.
|
|
||||||
for (auto &MO : UseMI->uses()) {
|
|
||||||
if (MO.isReg() && MO.getReg() == VReg) {
|
|
||||||
MO.setReg(Reload);
|
|
||||||
MO.setIsKill(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Changed;
|
|
||||||
}
|
|
||||||
|
|
@ -56,7 +56,6 @@ LLVMInitializeW65816Target() {
|
||||||
initializeW65816WidenAcc16Pass(PR);
|
initializeW65816WidenAcc16Pass(PR);
|
||||||
initializeW65816SpillToXPass(PR);
|
initializeW65816SpillToXPass(PR);
|
||||||
initializeW65816NegYIndYPass(PR);
|
initializeW65816NegYIndYPass(PR);
|
||||||
initializeW65816PreSpillCrossCallPass(PR);
|
|
||||||
initializeW65816SjLjFinalizePass(PR);
|
initializeW65816SjLjFinalizePass(PR);
|
||||||
initializeW65816LowerWide32Pass(PR);
|
initializeW65816LowerWide32Pass(PR);
|
||||||
initializeW65816I32IncFoldPass(PR);
|
initializeW65816I32IncFoldPass(PR);
|
||||||
|
|
@ -234,19 +233,17 @@ void W65816PassConfig::addPreRegAlloc() {
|
||||||
addPass(createW65816ABridgeViaX());
|
addPass(createW65816ABridgeViaX());
|
||||||
addPass(createW65816TiedDefSpill());
|
addPass(createW65816TiedDefSpill());
|
||||||
addPass(createW65816WidenAcc16());
|
addPass(createW65816WidenAcc16());
|
||||||
// Pre-spill cross-call Acc16 vregs in high-call functions to
|
// Note: there is intentionally no cross-call Acc16 pre-spill pass here.
|
||||||
// relieve greedy regalloc pressure. Currently disabled — the
|
// An earlier W65816PreSpillCrossCall pass tried to relieve greedy's
|
||||||
// first cut creates too many fresh stack slots and overflows the
|
// single-accumulator pressure by pre-spilling cross-call vregs, but it
|
||||||
// stack-relative addressing range (frame > 256 bytes) on
|
// was never enabled (it bloated frames) and, as of 2026-06-30, the
|
||||||
// moderately-sized functions like the soft-double routines.
|
// "ran out of registers" deadlock it targeted no longer reproduces on
|
||||||
// The pass is built and ready, gated behind future tuning of:
|
// any TU in the lua + coremark + runtime corpus (the i32 / SepRep /
|
||||||
// - lower call-count threshold (currently 4)
|
// regalloc codegen fixes resolved it). Measured cost of enabling it
|
||||||
// - smarter "should we spill THIS vreg" filter
|
// was +0.17% .text for zero deadlocks fixed, so the pass was removed.
|
||||||
// - stack slot reuse via a real liveness analysis
|
// The rare residual greedy failure (if any ever returns) is handled by
|
||||||
// Until then, the high-pressure failure is worked around with
|
// scripts/ccRegallocFallback.sh, which retries the one affected TU with
|
||||||
// `__attribute__((noinline))` on the heaviest helper or with
|
// -regalloc=basic.
|
||||||
// `-mllvm -regalloc=fast` for the affected TU.
|
|
||||||
// addPass(createW65816PreSpillCrossCall());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void W65816PassConfig::addPostRegAlloc() {
|
void W65816PassConfig::addPostRegAlloc() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue