Even more code gen issues fixed.

This commit is contained in:
Scott Duensing 2026-09-29 17:22:52 -05:00
parent 000aa1c079
commit be2336eb09
28 changed files with 1769 additions and 100 deletions

Binary file not shown.

View file

@ -136,9 +136,8 @@ float fmodf(float x, float y) {
// uses the IEEE-754 layout: y0 = bits >> 1 + bias_shift. Then ~6 // uses the IEEE-754 layout: y0 = bits >> 1 + bias_shift. Then ~6
// Newton iterations land within ~1 ULP for double precision. // Newton iterations land within ~1 ULP for double precision.
// //
// We dodge the snprintf-style libcall pitfalls by NOT comparing // The sign and zero tests read the IEEE-754 bits directly: cheaper than
// double-to-double via `<`/`>=`; instead we test the IEEE-754 // a `<` / `>=` libcall compare.
// sign bit and exponent directly.
double sqrt(double x) { double sqrt(double x) {
uint64_t b; uint64_t b;
__builtin_memcpy(&b, &x, sizeof(b)); __builtin_memcpy(&b, &x, sizeof(b));

View file

@ -493,10 +493,8 @@ static void emitDouble(double v, int prec, char spec, const Spec *s) {
if (prec > 9) { if (prec > 9) {
prec = 9; prec = 9;
} }
// Avoid `if (v < 0)` (which calls __ltdf2) — the W65816 codegen // Read and clear the IEEE-754 sign bit inline rather than `v < 0`:
// for that comparison passes its double arg with a missing word, // cheaper than the __ltdf2 libcall, and it also handles -0.0.
// and the test silently returns false for negatives. Read the
// IEEE-754 sign bit and clear it inline instead.
unsigned long long bits; unsigned long long bits;
__builtin_memcpy(&bits, &v, 8); __builtin_memcpy(&bits, &v, 8);
if (bits & ((unsigned long long)1 << 63)) { if (bits & ((unsigned long long)1 << 63)) {

View file

@ -305,12 +305,20 @@ s16 __unorddf2(u64 a, u64 b) {
return 0; 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 __eqdf2(u64 a, u64 b) { return __cmpdf2(a, b) != 0; }
s16 __nedf2(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 __ltdf2(u64 a, u64 b) { return __cmpdf2(a, b); }
s16 __ledf2(u64 a, u64 b) { return __cmpdf2(a, b) <= 0; } s16 __ledf2(u64 a, u64 b) { return __cmpdf2(a, b); }
s16 __gtdf2(u64 a, u64 b) { return __cmpdf2(a, b) > 0; } s16 __gtdf2(u64 a, u64 b) { return __cmpdf2(a, b); }
s16 __gedf2(u64 a, u64 b) { return __cmpdf2(a, b) >= 0; } s16 __gedf2(u64 a, u64 b) { return __cmpdf2(a, b); }
// double <-> float conversions. // double <-> float conversions.
u64 __extendsfdf2(u32 x) { u64 __extendsfdf2(u32 x) {

View file

@ -5,6 +5,15 @@
# entry, lets the program run, then reads check-address values. # entry, lets the program run, then reads check-address values.
# #
# Usage: runMultiSeg.sh <manifest.json> [check args like runInMame.sh] # Usage: runMultiSeg.sh <manifest.json> [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 set -euo pipefail
source "$(dirname "$0")/common.sh" source "$(dirname "$0")/common.sh"
@ -13,6 +22,10 @@ MANIFEST="$1"
shift shift
CHECK_FRAME=${MAME_CHECK_FRAME:-300} CHECK_FRAME=${MAME_CHECK_FRAME:-300}
SECS=${MAME_SECS:-6} 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. # Build address list as Lua table entries, mirroring runInMame.sh.
LUA_CHECKS="" LUA_CHECKS=""
@ -52,7 +65,11 @@ EOF
[ -n "$PARSED" ] || die "manifest parse failed" [ -n "$PARSED" ] || die "manifest parse failed"
LUA_PATH=$(mktemp --suffix=.lua) 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. # Build the per-segment load lua.
LOAD_LUA="" LOAD_LUA=""
@ -79,15 +96,41 @@ while IFS='|' read -r img base entry sz; do
fi fi
done <<< "$PARSED" 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" <<EOF cat > "$LUA_PATH" <<EOF
local frame = 0 local frame = 0
local loaded = false local loaded = false
emu.register_frame_done(function() emu.register_frame_done(function()
frame = frame + 1 frame = frame + 1
local cpu = manager.machine.devices[":maincpu"]
local mem = cpu.spaces["program"]
if frame == 30 and not loaded then if frame == 30 and not loaded then
local cpu = manager.machine.devices[":maincpu"]
local mem = cpu.spaces["program"]
$LOAD_LUA $LOAD_LUA
$INIT_LUA
loaded = true loaded = true
cpu.state["PC"].value = $ENTRY_BASE + $ENTRY_OFF cpu.state["PC"].value = $ENTRY_BASE + $ENTRY_OFF
cpu.state["PB"].value = ($ENTRY_BASE >> 16) & 0xff cpu.state["PB"].value = ($ENTRY_BASE >> 16) & 0xff
@ -98,9 +141,11 @@ $LOAD_LUA
cpu.state["S"].value = 0x01FF cpu.state["S"].value = 0x01FF
print('MAME-READY pc=0x' .. string.format('%06x', $ENTRY_BASE + $ENTRY_OFF)) print('MAME-READY pc=0x' .. string.format('%06x', $ENTRY_BASE + $ENTRY_OFF))
end end
if frame == $CHECK_FRAME then $TICK_LUA
local cpu = manager.machine.devices[":maincpu"] $DONE_LUA
local mem = cpu.spaces["program"] if done or frame == $CHECK_FRAME then
print(string.format('MAME-STOP frame=%d secs=%.3f', frame, emu.time()))
$TEXT_LUA
$LUA_CHECKS $LUA_CHECKS
manager.machine:exit() manager.machine:exit()
end end
@ -114,11 +159,14 @@ fi
OUT=$(SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout "${MAME_TIMEOUT:-30}" mame apple2gs \ OUT=$(SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout "${MAME_TIMEOUT:-30}" mame apple2gs \
"${RAMSIZE_ARG[@]}" \ "${RAMSIZE_ARG[@]}" \
-rompath "$PROJECT_ROOT/tools/mame/roms" \ -rompath "$PROJECT_ROOT/tools/mame/roms" \
-cfg_directory "$MAME_STATE" -nvram_directory "$MAME_STATE" \
-plugins -autoboot_script "$LUA_PATH" \ -plugins -autoboot_script "$LUA_PATH" \
-video none -sound none -nothrottle -seconds_to_run "$SECS" 2>&1 | /usr/bin/grep -E "^(MAME-|SEG-)") -video none -sound none -nothrottle -seconds_to_run "$SECS" 2>&1 | /usr/bin/grep -E "^(MAME-|SEG-)")
echo "$OUT" 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 ok=1
for i in "${!EXPECT_LIST[@]}"; do for i in "${!EXPECT_LIST[@]}"; do
if [ "${GOT_LIST[$i]:-}" != "${EXPECT_LIST[$i]}" ]; then if [ "${GOT_LIST[$i]:-}" != "${EXPECT_LIST[$i]}" ]; then

View file

@ -1633,6 +1633,107 @@ EOF
fi fi
rm -f "$cDblMame" "$oDblMame" "$oSdMame" "$binDblMame" 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 # Regression: W65816SepRepCleanup's PHI-copy hoist must not move a
# `STA slot` above an `AND/ORA/EOR/ADC/SBC/CMP_StackRel slot` read in # `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), # the gap. Before the fix (gap check only guarded LDA_StackRel),
@ -3089,6 +3190,410 @@ EOF
fi fi
rm -f "$cI32CmpFile" "$oI32CmpFile" "$binI32CmpFile" 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 <stdint.h>: 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 # the list, creating an overlapping free entry. A subsequent
# malloc could hand out the same memory to two callers. # malloc could hand out the same memory to two callers.
log "check: MAME runs malloc/free coalesce — three blocks freed in alloc order (#100)" log "check: MAME runs malloc/free coalesce — three blocks freed in alloc order (#100)"

View file

@ -38,6 +38,7 @@ add_llvm_target(W65816CodeGen
W65816I32IncFold.cpp W65816I32IncFold.cpp
W65816ImgCalleeSave.cpp W65816ImgCalleeSave.cpp
W65816NarrowI32Mul.cpp W65816NarrowI32Mul.cpp
W65816CmpPrepare.cpp
W65816PromoteFiToImg.cpp W65816PromoteFiToImg.cpp
W65816StackRelToImg.cpp W65816StackRelToImg.cpp
W65816StackSlotMerge.cpp W65816StackSlotMerge.cpp
@ -58,6 +59,7 @@ add_llvm_target(W65816CodeGen
Support Support
Target Target
TargetParser TargetParser
TransformUtils
W65816Desc W65816Desc
W65816Info W65816Info

View file

@ -125,6 +125,12 @@ FunctionPass *createW65816SjLjFinalize();
// zext that a SDAG-level combine would key off. See W65816NarrowI32Mul.cpp. // zext that a SDAG-level combine would key off. See W65816NarrowI32Mul.cpp.
FunctionPass *createW65816NarrowI32Mul(); 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 // Post-LSR IR pass: detect pointer-walking loops over GlobalAddress
// bases and rewrite to forward-counter + indexed GEP form. LSR's // bases and rewrite to forward-counter + indexed GEP form. LSR's
// transform is faster for pointer-arg loops (single ptr increment per // transform is faster for pointer-arg loops (single ptr increment per
@ -213,6 +219,7 @@ void initializeW65816SjLjFinalizePass(PassRegistry &);
void initializeW65816LowerWide32Pass(PassRegistry &); void initializeW65816LowerWide32Pass(PassRegistry &);
void initializeW65816ImgCalleeSavePass(PassRegistry &); void initializeW65816ImgCalleeSavePass(PassRegistry &);
void initializeW65816NarrowI32MulPass(PassRegistry &); void initializeW65816NarrowI32MulPass(PassRegistry &);
void initializeW65816CmpPreparePass(PassRegistry &);
void initializeW65816PromoteFiToImgPass(PassRegistry &); void initializeW65816PromoteFiToImgPass(PassRegistry &);
void initializeW65816StackSlotMergePass(PassRegistry &); void initializeW65816StackSlotMergePass(PassRegistry &);
void initializeW65816StackRelToImgPass(PassRegistry &); void initializeW65816StackRelToImgPass(PassRegistry &);

View file

@ -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<ScalarEvolutionWrapperPass>();
}
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<Instruction>(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<WeakTrackingVH> &MaybeDead) {
auto *C = dyn_cast<ConstantInt>(Cmp->getOperand(1));
Value *V = Cmp->getOperand(0);
// A constant V (not folded yet) has users all over the module.
if (!C || isa<Constant>(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<CondBrInst>(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<Constant>(R)) {
if (RC->isNullValue())
return false;
if (auto *RI = dyn_cast<ConstantInt>(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<ScalarEvolutionWrapperPass>().getSE();
const DataLayout &DL = F.getDataLayout();
SmallVector<ICmpInst *, 16> Cmps;
for (Instruction &I : instructions(F))
if (auto *Cmp = dyn_cast<ICmpInst>(&I))
Cmps.push_back(Cmp);
bool Changed = false;
SmallVector<WeakTrackingVH, 8> 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();
}

View file

@ -528,7 +528,6 @@ static void promoteI8Cmp(SDValue &LHS, SDValue &RHS, ISD::CondCode CC,
static W65816CC::CondCode normalizeCC(SDValue &LHS, SDValue &RHS, static W65816CC::CondCode normalizeCC(SDValue &LHS, SDValue &RHS,
ISD::CondCode &CC, SelectionDAG &DAG, ISD::CondCode &CC, SelectionDAG &DAG,
const SDLoc &DL) { const SDLoc &DL) {
promoteI8Cmp(LHS, RHS, CC, DAG, DL);
// CMP wants the comparand (constant or memory) on the right. If a DAG // 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. // pre-pass put the constant on the left, swap and flip the condition.
if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) { if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) {
@ -536,6 +535,35 @@ static W65816CC::CondCode normalizeCC(SDValue &LHS, SDValue &RHS,
CC = ISD::getSetCCSwappedOperands(CC); 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<ConstantSDNode>(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": // Signed compare via "EOR with sign bit then unsigned compare":
// a < b (signed) iff (a ^ 0x8000) < (b ^ 0x8000) (unsigned) // a < b (signed) iff (a ^ 0x8000) < (b ^ 0x8000) (unsigned)
// The XOR flips the sign bit, which converts signed-int ordering to // 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); 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);
}
}
} }
} }

View file

@ -67,6 +67,15 @@ public:
return TargetLoweringBase::getScalarShiftAmountTy(DL, LHSTy); 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- // ptr32-mode hook: with patches/0007-targetlowering-virtual-
// gettypeconversion making the base function virtual, this can be // gettypeconversion making the base function virtual, this can be
// overridden to force i64 to expand directly to i16 halves rather // overridden to force i64 to expand directly to i16 halves rather

View file

@ -264,6 +264,51 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
// Pass 3: rewrite uses. // Pass 3: rewrite uses.
SmallVector<MachineInstr *, 32> useToErase; SmallVector<MachineInstr *, 32> 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<Register, Register> 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 &MBB : MF) {
for (MachineBasicBlock::iterator It = MBB.begin(); It != MBB.end();) { for (MachineBasicBlock::iterator It = MBB.begin(); It != MBB.end();) {
MachineInstr *MI = &*It++; MachineInstr *MI = &*It++;
@ -276,9 +321,7 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
unsigned SubIdx = MI->getOperand(2).getImm(); unsigned SubIdx = MI->getOperand(2).getImm();
auto [Lo, Hi] = wideMap[Src]; auto [Lo, Hi] = wideMap[Src];
Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi; Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi;
Register Dst = MI->getOperand(0).getReg(); forwardHalf(MI, MI->getOperand(0).getReg(), Half);
MRI.replaceRegWith(Dst, Half);
useToErase.push_back(MI);
continue; continue;
} }
} }
@ -299,8 +342,7 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
unsigned SubIdx = SrcOp.getSubReg(); unsigned SubIdx = SrcOp.getSubReg();
auto [Lo, Hi] = wideMap[SrcOp.getReg()]; auto [Lo, Hi] = wideMap[SrcOp.getReg()];
Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi; Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi;
MRI.replaceRegWith(DstOp.getReg(), Half); forwardHalf(MI, DstOp.getReg(), Half);
useToErase.push_back(MI);
continue; continue;
} }
} }
@ -313,7 +355,8 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
if (isPtrOp) { if (isPtrOp) {
Register Ptr = MI->getOperand(1).getReg(); Register Ptr = MI->getOperand(1).getReg();
if (Ptr.isVirtual() && wideMap.count(Ptr)) { 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 unsigned NewOpc = (Opc == W65816::LDAptr32) ? W65816::LDAptr32S
: (Opc == W65816::STAptr32) ? W65816::STAptr32S : (Opc == W65816::STAptr32) ? W65816::STAptr32S
: W65816::STBptr32S; : W65816::STBptr32S;

View file

@ -1085,6 +1085,27 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
if (It == MBB.begin()) break; if (It == MBB.begin()) break;
} }
if (!Test || !FirstCorrupt) continue; 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 // Stack-relative ops inside the wrap need one more byte of
// displacement to compensate for PHP's S decrement. Without it, // 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, // `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; return false;
} }
}; };
// Returns true iff walking back from `Start` (exclusive) finds an // Returns the A-modifier, if walking back from `Start` (exclusive) finds
// A-modifier as the first non-skip op. Skips debug ops and // one as the first non-skip op, else null. Skips debug ops and
// A-transparent stores; stops at the first real op. Templated to // A-transparent stores; stops at the first real op. Templated to
// accept either iterator or const_iterator (Cmps came from a non- // accept either iterator or const_iterator (Cmps came from a non-
// const iteration; predecessors are walked via const_iterator). // 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; auto It = Start;
while (It != Begin) { while (It != Begin) {
--It; --It;
if (It->isDebugInstr()) continue; if (It->isDebugInstr()) continue;
if (isATransparent(*It)) 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 // Pass 1c can only eliminate CMPi16imm $a, 0 if the preceding
// A-modifier reliably sets N/Z to reflect A's final value. LDAfi // 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).isImm() ||
Cmp->getOperand(1).getImm() != 0) Cmp->getOperand(1).getImm() != 0)
continue; continue;
bool Found = walkbackBefore(Cmp->getIterator(), MBB.begin()); const MachineInstr *Writer =
if (!Found) continue; aWriterBefore(Cmp->getIterator(), MBB.begin());
if (!Writer) continue;
// Only eliminate if there are NO LdaLike instructions between // Only eliminate if there are NO LdaLike instructions between
// this CMP and the next Bxx (or end of MBB). Otherwise the // this CMP and the next Bxx (or end of MBB). Otherwise the
// CMP is the only flag-setting marker between the test value // 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 || if (BrOpc == W65816::BCC || BrOpc == W65816::BCS ||
BrOpc == W65816::BVC || BrOpc == W65816::BVS) BrOpc == W65816::BVC || BrOpc == W65816::BVS)
SafeToErase = false; SafeToErase = false;
if ((BrOpc == W65816::BMI || BrOpc == W65816::BPL) &&
!setsSign16(*Writer))
SafeToErase = false;
break; break;
} }
// PHP/PLP-wrap-aware: only safe when LDAfi-expansion sets N/Z // PHP/PLP-wrap-aware: only safe when LDAfi-expansion sets N/Z

View file

@ -63,6 +63,7 @@ LLVMInitializeW65816Target() {
initializeW65816I32IncFoldPass(PR); initializeW65816I32IncFoldPass(PR);
initializeW65816ImgCalleeSavePass(PR); initializeW65816ImgCalleeSavePass(PR);
initializeW65816NarrowI32MulPass(PR); initializeW65816NarrowI32MulPass(PR);
initializeW65816CmpPreparePass(PR);
initializeW65816PromoteFiToImgPass(PR); initializeW65816PromoteFiToImgPass(PR);
initializeW65816StackSlotMergePass(PR); initializeW65816StackSlotMergePass(PR);
initializeW65816StackRelToImgPass(PR); initializeW65816StackRelToImgPass(PR);
@ -204,6 +205,11 @@ void W65816PassConfig::addISelPrepare() {
// operands are zero. Catches the sumSquares-style `(u32)i * i` // operands are zero. Catches the sumSquares-style `(u32)i * i`
// pattern that SDAG-level analysis can't see across BB boundaries. // pattern that SDAG-level analysis can't see across BB boundaries.
addPass(createW65816NarrowI32Mul()); 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(); TargetPassConfig::addISelPrepare();
} }

View file

@ -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
}

View file

@ -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
}

View file

@ -8,10 +8,11 @@
; bias folds: 0x1234 -> sbc #0x9234). x <= K / x > K become x < K+1 / ; 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 ; 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, ; 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 ; eq / ne feeding a branch (W65816CmpPrepare): the low halves, bne, then
; constant 0 just lo | hi. ; 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 ; 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 ; 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 @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) { define void @br_eq(i32 %a, i32 %b) {
; CHECK-LABEL: br_eq: ; CHECK-LABEL: br_eq:
; CHECK-NOT: php ; CHECK-NOT: php
; CHECK: eor 0xa, s ; CHECK: stx 0xd0
; CHECK: eor 0x8, s ; CHECK-NEXT: cmp 0x4, s
; CHECK-NEXT: sta ; CHECK-NEXT: bne [[F:.LBB[0-9_]+]]
; CHECK-NEXT: ora ; CHECK: lda 0xd0
; CHECK-NEXT: bne ; CHECK-NEXT: cmp 0x6, s
; CHECK-NEXT: bne [[F]]
; CHECK: rtl ; CHECK: rtl
entry: entry:
%c = icmp eq i32 %a, %b %c = icmp eq i32 %a, %b
@ -46,11 +49,12 @@ f:
define void @br_ne(i32 %a, i32 %b) { define void @br_ne(i32 %a, i32 %b) {
; CHECK-LABEL: br_ne: ; CHECK-LABEL: br_ne:
; CHECK-NOT: php ; CHECK-NOT: php
; CHECK: eor 0xa, s ; CHECK: stx 0xd0
; CHECK: eor 0x8, s ; CHECK-NEXT: cmp 0x4, s
; CHECK-NEXT: sta ; CHECK-NEXT: bne [[T:.LBB[0-9_]+]]
; CHECK-NEXT: ora ; CHECK: lda 0xd0
; CHECK-NEXT: beq ; CHECK-NEXT: cmp 0x6, s
; CHECK-NEXT: bne [[T]]
; CHECK: rtl ; CHECK: rtl
entry: entry:
%c = icmp ne i32 %a, %b %c = icmp ne i32 %a, %b
@ -236,11 +240,11 @@ f:
define void @brk_eq(i32 %a) { define void @brk_eq(i32 %a) {
; CHECK-LABEL: brk_eq: ; CHECK-LABEL: brk_eq:
; CHECK-NOT: php ; CHECK-NOT: php
; CHECK: eor #0x1234 ; CHECK: cmp #0x5678
; CHECK: eor #0x5678 ; CHECK-NEXT: bne [[F:.LBB[0-9_]+]]
; CHECK-NEXT: sta ; CHECK: lda 0xd0
; CHECK-NEXT: ora ; CHECK-NEXT: cmp #0x1234
; CHECK-NEXT: bne ; CHECK-NEXT: bne [[F]]
; CHECK: rtl ; CHECK: rtl
entry: entry:
%c = icmp eq i32 %a, 305419896 %c = icmp eq i32 %a, 305419896
@ -256,11 +260,11 @@ f:
define void @brk_ne(i32 %a) { define void @brk_ne(i32 %a) {
; CHECK-LABEL: brk_ne: ; CHECK-LABEL: brk_ne:
; CHECK-NOT: php ; CHECK-NOT: php
; CHECK: eor #0x1234 ; CHECK: cmp #0x5678
; CHECK: eor #0x5678 ; CHECK-NEXT: bne [[T:.LBB[0-9_]+]]
; CHECK-NEXT: sta ; CHECK: lda 0xd0
; CHECK-NEXT: ora ; CHECK-NEXT: cmp #0x1234
; CHECK-NEXT: beq ; CHECK-NEXT: bne [[T]]
; CHECK: rtl ; CHECK: rtl
entry: entry:
%c = icmp ne i32 %a, 305419896 %c = icmp ne i32 %a, 305419896
@ -452,9 +456,7 @@ define void @brk_slt0(i32 %a) {
; CHECK-LABEL: brk_slt0: ; CHECK-LABEL: brk_slt0:
; CHECK-NOT: sbc ; CHECK-NOT: sbc
; CHECK: lda 0xd0 ; CHECK: lda 0xd0
; CHECK-NEXT: eor #0x8000 ; CHECK-NEXT: bmi
; CHECK-NEXT: cmp #0x8000
; CHECK-NEXT: bcc
; CHECK: rtl ; CHECK: rtl
entry: entry:
%c = icmp slt i32 %a, 0 %c = icmp slt i32 %a, 0
@ -472,9 +474,7 @@ define void @brk_sgtm1(i32 %a) {
; CHECK-LABEL: brk_sgtm1: ; CHECK-LABEL: brk_sgtm1:
; CHECK-NOT: sbc ; CHECK-NOT: sbc
; CHECK: lda 0xd0 ; CHECK: lda 0xd0
; CHECK-NEXT: eor #0x8000 ; CHECK-NEXT: bmi
; CHECK-NEXT: cmp #0x8000
; CHECK-NEXT: bcc
; CHECK: rtl ; CHECK: rtl
entry: entry:
%c = icmp sgt i32 %a, -1 %c = icmp sgt i32 %a, -1

View file

@ -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
}

View file

@ -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
...

View file

@ -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
}

View file

@ -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
...

View file

@ -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 Lua test (`--segment-cap`, extra segments at bank 4+) and runs it through
`scripts/runMultiSeg.sh`. `scripts/runMultiSeg.sh`.
Default iteration count is 1 (smallest valid run). Override for Default iteration count is 3. CoreMark only validates a run of at
publishable scores: 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 ```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 ## Running
```bash ```bash
bash tests/coremark/runCoreMark.sh --layer2 bash tests/coremark/runCoreMark.sh --layer2
``` ```
The run terminates with `0xC0DE` written to `$025000` on success. This is a correctness gate, not just a benchmark. core_portme.c sends
Elapsed VBL ticks (60 Hz) are stored at `$025002` (low/hi halves). 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 **Note:** running CoreMark under MAME inside this project's restricted
shell crashes MAME (same SIGSEGV as Lua's full interpreter run — shell crashes MAME (same SIGSEGV as Lua's full interpreter run —

View file

@ -1,7 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Build CoreMark for W65816. Compiles the 5 core .c files plus our # Build CoreMark for W65816. Compiles the 5 core .c files plus our
# porting layer (core_portme.c). Iteration count baked at compile # 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. # 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. # instead of hard-failing the build -- see scripts/ccRegallocFallback.sh.
CC="$PROJECT_ROOT/scripts/ccRegallocFallback.sh" CC="$PROJECT_ROOT/scripts/ccRegallocFallback.sh"
ITERATIONS="${ITERATIONS:-1}" ITERATIONS="${ITERATIONS:-3}"
LAYER2=() LAYER2=()
for arg in "$@"; do for arg in "$@"; do

View file

@ -7,9 +7,9 @@
"num": 1, "num": 1,
"name": "SEG1", "name": "SEG1",
"base": "0x001000", "base": "0x001000",
"size": 41548, "size": 40728,
"bss_start": "0x00d000", "bss_start": "0x00b700",
"bss_size": 6268, "bss_size": 2034,
"image": "coreMark.bin", "image": "coreMark.bin",
"entry_offset": "0x0000" "entry_offset": "0x0000"
}, },
@ -17,7 +17,7 @@
"num": 2, "num": 2,
"name": "SEG2", "name": "SEG2",
"base": "0x040000", "base": "0x040000",
"size": 16362, "size": 16480,
"bss_start": "0x000000", "bss_start": "0x000000",
"bss_size": 0, "bss_size": 0,
"image": "coreMark.seg2.bin", "image": "coreMark.seg2.bin",

View file

@ -1,7 +1,6 @@
// CoreMark porting layer for W65816. See core_portme.h for config. // CoreMark porting layer for W65816. See core_portme.h for config.
#include "coremark.h" #include "coremark.h"
#include "core_portme.h" #include "core_portme.h"
#include <time.h>
#include <stdio.h> #include <stdio.h>
#if VALIDATION_RUN #if VALIDATION_RUN
@ -22,15 +21,58 @@ volatile ee_s32 seed3_volatile = 0x8;
volatile ee_s32 seed4_volatile = ITERATIONS; volatile ee_s32 seed4_volatile = ITERATIONS;
volatile ee_s32 seed5_volatile = 0; volatile ee_s32 seed5_volatile = 0;
// Timing — uses libc's clock() (VBL counter at 60 Hz). // The harness (runCoreMark.sh) owns these addresses. Bank 2 is clear of
#define GETMYTIME(_t) (*_t = (CORETIMETYPE)clock()) // 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 MYTIMEDIFF(fin, ini) ((fin) - (ini))
#define TIMER_RES_DIVIDER 1 #define TIMER_RES_DIVIDER 1
#define SAMPLE_TIME_IMPLEMENTATION 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 start_time_val;
static CORETIMETYPE stop_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) { void start_time(void) {
GETMYTIME(&start_time_val); GETMYTIME(&start_time_val);

View file

@ -1,7 +1,8 @@
// CoreMark porting layer for the W65816 / Apple IIgs target. // CoreMark porting layer for the W65816 / Apple IIgs target.
// Adapts the barebones template: // Adapts the barebones template:
// - clock() via VBL counter at 60 Hz (CLOCKS_PER_SEC=60) // - time from a millisecond counter runCoreMark.sh's MAME harness keeps
// - printf via our libc // (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) // - MEM_STATIC (heap-free)
// - HAS_FLOAT=1 (soft-double; needed for the score calc) // - HAS_FLOAT=1 (soft-double; needed for the score calc)
// - Single-threaded (MULTITHREAD=1) // - Single-threaded (MULTITHREAD=1)
@ -10,8 +11,8 @@
#define CORE_PORTME_H #define CORE_PORTME_H
#define HAS_FLOAT 1 #define HAS_FLOAT 1
#define HAS_TIME_H 1 #define HAS_TIME_H 0
#define USE_CLOCK 1 #define USE_CLOCK 0
#define HAS_STDIO 1 #define HAS_STDIO 1
#define HAS_PRINTF 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)) #define align_mem(x) (void *)(4 + (((ee_ptr_int)(x)-1) & ~3))
// ITERATIONS — keep small for the 1 MHz IIgs. CoreMark spec wants // ITERATIONS: build.sh passes -DITERATIONS (see its default). One
// >= 10 sec of runtime; at this clock that's order-of-magnitude // iteration takes about 10.8 s of emulated time at -O2 (2026-09), and
// 100 iterations. Run with ITERATIONS=1 first to validate, then // CoreMark only validates a run of at least 10 s.
// scale up. The user can pass -DITERATIONS=N to override.
#ifndef ITERATIONS #ifndef ITERATIONS
#define ITERATIONS 1 #define ITERATIONS 3
#endif #endif
// Timing. CLOCKS_PER_SEC = 60 (VBL). Use TIMER_RES_DIVIDER=1 since // Timing: emulated milliseconds, updated once per video frame.
// 60 Hz is already coarse.
#define CORETIMETYPE ee_u32 #define CORETIMETYPE ee_u32
typedef ee_u32 CORE_TICKS; typedef ee_u32 CORE_TICKS;
typedef double secs_ret; typedef double secs_ret;

View file

@ -1,17 +1,58 @@
#!/usr/bin/env bash #!/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: # Usage:
# bash tests/coremark/runCoreMark.sh # default: ITERATIONS=1 # bash tests/coremark/runCoreMark.sh # ITERATIONS from build.sh
# ITERATIONS=5 bash tests/coremark/runCoreMark.sh # ITERATIONS=10 bash tests/coremark/runCoreMark.sh
# bash tests/coremark/runCoreMark.sh --layer2 # 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 set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" source "$SCRIPT_DIR/../../scripts/common.sh"
CM_OBJ="$SCRIPT_DIR/build" CM_OBJ="$SCRIPT_DIR/build"
RT="$PROJECT_ROOT/runtime" 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" cd "$SCRIPT_DIR"
rm -rf "$CM_OBJ" rm -rf "$CM_OBJ"
bash "$SCRIPT_DIR/build.sh" "$@" bash "$SCRIPT_DIR/build.sh" "$@"
@ -33,9 +74,49 @@ bash "$SCRIPT_DIR/build.sh" "$@"
ls -la coreMark*.bin ls -la coreMark*.bin
echo "" echo ""
# 0xC0DE sentinel = success; otherwise 0xBEEF = mid-run hang. # The run ends on portable_fini's 0xC0DE sentinel at $02:5000 ($BEEF there
# Elapsed ticks at 0x025002..0x025005 (low/hi). # means CoreMark started but never finished). The tick and text addresses
MAME_CHECK_FRAME=900 MAME_SECS=30 MAME_TIMEOUT=300 MAME_RAMSIZE=4M \ # 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" \ bash "$PROJECT_ROOT/scripts/runMultiSeg.sh" \
"$SCRIPT_DIR/coreMark.manifest.json" \ "$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"

View file

@ -7,7 +7,7 @@
"num": 1, "num": 1,
"name": "SEG1", "name": "SEG1",
"base": "0x001000", "base": "0x001000",
"size": 44548, "size": 43790,
"bss_start": "0x00e200", "bss_start": "0x00e200",
"bss_size": 4248, "bss_size": 4248,
"image": "luaTest.bin", "image": "luaTest.bin",