From 12de7c8a5776605355707669a475d1189b6b09fb Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Wed, 14 Oct 2026 21:17:29 -0500 Subject: [PATCH] More carry flag bugs fixed. --- demos/rsrcProbe.apl | Bin 47285 -> 47285 bytes runtime/src/libc.c | 76 +++++++------ scripts/smokeTest.sh | 88 +++++++++++++++ .../Target/W65816/W65816StackSlotCleanup.cpp | 32 +++++- .../W65816/shift-flag-corrupt-wrap.mir | 100 ++++++++++++++++++ 5 files changed, 260 insertions(+), 36 deletions(-) create mode 100644 src/llvm/test/CodeGen/W65816/shift-flag-corrupt-wrap.mir diff --git a/demos/rsrcProbe.apl b/demos/rsrcProbe.apl index 1574791a79b5d05724c741788d25e1d0a72c0be8..608b345f29aece63c597d7937a15de1bbf8e8741 100644 GIT binary patch delta 6056 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 delta 6210 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 R6~`J5;rZGf (size_t)0xFFFE / size) nmemb = (size_t)0xFFFE / size; + // size_t is u32 here, so the multiply itself can't overflow. if (stream->kind == FILE_KIND_GSOS) { // Drain unget byte first if present. char *out = (char *)ptr; unsigned long total = (unsigned long)size * (unsigned long)nmemb; unsigned long offset = 0; if (stream->unget >= 0 && total > 0) { - *out++ = (char)stream->unget; + *out = (char)stream->unget; stream->unget = -1; offset = 1; } - if (offset >= total) return offset / size; - __GsosIORecGS r = { - 4, stream->refNum, out, total - offset, 0 - }; - u16 rc = gsosRead(&r); - unsigned long got = offset + r.transferCount; - if (rc != 0 || r.transferCount < total - offset) { - stream->eof = 1; - if (rc != 0 && rc != 0x4C) { stream->err = 1; __gsosLatch(3, rc, stream->refNum, total - offset, r.transferCount); } + // GS/OS Read in pieces of at most 0xFFFE bytes (one bank's worth, + // as the stream layer has always asked for), until the whole + // request is in or a read comes back short: one capped call used + // to return a short count for any request past 64 KB, which a + // caller reads as the end of the file. + while (offset < total) { + unsigned long want = total - offset; + if (want > 0xFFFEul) want = 0xFFFEul; + __GsosIORecGS r = { + 4, stream->refNum, out + offset, want, 0 + }; + u16 rc = gsosRead(&r); + offset += r.transferCount; + if (rc != 0 || r.transferCount < want) { + stream->eof = 1; + if (rc != 0 && rc != 0x4C) { stream->err = 1; __gsosLatch(3, rc, stream->refNum, want, r.transferCount); } + break; + } } - return (size_t)(got / size); + return (size_t)(offset / size); } + // The memory stream addresses its buffer by 16-bit offset: one bank. + if (nmemb > (size_t)0xFFFE / size) nmemb = (size_t)0xFFFE / size; if (stream->kind != FILE_KIND_MEM) return 0; char *out = (char *)ptr; size_t items = 0; @@ -1753,11 +1760,7 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { if (!stream) return 0; if (size == 0 || nmemb == 0) return 0; - // size_t is u32 here, so the multiply itself can't overflow. The - // 0xFFFE cap is a "single 64KB bank" limit -- the underlying - // mem/GSOS backends address by 16-bit offset, so any single fwrite - // must fit in one bank. - if (nmemb > (size_t)0xFFFE / size) nmemb = (size_t)0xFFFE / size; + // size_t is u32 here, so the multiply itself can't overflow. const char *in = (const char *)ptr; if (stream->kind == FILE_KIND_STDOUT || stream->kind == FILE_KIND_STDERR) { size_t items = 0; @@ -1770,18 +1773,29 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { if (stream->kind == FILE_KIND_GSOS) { 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 }; - u16 wrc = gsosWrite(&r); - if (wrc != 0) { - stream->err = 1; - __gsosLatch(4, wrc, stream->refNum, total, r.transferCount); - return (size_t)(r.transferCount / size); + unsigned long done = 0; + // GS/OS Write in pieces of at most 0xFFFE bytes, until the whole + // request is out or a write comes back short (see fread). + while (done < total) { + unsigned long want = total - done; + if (want > 0xFFFEul) want = 0xFFFEul; + __GsosIORecGS r = { 4, stream->refNum, (void *)(in + done), want, 0 }; + u16 wrc = gsosWrite(&r); + done += r.transferCount; + if (wrc != 0) { + stream->err = 1; + __gsosLatch(4, wrc, stream->refNum, want, r.transferCount); + break; + } + if (r.transferCount < want) { + __gsosLatch(4, 0, stream->refNum, want, r.transferCount); + break; + } } - if (r.transferCount < total) { - __gsosLatch(4, 0, stream->refNum, total, r.transferCount); - } - return (size_t)(r.transferCount / size); + return (size_t)(done / size); } + // The memory stream addresses its buffer by 16-bit offset: one bank. + if (nmemb > (size_t)0xFFFE / size) nmemb = (size_t)0xFFFE / size; if (stream->kind != FILE_KIND_MEM) return 0; if (!stream->writable) { stream->err = 1; return 0; } size_t items = 0; diff --git a/scripts/smokeTest.sh b/scripts/smokeTest.sh index 204e0d5..b884880 100755 --- a/scripts/smokeTest.sh +++ b/scripts/smokeTest.sh @@ -9350,6 +9350,94 @@ EOF rm -rf "$bbDir" fi +# GS/OS fwrite and fread move a request past 64 KB in one call: libc issues +# GS/OS Write / Read in pieces of at most 0xFFFE bytes until the whole +# request is done. It used to cap the request itself at 0xFFFE bytes, so +# one fwrite of 66 KB wrote 65534 bytes and returned short (JoeyLib's +# jlSaveWrite of anything past 64 KB failed). 66 KB puts 2050 bytes in the +# second piece and keeps the run inside runViaFinder's 90 s check (an +# 80 KB one with a multiply per byte did not finish). $70 = 0x55 the +# write returned the full count, $71 = 0x66 the read did and the bytes +# match. Skips under SMOKE_SKIP_GSOSBIGIO=1. +if [ "${SMOKE_SKIP_GSOSBIGIO:-0}" != "1" ] \ + && [ -x "$CLANG" ] && [ -x "$CADIUS" ] && [ -f "$SYSDISK" ] \ + && command -v mame >/dev/null 2>&1; then + log "check: GS/OS fwrite/fread of 66 KB in one call" + bioDir="$(mktemp -d)" + cat > "$bioDir/bigIo.c" <<'EOF' +// One fwrite and one fread of 66 KB to a GS/OS file, from Memory Manager +// blocks. $70 = 0x55 the write returned the full count, $71 = 0x66 the +// read did and the bytes match what was written. +#include +#include +#include +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned long u32; +#define MB ((volatile u8 *)0x70) +#define FILE_LEN 0x10800UL +#define NH_LOCKED 0x8000u + + +static u8 pat(u32 i) { + return (u8)((u8)i * 7u + (u8)(i >> 8)); +} + + +int main(void) { + u16 id = MMStartUp(); + void **hw = (void **)NewHandle(FILE_LEN, id, NH_LOCKED, 0); + void **hr = (void **)NewHandle(FILE_LEN, id, NH_LOCKED, 0); + u8 *wr; + u8 *rd; + u32 i; + FILE *f; + if (!hw || !hr || !*hw || !*hr) { + return 0; + } + wr = (u8 *)*hw; + rd = (u8 *)*hr; + for (i = 0; i < FILE_LEN; i++) { + wr[i] = pat(i); + rd[i] = 0; + } + f = fopen("/DATA/BIGIO", "w"); + if (f && fwrite(wr, 1u, FILE_LEN, f) == FILE_LEN && fclose(f) == 0) { + MB[0] = 0x55u; + f = fopen("/DATA/BIGIO", "r"); + if (f && fread(rd, 1u, FILE_LEN, f) == FILE_LEN && fclose(f) == 0 && + memcmp(rd, wr, FILE_LEN) == 0) { + MB[1] = 0x66u; + } + } + return 0; +} +EOF + "$CLANG" --target=w65816 -I"$PROJECT_ROOT/runtime/include" -O2 -ffunction-sections \ + -c "$bioDir/bigIo.c" -o "$bioDir/bigIo.o" + "$PROJECT_ROOT/tools/link816" -o "$bioDir/bigIo.bin" --text-base 0x1000 --full-bank \ + --map "$bioDir/bigIo.map" --reloc-out "$bioDir/bigIo.reloc" \ + "$PROJECT_ROOT/runtime/crt0Gsos.o" "$bioDir/bigIo.o" \ + "$PROJECT_ROOT/runtime/libc.o" \ + "$PROJECT_ROOT/runtime/snprintf.o" \ + "$PROJECT_ROOT/runtime/extras.o" \ + "$PROJECT_ROOT/runtime/softFloat.o" \ + "$PROJECT_ROOT/runtime/softDouble.o" \ + "$PROJECT_ROOT/runtime/iigsGsos.o" \ + "$PROJECT_ROOT/runtime/iigsToolbox.o" "$PROJECT_ROOT/runtime/iigsToolboxExtra.o" \ + "$PROJECT_ROOT/runtime/libgcc.o" >/dev/null \ + || die "GS/OS 66 KB I/O smoke: link failed" + "$PROJECT_ROOT/tools/omfEmit" --input "$bioDir/bigIo.bin" --map "$bioDir/bigIo.map" \ + --base 0x1000 --entry __start --output "$bioDir/bigIo.omf" \ + --name HELLO --expressload --relocs "$bioDir/bigIo.reloc" >/dev/null 2>&1 + [ -s "$bioDir/bigIo.omf" ] || die "GS/OS 66 KB I/O smoke: omfEmit produced no OMF" + if ! bash "$PROJECT_ROOT/scripts/runViaFinder.sh" "$bioDir/bigIo.omf" \ + --check 0x70=0x55 0x71=0x66 >/dev/null 2>&1; then + die "GS/OS 66 KB I/O smoke FAILED (\$70 fwrite count, \$71 fread count + bytes)" + fi + rm -rf "$bioDir" +fi + # GS/OS program exit: returning from main is exit(main's value), which must # run the atexit handlers (last registered first), then the C++ dtors, then # QUIT back to the Finder. crt0Gsos used to issue QUIT with the pointer diff --git a/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp b/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp index af8dea9..16f625e 100644 --- a/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp +++ b/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp @@ -956,8 +956,8 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) { // real CMP. NOTE: the *fi variants (ANDfi/ORAfi/EORfi) and // ADC/SBC DO model Defs=[P] and can legitimately be the branch's // flag source, so they are handled by $p liveness and are NOT - // listed here; same for the shifts, which the shift-carry idiom - // reads directly. + // listed here. (The one-bit shift pseudos are listed below: + // they model no Defs=[P] either.) Opc == W65816::EORi16imm || Opc == W65816::EORabs || // INC/DEC in every form. None of them models Defs=[P] either, // so at this pass's point no branch can take its flags from @@ -1015,6 +1015,22 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) { // the BNE read the last AND's Z, so the break never fired // (JoeyLib's IIgs serial-loss probe, 2026-09-28). Opc == W65816::SRL8A || Opc == W65816::SHL8A || + // The shift pseudos, which lower in the AsmPrinter to ASL A / + // LSR A (ASRA16: CMP #$8000 ; ROR A; the *15A forms: a shift, + // LDA #0 and a rotate). They rewrite N, Z and C, model no + // Defs=[P], and are cheap, so Machine CSE's PRE hoists one to + // the end of a dominating block -- between its CMP and the + // terminator. Live symptom: `for (row = 0; row < h; row++)` + // after `dstByteStart = dx >> 1` lowered to + // `cmp #$8001 ; lda n,s ; lsr a ; sta m,s ; bcc exit`; the BCC + // read bit 0 of dx, so every even-x interpreted sprite draw + // skipped its rows (JoeyLib's IIgs sprite interpreter, + // 2026-10-13). They are carry corrupters too (below). + Opc == W65816::ASLA16 || Opc == W65816::LSRA16 || + Opc == W65816::ASLA8 || Opc == W65816::LSRA8 || + Opc == W65816::ASRA16 || + Opc == W65816::SRL15A || Opc == W65816::SHL15A || + Opc == W65816::SRA15A || Opc == W65816::XBA || Opc == W65816::XBA16 || Opc == W65816::NEGA16 || Opc == W65816::NEGA8 || // Every other load, pseudo or already lowered: a load is never @@ -1051,10 +1067,16 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) { Opc == W65816::CMP_StackRel; }; // 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. + // wrapped even for a BCC/BCS/BVC/BVS: ADDframe (TSC ; CLC ; ADC) and + // the shift pseudos (see isLdaLike). Every other entry in isLdaLike + // touches N/Z alone. auto isCarryCorrupting = [](unsigned Opc) { - return Opc == W65816::ADDframe; + return Opc == W65816::ADDframe || + Opc == W65816::ASLA16 || Opc == W65816::LSRA16 || + Opc == W65816::ASLA8 || Opc == W65816::LSRA8 || + Opc == W65816::ASRA16 || + Opc == W65816::SRL15A || Opc == W65816::SHL15A || + Opc == W65816::SRA15A; }; for (MachineInstr *Br : Branches) { unsigned BrOpc = Br->getOpcode(); diff --git a/src/llvm/test/CodeGen/W65816/shift-flag-corrupt-wrap.mir b/src/llvm/test/CodeGen/W65816/shift-flag-corrupt-wrap.mir new file mode 100644 index 0000000..fd0ca16 --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/shift-flag-corrupt-wrap.mir @@ -0,0 +1,100 @@ +# Pin: the shift pseudos (ASLA16 / LSRA16 / ASLA8 / LSRA8 / ASRA16 and the +# *15A forms) count as flag CORRUPTION -- of N/Z and of C -- in the PHP/PLP +# wrap (W65816StackSlotCleanup Pass -2.5), not as the flag-defining test. +# +# They lower in the AsmPrinter to ASL A / LSR A (ASRA16: CMP #$8000 ; ROR A; +# the *15A forms: a shift, LDA #0 and a rotate), which rewrite N, Z and C, +# but model no Defs=[P]. Being cheap, Machine CSE's PRE hoisted one to the +# end of a dominating block -- between its CMP and the terminator -- and +# isFlagDefining's default then took the shift for THE test: the walk +# stopped there and the pass declined to wrap. +# +# Live symptom (JoeyLib's IIgs sprite interpreter, 2026-10-13): a loop guard +# `row < h` after `dstByteStart = dx >> 1` lowered to +# cmp #$8001 ; lda n,s ; lsr a ; sta m,s ; bcc exit +# so the BCC read bit 0 of dx and every even-x draw skipped its rows. +# +# lsra16_carry: the reported shape under a BCC. The reload in front of the +# shift only corrupts N/Z, harmless to a carry branch, so the +# wrap covers the shift alone; the spill after it stays outside. +# asla16_nz: an ASLA16 under a BNE: the reload corrupts Z too, so the wrap +# starts at it. +# +# Each case reads %stack.2 after the branch so its store survives the pass's +# never-read slot sweep, and the branch targets take X live in, so the wrap +# cannot be replaced by the TAX ... CPX #k form (cmp-via-x.mir). +# +# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s +--- | + target triple = "w65816" + define void @lsra16_carry() { ret void } + define void @asla16_nz() { ret void } +... +--- +# CHECK-LABEL: name: lsra16_carry +# CHECK: CMPi16imm killed $a, 32769, implicit-def $p +# CHECK-NEXT: $a = LDAfi %stack.1, 0 +# CHECK-NEXT: PHP +# CHECK-NEXT: $a = LSRA16 killed $a +# CHECK-NEXT: PLP +# CHECK-NEXT: STAfi killed $a, %stack.2, 0 +# CHECK-NEXT: BCC %bb.1, implicit $p +name: lsra16_carry +tracksRegLiveness: false +stack: + - { id: 0, size: 2, alignment: 1 } + - { id: 1, size: 2, alignment: 1 } + - { id: 2, size: 2, alignment: 1 } +body: | + bb.0: + successors: %bb.1, %bb.2 + $a = LDAfi %stack.0, 0 + CMPi16imm killed $a, 32769, implicit-def $p + $a = LDAfi %stack.1, 0 + $a = LSRA16 killed $a + STAfi killed $a, %stack.2, 0, implicit-def $a + BCC %bb.1, implicit $p + BRA %bb.2 + bb.1: + liveins: $x + $a = LDAfi %stack.2, 0 + RTL + bb.2: + liveins: $x + $a = LDAfi %stack.2, 0 + RTL +... +--- +# CHECK-LABEL: name: asla16_nz +# CHECK: CMPi16imm killed $a, 3, implicit-def $p +# CHECK-NEXT: PHP +# CHECK-NEXT: $a = LDAfi %stack.1, 0 +# CHECK-NEXT: $a = ASLA16 killed $a +# CHECK-NEXT: PLP +# CHECK-NEXT: STAfi killed $a, %stack.2, 0 +# CHECK-NEXT: BNE %bb.1, implicit $p +name: asla16_nz +tracksRegLiveness: false +stack: + - { id: 0, size: 2, alignment: 1 } + - { id: 1, size: 2, alignment: 1 } + - { id: 2, 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 = LDAfi %stack.1, 0 + $a = ASLA16 killed $a + STAfi killed $a, %stack.2, 0, implicit-def $a + BNE %bb.1, implicit $p + BRA %bb.2 + bb.1: + liveins: $x + $a = LDAfi %stack.2, 0 + RTL + bb.2: + liveins: $x + $a = LDAfi %stack.2, 0 + RTL +...