Code generation fixes.
This commit is contained in:
parent
d9b8a98da2
commit
035c393500
8 changed files with 289 additions and 16 deletions
|
|
@ -1155,6 +1155,26 @@ typedef struct __sFILE {
|
|||
unsigned short refNum; // GS/OS file reference (kind=GSOS only)
|
||||
} FILE;
|
||||
|
||||
// Diagnostic latch: the last FAILING GS/OS call made by this stdio bridge.
|
||||
// Zero-cost when nothing fails; lets an app report WHY a write/read died
|
||||
// (fwrite itself can only say "short") without a debugger on the target.
|
||||
// __gsosLastOp values: 1=Open 2=Create 3=Read 4=Write 5=SetEOF 6=SetMark
|
||||
// 7=Close 8=GetEOF 9=GetMark.
|
||||
u16 __gsosLastErr;
|
||||
u16 __gsosLastOp;
|
||||
u16 __gsosLastRefNum;
|
||||
unsigned long __gsosLastReq;
|
||||
unsigned long __gsosLastXfer;
|
||||
|
||||
|
||||
static void __gsosLatch(u16 op, u16 err, u16 refNum, unsigned long req, unsigned long xfer) {
|
||||
__gsosLastOp = op;
|
||||
__gsosLastErr = err;
|
||||
__gsosLastRefNum = refNum;
|
||||
__gsosLastReq = req;
|
||||
__gsosLastXfer = xfer;
|
||||
}
|
||||
|
||||
// Slots 0-2 are stdin/stdout/stderr; 3..MFS_MAX_FILES-1 are usable FILE*
|
||||
// slots (both GS/OS and memory files draw from this table -- see fopen).
|
||||
// At 8 that left only 5 concurrent opens, which starved apps that keep
|
||||
|
|
@ -1574,7 +1594,8 @@ FILE *fopen(const char *path, const char *mode) {
|
|||
// "default values".
|
||||
u16 access = (u16)(wantWrite ? (wantRead ? 3 : 2) : 1);
|
||||
__GsosOpenParm op = { 3, 0, &__gsosPathBuf, access };
|
||||
if (gsosOpen(&op) != 0) return (FILE *)0;
|
||||
u16 orc = gsosOpen(&op);
|
||||
if (orc != 0) { __gsosLatch(1, orc, 0, 0, 0); return (FILE *)0; }
|
||||
|
||||
f->kind = FILE_KIND_GSOS;
|
||||
f->writable = (u8)(wantWrite ? 1 : 0);
|
||||
|
|
@ -1591,7 +1612,8 @@ FILE *fopen(const char *path, const char *mode) {
|
|||
if (truncate) {
|
||||
// "w" / "w+" — truncate to zero length.
|
||||
__GsosSetPosRecGS e = { 3, op.refNum, 0, 0 };
|
||||
if (gsosSetEOF(&e) != 0) f->err = 1;
|
||||
u16 erc = gsosSetEOF(&e);
|
||||
if (erc != 0) { f->err = 1; __gsosLatch(5, erc, op.refNum, 0, 0); }
|
||||
}
|
||||
if (append) {
|
||||
// "a" / "a+" — position at end-of-file.
|
||||
|
|
@ -1630,7 +1652,7 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) {
|
|||
unsigned long got = offset + r.transferCount;
|
||||
if (rc != 0 || r.transferCount < total - offset) {
|
||||
stream->eof = 1;
|
||||
if (rc != 0 && rc != 0x4C) stream->err = 1; // 0x4C = eofErr
|
||||
if (rc != 0 && rc != 0x4C) { stream->err = 1; __gsosLatch(3, rc, stream->refNum, total - offset, r.transferCount); }
|
||||
}
|
||||
return (size_t)(got / size);
|
||||
}
|
||||
|
|
@ -1678,10 +1700,15 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) {
|
|||
if (!stream->writable) { stream->err = 1; return 0; }
|
||||
unsigned long total = (unsigned long)size * (unsigned long)nmemb;
|
||||
__GsosIORecGS r = { 4, stream->refNum, (void *)in, total, 0 };
|
||||
if (gsosWrite(&r) != 0) {
|
||||
u16 wrc = gsosWrite(&r);
|
||||
if (wrc != 0) {
|
||||
stream->err = 1;
|
||||
__gsosLatch(4, wrc, stream->refNum, total, r.transferCount);
|
||||
return (size_t)(r.transferCount / size);
|
||||
}
|
||||
if (r.transferCount < total) {
|
||||
__gsosLatch(4, 0, stream->refNum, total, r.transferCount);
|
||||
}
|
||||
return (size_t)(r.transferCount / size);
|
||||
}
|
||||
if (stream->kind != FILE_KIND_MEM) return 0;
|
||||
|
|
|
|||
|
|
@ -1113,11 +1113,27 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
|||
return;
|
||||
}
|
||||
case W65816::NEGA8: {
|
||||
// EOR #$FF; INC A — same idea as NEGA16 but in 8-bit M.
|
||||
// The function context is already 8-bit M when an i8-only path
|
||||
// is selected, so no SEP/REP wrap is needed here.
|
||||
// SEP #$20 ; EOR #$FF ; INC A ; REP #$20 — same idea as NEGA16 but
|
||||
// through an explicit 8-bit M window, like LDA8absX/STA8absX.
|
||||
//
|
||||
// The wrap is REQUIRED, not belt-and-braces. NEGA8 is selected for
|
||||
// i8 subexpressions inside ordinary 16-bit-M functions (e.g. an i8
|
||||
// negate feeding a sign-extended array index), and the previous
|
||||
// unwrapped expansion assumed 8-bit M: in 16-bit M the CPU decodes
|
||||
// the 2-byte `EOR #imm8` as a 3-byte `EOR #imm16`, swallowing the
|
||||
// following INA opcode ($1A) as the immediate's high byte. The
|
||||
// stream re-synchronizes, so nothing crashes — but the +1 of the
|
||||
// two's-complement negate never executes and -x silently becomes ~x
|
||||
// (RetroNet's IIgs SipHash corruption: every sealed frame's MAC came
|
||||
// out one low in the rotate's first peeled iteration).
|
||||
//
|
||||
// Safe unconditionally: SepRepCleanup's isMNeutral whitelist does
|
||||
// not include NEGA8, so no coalesced M=8 window ever spans this
|
||||
// pseudo — the ambient mode here is always 16-bit M.
|
||||
emitSepM();
|
||||
emitOpImm(W65816::EOR_Imm8, 0xFF);
|
||||
emitOp(W65816::INA);
|
||||
emitRepM();
|
||||
return;
|
||||
}
|
||||
case W65816::NEGC16: {
|
||||
|
|
|
|||
|
|
@ -467,6 +467,17 @@ MachineBasicBlock::iterator W65816FrameLowering::eliminateCallFramePseudoInstr(
|
|||
} else {
|
||||
BuildMI(MBB, I, DL, TII.get(W65816::STA_DP)).addImm(0xF6);
|
||||
}
|
||||
// Re-zero the BANK byte too, not just the 16-bit pointer. The
|
||||
// [$F6],Y far-frame access reads a 24-bit pointer at $F6/$F7/$F8,
|
||||
// and $F8 sits inside the libcall scratch range ($E0..$FF):
|
||||
// libgcc's __muldi3 / __udivmoddi_core / __umulhisi3_qsq all
|
||||
// store live values there. With only the prologue's STZ, the
|
||||
// first mul/div anywhere in a callee chain left $F8 nonzero and
|
||||
// every later FP-relative access in the caller silently read and
|
||||
// wrote another bank -- the RetroNet IIgs corruption family
|
||||
// (record-walk losing records, spilled call args arriving as
|
||||
// garbage, wild jumps), whose victim moved with frame layout.
|
||||
BuildMI(MBB, I, DL, TII.get(W65816::STZ_DP)).addImm(0xF8);
|
||||
};
|
||||
bool YLive = false;
|
||||
bool XLive = false;
|
||||
|
|
|
|||
|
|
@ -3849,6 +3849,67 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
|||
|
||||
Register Ptr = MI.getOperand(1).getReg();
|
||||
|
||||
// PROVENANCE FIX: a deref of a MATERIALISED GLOBAL ADDRESS is exactly a
|
||||
// DBR-relative absolute access to that global -- the same thing a direct
|
||||
// `global` reference compiles to (LDAabs / STAabs). Detect it (the pointer
|
||||
// traces, through COPYs, to an LDAi16imm of a global/external symbol) and
|
||||
// emit the abs form, skipping the [dp],Y pointer deref entirely. The deref
|
||||
// path forces the bank byte to 0 (STZ $E2), which is correct for a stack
|
||||
// pointer (the 65816 stack is always bank 0) but WRONG for a global: under
|
||||
// the GS/OS Loader a global lives in DBR's bank, not bank 0, and abs is
|
||||
// DBR-relative so it lands in the right bank. Without this, a global whose
|
||||
// address gets spilled/reloaded under register pressure (losing its Wrapper
|
||||
// node, so it selects LDAptr instead of LDAabs) reads bank-0 garbage --
|
||||
// observed live as RetroNet's gX.id read via a spilled global pointer
|
||||
// returning 0xAD, so its FILE_REQUEST carried a junk transfer id. Note we
|
||||
// canNOT just source the bank from $BE: $BE is the code segment's PBR, and
|
||||
// in a multi-segment program the data (BSS) segment is a DIFFERENT bank, so
|
||||
// only DBR (via abs) is correct. Only globals take this path; stack/heap
|
||||
// pointers keep the deref. va_arg (ForceBank0) is a stack pointer, never a
|
||||
// global, but gate on it anyway for clarity.
|
||||
if (!ForceBank0) {
|
||||
MachineRegisterInfo &MRI = MF->getRegInfo();
|
||||
const MachineOperand *GlobalMO = nullptr;
|
||||
Register P = Ptr;
|
||||
for (int hops = 0; hops < 4 && P.isVirtual(); ++hops) {
|
||||
MachineInstr *Def = MRI.getVRegDef(P);
|
||||
if (!Def)
|
||||
break;
|
||||
unsigned DOp = Def->getOpcode();
|
||||
if (DOp == W65816::LDAi16imm && Def->getNumOperands() >= 2 &&
|
||||
(Def->getOperand(1).isGlobal() || Def->getOperand(1).isSymbol())) {
|
||||
GlobalMO = &Def->getOperand(1);
|
||||
break;
|
||||
}
|
||||
if (DOp == TargetOpcode::COPY && Def->getOperand(1).isReg() &&
|
||||
Def->getOperand(1).getReg().isVirtual()) {
|
||||
P = Def->getOperand(1).getReg();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (GlobalMO) {
|
||||
MachineOperand G = *GlobalMO;
|
||||
if (IsLoad) {
|
||||
Register Dst = MI.getOperand(0).getReg();
|
||||
BuildMI(*BB, MI.getIterator(), DL, TII.get(W65816::LDAabs), Dst)
|
||||
.add(G);
|
||||
} else if (IsByteStore) {
|
||||
Register Val = MI.getOperand(0).getReg();
|
||||
BuildMI(*BB, MI.getIterator(), DL, TII.get(TargetOpcode::COPY),
|
||||
W65816::A).addReg(Val);
|
||||
BuildMI(*BB, MI.getIterator(), DL, TII.get(W65816::STA8abs))
|
||||
.addReg(W65816::A).add(G);
|
||||
} else {
|
||||
Register Val = MI.getOperand(0).getReg();
|
||||
BuildMI(*BB, MI.getIterator(), DL, TII.get(W65816::STAabs))
|
||||
.addReg(Val).add(G);
|
||||
}
|
||||
MI.eraseFromParent();
|
||||
return BB;
|
||||
}
|
||||
}
|
||||
|
||||
// Why we spill the pointer to a fresh stack slot first:
|
||||
// a direct `COPY $a = ptr_vreg ; STA $E0` lets RA elide the COPY
|
||||
// when ptr_vreg is already allocated to A. In a loop body where
|
||||
|
|
|
|||
|
|
@ -379,6 +379,15 @@ bool W65816RegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
|
|||
case W65816::IMG15: srcDP = 0xCE; break;
|
||||
default: break;
|
||||
}
|
||||
// BYTE-SLOT STORE HAZARD. M=16 is the default mode, so `sta d,s` writes TWO bytes. When the target
|
||||
// frame object is only 1 byte (an i8 spill / i8 arg-save), the second byte overflows the slot. For
|
||||
// the TOP slot of a frame that overflow byte is the caller's saved RETURN ADDRESS low byte: the
|
||||
// callee's RTL then jumps into hyperspace (observed live: rnClientOnFileRecord saving its uint8_t
|
||||
// `op` clobbered its return 0x80->0x01, wedging the RetroNet IIgs DISPLAY walk). Wrap the store in
|
||||
// SEP #$20 / REP #$20 so a 1-byte slot gets a genuine 8-bit store. Only the STORE is a hazard - a
|
||||
// 16-bit load of the extra byte is a harmless read (the high byte of an i8 value is ignored), so
|
||||
// LDAfi is left 16-bit. The load side never corrupts anything.
|
||||
bool ByteSlot = MFI.getObjectSize(FI) == 1;
|
||||
if (srcDP >= 0 || Src == W65816::X || Src == W65816::Y) {
|
||||
// STAfi with non-A source: must clobber A to land the value in
|
||||
// A and then `sta d,s`. PHA-bracket so A's incoming value is
|
||||
|
|
@ -400,18 +409,26 @@ bool W65816RegisterInfo::eliminateFrameIndex(MachineBasicBlock::iterator II,
|
|||
unsigned XferOp = (Src == W65816::X) ? W65816::TXA : W65816::TYA;
|
||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(XferOp));
|
||||
}
|
||||
if (ByteSlot)
|
||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(W65816::SEP)).addImm(0x20);
|
||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(),
|
||||
TII.get(W65816::STA_StackRel))
|
||||
.addImm(Offset + 2) // PHA shifted SP by 2
|
||||
.addReg(W65816::A, RegState::Implicit);
|
||||
if (ByteSlot)
|
||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(W65816::REP)).addImm(0x20);
|
||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(W65816::PLA));
|
||||
} else {
|
||||
// Direct A source: simple sta d,s — A is the source, A is fine
|
||||
// afterward (no implicit clobber).
|
||||
if (ByteSlot)
|
||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(W65816::SEP)).addImm(0x20);
|
||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(),
|
||||
TII.get(W65816::STA_StackRel))
|
||||
.addImm(Offset)
|
||||
.addReg(W65816::A, RegState::Implicit);
|
||||
if (ByteSlot)
|
||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(W65816::REP)).addImm(0x20);
|
||||
}
|
||||
MI.eraseFromParent();
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -810,8 +810,14 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &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::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) {
|
||||
|
|
@ -878,6 +884,17 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
// 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
|
||||
|
|
@ -972,7 +989,16 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
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
|
||||
|
|
@ -998,9 +1024,12 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
if (!MI.isDebugInstr()) {
|
||||
if (isFlagPreserving(MI.getOpcode())) {
|
||||
// skip
|
||||
} else if (isLdaLike(MI.getOpcode())) {
|
||||
} 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;
|
||||
|
|
@ -1056,7 +1085,11 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
// 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.
|
||||
if (IsPseudo && UsesFPRel) {
|
||||
// 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();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <cstdlib>
|
||||
#include "W65816TargetMachine.h"
|
||||
#include "W65816.h"
|
||||
#include "W65816MachineFunctionInfo.h"
|
||||
|
|
@ -265,9 +266,16 @@ void W65816PassConfig::addPostRegAlloc() {
|
|||
// then deletes still-adjacent redundant spills. A second SpillToX
|
||||
// invocation collapses any TAX/TXA pair left adjacent by cleanup
|
||||
// (e.g. when an inner copy between bridge endpoints went away).
|
||||
// Diagnostic lever: W65816_DISABLE_STACKOPT=1 turns off the stack-slot
|
||||
// rewriting passes (SpillToX / StackSlotCleanup / StackSlotMerge /
|
||||
// StackRelToImg) wholesale. Used to bisect wrong-VALUES corruption in
|
||||
// large inlined frames down to this pass family without per-pass
|
||||
// rebuilds; costs size/speed, never correctness.
|
||||
if (!getenv("W65816_DISABLE_STACKOPT")) {
|
||||
addPass(createW65816SpillToX());
|
||||
addPass(createW65816StackSlotCleanup());
|
||||
addPass(createW65816SpillToX());
|
||||
}
|
||||
// Disable MachineCopyPropagation: it eliminates `COPY $img = $a`
|
||||
// thinking the IMG dest is dead (no explicit physreg use of $img
|
||||
// remains after PEI expands STAfi-with-Img16-source into LDA_DP).
|
||||
|
|
@ -287,7 +295,9 @@ void W65816PassConfig::addPreEmitPass() {
|
|||
// physreg-COPY pseudos into the real TAX/TXA opcodes, adjacent
|
||||
// TXA;TAX pairs (which the earlier SpillToX invocations couldn't
|
||||
// see in COPY form) become collapsable.
|
||||
if (!getenv("W65816_DISABLE_STACKOPT")) {
|
||||
addPass(createW65816SpillToX());
|
||||
}
|
||||
// Rewrite negative-Y indirect-Y stack-rel ops. Must run BEFORE
|
||||
// BranchExpand because the rewrite expands one instruction into
|
||||
// several and shifts branch distances. The pass internally checks
|
||||
|
|
@ -328,8 +338,12 @@ void W65816PassConfig::addPreEmitPass() {
|
|||
// Saves 2 inst per PHI-copy occurrence (the memory copy round-trip
|
||||
// collapses when X and Y are renamed to the same slot). See
|
||||
// W65816StackSlotMerge.cpp.
|
||||
if (!getenv("W65816_DISABLE_STACKOPT") && !getenv("W65816_DISABLE_SLOTMERGE")) {
|
||||
addPass(createW65816StackSlotMerge());
|
||||
}
|
||||
if (!getenv("W65816_DISABLE_STACKOPT")) {
|
||||
addPass(createW65816StackRelToImg());
|
||||
}
|
||||
}
|
||||
|
||||
MachineFunctionInfo *W65816TargetMachine::createMachineFunctionInfo(
|
||||
|
|
|
|||
94
src/llvm/test/CodeGen/W65816/addframe-flag-wrap.mir
Normal file
94
src/llvm/test/CodeGen/W65816/addframe-flag-wrap.mir
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Pin: an ADDframe (LEA of a stack slot) scheduled between a CMP and the
|
||||
# branch that consumes it must be PHP/PLP-wrapped by the stack-slot-cleanup
|
||||
# pass (Pass -2.5), and its ImmOffset bumped by one for PHP's S decrement.
|
||||
#
|
||||
# ADDframe expands at PEI to `TSC; CLC; ADC #disp`, which rewrites N/Z, C
|
||||
# and V. It carries no Defs=[P] and is rematerializable, so Machine Copy
|
||||
# Propagation legitimately placed one between `cmp #3` and `bne` in
|
||||
# RetroNet's rnClient.c - the `act.kind == RN_WA_SUBMIT` test then branched
|
||||
# on (SP+disp) != 0 and the form was never submitted. The wrap pass used
|
||||
# to treat ADDframe as the flag-DEFINING test and decline to wrap.
|
||||
#
|
||||
# The BCC case pins the second half of the fix: ADDframe corrupts C, so a
|
||||
# carry branch needs the wrap too (LDA-like ops never did, and still don't).
|
||||
#
|
||||
# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s
|
||||
--- |
|
||||
target triple = "w65816"
|
||||
define void @nz() { ret void }
|
||||
define void @carry() { ret void }
|
||||
define void @lda_under_carry() { ret void }
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: nz
|
||||
# CHECK: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: $a = ADDframe %stack.1, 1
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BEQ %bb.1, implicit $p
|
||||
name: nz
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
- { id: 1, size: 6, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1, %bb.2
|
||||
$a = LDAfi %stack.0, 0
|
||||
CMPi16imm killed $a, 3, implicit-def $p
|
||||
$a = ADDframe %stack.1, 0
|
||||
BEQ %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# CHECK-LABEL: name: carry
|
||||
# CHECK: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# CHECK-NEXT: PHP
|
||||
# CHECK-NEXT: $a = ADDframe %stack.1, 1
|
||||
# CHECK-NEXT: PLP
|
||||
# CHECK-NEXT: BCC %bb.1, implicit $p
|
||||
name: carry
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
- { id: 1, size: 6, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1, %bb.2
|
||||
$a = LDAfi %stack.0, 0
|
||||
CMPi16imm killed $a, 3, implicit-def $p
|
||||
$a = ADDframe %stack.1, 0
|
||||
BCC %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
---
|
||||
# An N/Z-only corrupter under a carry branch stays unwrapped (no size cost).
|
||||
# CHECK-LABEL: name: lda_under_carry
|
||||
# CHECK: CMPi16imm killed $a, 3, implicit-def $p
|
||||
# CHECK-NEXT: $a = LDAi16imm 7
|
||||
# CHECK-NEXT: BCC %bb.1, implicit $p
|
||||
name: lda_under_carry
|
||||
tracksRegLiveness: false
|
||||
stack:
|
||||
- { id: 0, size: 2, alignment: 1 }
|
||||
body: |
|
||||
bb.0:
|
||||
successors: %bb.1, %bb.2
|
||||
$a = LDAfi %stack.0, 0
|
||||
CMPi16imm killed $a, 3, implicit-def $p
|
||||
$a = LDAi16imm 7
|
||||
BCC %bb.1, implicit $p
|
||||
BRA %bb.2
|
||||
bb.1:
|
||||
RTL
|
||||
bb.2:
|
||||
RTL
|
||||
...
|
||||
Loading…
Add table
Reference in a new issue