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)
|
unsigned short refNum; // GS/OS file reference (kind=GSOS only)
|
||||||
} FILE;
|
} 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 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).
|
// 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
|
// 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".
|
// "default values".
|
||||||
u16 access = (u16)(wantWrite ? (wantRead ? 3 : 2) : 1);
|
u16 access = (u16)(wantWrite ? (wantRead ? 3 : 2) : 1);
|
||||||
__GsosOpenParm op = { 3, 0, &__gsosPathBuf, access };
|
__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->kind = FILE_KIND_GSOS;
|
||||||
f->writable = (u8)(wantWrite ? 1 : 0);
|
f->writable = (u8)(wantWrite ? 1 : 0);
|
||||||
|
|
@ -1591,7 +1612,8 @@ FILE *fopen(const char *path, const char *mode) {
|
||||||
if (truncate) {
|
if (truncate) {
|
||||||
// "w" / "w+" — truncate to zero length.
|
// "w" / "w+" — truncate to zero length.
|
||||||
__GsosSetPosRecGS e = { 3, op.refNum, 0, 0 };
|
__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) {
|
if (append) {
|
||||||
// "a" / "a+" — position at end-of-file.
|
// "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;
|
unsigned long got = offset + r.transferCount;
|
||||||
if (rc != 0 || r.transferCount < total - offset) {
|
if (rc != 0 || r.transferCount < total - offset) {
|
||||||
stream->eof = 1;
|
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);
|
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; }
|
if (!stream->writable) { stream->err = 1; return 0; }
|
||||||
unsigned long total = (unsigned long)size * (unsigned long)nmemb;
|
unsigned long total = (unsigned long)size * (unsigned long)nmemb;
|
||||||
__GsosIORecGS r = { 4, stream->refNum, (void *)in, total, 0 };
|
__GsosIORecGS r = { 4, stream->refNum, (void *)in, total, 0 };
|
||||||
if (gsosWrite(&r) != 0) {
|
u16 wrc = gsosWrite(&r);
|
||||||
|
if (wrc != 0) {
|
||||||
stream->err = 1;
|
stream->err = 1;
|
||||||
|
__gsosLatch(4, wrc, stream->refNum, total, r.transferCount);
|
||||||
return (size_t)(r.transferCount / size);
|
return (size_t)(r.transferCount / size);
|
||||||
}
|
}
|
||||||
|
if (r.transferCount < total) {
|
||||||
|
__gsosLatch(4, 0, stream->refNum, total, r.transferCount);
|
||||||
|
}
|
||||||
return (size_t)(r.transferCount / size);
|
return (size_t)(r.transferCount / size);
|
||||||
}
|
}
|
||||||
if (stream->kind != FILE_KIND_MEM) return 0;
|
if (stream->kind != FILE_KIND_MEM) return 0;
|
||||||
|
|
|
||||||
|
|
@ -1113,11 +1113,27 @@ void W65816AsmPrinter::emitInstruction(const MachineInstr *MI) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case W65816::NEGA8: {
|
case W65816::NEGA8: {
|
||||||
// EOR #$FF; INC A — same idea as NEGA16 but in 8-bit M.
|
// SEP #$20 ; EOR #$FF ; INC A ; REP #$20 — same idea as NEGA16 but
|
||||||
// The function context is already 8-bit M when an i8-only path
|
// through an explicit 8-bit M window, like LDA8absX/STA8absX.
|
||||||
// is selected, so no SEP/REP wrap is needed here.
|
//
|
||||||
|
// 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);
|
emitOpImm(W65816::EOR_Imm8, 0xFF);
|
||||||
emitOp(W65816::INA);
|
emitOp(W65816::INA);
|
||||||
|
emitRepM();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case W65816::NEGC16: {
|
case W65816::NEGC16: {
|
||||||
|
|
|
||||||
|
|
@ -467,6 +467,17 @@ MachineBasicBlock::iterator W65816FrameLowering::eliminateCallFramePseudoInstr(
|
||||||
} else {
|
} else {
|
||||||
BuildMI(MBB, I, DL, TII.get(W65816::STA_DP)).addImm(0xF6);
|
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 YLive = false;
|
||||||
bool XLive = false;
|
bool XLive = false;
|
||||||
|
|
|
||||||
|
|
@ -3849,6 +3849,67 @@ W65816TargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI,
|
||||||
|
|
||||||
Register Ptr = MI.getOperand(1).getReg();
|
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:
|
// Why we spill the pointer to a fresh stack slot first:
|
||||||
// a direct `COPY $a = ptr_vreg ; STA $E0` lets RA elide the COPY
|
// 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
|
// 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;
|
case W65816::IMG15: srcDP = 0xCE; break;
|
||||||
default: 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) {
|
if (srcDP >= 0 || Src == W65816::X || Src == W65816::Y) {
|
||||||
// STAfi with non-A source: must clobber A to land the value in
|
// 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
|
// 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;
|
unsigned XferOp = (Src == W65816::X) ? W65816::TXA : W65816::TYA;
|
||||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(XferOp));
|
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(),
|
BuildMI(*MI.getParent(), II, MI.getDebugLoc(),
|
||||||
TII.get(W65816::STA_StackRel))
|
TII.get(W65816::STA_StackRel))
|
||||||
.addImm(Offset + 2) // PHA shifted SP by 2
|
.addImm(Offset + 2) // PHA shifted SP by 2
|
||||||
.addReg(W65816::A, RegState::Implicit);
|
.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));
|
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(W65816::PLA));
|
||||||
} else {
|
} else {
|
||||||
// Direct A source: simple sta d,s — A is the source, A is fine
|
// Direct A source: simple sta d,s — A is the source, A is fine
|
||||||
// afterward (no implicit clobber).
|
// afterward (no implicit clobber).
|
||||||
|
if (ByteSlot)
|
||||||
|
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(W65816::SEP)).addImm(0x20);
|
||||||
BuildMI(*MI.getParent(), II, MI.getDebugLoc(),
|
BuildMI(*MI.getParent(), II, MI.getDebugLoc(),
|
||||||
TII.get(W65816::STA_StackRel))
|
TII.get(W65816::STA_StackRel))
|
||||||
.addImm(Offset)
|
.addImm(Offset)
|
||||||
.addReg(W65816::A, RegState::Implicit);
|
.addReg(W65816::A, RegState::Implicit);
|
||||||
|
if (ByteSlot)
|
||||||
|
BuildMI(*MI.getParent(), II, MI.getDebugLoc(), TII.get(W65816::REP)).addImm(0x20);
|
||||||
}
|
}
|
||||||
MI.eraseFromParent();
|
MI.eraseFromParent();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -810,8 +810,14 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
||||||
SmallVector<MachineInstr *, 4> Branches;
|
SmallVector<MachineInstr *, 4> Branches;
|
||||||
for (MachineInstr &MI : MBB) {
|
for (MachineInstr &MI : MBB) {
|
||||||
unsigned Opc = MI.getOpcode();
|
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 ||
|
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);
|
Branches.push_back(&MI);
|
||||||
}
|
}
|
||||||
auto isFlagPreserving = [VLAFunc](unsigned Opc) {
|
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
|
// COPY in the corrupting set forces the pass to walk past these
|
||||||
// PHI-elim copies to find the real test (a CMP).
|
// PHI-elim copies to find the real test (a CMP).
|
||||||
if (Opc == TargetOpcode::COPY) return true;
|
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
|
// Pure load / register-transfer instructions: only side effect on
|
||||||
// flags is N/Z from the loaded/transferred value. Never a "test"
|
// flags is N/Z from the loaded/transferred value. Never a "test"
|
||||||
// — they just move data. Treated as corruption when between the
|
// — they just move data. Treated as corruption when between the
|
||||||
|
|
@ -972,7 +989,16 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
||||||
Opc == W65816::CMPfi ||
|
Opc == W65816::CMPfi ||
|
||||||
Opc == W65816::ADDframe;
|
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) {
|
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:
|
// Walk back from Br looking for the pattern:
|
||||||
// <test>; (mix of preserving + corrupting ops); Br
|
// <test>; (mix of preserving + corrupting ops); Br
|
||||||
// where <test> is a flag-defining op (CMP/ORA/AND/ADC/...) and
|
// 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 (!MI.isDebugInstr()) {
|
||||||
if (isFlagPreserving(MI.getOpcode())) {
|
if (isFlagPreserving(MI.getOpcode())) {
|
||||||
// skip
|
// skip
|
||||||
} else if (isLdaLike(MI.getOpcode())) {
|
} else if (isCarryCorrupting(MI.getOpcode()) ||
|
||||||
|
(TestsNZ && isLdaLike(MI.getOpcode()))) {
|
||||||
if (!LastCorrupt) LastCorrupt = &MI;
|
if (!LastCorrupt) LastCorrupt = &MI;
|
||||||
FirstCorrupt = &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)) {
|
} else if (isFlagDefining(MI)) {
|
||||||
Test = &MI;
|
Test = &MI;
|
||||||
break;
|
break;
|
||||||
|
|
@ -1056,7 +1085,11 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
||||||
// i32-libcall loops) get their FP-rel pseudos bumped, which
|
// i32-libcall loops) get their FP-rel pseudos bumped, which
|
||||||
// shifts reads/writes by one byte and corrupts state at
|
// shifts reads/writes by one byte and corrupts state at
|
||||||
// iteration N proportional to the i32-libcall count.
|
// 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();
|
const MachineFrameInfo &MFI = MF.getFrameInfo();
|
||||||
if (It->getOperand(1).isFI()) {
|
if (It->getOperand(1).isFI()) {
|
||||||
int FI = It->getOperand(1).getIndex();
|
int FI = It->getOperand(1).getIndex();
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include "W65816TargetMachine.h"
|
#include "W65816TargetMachine.h"
|
||||||
#include "W65816.h"
|
#include "W65816.h"
|
||||||
#include "W65816MachineFunctionInfo.h"
|
#include "W65816MachineFunctionInfo.h"
|
||||||
|
|
@ -265,9 +266,16 @@ void W65816PassConfig::addPostRegAlloc() {
|
||||||
// then deletes still-adjacent redundant spills. A second SpillToX
|
// then deletes still-adjacent redundant spills. A second SpillToX
|
||||||
// invocation collapses any TAX/TXA pair left adjacent by cleanup
|
// invocation collapses any TAX/TXA pair left adjacent by cleanup
|
||||||
// (e.g. when an inner copy between bridge endpoints went away).
|
// (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(createW65816SpillToX());
|
||||||
addPass(createW65816StackSlotCleanup());
|
addPass(createW65816StackSlotCleanup());
|
||||||
addPass(createW65816SpillToX());
|
addPass(createW65816SpillToX());
|
||||||
|
}
|
||||||
// Disable MachineCopyPropagation: it eliminates `COPY $img = $a`
|
// Disable MachineCopyPropagation: it eliminates `COPY $img = $a`
|
||||||
// thinking the IMG dest is dead (no explicit physreg use of $img
|
// thinking the IMG dest is dead (no explicit physreg use of $img
|
||||||
// remains after PEI expands STAfi-with-Img16-source into LDA_DP).
|
// 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
|
// physreg-COPY pseudos into the real TAX/TXA opcodes, adjacent
|
||||||
// TXA;TAX pairs (which the earlier SpillToX invocations couldn't
|
// TXA;TAX pairs (which the earlier SpillToX invocations couldn't
|
||||||
// see in COPY form) become collapsable.
|
// see in COPY form) become collapsable.
|
||||||
|
if (!getenv("W65816_DISABLE_STACKOPT")) {
|
||||||
addPass(createW65816SpillToX());
|
addPass(createW65816SpillToX());
|
||||||
|
}
|
||||||
// Rewrite negative-Y indirect-Y stack-rel ops. Must run BEFORE
|
// Rewrite negative-Y indirect-Y stack-rel ops. Must run BEFORE
|
||||||
// BranchExpand because the rewrite expands one instruction into
|
// BranchExpand because the rewrite expands one instruction into
|
||||||
// several and shifts branch distances. The pass internally checks
|
// several and shifts branch distances. The pass internally checks
|
||||||
|
|
@ -328,9 +338,13 @@ void W65816PassConfig::addPreEmitPass() {
|
||||||
// Saves 2 inst per PHI-copy occurrence (the memory copy round-trip
|
// Saves 2 inst per PHI-copy occurrence (the memory copy round-trip
|
||||||
// collapses when X and Y are renamed to the same slot). See
|
// collapses when X and Y are renamed to the same slot). See
|
||||||
// W65816StackSlotMerge.cpp.
|
// W65816StackSlotMerge.cpp.
|
||||||
|
if (!getenv("W65816_DISABLE_STACKOPT") && !getenv("W65816_DISABLE_SLOTMERGE")) {
|
||||||
addPass(createW65816StackSlotMerge());
|
addPass(createW65816StackSlotMerge());
|
||||||
|
}
|
||||||
|
if (!getenv("W65816_DISABLE_STACKOPT")) {
|
||||||
addPass(createW65816StackRelToImg());
|
addPass(createW65816StackRelToImg());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MachineFunctionInfo *W65816TargetMachine::createMachineFunctionInfo(
|
MachineFunctionInfo *W65816TargetMachine::createMachineFunctionInfo(
|
||||||
BumpPtrAllocator &Allocator, const Function &F,
|
BumpPtrAllocator &Allocator, const Function &F,
|
||||||
|
|
|
||||||
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