From be2336eb095ed2e140166528275966d45897a759 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Tue, 29 Sep 2026 17:22:52 -0500 Subject: [PATCH] Even more code gen issues fixed. --- demos/rsrcProbe.apl | Bin 47309 -> 47302 bytes runtime/src/math.c | 5 +- runtime/src/snprintf.c | 6 +- runtime/src/softDouble.c | 16 +- scripts/runMultiSeg.sh | 62 ++- scripts/smokeTest.sh | 505 ++++++++++++++++++ src/llvm/lib/Target/W65816/CMakeLists.txt | 2 + src/llvm/lib/Target/W65816/W65816.h | 7 + .../lib/Target/W65816/W65816CmpPrepare.cpp | 223 ++++++++ .../lib/Target/W65816/W65816ISelLowering.cpp | 58 +- .../lib/Target/W65816/W65816ISelLowering.h | 9 + .../lib/Target/W65816/W65816LowerWide32.cpp | 55 +- .../Target/W65816/W65816StackSlotCleanup.cpp | 61 ++- .../lib/Target/W65816/W65816TargetMachine.cpp | 6 + src/llvm/test/CodeGen/W65816/cmp-prepare.ll | 140 +++++ .../test/CodeGen/W65816/fp-cmp-libcall-i16.ll | 44 ++ .../test/CodeGen/W65816/i32-compare-chain.ll | 58 +- src/llvm/test/CodeGen/W65816/i32-shift-16.ll | 66 +++ .../W65816/lower-wide32-half-class.mir | 98 ++++ src/llvm/test/CodeGen/W65816/sign-test-bmi.ll | 107 ++++ .../test/CodeGen/W65816/sign-test-flags.mir | 131 +++++ tests/coremark/README.md | 26 +- tests/coremark/build.sh | 6 +- tests/coremark/coreMark.manifest.json | 8 +- tests/coremark/core_portme.c | 50 +- tests/coremark/core_portme.h | 21 +- tests/coremark/runCoreMark.sh | 97 +++- tests/lua/luaTest.manifest.json | 2 +- 28 files changed, 1769 insertions(+), 100 deletions(-) create mode 100644 src/llvm/lib/Target/W65816/W65816CmpPrepare.cpp create mode 100644 src/llvm/test/CodeGen/W65816/cmp-prepare.ll create mode 100644 src/llvm/test/CodeGen/W65816/fp-cmp-libcall-i16.ll create mode 100644 src/llvm/test/CodeGen/W65816/i32-shift-16.ll create mode 100644 src/llvm/test/CodeGen/W65816/lower-wide32-half-class.mir create mode 100644 src/llvm/test/CodeGen/W65816/sign-test-bmi.ll create mode 100644 src/llvm/test/CodeGen/W65816/sign-test-flags.mir diff --git a/demos/rsrcProbe.apl b/demos/rsrcProbe.apl index b180c131ccce99cc3ba522608397d62da066939c..74552e45c68b46f5f43b80ba89c2f02833b746aa 100644 GIT binary patch delta 6480 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 K06%oN^S=S7kbCj~ diff --git a/runtime/src/math.c b/runtime/src/math.c index c4f9815..0398c8c 100644 --- a/runtime/src/math.c +++ b/runtime/src/math.c @@ -136,9 +136,8 @@ float fmodf(float x, float y) { // uses the IEEE-754 layout: y0 = bits >> 1 + bias_shift. Then ~6 // Newton iterations land within ~1 ULP for double precision. // -// We dodge the snprintf-style libcall pitfalls by NOT comparing -// double-to-double via `<`/`>=`; instead we test the IEEE-754 -// sign bit and exponent directly. +// The sign and zero tests read the IEEE-754 bits directly: cheaper than +// a `<` / `>=` libcall compare. double sqrt(double x) { uint64_t b; __builtin_memcpy(&b, &x, sizeof(b)); diff --git a/runtime/src/snprintf.c b/runtime/src/snprintf.c index 0591efa..57f4433 100644 --- a/runtime/src/snprintf.c +++ b/runtime/src/snprintf.c @@ -493,10 +493,8 @@ static void emitDouble(double v, int prec, char spec, const Spec *s) { if (prec > 9) { prec = 9; } - // Avoid `if (v < 0)` (which calls __ltdf2) — the W65816 codegen - // for that comparison passes its double arg with a missing word, - // and the test silently returns false for negatives. Read the - // IEEE-754 sign bit and clear it inline instead. + // Read and clear the IEEE-754 sign bit inline rather than `v < 0`: + // cheaper than the __ltdf2 libcall, and it also handles -0.0. unsigned long long bits; __builtin_memcpy(&bits, &v, 8); if (bits & ((unsigned long long)1 << 63)) { diff --git a/runtime/src/softDouble.c b/runtime/src/softDouble.c index beb6bb8..398656b 100644 --- a/runtime/src/softDouble.c +++ b/runtime/src/softDouble.c @@ -305,12 +305,20 @@ s16 __unorddf2(u64 a, u64 b) { return 0; } +// LLVM's soft-float lowering tests each helper's result against zero with +// the comparison's own operator: a < b is __ltdf2(a, b) < 0, a <= b is +// __ledf2(a, b) <= 0, a >= b is __gedf2(a, b) >= 0. So the ordered helpers +// return the three-way order, as softFloat.c's do. They used to return C +// booleans, which made every double `<` false, `>=` always true and `<=` +// inverted (CoreMark's "must run 10 s" check, fmod of a negative quotient +// and asin all went wrong). eq/ne are tested with == 0 / != 0, for which +// "nonzero when different" is enough. s16 __eqdf2(u64 a, u64 b) { return __cmpdf2(a, b) != 0; } s16 __nedf2(u64 a, u64 b) { return __cmpdf2(a, b) != 0; } -s16 __ltdf2(u64 a, u64 b) { return __cmpdf2(a, b) < 0; } -s16 __ledf2(u64 a, u64 b) { return __cmpdf2(a, b) <= 0; } -s16 __gtdf2(u64 a, u64 b) { return __cmpdf2(a, b) > 0; } -s16 __gedf2(u64 a, u64 b) { return __cmpdf2(a, b) >= 0; } +s16 __ltdf2(u64 a, u64 b) { return __cmpdf2(a, b); } +s16 __ledf2(u64 a, u64 b) { return __cmpdf2(a, b); } +s16 __gtdf2(u64 a, u64 b) { return __cmpdf2(a, b); } +s16 __gedf2(u64 a, u64 b) { return __cmpdf2(a, b); } // double <-> float conversions. u64 __extendsfdf2(u32 x) { diff --git a/scripts/runMultiSeg.sh b/scripts/runMultiSeg.sh index 38f5813..9a03129 100755 --- a/scripts/runMultiSeg.sh +++ b/scripts/runMultiSeg.sh @@ -5,6 +5,15 @@ # entry, lets the program run, then reads check-address values. # # Usage: runMultiSeg.sh [check args like runInMame.sh] +# +# Optional hooks for long-running programs (all off by default): +# MAME_DONE_ADDR / MAME_DONE_VAL run the checks as soon as the u16 at +# DONE_ADDR reads DONE_VAL, instead of at MAME_CHECK_FRAME (which then +# only caps the run), so the wall time follows the program's own. +# MAME_TICK_ADDR store the emulated time in milliseconds there (u32) every +# frame: a clock for bare-metal code, which runs with IRQs off. +# MAME_TEXT_ADDR print the NUL-terminated text there (4 KB at most) as +# MAME-TEXT lines when the checks run; its first byte is zeroed at load. set -euo pipefail source "$(dirname "$0")/common.sh" @@ -13,6 +22,10 @@ MANIFEST="$1" shift CHECK_FRAME=${MAME_CHECK_FRAME:-300} SECS=${MAME_SECS:-6} +DONE_ADDR=${MAME_DONE_ADDR:-} +DONE_VAL=${MAME_DONE_VAL:-} +TICK_ADDR=${MAME_TICK_ADDR:-} +TEXT_ADDR=${MAME_TEXT_ADDR:-} # Build address list as Lua table entries, mirroring runInMame.sh. LUA_CHECKS="" @@ -52,7 +65,11 @@ EOF [ -n "$PARSED" ] || die "manifest parse failed" LUA_PATH=$(mktemp --suffix=.lua) -trap 'rm -f "$LUA_PATH"' EXIT +# MAME's cfg / nvram (the IIgs battery RAM) go to a throwaway directory: +# run from a test directory with no mame.ini, MAME would otherwise leave +# them there, and a saved BRAM would carry settings from run to run. +MAME_STATE=$(mktemp -d) +trap 'rm -f "$LUA_PATH"; rm -rf "${MAME_STATE:?}"' EXIT # Build the per-segment load lua. LOAD_LUA="" @@ -79,15 +96,41 @@ while IFS='|' read -r img base entry sz; do fi done <<< "$PARSED" +# Lua fragments for the optional hooks. +# Each hook's memory is cleared at load, so stale RAM can neither end the +# run early nor pass for program output. +DONE_LUA="local done = false" +INIT_LUA="" +if [ -n "$DONE_ADDR" ]; then + DONE_LUA="local done = loaded and mem:read_u16($DONE_ADDR) == $DONE_VAL" + INIT_LUA="$INIT_LUA mem:write_u16($DONE_ADDR, 0)" +fi +TICK_LUA="" +if [ -n "$TICK_ADDR" ]; then + TICK_LUA="if loaded then mem:write_u32($TICK_ADDR, math.floor(emu.time() * 1000) & 0xFFFFFFFF) end" +fi +TEXT_LUA="" +if [ -n "$TEXT_ADDR" ]; then + INIT_LUA="$INIT_LUA mem:write_u8($TEXT_ADDR, 0)" + TEXT_LUA="local text = '' + for i = 0, 0x0FFF do + local c = mem:read_u8($TEXT_ADDR + i) + if c == 0 then break end + text = text .. string.char(c) + end + for line in text:gmatch('[^\\n]+') do print('MAME-TEXT ' .. line) end" +fi + cat > "$LUA_PATH" <> 16) & 0xff @@ -98,9 +141,11 @@ $LOAD_LUA cpu.state["S"].value = 0x01FF print('MAME-READY pc=0x' .. string.format('%06x', $ENTRY_BASE + $ENTRY_OFF)) end - if frame == $CHECK_FRAME then - local cpu = manager.machine.devices[":maincpu"] - local mem = cpu.spaces["program"] + $TICK_LUA + $DONE_LUA + if done or frame == $CHECK_FRAME then + print(string.format('MAME-STOP frame=%d secs=%.3f', frame, emu.time())) + $TEXT_LUA $LUA_CHECKS manager.machine:exit() end @@ -114,11 +159,14 @@ fi OUT=$(SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout "${MAME_TIMEOUT:-30}" mame apple2gs \ "${RAMSIZE_ARG[@]}" \ -rompath "$PROJECT_ROOT/tools/mame/roms" \ + -cfg_directory "$MAME_STATE" -nvram_directory "$MAME_STATE" \ -plugins -autoboot_script "$LUA_PATH" \ -video none -sound none -nothrottle -seconds_to_run "$SECS" 2>&1 | /usr/bin/grep -E "^(MAME-|SEG-)") echo "$OUT" -mapfile -t GOT_LIST < <(printf '%s\n' "$OUT" | /usr/bin/grep -oE 'val=0x[0-9a-f]+' | sed 's/val=0x//') +# Only the MAME-READ lines: MAME-TEXT is program output and could contain +# anything. +mapfile -t GOT_LIST < <(printf '%s\n' "$OUT" | /usr/bin/grep '^MAME-READ' | /usr/bin/grep -oE 'val=0x[0-9a-f]+' | sed 's/val=0x//') ok=1 for i in "${!EXPECT_LIST[@]}"; do if [ "${GOT_LIST[$i]:-}" != "${EXPECT_LIST[$i]}" ]; then diff --git a/scripts/smokeTest.sh b/scripts/smokeTest.sh index 3a34c98..f5914e3 100755 --- a/scripts/smokeTest.sh +++ b/scripts/smokeTest.sh @@ -1633,6 +1633,107 @@ EOF fi rm -f "$cDblMame" "$oDblMame" "$oSdMame" "$binDblMame" + # Every ordered float and double predicate (< <= > >= == !=) on eight + # values, signed zeros included, as a branch and as a 0/1 value; kExp is + # the host-computed expected bit table. Pins two 2026-09-29 fixes: the + # backend read the compare helpers' 16-bit int result as an i32 and + # tested the sign of X (getCmpLibcallReturnType), and softDouble.c's + # __ltdf2 / __ledf2 / __gedf2 returned booleans where LLVM tests a + # three-way order. Before them 396 of the 768 answers were wrong. + log "check: MAME float/double compares: every ordered predicate, signed zeros" + cFpCmpFile="$(mktemp --suffix=.c)" + oFpCmpFile="$(mktemp --suffix=.o)" + binFpCmpFile="$(mktemp --suffix=.bin)" + cat > "$cFpCmpFile" <<'EOF' +// Every ordered float and double predicate on 8 values (signed zeros +// included) as a branch and as a 0/1 value. kExp is the host-computed +// expected bit table. Mailbox: 0x025000 = mismatches, 0x025002 = first +// failing case, 0x025004 = 0xC0DE done. +typedef unsigned char u8; +typedef unsigned short u16; +#define NV 8 +#define NP 6 +#define BAR() __asm__ volatile("" ::: "memory") +#define NOI __attribute__((noinline)) static +static const float kF[NV] = { -2.5f, -1.0f, -0.0f, 0.0f, 1.0f, 1.5f, 10.0f, 1e30f }; +static const double kD[NV] = { -2.5, -1.0, -0.0, 0.0, 1.0, 1.5, 10.0, 1e30 }; +static const u8 kExp[96] = { + 0xFE, 0xFC, 0xF0, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0xFF, 0xFE, 0xFC, 0xFC, 0xF0, 0xE0, 0xC0, 0x80, + 0x00, 0x01, 0x03, 0x03, 0x0F, 0x1F, 0x3F, 0x7F, 0x01, 0x03, 0x0F, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF, + 0x01, 0x02, 0x0C, 0x0C, 0x10, 0x20, 0x40, 0x80, 0xFE, 0xFD, 0xF3, 0xF3, 0xEF, 0xDF, 0xBF, 0x7F, + 0xFE, 0xFC, 0xF0, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0xFF, 0xFE, 0xFC, 0xFC, 0xF0, 0xE0, 0xC0, 0x80, + 0x00, 0x01, 0x03, 0x03, 0x0F, 0x1F, 0x3F, 0x7F, 0x01, 0x03, 0x0F, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF, + 0x01, 0x02, 0x0C, 0x0C, 0x10, 0x20, 0x40, 0x80, 0xFE, 0xFD, 0xF3, 0xF3, 0xEF, 0xDF, 0xBF, 0x7F, +}; +#define ALL(M, T, p) M(T, p, lt, <) M(T, p, le, <=) M(T, p, gt, >) M(T, p, ge, >=) M(T, p, eq, ==) M(T, p, ne, !=) +#define BR(T, p, n, op) NOI u16 br##p##_##n(T a, T b) { if (a op b) { BAR(); return 1u; } return 0u; } +#define VA(T, p, n, op) NOI u16 va##p##_##n(T a, T b) { return (u16)(a op b); } +ALL(BR, float, f) +ALL(VA, float, f) +ALL(BR, double, d) +ALL(VA, double, d) +typedef u16 (*FnF)(float, float); +typedef u16 (*FnD)(double, double); +#define R(p, k) p##k##_lt, p##k##_le, p##k##_gt, p##k##_ge, p##k##_eq, p##k##_ne +static FnF const kBrF[NP] = { R(br, f) }; +static FnF const kVaF[NP] = { R(va, f) }; +static FnD const kBrD[NP] = { R(br, d) }; +static FnD const kVaD[NP] = { R(va, d) }; +static u16 gBad; +static u16 gFirst = 0xFFFFu; +static void check(u16 got, u16 want, u16 id) { + if (got != want) { + if (gBad == 0u) { + gFirst = id; + } + gBad++; + } +} +int main(void) { + u16 p; + u16 i; + u16 j; + u16 bit = 0u; + for (p = 0u; p < NP; p++) { + for (i = 0u; i < NV; i++) { + for (j = 0u; j < NV; j++) { + u16 want = (u16)((kExp[bit >> 3] >> (bit & 7u)) & 1u); + check(kBrF[p](kF[i], kF[j]), want, bit); + check(kVaF[p](kF[i], kF[j]) != 0u, want, (u16)(bit | 0x4000u)); + bit++; + } + } + } + for (p = 0u; p < NP; p++) { + for (i = 0u; i < NV; i++) { + for (j = 0u; j < NV; j++) { + u16 want = (u16)((kExp[bit >> 3] >> (bit & 7u)) & 1u); + check(kBrD[p](kD[i], kD[j]), want, bit); + check(kVaD[p](kD[i], kD[j]) != 0u, want, (u16)(bit | 0x4000u)); + bit++; + } + } + } + *(volatile u16 *)0x025000UL = gBad; + *(volatile u16 *)0x025002UL = gFirst; + *(volatile u16 *)0x025004UL = 0xC0DEu; + for (;;) { + } + return 0; +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cFpCmpFile" -o "$oFpCmpFile" + "$PROJECT_ROOT/tools/link816" -o "$binFpCmpFile" --text-base 0x1000 \ + "$oCrt0File" "$oFpCmpFile" "$PROJECT_ROOT/runtime/softFloat.o" \ + "$PROJECT_ROOT/runtime/softDouble.o" "$oLibgccFile" >/dev/null 2>&1 + if ! MAME_CHECK_FRAME=900 MAME_SECS=20 \ + bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binFpCmpFile" --check \ + 0x025000=0000 0x025002=ffff 0x025004=c0de >/dev/null 2>&1; then + die "MAME: float/double compares wrong (mailbox 0x025000 = mismatches, 0x025002 = first case)" + fi + rm -f "$cFpCmpFile" "$oFpCmpFile" "$binFpCmpFile" + # Regression: W65816SepRepCleanup's PHI-copy hoist must not move a # `STA slot` above an `AND/ORA/EOR/ADC/SBC/CMP_StackRel slot` read in # the gap. Before the fix (gap check only guarded LDA_StackRel), @@ -3089,6 +3190,410 @@ EOF fi rm -f "$cI32CmpFile" "$oI32CmpFile" "$binI32CmpFile" + # The 2026-09-29 compare / loop lowering: sign tests on i8 / i16 / + # i32 (bmi / bpl, `and #$80`, the `cmp #$8000` carry form under a PHI + # copy), i32 == / != split into per-half branches (W65816CmpPrepare, + # including an argument's high word, which arrives in X: the + # W65816LowerWide32 register-class fix), i32 shifts by 16 to 31, and + # loops with constant bounds across 0x7FFF / 0x8000 / 0xFFFF / + # 0x10000 whose test moved onto the incremented counter. kExp holds + # the same functions' results built for the host by gcc (1663 of + # them). Without the LowerWide32 fix the program never finishes. + log "check: MAME compare/loop lowering: sign tests, i32 ==/!= split, shifts >= 16, post-increment loop bounds" + cLowerFile="$(mktemp --suffix=.c)" + oLowerFile="$(mktemp --suffix=.o)" + binLowerFile="$(mktemp --suffix=.bin)" + cat > "$cLowerFile" <<'EOF' +// Sign tests (i8 / i16 / i32 against 0 and -1), i32 == / != split into +// per-half branches, i32 shifts by 16 and more, and loops with constant +// bounds across 0x7FFF / 0x8000 / 0xFFFF / 0x10000 tested on the +// incremented counter. kExp holds the results of the same functions +// built for the host by gcc. Mailbox: 0x025000 = mismatches, 0x025002 = +// first failing result index, 0x025004 = results checked, 0x025006 = +// 0xC0DE done. No : int is 16 bits and long 32 here. +typedef unsigned short u16; +typedef short s16; +typedef unsigned long u32; +typedef long s32; +#define BAR() __asm__ volatile("" ::: "memory") +#define NOI __attribute__((noinline)) static +#define NV16 15 +#define NV8 6 +#define NV32 12 +static volatile u16 gSink; +static volatile s16 gNeg16 = -5; +static volatile signed char gNeg8 = -5; +static const u16 kV16[NV16] = { 0x0000, 0x0001, 0x0002, 0x007F, 0x0080, 0x00FF, 0x0100, 0x7FFE, 0x7FFF, 0x8000, 0x8001, 0xFF7F, 0xFF80, 0xFFFE, 0xFFFF }; +static const unsigned char kV8[NV8] = { 0x00, 0x01, 0x7F, 0x80, 0x81, 0xFF }; +static const u32 kV32[NV32] = { 0x00000000, 0x00000001, 0x00007FFF, 0x00008000, 0x0000FFFF, 0x00010000, 0x7FFFFFFF, 0x80000000, 0xFFFFFFFF, 0xFFFF0000, 0x80008000, 0x12345678 }; +// Sign tests on i16: branch, value, select, and on values made by an add +// (the flags of the ADC stand in for `cmp #0`), a subtract, and a call. +NOI u16 b16lt(s16 x) { if (x < 0) { BAR(); return 1; } return 0; } +NOI u16 b16ge(s16 x) { if (x >= 0) { BAR(); return 1; } return 0; } +NOI u16 b16gtm1(s16 x) { if (x > -1) { BAR(); return 1; } return 0; } +NOI u16 b16lem1(s16 x) { if (x <= -1) { BAR(); return 1; } return 0; } +NOI u16 v16lt(s16 x) { return (u16)(x < 0); } +NOI u16 v16ge(s16 x) { return (u16)(x >= 0); } +NOI u16 s16lt(s16 x, u16 a, u16 b) { return x < 0 ? a : b; } +NOI u16 s16ge(s16 x, u16 a, u16 b) { return x >= 0 ? a : b; } +NOI u16 b16addlt(s16 x, s16 y) { if ((s16)((u16)x + (u16)y) < 0) { BAR(); return 1; } return 0; } +NOI u16 b16subge(s16 x, s16 y) { if ((s16)((u16)x - (u16)y) >= 0) { BAR(); return 1; } return 0; } +NOI s16 id16(s16 x) { BAR(); return x; } +NOI u16 b16calllt(s16 x) { if (id16(x) < 0) { BAR(); return 1; } return 0; } +NOI u16 b16memlt(void) { if (gNeg16 < 0) { BAR(); return 1; } return 0; } +NOI u16 b16loopge(s16 n) { u16 c = 0; s16 i; for (i = n; i >= 0; i--) { gSink = (u16)i; c++; } return c; } +// Sign tests on i8, as values and from memory. +NOI u16 b8lt(signed char x) { if (x < 0) { BAR(); return 1; } return 0; } +NOI u16 b8ge(signed char x) { if (x >= 0) { BAR(); return 1; } return 0; } +NOI u16 v8lt(signed char x) { return (u16)(x < 0); } +NOI u16 s8ge(signed char x, u16 a, u16 b) { return x >= 0 ? a : b; } +NOI u16 b8plt(const signed char *p) { if (*p < 0) { BAR(); return 1; } return 0; } +NOI u16 b8memlt(void) { if (gNeg8 < 0) { BAR(); return 1; } return 0; } +// Sign tests on i32 (the high half's sign). +NOI u16 b32lt(s32 x) { if (x < 0) { BAR(); return 1; } return 0; } +NOI u16 b32ge(s32 x) { if (x >= 0) { BAR(); return 1; } return 0; } +NOI u16 b32gtm1(s32 x) { if (x > -1) { BAR(); return 1; } return 0; } +NOI u16 b32lem1(s32 x) { if (x <= -1) { BAR(); return 1; } return 0; } +NOI u16 v32lt(s32 x) { return (u16)(x < 0); } +NOI u16 s32ge(s32 x, u16 a, u16 b) { return x >= 0 ? a : b; } +// i32 == / != feeding a branch, against a variable, against constants +// with two non-zero halves and with a zero half, on pointers, and the +// high half of an argument compared on its own. +NOI u16 beq(u32 a, u32 b) { if (a == b) { BAR(); return 1; } return 0; } +NOI u16 bne(u32 a, u32 b) { if (a != b) { BAR(); return 1; } return 0; } +NOI u16 beqK(u32 a) { if (a == 0x12345678) { BAR(); return 1; } return 0; } +NOI u16 bneK(u32 a) { if (a != 0x80008000) { BAR(); return 1; } return 0; } +NOI u16 beqKm1(u32 a) { if (a == 0xFFFFFFFF) { BAR(); return 1; } return 0; } +NOI u16 bneK16(u32 a) { if (a != 0x00008000) { BAR(); return 1; } return 0; } +NOI u16 beqKhi(u32 a) { if (a == 0x00010000) { BAR(); return 1; } return 0; } +NOI u16 bhieq(u32 a, u16 k) { if ((u16)(a >> 16) == k) { BAR(); return 1; } return 0; } +NOI u16 beqsum(u32 a, u32 b, u32 c) { if (a + b == c) { BAR(); return 1; } return 0; } +// i32 shifts by 16 and more. +NOI u32 shr16(u32 a) { return a >> 16; } +NOI u32 shr20(u32 a) { return a >> 20; } +NOI u32 shr31(u32 a) { return a >> 31; } +NOI u32 shl16(u32 a) { return a << 16; } +NOI u32 shl24(u32 a) { return a << 24; } +NOI s32 sar16(s32 a) { return a >> 16; } +NOI s32 sar31(s32 a) { return a >> 31; } +NOI u16 hiword(u32 a) { return (u16)(a >> 16); } +// Loops with constant bounds across 0x7FFF / 0x8000 / 0xFFFF / 0x10000: +// the test is on the incremented counter once W65816CmpPrepare runs. +// Each returns the trip count; the counter is also stored, so it stays +// live in the body. +#define ULT32(n, K) NOI u16 n(u32 s) { u16 c = 0; u32 i; for (i = s; i < (u32)(K); i++) { gSink = (u16)i; c++; } return c; } +#define ULE32(n, K) NOI u16 n(u32 s) { u16 c = 0; u32 i; for (i = s; i <= (u32)(K); i++) { gSink = (u16)i; c++; } return c; } +#define SLT32(n, K) NOI u16 n(s32 s) { u16 c = 0; s32 i; for (i = s; i < (s32)(K); i++) { gSink = (u16)i; c++; } return c; } +#define SLE32(n, K) NOI u16 n(s32 s) { u16 c = 0; s32 i; for (i = s; i <= (s32)(K); i++) { gSink = (u16)i; c++; } return c; } +#define NE32(n, K) NOI u16 n(u32 s) { u16 c = 0; u32 i; for (i = s; i != (u32)(K); i++) { gSink = (u16)i; c++; } return c; } +#define UGT32D(n, K) NOI u16 n(u32 s) { u16 c = 0; u32 i; for (i = s; i > (u32)(K); i--) { gSink = (u16)i; c++; } return c; } +#define SGE32D(n, K) NOI u16 n(s32 s) { u16 c = 0; s32 i; for (i = s; i >= (s32)(K); i--) { gSink = (u16)i; c++; } return c; } +#define ULT16(n, K) NOI u16 n(u16 s) { u16 c = 0; u16 i; for (i = s; i < (u16)(K); i++) { gSink = i; c++; } return c; } +#define SLT16(n, K) NOI u16 n(s16 s) { u16 c = 0; s16 i; for (i = s; i < (s16)(K); i++) { gSink = (u16)i; c++; } return c; } +#define SLE16(n, K) NOI u16 n(s16 s) { u16 c = 0; s16 i; for (i = s; i <= (s16)(K); i++) { gSink = (u16)i; c++; } return c; } +#define CNT32(n, K) NOI u16 n(u32 s) { u16 c = 0; u32 i; for (i = s; i < (u32)(K); i++) { c++; } BAR(); return c; } +ULT32(lu7fff, 0x7FFF) ULT32(lu8000, 0x8000) ULT32(lu8001, 0x8001) ULT32(luffff, 0xFFFF) +ULT32(lu10000, 0x10000) ULT32(lu10001, 0x10001) ULT32(lu1fff0, 0x1FFF0) ULT32(lufffe, 0xFFFFFFFE) ULT32(luffffffff, 0xFFFFFFFF) +ULE32(le7fff, 0x7FFF) ULE32(leffff, 0xFFFF) ULE32(le10000, 0x10000) ULE32(lefffffffe, 0xFFFFFFFE) +SLT32(ls8000, 0x8000) ULT32(ls10000u, 0x10000) SLT32(ls10000, 0x10000) SLT32(ls0, 0) SLT32(ls5, 5) SLT32(lsm8000, -0x8000) SLT32(ls7fffffff, 0x7FFFFFFF) +SLE32(lse7fff, 0x7FFF) SLE32(lsem1, -1) SLE32(lse7ffffffe, 0x7FFFFFFE) +NE32(ln8000, 0x8000) NE32(ln10000, 0x10000) NE32(ln20008, 0x20008) +UGT32D(dgffff, 0xFFFF) UGT32D(dg10000, 0x10000) UGT32D(dg0, 0) +SGE32D(dsm1, -1) SGE32D(ds0, 0) SGE32D(dsm10000, -0x10000) +ULT16(l16u7fff, 0x7FFF) ULT16(l16u8000, 0x8000) ULT16(l16uffff, 0xFFFF) +SLT16(l16s7fff, 0x7FFF) SLT16(l16s0, 0) SLT16(l16s100, 100) SLT16(l16sm100, -100) +SLE16(l16se7ffe, 0x7FFE) SLE16(l16sem1, -1) +CNT32(c8172, 8172) CNT32(c10000, 0x10000) CNT32(c18000, 0x18000) +// A two-pointer byte loop with a != bound (the probe's shape, via +// pointers) across a bank boundary: only the pointers are compared, never +// dereferenced. +NOI u16 ptrwalk(u32 a, u32 e) { u16 c = 0; unsigned char *p = (unsigned char *)a; unsigned char *end = (unsigned char *)e; while (p != end) { gSink = (u16)(u32)p; p++; c++; } return c; } +#define NEXP 1663u +static const u16 kExp[NEXP] = { + 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0xA5A5, 0x5A5A, 0x0000, 0x0000, 0x0001, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0xA5A5, 0x5A5A, 0x0000, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, + 0xA5A5, 0x5A5A, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0xA5A5, 0x5A5A, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0xA5A5, 0x5A5A, 0x0000, 0x0000, 0x0001, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0xA5A5, 0x5A5A, 0x0000, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, + 0xA5A5, 0x5A5A, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0xA5A5, 0x5A5A, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0xA5A5, 0x5A5A, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x5A5A, 0xA5A5, 0x0001, + 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, + 0x5A5A, 0xA5A5, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, + 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, + 0x0001, 0x0001, 0x0000, 0x5A5A, 0xA5A5, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, + 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x5A5A, 0xA5A5, 0x0001, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x5A5A, 0xA5A5, 0x0001, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, + 0x5A5A, 0xA5A5, 0x0001, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, + 0x0006, 0x0065, 0x0000, 0x0001, 0x0000, 0x5A5A, 0x0000, 0x0000, 0x0001, 0x0000, 0x5A5A, 0x0000, + 0x0000, 0x0001, 0x0000, 0x5A5A, 0x0000, 0x0001, 0x0000, 0x0001, 0xA5A5, 0x0001, 0x0001, 0x0000, + 0x0001, 0xA5A5, 0x0001, 0x0001, 0x0000, 0x0001, 0xA5A5, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x5A5A, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x5A5A, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0100, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x5A5A, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x7FFF, + 0x0000, 0xFF00, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x5A5A, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x5A5A, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFF00, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x5A5A, 0x0000, + 0x0001, 0x0000, 0x0001, 0x0001, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x5A5A, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x7FFF, 0x0000, 0x07FF, 0x0000, + 0x0000, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFF00, 0x7FFF, 0x0000, 0x0000, 0x0000, 0x7FFF, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0xA5A5, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, + 0x8000, 0x0000, 0x0800, 0x0000, 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8000, 0xFFFF, + 0xFFFF, 0xFFFF, 0x8000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0xA5A5, 0x0000, + 0x0001, 0x0001, 0x0001, 0x0000, 0xFFFF, 0x0000, 0x0FFF, 0x0000, 0x0001, 0x0000, 0x0000, 0xFFFF, + 0x0000, 0xFF00, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, + 0x0001, 0x0001, 0xA5A5, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0xFFFF, 0x0000, 0x0FFF, 0x0000, + 0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0xA5A5, 0x0000, 0x0000, 0x0000, 0x0001, 0x0000, + 0x8000, 0x0000, 0x0800, 0x0000, 0x0001, 0x0000, 0x0000, 0x8000, 0x0000, 0x0000, 0x8000, 0xFFFF, + 0xFFFF, 0xFFFF, 0x8000, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x5A5A, 0x0001, + 0x0001, 0x0000, 0x0001, 0x0000, 0x1234, 0x0000, 0x0123, 0x0000, 0x0000, 0x0000, 0x0000, 0x5678, + 0x0000, 0x7800, 0x1234, 0x0000, 0x0000, 0x0000, 0x1234, 0x0001, 0x0001, 0x0000, 0x0001, 0x0001, + 0x0001, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0000, 0x0001, 0x0001, + 0x0000, 0x0000, 0x0001, 0x0001, 0x0000, 0x0001, 0x0000, 0x0001, 0x0000, 0x0010, 0x0001, 0x0000, + 0x0000, 0x0010, 0x0001, 0x0000, 0x0000, 0x0010, 0x0001, 0x0000, 0x0000, 0x0010, 0x0001, 0x0000, + 0x0000, 0x0010, 0x0001, 0x0000, 0x0000, 0x0010, 0x0001, 0x0000, 0x0000, 0x0010, 0x0001, 0x0000, + 0x0010, 0x0001, 0x0000, 0x0000, 0x000F, 0x0001, 0x0000, 0x0011, 0x0001, 0x0000, 0x0011, 0x0001, + 0x0000, 0x0011, 0x0001, 0x0000, 0x000F, 0x0001, 0x0000, 0x0010, 0x0001, 0x0000, 0x8003, 0x0010, + 0x0010, 0x0001, 0x0000, 0x0010, 0x0001, 0x0000, 0x0000, 0x0008, 0x0001, 0x0000, 0x0010, 0x0001, + 0x0000, 0x000F, 0x0001, 0x0000, 0x0010, 0x0001, 0x0000, 0x0010, 0x0001, 0x0000, 0x000F, 0x0001, + 0x0000, 0x0010, 0x0000, 0x0010, 0x0010, 0x0011, 0x0001, 0x0000, 0x0010, 0x0000, 0x0010, 0x0001, + 0x0000, 0x0012, 0x0002, 0x0001, 0x0000, 0x0011, 0x0001, 0x0000, 0x0011, 0x0001, 0x0000, 0x000F, + 0x0000, 0x0000, 0x0010, 0x0000, 0x000F, 0x0001, 0x0000, 0x000F, 0x0000, 0x0010, 0x0001, 0x0000, + 0x000A, 0x0000, 0x0014, 0x0000, 0x0000, 0x000F, 0x0001, 0x0000, 0x0010, 0x0001, 0x0000, 0x1FEC, + 0x00AC, 0x0010, 0x0010, 0x0000, 0x0020, 0x0002, 0x0000, +}; +static u16 gIdx; +static u16 gBad; +static u16 gFirst = 0xFFFFu; +static void emit(u16 v) { + if (gIdx >= NEXP || v != kExp[gIdx]) { + if (gBad == 0u) { + gFirst = gIdx; + } + gBad++; + } + gIdx++; +} +static void emit32(u32 v) { + emit((u16)(v & 0xFFFF)); + emit((u16)(v >> 16)); +} +static void runAll(void) { + u16 i; + u16 j; + for (i = 0; i < NV16; i++) { + s16 x = (s16)kV16[i]; + emit(b16lt(x)); + emit(b16ge(x)); + emit(b16gtm1(x)); + emit(b16lem1(x)); + emit(v16lt(x)); + emit(v16ge(x)); + emit(s16lt(x, 0x5A5A, 0xA5A5)); + emit(s16ge(x, 0x5A5A, 0xA5A5)); + emit(b16calllt(x)); + for (j = 0; j < NV16; j++) { + emit(b16addlt(x, (s16)kV16[j])); + emit(b16subge(x, (s16)kV16[j])); + } + } + emit(b16memlt()); + emit(b16loopge(-1)); + emit(b16loopge(0)); + emit(b16loopge(5)); + emit(b16loopge(100)); + for (i = 0; i < NV8; i++) { + signed char c = (signed char)kV8[i]; + emit(b8lt(c)); + emit(b8ge(c)); + emit(v8lt(c)); + emit(s8ge(c, 0x5A5A, 0xA5A5)); + emit(b8plt(&c)); + } + emit(b8memlt()); + for (i = 0; i < NV32; i++) { + u32 v = kV32[i]; + emit(b32lt((s32)v)); + emit(b32ge((s32)v)); + emit(b32gtm1((s32)v)); + emit(b32lem1((s32)v)); + emit(v32lt((s32)v)); + emit(s32ge((s32)v, 0x5A5A, 0xA5A5)); + emit(beqK(v)); + emit(bneK(v)); + emit(beqKm1(v)); + emit(bneK16(v)); + emit(beqKhi(v)); + emit32(shr16(v)); + emit32(shr20(v)); + emit32(shr31(v)); + emit32(shl16(v)); + emit32(shl24(v)); + emit32((u32)sar16((s32)v)); + emit32((u32)sar31((s32)v)); + emit(hiword(v)); + emit(bhieq(v, (u16)(v >> 16))); + emit(bhieq(v, 0x1234)); + for (j = 0; j < NV32; j++) { + u32 w = kV32[j]; + emit(beq(v, w)); + emit(bne(v, w)); + emit(beqsum(v, w, v + w)); + emit(beqsum(v, w, v)); + } + } + emit(lu7fff(0x7FEF)); emit(lu7fff(0x7FFE)); emit(lu7fff(0x7FFF)); emit(lu7fff(0x8000)); + emit(lu8000(0x7FF0)); emit(lu8000(0x7FFF)); emit(lu8000(0x8000)); emit(lu8000(0x8001)); + emit(lu8001(0x7FF1)); emit(lu8001(0x8000)); emit(lu8001(0x8001)); emit(lu8001(0x8002)); + emit(luffff(0xFFEF)); emit(luffff(0xFFFE)); emit(luffff(0xFFFF)); emit(luffff(0x10000)); + emit(lu10000(0xFFF0)); emit(lu10000(0xFFFF)); emit(lu10000(0x10000)); emit(lu10000(0x10001)); + emit(lu10001(0xFFF1)); emit(lu10001(0x10000)); emit(lu10001(0x10001)); emit(lu10001(0x10002)); + emit(lu1fff0(0x1FFE0)); emit(lu1fff0(0x1FFEF)); emit(lu1fff0(0x1FFF0)); + emit(lufffe(0xFFFFFFEE)); emit(lufffe(0xFFFFFFFD)); emit(lufffe(0xFFFFFFFE)); emit(lufffe(0xFFFFFFFF)); + emit(luffffffff(0xFFFFFFF0)); emit(luffffffff(0xFFFFFFFE)); emit(luffffffff(0xFFFFFFFF)); + emit(le7fff(0x7FEF)); emit(le7fff(0x7FFF)); emit(le7fff(0x8000)); + emit(leffff(0xFFEF)); emit(leffff(0xFFFF)); emit(leffff(0x10000)); + emit(le10000(0xFFF0)); emit(le10000(0x10000)); emit(le10000(0x10001)); + emit(lefffffffe(0xFFFFFFF0)); emit(lefffffffe(0xFFFFFFFE)); emit(lefffffffe(0xFFFFFFFF)); + emit(ls8000(0x7FF0)); emit(ls8000(0x7FFF)); emit(ls8000(0x8000)); emit(ls8000(-3)); + emit(ls10000u(0xFFF0)); emit(ls10000(0xFFF0)); emit(ls10000(0xFFFF)); emit(ls10000(0x10000)); + emit(ls0(-16)); emit(ls0(-1)); emit(ls0(0)); emit(ls0(1)); + emit(ls5(-3)); emit(ls5(4)); emit(ls5(5)); + emit(lsm8000(-0x8010)); emit(lsm8000(-0x8001)); emit(lsm8000(-0x8000)); + emit(ls7fffffff(0x7FFFFFF0)); emit(ls7fffffff(0x7FFFFFFE)); emit(ls7fffffff(0x7FFFFFFF)); + emit(lse7fff(0x7FF0)); emit(lse7fff(0x7FFF)); emit(lse7fff(0x8000)); + emit(lsem1(-16)); emit(lsem1(-1)); emit(lsem1(0)); + emit(lse7ffffffe(0x7FFFFFF0)); emit(lse7ffffffe(0x7FFFFFFE)); emit(lse7ffffffe(0x7FFFFFFF)); + emit(ln8000(0x7FF0)); emit(ln8000(0x8000)); emit(ln10000(0xFFF0)); emit(ln20008(0x1FFF8)); + emit(dgffff(0x10010)); emit(dgffff(0x10000)); emit(dgffff(0xFFFF)); + emit(dg10000(0x10010)); emit(dg10000(0x10000)); + emit(dg0(16)); emit(dg0(1)); emit(dg0(0)); + emit(dsm1(16)); emit(dsm1(0)); emit(dsm1(-1)); emit(dsm1(-2)); + emit(ds0(16)); emit(ds0(0)); emit(ds0(-1)); + emit(dsm10000(-0xFFF0)); emit(dsm10000(-0x10000)); emit(dsm10000(-0x10001)); + emit(l16u7fff(0x7FF0)); emit(l16u7fff(0x7FFF)); emit(l16u7fff(0x8000)); + emit(l16u8000(0x7FF0)); emit(l16u8000(0x8000)); + emit(l16uffff(0xFFF0)); emit(l16uffff(0xFFFE)); emit(l16uffff(0xFFFF)); + emit(l16s7fff(0x7FF0)); emit(l16s7fff(0x7FFF)); + emit(l16s0(-16)); emit(l16s0(-1)); emit(l16s0(0)); + emit(l16s100(90)); emit(l16s100(100)); + emit(l16sm100(-120)); emit(l16sm100(-100)); emit(l16sm100(-99)); + emit(l16se7ffe(0x7FF0)); emit(l16se7ffe(0x7FFE)); emit(l16se7ffe(0x7FFF)); + emit(l16sem1(-16)); emit(l16sem1(-1)); emit(l16sem1(0)); + emit(c8172(0)); emit(c8172(8000)); emit(c10000(0xFFF0)); emit(c18000(0x17FF0)); emit(c18000(0x18000)); + emit(ptrwalk(0x01FFF0, 0x020010)); emit(ptrwalk(0x02FFFF, 0x030001)); emit(ptrwalk(0x012345, 0x012345)); +} +int main(void) { + runAll(); + *(volatile u16 *)0x025000UL = gBad; + *(volatile u16 *)0x025002UL = gFirst; + *(volatile u16 *)0x025004UL = gIdx; + *(volatile u16 *)0x025006UL = 0xC0DEu; + for (;;) { + } + return 0; +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cLowerFile" -o "$oLowerFile" + "$PROJECT_ROOT/tools/link816" -o "$binLowerFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibgccFile" "$oLowerFile" \ + >/dev/null 2>&1 + if ! MAME_CHECK_FRAME=900 MAME_SECS=20 \ + bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binLowerFile" --check \ + 0x025000=0000 0x025002=ffff 0x025004=067f 0x025006=c0de >/dev/null 2>&1; then + die "MAME: compare/loop lowering wrong (mailbox 0x025000 = mismatches, 0x025002 = first result index)" + fi + rm -f "$cLowerFile" "$oLowerFile" "$binLowerFile" + # the list, creating an overlapping free entry. A subsequent # malloc could hand out the same memory to two callers. log "check: MAME runs malloc/free coalesce — three blocks freed in alloc order (#100)" diff --git a/src/llvm/lib/Target/W65816/CMakeLists.txt b/src/llvm/lib/Target/W65816/CMakeLists.txt index 9400e96..06324ec 100644 --- a/src/llvm/lib/Target/W65816/CMakeLists.txt +++ b/src/llvm/lib/Target/W65816/CMakeLists.txt @@ -38,6 +38,7 @@ add_llvm_target(W65816CodeGen W65816I32IncFold.cpp W65816ImgCalleeSave.cpp W65816NarrowI32Mul.cpp + W65816CmpPrepare.cpp W65816PromoteFiToImg.cpp W65816StackRelToImg.cpp W65816StackSlotMerge.cpp @@ -58,6 +59,7 @@ add_llvm_target(W65816CodeGen Support Target TargetParser + TransformUtils W65816Desc W65816Info diff --git a/src/llvm/lib/Target/W65816/W65816.h b/src/llvm/lib/Target/W65816/W65816.h index 3a84bf1..f8c784a 100644 --- a/src/llvm/lib/Target/W65816/W65816.h +++ b/src/llvm/lib/Target/W65816/W65816.h @@ -125,6 +125,12 @@ FunctionPass *createW65816SjLjFinalize(); // zext that a SDAG-level combine would key off. See W65816NarrowI32Mul.cpp. FunctionPass *createW65816NarrowI32Mul(); +// IR pass before ISel: loop tests on the incremented counter (`V < C` as +// `V+1 < C+1` when V+1 is already computed), and i32 == / != feeding a +// branch split into per-half i16 compares SelectionDAGBuilder branches on +// one at a time. See W65816CmpPrepare.cpp. +FunctionPass *createW65816CmpPrepare(); + // Post-LSR IR pass: detect pointer-walking loops over GlobalAddress // bases and rewrite to forward-counter + indexed GEP form. LSR's // transform is faster for pointer-arg loops (single ptr increment per @@ -213,6 +219,7 @@ void initializeW65816SjLjFinalizePass(PassRegistry &); void initializeW65816LowerWide32Pass(PassRegistry &); void initializeW65816ImgCalleeSavePass(PassRegistry &); void initializeW65816NarrowI32MulPass(PassRegistry &); +void initializeW65816CmpPreparePass(PassRegistry &); void initializeW65816PromoteFiToImgPass(PassRegistry &); void initializeW65816StackSlotMergePass(PassRegistry &); void initializeW65816StackRelToImgPass(PassRegistry &); diff --git a/src/llvm/lib/Target/W65816/W65816CmpPrepare.cpp b/src/llvm/lib/Target/W65816/W65816CmpPrepare.cpp new file mode 100644 index 0000000..4f2eaa0 --- /dev/null +++ b/src/llvm/lib/Target/W65816/W65816CmpPrepare.cpp @@ -0,0 +1,223 @@ +//===-- W65816CmpPrepare.cpp - Shape compares before ISel ----------------===// +// +// 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 +// +//===---------------------------------------------------------------------===// +// +// IR-level compare shaping. Runs in addISelPrepare, after LSR and +// CodeGenPrepare, so no later IR pass reshapes what it produces. +// +// 1. Loop tests on the incremented counter. For a constant bound, +// instcombine turns `++i < N` into `i < N-1`; LSR's post-increment +// rewrite gets only as far as `(i+1) - 1 < N-1`, which SelectionDAG folds +// straight back to `i < N-1`. Either way the old and the new counter are +// both live at the test, which for an i32 counter is a second register +// pair, an add into it and the copies back (~27 cycles a trip in a byte +// loop). Rewriting `V pred C` as `(V+1) pred (C+1)`, with V+1 already +// computed in the block, leaves one counter live and lets +// W65816I32IncFold increment it in place. Valid when neither V+1 nor C+1 +// wraps in the predicate's signedness (ScalarEvolution's range of V rules +// out V == MAX); == and != hold either way. +// +// 2. i32 == / != feeding a branch. lowerI32Cmp answers them without a +// branch as (lo ^ rlo) | (hi ^ rhi), which costs a temporary and ~24-27 +// cycles against another variable (a pointer end, say) or a constant +// whose halves are both non-zero. Splitting the compare into two i16 +// compares joined by `or` (!=) or `and` (==) lets SelectionDAGBuilder +// emit one branch per half. The low halves go first: values that differ +// mostly differ there, so one compare usually decides. Against zero, or +// a constant with a zero half, the XOR form is a single `lda ; eor ; ora` +// and stays. +// +//===---------------------------------------------------------------------===// + +#include "W65816.h" +#include "llvm/Analysis/ScalarEvolution.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/PatternMatch.h" +#include "llvm/InitializePasses.h" +#include "llvm/Pass.h" +#include "llvm/Transforms/Utils/Local.h" + +using namespace llvm; +using namespace llvm::PatternMatch; + +#define DEBUG_TYPE "w65816-cmp-prepare" + + +namespace { + + +class W65816CmpPrepare : public FunctionPass { +public: + static char ID; + W65816CmpPrepare() : FunctionPass(ID) {} + + StringRef getPassName() const override { + return "W65816 compare shaping before ISel"; + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.addRequired(); + } + + bool runOnFunction(Function &F) override; +}; + + +} // namespace + + +char W65816CmpPrepare::ID = 0; + +INITIALIZE_PASS_BEGIN(W65816CmpPrepare, DEBUG_TYPE, + "W65816 compare shaping before ISel", false, false) +INITIALIZE_PASS_DEPENDENCY(ScalarEvolutionWrapperPass) +INITIALIZE_PASS_END(W65816CmpPrepare, DEBUG_TYPE, + "W65816 compare shaping before ISel", false, false) + + +// The value V + 1, if the block already computes it before Cmp: either V is +// `(X + 1) - 1` (LSR's post-increment form) or a user of V is `add V, 1`. +// W must be the increment itself: for V = `i - 1` in a count-down loop, i is +// the OLD counter, and moving the test onto it is the very problem this +// fixes, in reverse. +static Value *findIncremented(Value *V, ICmpInst *Cmp) { + Value *W; + if (match(V, m_Add(m_Value(W), m_AllOnes())) && + match(W, m_Add(m_Value(), m_One()))) + return W; + for (User *U : V->users()) { + auto *Inc = dyn_cast(U); + if (!Inc || Inc->getParent() != Cmp->getParent() || + !Inc->comesBefore(Cmp)) + continue; + if (match(Inc, m_Add(m_Specific(V), m_One()))) + return Inc; + } + return nullptr; +} + + +// Transform 1: `V pred C` -> `(V+1) pred (C+1)`. V may be left dead; it is +// queued on MaybeDead rather than deleted here, because deleting it could +// take another compare still on the caller's worklist with it. +static bool compareIncremented(ICmpInst *Cmp, ScalarEvolution &SE, + SmallVectorImpl &MaybeDead) { + auto *C = dyn_cast(Cmp->getOperand(1)); + Value *V = Cmp->getOperand(0); + // A constant V (not folded yet) has users all over the module. + if (!C || isa(V) || !V->getType()->isIntegerTy()) + return false; + Value *W = findIncremented(V, Cmp); + if (!W) + return false; + const APInt &K = C->getValue(); + if (!Cmp->isEquality()) { + bool Signed = Cmp->isSigned(); + if (Signed ? K.isMaxSignedValue() : K.isMaxValue()) + return false; + if (!SE.isSCEVable(V->getType())) + return false; + // V's range alone settles a counter with a constant start; one with a + // variable start needs the induction proof isKnownPredicate does (below + // the maximum on loop entry, and after every step the back edge takes). + const SCEV *S = SE.getSCEV(V); + unsigned Bits = K.getBitWidth(); + APInt Max = Signed ? APInt::getSignedMaxValue(Bits) + : APInt::getMaxValue(Bits); + ConstantRange R = Signed ? SE.getSignedRange(S) : SE.getUnsignedRange(S); + if (R.contains(Max) && + !SE.isKnownPredicate(Signed ? ICmpInst::ICMP_SLT : ICmpInst::ICMP_ULT, + S, SE.getConstant(Max))) + return false; + } + Cmp->setOperand(0, W); + Cmp->setOperand(1, ConstantInt::get(C->getType(), K + 1)); + // samesign was a fact about V and C, not about V+1 and C+1. + Cmp->setSameSign(false); + MaybeDead.push_back(V); + return true; +} + + +// True if one half of the i32 constant is zero, where the XOR form of an +// equality test is already as cheap as a branch pair. +static bool hasZeroHalf(const ConstantInt *C) { + uint64_t K = C->getZExtValue(); + return (K & 0xFFFFu) == 0 || (K >> 16) == 0; +} + + +// Transform 2: split an i32 (or pointer) == / != that only feeds its block's +// conditional branch into per-half i16 compares. +static bool splitEquality32(ICmpInst *Cmp, const DataLayout &DL) { + if (!Cmp->isEquality() || !Cmp->hasOneUse()) + return false; + auto *Br = dyn_cast(Cmp->user_back()); + if (!Br || Br->getParent() != Cmp->getParent()) + return false; + Type *Ty = Cmp->getOperand(0)->getType(); + if (Ty->isPointerTy() ? DL.getPointerTypeSizeInBits(Ty) != 32 + : !Ty->isIntegerTy(32)) + return false; + Value *L = Cmp->getOperand(0); + Value *R = Cmp->getOperand(1); + if (auto *RC = dyn_cast(R)) { + if (RC->isNullValue()) + return false; + if (auto *RI = dyn_cast(RC); RI && hasZeroHalf(RI)) + return false; + } + IRBuilder<> B(Cmp); + Type *I16 = B.getInt16Ty(); + Type *I32 = B.getInt32Ty(); + if (Ty->isPointerTy()) { + L = B.CreatePtrToInt(L, I32); + R = B.CreatePtrToInt(R, I32); + } + ICmpInst::Predicate P = Cmp->getPredicate(); + Value *Lo = B.CreateICmp(P, B.CreateTrunc(L, I16), B.CreateTrunc(R, I16)); + Value *Hi = B.CreateICmp(P, B.CreateTrunc(B.CreateLShr(L, 16), I16), + B.CreateTrunc(B.CreateLShr(R, 16), I16)); + Value *Both = P == ICmpInst::ICMP_NE ? B.CreateOr(Lo, Hi) + : B.CreateAnd(Lo, Hi); + Cmp->replaceAllUsesWith(Both); + Cmp->eraseFromParent(); + return true; +} + + +bool W65816CmpPrepare::runOnFunction(Function &F) { + if (skipFunction(F)) + return false; + ScalarEvolution &SE = getAnalysis().getSE(); + const DataLayout &DL = F.getDataLayout(); + + SmallVector Cmps; + for (Instruction &I : instructions(F)) + if (auto *Cmp = dyn_cast(&I)) + Cmps.push_back(Cmp); + + bool Changed = false; + SmallVector MaybeDead; + for (ICmpInst *Cmp : Cmps) { + // The increment rewrite first: it can turn a loop's `!=` test onto the + // new counter, which the split then handles like any other. + Changed |= compareIncremented(Cmp, SE, MaybeDead); + Changed |= splitEquality32(Cmp, DL); + } + RecursivelyDeleteTriviallyDeadInstructionsPermissive(MaybeDead); + return Changed; +} + + +FunctionPass *llvm::createW65816CmpPrepare() { + return new W65816CmpPrepare(); +} diff --git a/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp b/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp index b11759d..8b8168a 100644 --- a/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp +++ b/src/llvm/lib/Target/W65816/W65816ISelLowering.cpp @@ -528,7 +528,6 @@ static void promoteI8Cmp(SDValue &LHS, SDValue &RHS, ISD::CondCode CC, static W65816CC::CondCode normalizeCC(SDValue &LHS, SDValue &RHS, ISD::CondCode &CC, SelectionDAG &DAG, const SDLoc &DL) { - promoteI8Cmp(LHS, RHS, CC, DAG, DL); // CMP wants the comparand (constant or memory) on the right. If a DAG // pre-pass put the constant on the left, swap and flip the condition. if (isa(LHS) && !isa(RHS)) { @@ -536,6 +535,35 @@ static W65816CC::CondCode normalizeCC(SDValue &LHS, SDValue &RHS, CC = ISD::getSetCCSwappedOperands(CC); } + // Sign tests: x < 0 and x >= 0, and the x <= -1 / x > -1 forms instcombine + // canonicalises some of them to. The sign is a single bit, so no ordered + // compare is needed: an i16 is `cmp #0 ; bmi/bpl` (StackSlotCleanup then + // drops the cmp when the op that produced x already set N), 6 cycles less + // than the sign-bias XOR below; an i8's sign is bit 7, so it is + // `and #$80 ; bne/beq` instead of an 11-cycle sign extension. + if (auto *KC = dyn_cast(RHS)) { + int64_t K = KC->getSExtValue(); + bool Negative = + (CC == ISD::SETLT && K == 0) || (CC == ISD::SETLE && K == -1); + bool NonNegative = + (CC == ISD::SETGE && K == 0) || (CC == ISD::SETGT && K == -1); + EVT VT = LHS.getValueType(); + if ((Negative || NonNegative) && (VT == MVT::i16 || VT == MVT::i8)) { + RHS = DAG.getConstant(0, DL, MVT::i16); + if (VT == MVT::i8) { + SDValue Wide = DAG.getNode(ISD::ANY_EXTEND, DL, MVT::i16, LHS); + LHS = DAG.getNode(ISD::AND, DL, MVT::i16, Wide, + DAG.getConstant(0x80, DL, MVT::i16)); + CC = Negative ? ISD::SETNE : ISD::SETEQ; + return Negative ? W65816CC::COND_NE : W65816CC::COND_EQ; + } + CC = Negative ? ISD::SETLT : ISD::SETGE; + return Negative ? W65816CC::COND_MI : W65816CC::COND_PL; + } + } + + promoteI8Cmp(LHS, RHS, CC, DAG, DL); + // Signed compare via "EOR with sign bit then unsigned compare": // a < b (signed) iff (a ^ 0x8000) < (b ^ 0x8000) (unsigned) // The XOR flips the sign bit, which converts signed-int ordering to @@ -2003,6 +2031,34 @@ SDValue W65816TargetLowering::LowerShift(SDValue Op, SelectionDAG &DAG) const { return buildWide32(DAG, DL, NewLo, NewHi); } } + // By 16 or more one half moves into the other and the rest is at most + // an i16 shift, which the i16 patterns do inline for 1..15 (SRA only + // for 15, its sign fill). `(u16)(x >> 16)`, the high word, was a + // __lshrsi3 call; now it is the high half itself. + if (N >= 16 && N <= 31) { + SDLoc DL(Op); + SDValue X = Op.getOperand(0); + SDValue Zero = DAG.getConstant(0, DL, MVT::i16); + SDValue ShN = DAG.getConstant(N - 16, DL, MVT::i16); + if (Op0 == ISD::SHL) { + SDValue Lo = extractWide32Lo(DAG, DL, X); + SDValue NewHi = + N == 16 ? Lo : DAG.getNode(ISD::SHL, DL, MVT::i16, Lo, ShN); + return buildWide32(DAG, DL, Zero, NewHi); + } + if (Op0 == ISD::SRL) { + SDValue Hi = extractWide32Hi(DAG, DL, X); + SDValue NewLo = + N == 16 ? Hi : DAG.getNode(ISD::SRL, DL, MVT::i16, Hi, ShN); + return buildWide32(DAG, DL, NewLo, Zero); + } + if (Op0 == ISD::SRA && (N == 16 || N == 31)) { + SDValue Hi = extractWide32Hi(DAG, DL, X); + SDValue Fill = DAG.getNode(ISD::SRA, DL, MVT::i16, Hi, + DAG.getConstant(15, DL, MVT::i16)); + return buildWide32(DAG, DL, N == 16 ? Hi : Fill, Fill); + } + } } } diff --git a/src/llvm/lib/Target/W65816/W65816ISelLowering.h b/src/llvm/lib/Target/W65816/W65816ISelLowering.h index e026fb8..6f0dc69 100644 --- a/src/llvm/lib/Target/W65816/W65816ISelLowering.h +++ b/src/llvm/lib/Target/W65816/W65816ISelLowering.h @@ -67,6 +67,15 @@ public: return TargetLoweringBase::getScalarShiftAmountTy(DL, LHSTy); } + // The soft-float compare helpers (__ltsf2, __gedf2, __unorddf2, ...) + // return C `int`, which is 16 bits here: the result is in A alone. The + // i32 default read the i32 return pair A:X and tested the sign of X, a + // register the helper never sets, so float and double `<`, `<=`, `>` + // and `>=` answered with whatever X happened to hold. + MVT::SimpleValueType getCmpLibcallReturnType() const override { + return MVT::i16; + } + // ptr32-mode hook: with patches/0007-targetlowering-virtual- // gettypeconversion making the base function virtual, this can be // overridden to force i64 to expand directly to i16 halves rather diff --git a/src/llvm/lib/Target/W65816/W65816LowerWide32.cpp b/src/llvm/lib/Target/W65816/W65816LowerWide32.cpp index 74d1cdb..afc2d9d 100644 --- a/src/llvm/lib/Target/W65816/W65816LowerWide32.cpp +++ b/src/llvm/lib/Target/W65816/W65816LowerWide32.cpp @@ -264,6 +264,51 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) { // Pass 3: rewrite uses. SmallVector useToErase; + // Hand a half to the users of the vreg projected out of the pair, Dst. + // A user whose operand class does not take the half's own class gets a + // COPY of it into that class right before it, as TwoAddress does for a + // tied operand (so tied uses are left to it). Replacing unconditionally + // gave an Img16 half (an argument's high word arrives in X and lives in a + // DP slot) to CMP, whose register operand is implicitly A: the compare + // read whatever A held. The COPY goes at the user rather than at the + // projection so that A, a single register, is held only across the use. + // + // wideMap was filled in Pass 2 and is not updated by the replacements. A + // pair's half can itself be projected out of another pair (`%h = COPY + // %p.sub_hi`, then `%q = REG_SEQUENCE %h, ...`, as an i32 shift by 16 + // builds); once %h is replaced and its COPY erased, handing out %h for + // %q's half would leave uses with no def. So every half read from + // wideMap goes through `current`, which follows the replacements. + const TargetRegisterInfo *TRI = STI.getRegisterInfo(); + DenseMap forwarded; + auto current = [&](Register R) { + for (auto It = forwarded.find(R); It != forwarded.end(); + It = forwarded.find(R)) + R = It->second; + return R; + }; + auto forwardHalf = [&](MachineInstr *MI, Register Dst, Register Half) { + Half = current(Half); + const TargetRegisterClass *HalfRC = MRI.getRegClass(Half); + for (MachineOperand &MO : + make_early_inc_range(MRI.use_nodbg_operands(Dst))) { + MachineInstr *User = MO.getParent(); + unsigned OpNo = User->getOperandNo(&MO); + const TargetRegisterClass *Need = + User->getRegClassConstraint(OpNo, &TII, TRI); + if (!Need || Need->hasSubClassEq(HalfRC) || + User->isRegTiedToDefOperand(OpNo)) + continue; + Register Fit = MRI.createVirtualRegister(Need); + BuildMI(*User->getParent(), User, User->getDebugLoc(), + TII.get(TargetOpcode::COPY), Fit) + .addReg(Half); + MO.setReg(Fit); + } + MRI.replaceRegWith(Dst, Half); + forwarded[Dst] = Half; + useToErase.push_back(MI); + }; for (MachineBasicBlock &MBB : MF) { for (MachineBasicBlock::iterator It = MBB.begin(); It != MBB.end();) { MachineInstr *MI = &*It++; @@ -276,9 +321,7 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) { unsigned SubIdx = MI->getOperand(2).getImm(); auto [Lo, Hi] = wideMap[Src]; Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi; - Register Dst = MI->getOperand(0).getReg(); - MRI.replaceRegWith(Dst, Half); - useToErase.push_back(MI); + forwardHalf(MI, MI->getOperand(0).getReg(), Half); continue; } } @@ -299,8 +342,7 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) { unsigned SubIdx = SrcOp.getSubReg(); auto [Lo, Hi] = wideMap[SrcOp.getReg()]; Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi; - MRI.replaceRegWith(DstOp.getReg(), Half); - useToErase.push_back(MI); + forwardHalf(MI, DstOp.getReg(), Half); continue; } } @@ -313,7 +355,8 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) { if (isPtrOp) { Register Ptr = MI->getOperand(1).getReg(); if (Ptr.isVirtual() && wideMap.count(Ptr)) { - auto [Lo, Hi] = wideMap[Ptr]; + Register Lo = current(wideMap[Ptr].first); + Register Hi = current(wideMap[Ptr].second); unsigned NewOpc = (Opc == W65816::LDAptr32) ? W65816::LDAptr32S : (Opc == W65816::STAptr32) ? W65816::STAptr32S : W65816::STBptr32S; diff --git a/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp b/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp index 1e27cc7..b309a79 100644 --- a/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp +++ b/src/llvm/lib/Target/W65816/W65816StackSlotCleanup.cpp @@ -1085,6 +1085,27 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) { if (It == MBB.begin()) break; } if (!Test || !FirstCorrupt) continue; + // A sign test needs no wrap: `cmp #0 ; bmi` is `cmp #$8000 ; bcs` + // (A >= $8000 unsigned exactly when bit 15 is set), and C survives + // the N/Z-only corrupters, typically a PHI copy of a constant. The + // CMP costs the same, the wrap's 7 cycles go. + if ((BrOpc == W65816::BMI || BrOpc == W65816::BPL) && + Test->getOpcode() == W65816::CMPi16imm && + Test->getOperand(1).isImm() && Test->getOperand(1).getImm() == 0) { + bool CarryClobbered = false; + for (auto It = FirstCorrupt->getIterator(); + It != std::next(LastCorrupt->getIterator()); ++It) + if (!It->isDebugInstr() && isCarryCorrupting(It->getOpcode())) + CarryClobbered = true; + if (!CarryClobbered) { + const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo(); + Test->getOperand(1).setImm(0x8000); + Br->setDesc( + TII->get(BrOpc == W65816::BMI ? W65816::BCS : W65816::BCC)); + Changed = true; + continue; + } + } // Stack-relative ops inside the wrap need one more byte of // displacement to compensate for PHP's S decrement. Without it, // `lda 5,s` between PHP and PLP reads at (orig_S-1)+5 = orig_S+4, @@ -1590,20 +1611,42 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) { return false; } }; - // Returns true iff walking back from `Start` (exclusive) finds an - // A-modifier as the first non-skip op. Skips debug ops and + // Returns the A-modifier, if walking back from `Start` (exclusive) finds + // one as the first non-skip op, else null. Skips debug ops and // A-transparent stores; stops at the first real op. Templated to // accept either iterator or const_iterator (Cmps came from a non- // const iteration; predecessors are walked via const_iterator). - auto walkbackBefore = [&](auto Start, auto Begin) -> bool { + auto aWriterBefore = [&](auto Start, auto Begin) -> const MachineInstr * { auto It = Start; while (It != Begin) { --It; if (It->isDebugInstr()) continue; if (isATransparent(*It)) continue; - return It->modifiesRegister(W65816::A, TRI); + return It->modifiesRegister(W65816::A, TRI) ? &*It : nullptr; } - return false; + return nullptr; + }; + // BMI / BPL read N, which the A-writer sets from bit 15 of A only when it + // is a full 16-bit op: an 8-bit op sets it from bit 7 and XBA from the new + // low byte. (Z, all BEQ / BNE read, comes out of the same instruction as + // N, so for the 16-bit writers the two are equally trustworthy.) Keep the + // `cmp #0` unless the writer defines the 16-bit accumulator class, or is a + // 16-bit copy or pull. + auto setsSign16 = [](const MachineInstr &W) { + switch (W.getOpcode()) { + case TargetOpcode::COPY: + case W65816::TXA: + case W65816::TYA: + case W65816::PLA: + return true; + case W65816::XBA16: + return false; + default: + break; + } + const MCInstrDesc &D = W.getDesc(); + return D.getNumDefs() > 0 && + D.operands()[0].RegClass == W65816::Acc16RegClassID; }; // Pass 1c can only eliminate CMPi16imm $a, 0 if the preceding // A-modifier reliably sets N/Z to reflect A's final value. LDAfi @@ -1626,8 +1669,9 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) { !Cmp->getOperand(1).isImm() || Cmp->getOperand(1).getImm() != 0) continue; - bool Found = walkbackBefore(Cmp->getIterator(), MBB.begin()); - if (!Found) continue; + const MachineInstr *Writer = + aWriterBefore(Cmp->getIterator(), MBB.begin()); + if (!Writer) continue; // Only eliminate if there are NO LdaLike instructions between // this CMP and the next Bxx (or end of MBB). Otherwise the // CMP is the only flag-setting marker between the test value @@ -1653,6 +1697,9 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) { if (BrOpc == W65816::BCC || BrOpc == W65816::BCS || BrOpc == W65816::BVC || BrOpc == W65816::BVS) SafeToErase = false; + if ((BrOpc == W65816::BMI || BrOpc == W65816::BPL) && + !setsSign16(*Writer)) + SafeToErase = false; break; } // PHP/PLP-wrap-aware: only safe when LDAfi-expansion sets N/Z diff --git a/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp b/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp index 3ec1ad7..39e7f98 100644 --- a/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp +++ b/src/llvm/lib/Target/W65816/W65816TargetMachine.cpp @@ -63,6 +63,7 @@ LLVMInitializeW65816Target() { initializeW65816I32IncFoldPass(PR); initializeW65816ImgCalleeSavePass(PR); initializeW65816NarrowI32MulPass(PR); + initializeW65816CmpPreparePass(PR); initializeW65816PromoteFiToImgPass(PR); initializeW65816StackSlotMergePass(PR); initializeW65816StackRelToImgPass(PR); @@ -204,6 +205,11 @@ void W65816PassConfig::addISelPrepare() { // operands are zero. Catches the sumSquares-style `(u32)i * i` // pattern that SDAG-level analysis can't see across BB boundaries. addPass(createW65816NarrowI32Mul()); + // Compare shaping (post-increment loop tests, i32 == / != as a branch + // pair). After CodeGenPrepare so nothing re-merges the compares, and + // only when optimizing: -O0 keeps the straight lowering. + if (getOptLevel() != CodeGenOptLevel::None) + addPass(createW65816CmpPrepare()); TargetPassConfig::addISelPrepare(); } diff --git a/src/llvm/test/CodeGen/W65816/cmp-prepare.ll b/src/llvm/test/CodeGen/W65816/cmp-prepare.ll new file mode 100644 index 0000000..e965afe --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/cmp-prepare.ll @@ -0,0 +1,140 @@ +; Pin: W65816CmpPrepare. +; +; 1. A loop test against a constant uses the incremented counter: +; instcombine's `i < N-1` (on the old i, with i+1 also live) becomes +; `i+1 < N`, when ScalarEvolution shows i+1 cannot wrap. In the asm the +; counter is incremented in place and compared with N (0x1fec = 8172), +; not copied to a second pair and compared with N-1. +; 2. An i32 or pointer == / != feeding a branch is split into per-half i16 +; compares joined by or / and, which SelectionDAGBuilder emits as one +; branch per half. Against a constant with a zero half, or when the +; result is a value rather than a branch, the compare stays whole. +; +; RUN: llc -mtriple=w65816 -O2 -print-after=w65816-cmp-prepare -o /dev/null < %s 2>&1 | FileCheck %s --check-prefix=IR +; RUN: llc -mtriple=w65816 -O2 < %s | FileCheck %s + +target datalayout = "e-m:e-p:32:16-i16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16-S16" + +@gSink = global i16 0 + +; for (u32 i = 0; i < 8172; i++) dst[i] = src[i]; (clang -O2 IR) +define void @postinc_ult(ptr %dst, ptr %src) { +; IR-LABEL: define void @postinc_ult +; IR: %inc = add nuw nsw i32 %i, 1 +; IR: icmp ult i32 %inc, 8172 +; CHECK-LABEL: postinc_ult: +; CHECK: inc 0x{{[0-9a-f]+}} +; CHECK: cmp #0x1fec +; CHECK-NEXT: lda +; CHECK-NEXT: sbc #0x0 +; CHECK-NOT: cmp #0x1feb +; CHECK: rtl +entry: + br label %body +body: + %i = phi i32 [ 0, %entry ], [ %inc, %body ] + %s = getelementptr inbounds nuw i8, ptr %src, i32 %i + %v = load i8, ptr %s, align 1 + %d = getelementptr inbounds nuw i8, ptr %dst, i32 %i + store i8 %v, ptr %d, align 1 + %inc = add nuw nsw i32 %i, 1 + %c = icmp samesign ult i32 %i, 8171 + br i1 %c, label %body, label %exit +exit: + ret void +} + +; for (s32 i = s; i < 100; i++) dst[i] = 7; The start is unknown, so the +; counter's range includes SMAX; ScalarEvolution proves i < SMAX by +; induction (s < 100 on entry, the back edge only while i < 99). +define void @postinc_slt(ptr %dst, i32 %s) { +; IR-LABEL: define void @postinc_slt +; IR: icmp slt i32 %inc, 100 +entry: + %g = icmp slt i32 %s, 100 + br i1 %g, label %body, label %exit +body: + %i = phi i32 [ %inc, %body ], [ %s, %entry ] + %d = getelementptr inbounds i8, ptr %dst, i32 %i + store i8 7, ptr %d, align 1 + %inc = add nsw i32 %i, 1 + %c = icmp slt i32 %i, 99 + br i1 %c, label %body, label %exit +exit: + ret void +} + +; Nothing bounds x, so x+1 may wrap: the compare stays on x. +define i16 @no_range(i32 %x) { +; IR-LABEL: define i16 @no_range +; IR: icmp ult i32 %x, 100 +entry: + %inc = add i32 %x, 1 + %t = trunc i32 %inc to i16 + store volatile i16 %t, ptr @gSink + %c = icmp ult i32 %x, 100 + %r = zext i1 %c to i16 + ret i16 %r +} + +define void @split_ne(i32 %a, i32 %b) { +; IR-LABEL: define void @split_ne +; IR: icmp ne i16 +; IR: icmp ne i16 +; IR: or i1 +; CHECK-LABEL: split_ne: +; CHECK: cmp 0x4, s +; CHECK-NEXT: bne [[T:.LBB[0-9_]+]] +; CHECK: lda 0xd0 +; CHECK-NEXT: cmp 0x6, s +; CHECK-NEXT: bne [[T]] +; CHECK-NOT: eor +; CHECK: rtl +entry: + %c = icmp ne i32 %a, %b + br i1 %c, label %t, label %f +t: + store volatile i16 1, ptr @gSink + ret void +f: + ret void +} + +define void @split_ptr_eq(ptr %p, ptr %q) { +; IR-LABEL: define void @split_ptr_eq +; IR: ptrtoint ptr %p to i32 +; IR: icmp eq i16 +; IR: icmp eq i16 +; IR: and i1 +entry: + %c = icmp eq ptr %p, %q + br i1 %c, label %t, label %f +t: + store volatile i16 1, ptr @gSink + ret void +f: + ret void +} + +; A zero high half: `lda lo ; eor #5 ; ora hi` is already as cheap. +define void @keep_zero_half(i32 %a) { +; IR-LABEL: define void @keep_zero_half +; IR: icmp ne i32 %a, 5 +entry: + %c = icmp ne i32 %a, 5 + br i1 %c, label %t, label %f +t: + store volatile i16 1, ptr @gSink + ret void +f: + ret void +} + +define i16 @keep_value(i32 %a, i32 %b) { +; IR-LABEL: define i16 @keep_value +; IR: icmp eq i32 %a, %b +entry: + %c = icmp eq i32 %a, %b + %r = zext i1 %c to i16 + ret i16 %r +} diff --git a/src/llvm/test/CodeGen/W65816/fp-cmp-libcall-i16.ll b/src/llvm/test/CodeGen/W65816/fp-cmp-libcall-i16.ll new file mode 100644 index 0000000..2561bb4 --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/fp-cmp-libcall-i16.ll @@ -0,0 +1,44 @@ +; Pin: the soft-float compare helpers (__ltsf2, __gedf2, ...) return C int, +; 16 bits on this target, in A. The test of their result must read A. +; With LLVM's default i32 compare-libcall type, `a < b` compiled to +; `jsl __ltsf2 ; ply ; ply ; txa ; eor #0x8000 ; ...`: the sign of X, the +; high half of an i32 return, which the helper never sets. Every ordered +; float and double compare answered with whatever X held. +; +; RUN: llc -mtriple=w65816 -O2 < %s | FileCheck %s + +define i16 @flt(float %a, float %b) { +; CHECK-LABEL: flt: +; CHECK: jsl __ltsf2 +; CHECK-NOT: txa +; CHECK: rtl + %c = fcmp olt float %a, %b + %r = zext i1 %c to i16 + ret i16 %r +} + +define i16 @dge(double %a, double %b) { +; CHECK-LABEL: dge: +; CHECK: jsl __gedf2 +; CHECK-NOT: txa +; CHECK: rtl + %c = fcmp oge double %a, %b + %r = zext i1 %c to i16 + ret i16 %r +} + +declare void @sink() + +define void @dlt_branch(double %a, double %b) { +; CHECK-LABEL: dlt_branch: +; CHECK: jsl __ltdf2 +; CHECK-NOT: txa +; CHECK: jsl sink + %c = fcmp olt double %a, %b + br i1 %c, label %t, label %f +t: + call void @sink() + br label %f +f: + ret void +} diff --git a/src/llvm/test/CodeGen/W65816/i32-compare-chain.ll b/src/llvm/test/CodeGen/W65816/i32-compare-chain.ll index 85a84fc..2998f22 100644 --- a/src/llvm/test/CodeGen/W65816/i32-compare-chain.ll +++ b/src/llvm/test/CodeGen/W65816/i32-compare-chain.ll @@ -8,10 +8,11 @@ ; bias folds: 0x1234 -> sbc #0x9234). x <= K / x > K become x < K+1 / ; x >= K+1; reg/reg <= and > swap operands. A constant whose low half is ; 0 reduces to an i16 compare of the high halves (x < 0x10000 is hi < 1, -; signed x < 0 is hi < 0). +; signed x < 0 is hi < 0, which is just `lda hi ; bmi`). ; -; eq / ne: (lo ^ rhs.lo) | (hi ^ rhs.hi) tested against 0, and against a -; constant 0 just lo | hi. +; eq / ne feeding a branch (W65816CmpPrepare): the low halves, bne, then +; the high halves, bne. Against 0 (or a constant with a zero half) it stays +; the branch-free lo | hi, (lo ^ K) | hi, ... tested against 0. ; ; The old lowering built (hiLT | (hiEQ & loLT)) from three i16 setcc ; diamonds and branched on the OR: every `i < n` in a 32-bit loop paid for @@ -23,14 +24,16 @@ target datalayout = "e-m:e-p:32:16-i16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16- @sink = global i16 0 +; a.hi arrives in X: it must be loaded into A for the second compare. define void @br_eq(i32 %a, i32 %b) { ; CHECK-LABEL: br_eq: ; CHECK-NOT: php -; CHECK: eor 0xa, s -; CHECK: eor 0x8, s -; CHECK-NEXT: sta -; CHECK-NEXT: ora -; CHECK-NEXT: bne +; CHECK: stx 0xd0 +; CHECK-NEXT: cmp 0x4, s +; CHECK-NEXT: bne [[F:.LBB[0-9_]+]] +; CHECK: lda 0xd0 +; CHECK-NEXT: cmp 0x6, s +; CHECK-NEXT: bne [[F]] ; CHECK: rtl entry: %c = icmp eq i32 %a, %b @@ -46,11 +49,12 @@ f: define void @br_ne(i32 %a, i32 %b) { ; CHECK-LABEL: br_ne: ; CHECK-NOT: php -; CHECK: eor 0xa, s -; CHECK: eor 0x8, s -; CHECK-NEXT: sta -; CHECK-NEXT: ora -; CHECK-NEXT: beq +; CHECK: stx 0xd0 +; CHECK-NEXT: cmp 0x4, s +; CHECK-NEXT: bne [[T:.LBB[0-9_]+]] +; CHECK: lda 0xd0 +; CHECK-NEXT: cmp 0x6, s +; CHECK-NEXT: bne [[T]] ; CHECK: rtl entry: %c = icmp ne i32 %a, %b @@ -236,11 +240,11 @@ f: define void @brk_eq(i32 %a) { ; CHECK-LABEL: brk_eq: ; CHECK-NOT: php -; CHECK: eor #0x1234 -; CHECK: eor #0x5678 -; CHECK-NEXT: sta -; CHECK-NEXT: ora -; CHECK-NEXT: bne +; CHECK: cmp #0x5678 +; CHECK-NEXT: bne [[F:.LBB[0-9_]+]] +; CHECK: lda 0xd0 +; CHECK-NEXT: cmp #0x1234 +; CHECK-NEXT: bne [[F]] ; CHECK: rtl entry: %c = icmp eq i32 %a, 305419896 @@ -256,11 +260,11 @@ f: define void @brk_ne(i32 %a) { ; CHECK-LABEL: brk_ne: ; CHECK-NOT: php -; CHECK: eor #0x1234 -; CHECK: eor #0x5678 -; CHECK-NEXT: sta -; CHECK-NEXT: ora -; CHECK-NEXT: beq +; CHECK: cmp #0x5678 +; CHECK-NEXT: bne [[T:.LBB[0-9_]+]] +; CHECK: lda 0xd0 +; CHECK-NEXT: cmp #0x1234 +; CHECK-NEXT: bne [[T]] ; CHECK: rtl entry: %c = icmp ne i32 %a, 305419896 @@ -452,9 +456,7 @@ define void @brk_slt0(i32 %a) { ; CHECK-LABEL: brk_slt0: ; CHECK-NOT: sbc ; CHECK: lda 0xd0 -; CHECK-NEXT: eor #0x8000 -; CHECK-NEXT: cmp #0x8000 -; CHECK-NEXT: bcc +; CHECK-NEXT: bmi ; CHECK: rtl entry: %c = icmp slt i32 %a, 0 @@ -472,9 +474,7 @@ define void @brk_sgtm1(i32 %a) { ; CHECK-LABEL: brk_sgtm1: ; CHECK-NOT: sbc ; CHECK: lda 0xd0 -; CHECK-NEXT: eor #0x8000 -; CHECK-NEXT: cmp #0x8000 -; CHECK-NEXT: bcc +; CHECK-NEXT: bmi ; CHECK: rtl entry: %c = icmp sgt i32 %a, -1 diff --git a/src/llvm/test/CodeGen/W65816/i32-shift-16.ll b/src/llvm/test/CodeGen/W65816/i32-shift-16.ll new file mode 100644 index 0000000..5ae8ee3 --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/i32-shift-16.ll @@ -0,0 +1,66 @@ +; Pin: i32 shifts by 16 or more are half moves plus at most an i16 shift, +; not __lshrsi3 / __ashlsi3 / __ashrsi3 calls. `(u16)(x >> 16)`, the high +; word, is the high half itself. SRA by a count other than 16 or 31 still +; calls the helper (the i16 SRA patterns only do 1 and 15). +; +; RUN: llc -mtriple=w65816 -O2 < %s | FileCheck %s + +target datalayout = "e-m:e-p:32:16-i16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16-S16" + +define i16 @hiword(i32 %x) { +; CHECK-LABEL: hiword: +; CHECK-NOT: jsl +; CHECK: stx 0xd0 +; CHECK-NEXT: lda 0xd0 +; CHECK-NEXT: rtl + %s = lshr i32 %x, 16 + %t = trunc i32 %s to i16 + ret i16 %t +} + +define i32 @shr20(i32 %x) { +; CHECK-LABEL: shr20: +; CHECK-NOT: jsl +; CHECK: lsr a +; CHECK-NEXT: lsr a +; CHECK-NEXT: lsr a +; CHECK-NEXT: lsr a +; CHECK-NEXT: ldx #0x0 +; CHECK: rtl + %s = lshr i32 %x, 20 + ret i32 %s +} + +define i32 @shl16(i32 %x) { +; CHECK-LABEL: shl16: +; CHECK-NOT: jsl +; CHECK: tax +; CHECK-NEXT: lda #0x0 +; CHECK-NEXT: rtl + %s = shl i32 %x, 16 + ret i32 %s +} + +define i32 @sar16(i32 %x) { +; CHECK-LABEL: sar16: +; CHECK-NOT: jsl +; CHECK: rtl + %s = ashr i32 %x, 16 + ret i32 %s +} + +define i32 @sar31(i32 %x) { +; CHECK-LABEL: sar31: +; CHECK-NOT: jsl +; CHECK: rtl + %s = ashr i32 %x, 31 + ret i32 %s +} + +define i32 @sar20(i32 %x) { +; CHECK-LABEL: sar20: +; CHECK: jsl __ashrsi3 +; CHECK: rtl + %s = ashr i32 %x, 20 + ret i32 %s +} diff --git a/src/llvm/test/CodeGen/W65816/lower-wide32-half-class.mir b/src/llvm/test/CodeGen/W65816/lower-wide32-half-class.mir new file mode 100644 index 0000000..0f620b1 --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/lower-wide32-half-class.mir @@ -0,0 +1,98 @@ +# Pin: W65816LowerWide32 hands a pair's half to the users of the vreg +# projected out of it (`%d = COPY %pair.sub_lo`) only when the half's class +# can serve them. An i32 argument's high word arrives in X and lives in an +# Img16 DP slot; CMP's operand is implicitly A (Acc16). Replacing %d with +# the Img16 half unconditionally made the compare read whatever A held: an +# i32 `a != b` split into per-half compares tested a.lo against b.hi. +# +# img_half: the classes are disjoint, so a plain COPY into Acc16 stays. +# acc_half: the half is already Acc16 and replaces %d. +# chained: a half that is itself projected out of another pair (an i32 +# shift by 16 builds `%h = COPY %p.sub_hi ; %q = REG_SEQUENCE %h, +# ...`). Once %h is replaced and its COPY erased, %q's half must +# be what replaced it, not %h: that left uses with no def and +# crashed LiveVariables on JoeyLib's UBER. +# +# RUN: llc -mtriple=w65816 -run-pass=w65816-lower-wide32 -verify-machineinstrs -o - %s | FileCheck %s +--- | + target triple = "w65816" + define void @img_half() { ret void } + define void @acc_half() { ret void } + define void @chained() { ret void } +... +--- +# CHECK-LABEL: name: img_half +# CHECK: [[H:%[0-9]+]]:img16 = COPY $x +# CHECK: [[D:%[0-9]+]]:acc16 = COPY [[H]] +# CHECK-NEXT: CMPfi [[D]], %stack.0, 0, implicit-def $p +name: img_half +tracksRegLiveness: true +stack: + - { id: 0, size: 2, alignment: 2 } +body: | + bb.0: + successors: %bb.1, %bb.2 + liveins: $a, $x + %1:img16 = COPY $x + %4:acc16 = LDAi16imm 0 + %5:wide32 = REG_SEQUENCE %1:img16, %subreg.sub_lo, %4:acc16, %subreg.sub_hi + %2:acc16 = COPY %5.sub_lo:wide32 + CMPfi %2:acc16, %stack.0, 0, implicit-def $p + BNE %bb.1, implicit $p + BRA %bb.2 + bb.1: + RTL + bb.2: + RTL +... +--- +# CHECK-LABEL: name: acc_half +# CHECK: [[H:%[0-9]+]]:acc16 = LDAfi %stack.0, 0 +# CHECK-NOT: COPY +# CHECK: CMPfi [[H]], %stack.0, 0, implicit-def $p +name: acc_half +tracksRegLiveness: true +stack: + - { id: 0, size: 2, alignment: 2 } +body: | + bb.0: + successors: %bb.1, %bb.2 + %1:acc16 = LDAfi %stack.0, 0 + %4:acc16 = LDAi16imm 0 + %5:wide32 = REG_SEQUENCE %1:acc16, %subreg.sub_lo, %4:acc16, %subreg.sub_hi + %2:acc16 = COPY %5.sub_lo:wide32 + CMPfi %2:acc16, %stack.0, 0, implicit-def $p + BNE %bb.1, implicit $p + BRA %bb.2 + bb.1: + RTL + bb.2: + RTL +... +--- +# CHECK-LABEL: name: chained +# CHECK: [[HI:%[0-9]+]]:acc16 = LDAfi %stack.0, 2 +# CHECK-NOT: COPY +# CHECK: CMPfi [[HI]], %stack.0, 0, implicit-def $p +name: chained +tracksRegLiveness: true +stack: + - { id: 0, size: 4, alignment: 2 } +body: | + bb.0: + successors: %bb.1, %bb.2 + %1:acc16 = LDAfi %stack.0, 0 + %2:acc16 = LDAfi %stack.0, 2 + %4:acc16 = LDAi16imm 0 + %5:wide32 = REG_SEQUENCE %1:acc16, %subreg.sub_lo, %2:acc16, %subreg.sub_hi + %6:acc16 = COPY %5.sub_hi:wide32 + %7:wide32 = REG_SEQUENCE %6:acc16, %subreg.sub_lo, %4:acc16, %subreg.sub_hi + %8:acc16 = COPY %7.sub_lo:wide32 + CMPfi %8:acc16, %stack.0, 0, implicit-def $p + BNE %bb.1, implicit $p + BRA %bb.2 + bb.1: + RTL + bb.2: + RTL +... diff --git a/src/llvm/test/CodeGen/W65816/sign-test-bmi.ll b/src/llvm/test/CodeGen/W65816/sign-test-bmi.ll new file mode 100644 index 0000000..a1aadec --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/sign-test-bmi.ll @@ -0,0 +1,107 @@ +; Pin: sign tests read the sign bit instead of ordering through the +; 0x8000 bias. i16 `x < 0` / `x >= 0` (and instcombine's x <= -1 / x > -1) +; are `cmp #0 ; bmi/bpl`, with the cmp dropped after an op that already set +; N, so a load is just `lda ; bmi` (was `lda #$8000 ; eor ; cmp #$8000 ; +; bcc`). An i32 tests its high half the same way. An i8's sign is bit 7: +; `and #$80 ; bne`, not an 11-cycle sign extension. +; +; RUN: llc -mtriple=w65816 -O2 < %s | FileCheck %s + +target datalayout = "e-m:e-p:32:16-i16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16-S16" + +@g = global i16 0 +@sink = global i16 0 + +define void @lt16(i16 %x) { +; CHECK-LABEL: lt16: +; CHECK-NOT: eor +; CHECK: cmp #0x0 +; CHECK-NEXT: bmi +; CHECK: rtl + %c = icmp slt i16 %x, 0 + br i1 %c, label %t, label %f +t: + store volatile i16 1, ptr @sink + ret void +f: + ret void +} + +define void @gtm1_16(i16 %x) { +; CHECK-LABEL: gtm1_16: +; CHECK-NOT: eor +; CHECK: cmp #0x0 +; CHECK-NEXT: bmi +; CHECK: rtl + %c = icmp sgt i16 %x, -1 + br i1 %c, label %t, label %f +t: + store volatile i16 1, ptr @sink + ret void +f: + ret void +} + +define void @mem16() { +; CHECK-LABEL: mem16: +; CHECK: lda g +; CHECK-NEXT: bmi +; CHECK: rtl + %x = load volatile i16, ptr @g + %c = icmp slt i16 %x, 0 + br i1 %c, label %t, label %f +t: + store volatile i16 1, ptr @sink + ret void +f: + ret void +} + +define void @lt32(i32 %x) { +; CHECK-LABEL: lt32: +; CHECK: lda 0xd0 +; CHECK-NEXT: bmi +; CHECK: rtl + %c = icmp slt i32 %x, 0 + br i1 %c, label %t, label %f +t: + store volatile i16 1, ptr @sink + ret void +f: + ret void +} + +define void @lt8(i8 %x) { +; CHECK-LABEL: lt8: +; CHECK: and #0x80 +; CHECK-NEXT: bne +; CHECK: rtl + %c = icmp slt i8 %x, 0 + br i1 %c, label %t, label %f +t: + store volatile i16 1, ptr @sink + ret void +f: + ret void +} + +; A value through a PHI: the PHI's constant is loaded between the compare +; and the branch, which would clobber N; the test takes the carry form, +; `cmp #$8000 ; bcs`, which the load leaves intact, instead of a PHP/PLP. +define i16 @val16(i16 %x) { +; CHECK-LABEL: val16: +; CHECK-NOT: php +; CHECK: cmp #0x8000 +; CHECK-NEXT: lda #0x0 +; CHECK-NEXT: bcs +; CHECK: rtl +entry: + %c = icmp slt i16 %x, 0 + br i1 %c, label %t, label %done +t: + call void asm sideeffect "", "~{memory}"() + br label %done +done: + %r = phi i16 [ 1, %t ], [ 0, %entry ] + ret i16 %r +} diff --git a/src/llvm/test/CodeGen/W65816/sign-test-flags.mir b/src/llvm/test/CodeGen/W65816/sign-test-flags.mir new file mode 100644 index 0000000..71af233 --- /dev/null +++ b/src/llvm/test/CodeGen/W65816/sign-test-flags.mir @@ -0,0 +1,131 @@ +# Pin: the i16 sign test `x < 0` / `x >= 0` is `cmp #0 ; bmi/bpl`, and +# W65816StackSlotCleanup looks after its flags: +# +# carry_bmi / carry_bpl: a PHI copy (here `lda #0`) between the CMP and the +# branch clobbers N. Rather than wrap it in PHP/PLP (7 cycles), the test +# becomes `cmp #$8000 ; bcs/bcc`: A >= $8000 unsigned exactly when bit 15 +# is set, and the copy leaves C alone. +# keep_wrap: an ADDframe in the span rewrites C too, so it keeps the wrap +# (and Pass 1c then drops the `cmp #0`: the wrap carries the LDA's N). +# drop_after_lda: nothing in between: Pass 1c drops the `cmp #0`, since the +# 16-bit LDA already set N from bit 15. +# keep_after_8bit: an 8-bit op sets N from bit 7, so the `cmp #0` stays. +# +# RUN: llc -mtriple=w65816 -run-pass=w65816-stack-slot-cleanup -o - %s | FileCheck %s +--- | + target triple = "w65816" + define void @carry_bmi() { ret void } + define void @carry_bpl() { ret void } + define void @keep_wrap() { ret void } + define void @drop_after_lda() { ret void } + define void @keep_after_8bit() { ret void } +... +--- +# CHECK-LABEL: name: carry_bmi +# CHECK: CMPi16imm killed $a, 32768, implicit-def $p +# CHECK-NEXT: $a = LDAi16imm 0 +# CHECK-NEXT: BCS %bb.1, implicit $p +# CHECK-NOT: PHP +name: carry_bmi +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, 0, implicit-def $p + $a = LDAi16imm 0 + BMI %bb.1, implicit $p + BRA %bb.2 + bb.1: + RTL + bb.2: + RTL +... +--- +# CHECK-LABEL: name: carry_bpl +# CHECK: CMPi16imm killed $a, 32768, implicit-def $p +# CHECK-NEXT: $a = LDAi16imm 1 +# CHECK-NEXT: BCC %bb.1, implicit $p +# CHECK-NOT: PHP +name: carry_bpl +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, 0, implicit-def $p + $a = LDAi16imm 1 + BPL %bb.1, implicit $p + BRA %bb.2 + bb.1: + RTL + bb.2: + RTL +... +--- +# CHECK-LABEL: name: keep_wrap +# CHECK: $a = LDAfi %stack.0, 0 +# CHECK-NEXT: PHP +# CHECK-NEXT: $a = ADDframe %stack.0, 0 +# CHECK-NEXT: PLP +# CHECK-NEXT: BMI %bb.1, implicit $p +name: keep_wrap +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, 0, implicit-def $p + $a = ADDframe %stack.0, 0 + BMI %bb.1, implicit $p + BRA %bb.2 + bb.1: + RTL + bb.2: + RTL +... +--- +# CHECK-LABEL: name: drop_after_lda +# CHECK: $a = LDAfi %stack.0, 0 +# CHECK-NEXT: BMI %bb.1, implicit $p +name: drop_after_lda +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, 0, implicit-def $p + BMI %bb.1, implicit $p + BRA %bb.2 + bb.1: + RTL + bb.2: + RTL +... +--- +# CHECK-LABEL: name: keep_after_8bit +# CHECK: $a = LDAi8imm 128 +# CHECK-NEXT: CMPi16imm killed $a, 0, implicit-def $p +# CHECK-NEXT: BMI %bb.1, implicit $p +name: keep_after_8bit +tracksRegLiveness: false +body: | + bb.0: + successors: %bb.1, %bb.2 + $a = LDAi8imm 128 + CMPi16imm killed $a, 0, implicit-def $p + BMI %bb.1, implicit $p + BRA %bb.2 + bb.1: + RTL + bb.2: + RTL +... diff --git a/tests/coremark/README.md b/tests/coremark/README.md index 4ad7244..1cfd7d4 100644 --- a/tests/coremark/README.md +++ b/tests/coremark/README.md @@ -38,25 +38,33 @@ array memory). It is optional: CoreMark plus the runtime it pulls in Lua test (`--segment-cap`, extra segments at bank 4+) and runs it through `scripts/runMultiSeg.sh`. -Default iteration count is 1 (smallest valid run). Override for -publishable scores: +Default iteration count is 3. CoreMark only validates a run of at +least 10 seconds, and one iteration takes about 10.8 s of emulated time +(2026-09), so 3 leaves room for the compiler to get faster. Override +for longer runs: ```bash -ITERATIONS=5 bash tests/coremark/build.sh --layer2 +ITERATIONS=10 bash tests/coremark/runCoreMark.sh ``` -CoreMark spec recommends >= 10 seconds of runtime. At ~1 MHz, expect -roughly one iteration per second of in-IIgs time — so iteration counts -of 10–60 give a representative score. - ## Running ```bash bash tests/coremark/runCoreMark.sh --layer2 ``` -The run terminates with `0xC0DE` written to `$025000` on success. -Elapsed VBL ticks (60 Hz) are stored at `$025002` (low/hi halves). +This is a correctness gate, not just a benchmark. core_portme.c sends +CoreMark's console output to `$02:6000` and takes its clock from a +millisecond counter at `$02:5010` that the MAME harness +(`scripts/runMultiSeg.sh`) updates every frame; bare metal runs with +IRQs off, so libc's VBL `clock()` never moves. The script fails unless +CoreMark reports "Correct operation validated" (its own CRC checks plus +the 10-second rule) and the seed, list, matrix, state and final CRCs +match the values a host build of the same sources prints. CoreMark's +own "Total time" and "Iterations/Sec" are the score. + +The run ends with `0xC0DE` written to `$025000` (`0xBEEF` there means +it started but never finished); elapsed ticks are at `$025002`. **Note:** running CoreMark under MAME inside this project's restricted shell crashes MAME (same SIGSEGV as Lua's full interpreter run — diff --git a/tests/coremark/build.sh b/tests/coremark/build.sh index 1407933..e1e0593 100755 --- a/tests/coremark/build.sh +++ b/tests/coremark/build.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash # Build CoreMark for W65816. Compiles the 5 core .c files plus our # porting layer (core_portme.c). Iteration count baked at compile -# time via -DITERATIONS=N (default 1 — runs ~1 sec at 1 MHz IIgs). +# time via -DITERATIONS=N. The default of 3 runs about 32 s of emulated +# time (2026-09): CoreMark only validates a run of at least 10 s, so this +# leaves room for the compiler to get 3x faster before the count must grow. # # Output: build/*.o for each TU. # @@ -18,7 +20,7 @@ CLANG="$PROJECT_ROOT/tools/llvm-mos-build/bin/clang" # instead of hard-failing the build -- see scripts/ccRegallocFallback.sh. CC="$PROJECT_ROOT/scripts/ccRegallocFallback.sh" -ITERATIONS="${ITERATIONS:-1}" +ITERATIONS="${ITERATIONS:-3}" LAYER2=() for arg in "$@"; do diff --git a/tests/coremark/coreMark.manifest.json b/tests/coremark/coreMark.manifest.json index 4a88cfc..617c7e3 100644 --- a/tests/coremark/coreMark.manifest.json +++ b/tests/coremark/coreMark.manifest.json @@ -7,9 +7,9 @@ "num": 1, "name": "SEG1", "base": "0x001000", - "size": 41548, - "bss_start": "0x00d000", - "bss_size": 6268, + "size": 40728, + "bss_start": "0x00b700", + "bss_size": 2034, "image": "coreMark.bin", "entry_offset": "0x0000" }, @@ -17,7 +17,7 @@ "num": 2, "name": "SEG2", "base": "0x040000", - "size": 16362, + "size": 16480, "bss_start": "0x000000", "bss_size": 0, "image": "coreMark.seg2.bin", diff --git a/tests/coremark/core_portme.c b/tests/coremark/core_portme.c index 167b1aa..ee6bbd2 100644 --- a/tests/coremark/core_portme.c +++ b/tests/coremark/core_portme.c @@ -1,7 +1,6 @@ // CoreMark porting layer for W65816. See core_portme.h for config. #include "coremark.h" #include "core_portme.h" -#include #include #if VALIDATION_RUN @@ -22,15 +21,58 @@ volatile ee_s32 seed3_volatile = 0x8; volatile ee_s32 seed4_volatile = ITERATIONS; volatile ee_s32 seed5_volatile = 0; -// Timing — uses libc's clock() (VBL counter at 60 Hz). -#define GETMYTIME(_t) (*_t = (CORETIMETYPE)clock()) +// The harness (runCoreMark.sh) owns these addresses. Bank 2 is clear of +// the program (bank 0 and banks 4+) and of its static data (bank 0). +// CM_TICK_ADDR u32 emulated milliseconds, rewritten by the harness every +// video frame. libc's clock() reads the ROM's VBL counter, +// which never moves here: bare metal runs with IRQs off. +// CM_OUT_ADDR CM_OUT_BYTES of captured console output, NUL-terminated; +// the harness checks the CRCs CoreMark prints. +#define CM_TICK_ADDR 0x025010UL +#define CM_OUT_ADDR 0x026000UL +#define CM_OUT_BYTES 0x1000u +#define GETMYTIME(_t) (*_t = harnessTicks()) #define MYTIMEDIFF(fin, ini) ((fin) - (ini)) #define TIMER_RES_DIVIDER 1 #define SAMPLE_TIME_IMPLEMENTATION 1 -#define EE_TICKS_PER_SEC (CLOCKS_PER_SEC / TIMER_RES_DIVIDER) +#define EE_TICKS_PER_SEC (1000u / TIMER_RES_DIVIDER) + +static CORETIMETYPE harnessTicks(void); +void __putByte(char c); static CORETIMETYPE start_time_val; static CORETIMETYPE stop_time_val; +static ee_u16 outPos; + + +// The two 16-bit halves are read separately, so a frame boundary between +// them could pair an old low half with a new high half; re-read until the +// high half is stable. +static CORETIMETYPE harnessTicks(void) { + volatile ee_u16 *tick = (volatile ee_u16 *)CM_TICK_ADDR; + ee_u16 hi; + ee_u16 lo; + + do { + hi = tick[1]; + lo = tick[0]; + } while (hi != tick[1]); + return ((CORETIMETYPE)hi << 16) | lo; +} + + +// Overrides libc's weak console hook (putchar), keeping one byte free for +// the terminating NUL the harness stops at. +void __putByte(char c) { + volatile char *out = (volatile char *)CM_OUT_ADDR; + + if (outPos < CM_OUT_BYTES - 1u) { + out[outPos] = c; + outPos++; + out[outPos] = 0; + } +} + void start_time(void) { GETMYTIME(&start_time_val); diff --git a/tests/coremark/core_portme.h b/tests/coremark/core_portme.h index 066be98..e79967b 100644 --- a/tests/coremark/core_portme.h +++ b/tests/coremark/core_portme.h @@ -1,7 +1,8 @@ // CoreMark porting layer for the W65816 / Apple IIgs target. // Adapts the barebones template: -// - clock() via VBL counter at 60 Hz (CLOCKS_PER_SEC=60) -// - printf via our libc +// - time from a millisecond counter runCoreMark.sh's MAME harness keeps +// (bare metal runs with interrupts off, so libc's VBL clock() is 0) +// - printf via our libc, captured by the harness (see core_portme.c) // - MEM_STATIC (heap-free) // - HAS_FLOAT=1 (soft-double; needed for the score calc) // - Single-threaded (MULTITHREAD=1) @@ -10,8 +11,8 @@ #define CORE_PORTME_H #define HAS_FLOAT 1 -#define HAS_TIME_H 1 -#define USE_CLOCK 1 +#define HAS_TIME_H 0 +#define USE_CLOCK 0 #define HAS_STDIO 1 #define HAS_PRINTF 1 @@ -33,16 +34,14 @@ typedef unsigned long ee_size_t; #define align_mem(x) (void *)(4 + (((ee_ptr_int)(x)-1) & ~3)) -// ITERATIONS — keep small for the 1 MHz IIgs. CoreMark spec wants -// >= 10 sec of runtime; at this clock that's order-of-magnitude -// 100 iterations. Run with ITERATIONS=1 first to validate, then -// scale up. The user can pass -DITERATIONS=N to override. +// ITERATIONS: build.sh passes -DITERATIONS (see its default). One +// iteration takes about 10.8 s of emulated time at -O2 (2026-09), and +// CoreMark only validates a run of at least 10 s. #ifndef ITERATIONS -#define ITERATIONS 1 +#define ITERATIONS 3 #endif -// Timing. CLOCKS_PER_SEC = 60 (VBL). Use TIMER_RES_DIVIDER=1 since -// 60 Hz is already coarse. +// Timing: emulated milliseconds, updated once per video frame. #define CORETIMETYPE ee_u32 typedef ee_u32 CORE_TICKS; typedef double secs_ret; diff --git a/tests/coremark/runCoreMark.sh b/tests/coremark/runCoreMark.sh index dfc5f14..8dcc079 100755 --- a/tests/coremark/runCoreMark.sh +++ b/tests/coremark/runCoreMark.sh @@ -1,17 +1,58 @@ #!/usr/bin/env bash -# Build + link + run CoreMark under MAME. +# Build + link + run CoreMark under MAME, and fail unless CoreMark's own +# report says the results are right. # # Usage: -# bash tests/coremark/runCoreMark.sh # default: ITERATIONS=1 -# ITERATIONS=5 bash tests/coremark/runCoreMark.sh +# bash tests/coremark/runCoreMark.sh # ITERATIONS from build.sh +# ITERATIONS=10 bash tests/coremark/runCoreMark.sh # bash tests/coremark/runCoreMark.sh --layer2 +# +# The gate reads CoreMark's printed report (core_portme.c captures the +# console in bank 2 and runMultiSeg.sh prints it as MAME-TEXT lines) and +# requires: +# - "Correct operation validated": CoreMark compared its list, matrix and +# state CRCs with the known values for these seeds, and the run lasted +# at least 10 s by core_portme.c's clock (the harness's emulated +# milliseconds); +# - the seed, list, matrix, state and final CRCs below, so a failure names +# the wrong one. crcfinal chains every iteration's results. +# The done sentinel alone only proved that CoreMark returned: a compiler +# whose CoreMark printed list 0x9865 / matrix 0xa7f7 / state 0xe10b passed. set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +source "$SCRIPT_DIR/../../scripts/common.sh" CM_OBJ="$SCRIPT_DIR/build" RT="$PROJECT_ROOT/runtime" +# CoreMark's known values for the 2K performance run (seeds 0, 0, 0x66). +WANT_SEEDCRC=0xe9f5 +WANT_CRCLIST=0xe714 +WANT_CRCMATRIX=0x1fd7 +WANT_CRCSTATE=0x8e3a + +# crcfinal by iteration count, from the vendored sources built for the host +# (posix port, `coremark.exe 0x0 0x0 0x66 N`). +crcFinalFor() { + case "$1" in + 1) echo 0xe714 ;; + 2) echo 0x72be ;; + 3) echo 0x2e87 ;; + 4) echo 0x9f95 ;; + 5) echo 0xf24c ;; + 6) echo 0x9db0 ;; + 7) echo 0x3413 ;; + 8) echo 0xccd1 ;; + 9) echo 0x7de8 ;; + 10) echo 0xfcaf ;; + 20) echo 0x4983 ;; + 30) echo 0xf8b3 ;; + 50) echo 0x0158 ;; + 100) echo 0x988c ;; + *) echo "" ;; + esac +} + cd "$SCRIPT_DIR" rm -rf "$CM_OBJ" bash "$SCRIPT_DIR/build.sh" "$@" @@ -33,9 +74,49 @@ bash "$SCRIPT_DIR/build.sh" "$@" ls -la coreMark*.bin echo "" -# 0xC0DE sentinel = success; otherwise 0xBEEF = mid-run hang. -# Elapsed ticks at 0x025002..0x025005 (low/hi). -MAME_CHECK_FRAME=900 MAME_SECS=30 MAME_TIMEOUT=300 MAME_RAMSIZE=4M \ +# The run ends on portable_fini's 0xC0DE sentinel at $02:5000 ($BEEF there +# means CoreMark started but never finished). The tick and text addresses +# are core_portme.c's CM_TICK_ADDR and CM_OUT_ADDR. 900 emulated seconds +# only caps a hang; the wall time follows CoreMark's own run time. +runOk=1 +OUT=$(MAME_CHECK_FRAME=54000 MAME_SECS=905 MAME_TIMEOUT=900 MAME_RAMSIZE=4M \ + MAME_DONE_ADDR=0x025000 MAME_DONE_VAL=0xC0DE \ + MAME_TICK_ADDR=0x025010 MAME_TEXT_ADDR=0x026000 \ bash "$PROJECT_ROOT/scripts/runMultiSeg.sh" \ "$SCRIPT_DIR/coreMark.manifest.json" \ - --check 0x025000=c0de + --check 0x025000=c0de) || runOk=0 +echo "$OUT" +[ $runOk -eq 1 ] || die "CoreMark: no 0xC0DE done sentinel (hang or crash)" + +report=$(sed -n 's/^MAME-TEXT //p' <<< "$OUT") +# One "name : value" field of the report, e.g. field '[0]crclist'. +field() { + awk -F' *: ' -v k="$1" '$1 == k && !seen { print $2; seen = 1 }' <<< "$report" +} + +bad=0 +expect() { + local got + got=$(field "$1") + if [ "$got" != "$2" ]; then + warn "CoreMark: $1 is '${got:-missing}', expected $2" + bad=1 + fi +} +iterations=$(field "Iterations") +wantFinal=$(crcFinalFor "$iterations") +expect "seedcrc" "$WANT_SEEDCRC" +expect "[0]crclist" "$WANT_CRCLIST" +expect "[0]crcmatrix" "$WANT_CRCMATRIX" +expect "[0]crcstate" "$WANT_CRCSTATE" +if [ -n "$wantFinal" ]; then + expect "[0]crcfinal" "$wantFinal" +else + warn "CoreMark: no known crcfinal for ${iterations:-?} iterations; not checked" +fi +if ! grep -q '^Correct operation validated' <<< "$report"; then + warn "CoreMark did not report \"Correct operation validated\" (its ERROR lines are above)" + bad=1 +fi +[ $bad -eq 0 ] || die "CoreMark: results not validated (warnings above)" +log "CoreMark OK: $iterations iterations, CRCs match, $(field 'Total time (secs)') s, $(field 'Iterations/Sec') iterations/s" diff --git a/tests/lua/luaTest.manifest.json b/tests/lua/luaTest.manifest.json index dbb4df6..b42592b 100644 --- a/tests/lua/luaTest.manifest.json +++ b/tests/lua/luaTest.manifest.json @@ -7,7 +7,7 @@ "num": 1, "name": "SEG1", "base": "0x001000", - "size": 44548, + "size": 43790, "bss_start": "0x00e200", "bss_size": 4248, "image": "luaTest.bin",