From 035c3935007ed18dbdce6376e47ae19796abc3c4 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Tue, 25 Aug 2026 16:44:08 -0500 Subject: [PATCH] Code generation fixes. --- runtime/src/libc.c | 35 ++++++- .../lib/Target/W65816/W65816AsmPrinter.cpp | 22 ++++- .../lib/Target/W65816/W65816FrameLowering.cpp | 11 +++ .../lib/Target/W65816/W65816ISelLowering.cpp | 61 ++++++++++++ .../lib/Target/W65816/W65816RegisterInfo.cpp | 17 ++++ .../Target/W65816/W65816StackSlotCleanup.cpp | 39 +++++++- .../lib/Target/W65816/W65816TargetMachine.cpp | 26 +++-- .../CodeGen/W65816/addframe-flag-wrap.mir | 94 +++++++++++++++++++ 8 files changed, 289 insertions(+), 16 deletions(-) create mode 100644 src/llvm/test/CodeGen/W65816/addframe-flag-wrap.mir diff --git a/runtime/src/libc.c b/runtime/src/libc.c index e8b9806..9c93be9 100644 --- a/runtime/src/libc.c +++ b/runtime/src/libc.c @@ -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; diff --git a/src/llvm/lib/Target/W65816/W65816AsmPrinter.cpp b/src/llvm/lib/Target/W65816/W65816AsmPrinter.cpp index 0540452..98f0297 100644 --- a/src/llvm/lib/Target/W65816/W65816AsmPrinter.cpp +++ b/src/llvm/lib/Target/W65816/W65816AsmPrinter.cpp @@ -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: { diff --git a/src/llvm/lib/Target/W65816/W65816FrameLowering.cpp b/src/llvm/lib/Target/W65816/W65816FrameLowering.cpp index 47c02c4..b62b6e6 100644 --- a/src/llvm/lib/Target/W65816/W65816FrameLowering.cpp +++ b/src/llvm/lib/Target/W65816/W65816FrameLowering.cpp @@ -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; diff --git a/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp b/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp index 4e322cf..e04006e 100644 --- a/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp +++ b/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp @@ -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 diff --git a/src/llvm/lib/Target/W65816/W65816RegisterInfo.cpp b/src/llvm/lib/Target/W65816/W65816RegisterInfo.cpp index 64439ad..99df096 100644 --- a/src/llvm/lib/Target/W65816/W65816RegisterInfo.cpp +++ b/src/llvm/lib/Target/W65816/W65816RegisterInfo.cpp @@ -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; diff --git a/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp b/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp index 159007e..f43f559 100644 --- a/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp +++ b/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp @@ -810,8 +810,14 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) { SmallVector 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: // ; (mix of preserving + corrupting ops); Br // where 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(); diff --git a/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp b/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp index 087a115..a4a5dce 100644 --- a/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp +++ b/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include #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). - addPass(createW65816SpillToX()); - addPass(createW65816StackSlotCleanup()); - addPass(createW65816SpillToX()); + // 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. - addPass(createW65816SpillToX()); + 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. - addPass(createW65816StackSlotMerge()); - addPass(createW65816StackRelToImg()); + if (!getenv("W65816_DISABLE_STACKOPT") && !getenv("W65816_DISABLE_SLOTMERGE")) { + addPass(createW65816StackSlotMerge()); + } + if (!getenv("W65816_DISABLE_STACKOPT")) { + addPass(createW65816StackRelToImg()); + } } MachineFunctionInfo *W65816TargetMachine::createMachineFunctionInfo( diff --git a/src/llvm/test/CodeGen/W65816/addframe-flag-wrap.mir b/src/llvm/test/CodeGen/W65816/addframe-flag-wrap.mir new file mode 100644 index 0000000..47c4202 --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/addframe-flag-wrap.mir @@ -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 +...