Even more code gen issues fixed.
This commit is contained in:
parent
000aa1c079
commit
be2336eb09
28 changed files with 1769 additions and 100 deletions
Binary file not shown.
|
|
@ -136,9 +136,8 @@ float fmodf(float x, float y) {
|
|||
// uses the IEEE-754 layout: y0 = bits >> 1 + bias_shift. Then ~6
|
||||
// Newton iterations land within ~1 ULP for double precision.
|
||||
//
|
||||
// We dodge the snprintf-style libcall pitfalls by NOT comparing
|
||||
// double-to-double via `<`/`>=`; instead we test the IEEE-754
|
||||
// sign bit and exponent directly.
|
||||
// The sign and zero tests read the IEEE-754 bits directly: cheaper than
|
||||
// a `<` / `>=` libcall compare.
|
||||
double sqrt(double x) {
|
||||
uint64_t b;
|
||||
__builtin_memcpy(&b, &x, sizeof(b));
|
||||
|
|
|
|||
|
|
@ -493,10 +493,8 @@ static void emitDouble(double v, int prec, char spec, const Spec *s) {
|
|||
if (prec > 9) {
|
||||
prec = 9;
|
||||
}
|
||||
// Avoid `if (v < 0)` (which calls __ltdf2) — the W65816 codegen
|
||||
// for that comparison passes its double arg with a missing word,
|
||||
// and the test silently returns false for negatives. Read the
|
||||
// IEEE-754 sign bit and clear it inline instead.
|
||||
// Read and clear the IEEE-754 sign bit inline rather than `v < 0`:
|
||||
// cheaper than the __ltdf2 libcall, and it also handles -0.0.
|
||||
unsigned long long bits;
|
||||
__builtin_memcpy(&bits, &v, 8);
|
||||
if (bits & ((unsigned long long)1 << 63)) {
|
||||
|
|
|
|||
|
|
@ -305,12 +305,20 @@ s16 __unorddf2(u64 a, u64 b) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// LLVM's soft-float lowering tests each helper's result against zero with
|
||||
// the comparison's own operator: a < b is __ltdf2(a, b) < 0, a <= b is
|
||||
// __ledf2(a, b) <= 0, a >= b is __gedf2(a, b) >= 0. So the ordered helpers
|
||||
// return the three-way order, as softFloat.c's do. They used to return C
|
||||
// booleans, which made every double `<` false, `>=` always true and `<=`
|
||||
// inverted (CoreMark's "must run 10 s" check, fmod of a negative quotient
|
||||
// and asin all went wrong). eq/ne are tested with == 0 / != 0, for which
|
||||
// "nonzero when different" is enough.
|
||||
s16 __eqdf2(u64 a, u64 b) { return __cmpdf2(a, b) != 0; }
|
||||
s16 __nedf2(u64 a, u64 b) { return __cmpdf2(a, b) != 0; }
|
||||
s16 __ltdf2(u64 a, u64 b) { return __cmpdf2(a, b) < 0; }
|
||||
s16 __ledf2(u64 a, u64 b) { return __cmpdf2(a, b) <= 0; }
|
||||
s16 __gtdf2(u64 a, u64 b) { return __cmpdf2(a, b) > 0; }
|
||||
s16 __gedf2(u64 a, u64 b) { return __cmpdf2(a, b) >= 0; }
|
||||
s16 __ltdf2(u64 a, u64 b) { return __cmpdf2(a, b); }
|
||||
s16 __ledf2(u64 a, u64 b) { return __cmpdf2(a, b); }
|
||||
s16 __gtdf2(u64 a, u64 b) { return __cmpdf2(a, b); }
|
||||
s16 __gedf2(u64 a, u64 b) { return __cmpdf2(a, b); }
|
||||
|
||||
// double <-> float conversions.
|
||||
u64 __extendsfdf2(u32 x) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,15 @@
|
|||
# entry, lets the program run, then reads check-address values.
|
||||
#
|
||||
# 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
|
||||
source "$(dirname "$0")/common.sh"
|
||||
|
|
@ -13,6 +22,10 @@ MANIFEST="$1"
|
|||
shift
|
||||
CHECK_FRAME=${MAME_CHECK_FRAME:-300}
|
||||
SECS=${MAME_SECS:-6}
|
||||
DONE_ADDR=${MAME_DONE_ADDR:-}
|
||||
DONE_VAL=${MAME_DONE_VAL:-}
|
||||
TICK_ADDR=${MAME_TICK_ADDR:-}
|
||||
TEXT_ADDR=${MAME_TEXT_ADDR:-}
|
||||
|
||||
# Build address list as Lua table entries, mirroring runInMame.sh.
|
||||
LUA_CHECKS=""
|
||||
|
|
@ -52,7 +65,11 @@ EOF
|
|||
[ -n "$PARSED" ] || die "manifest parse failed"
|
||||
|
||||
LUA_PATH=$(mktemp --suffix=.lua)
|
||||
trap 'rm -f "$LUA_PATH"' EXIT
|
||||
# MAME's cfg / nvram (the IIgs battery RAM) go to a throwaway directory:
|
||||
# run from a test directory with no mame.ini, MAME would otherwise leave
|
||||
# them there, and a saved BRAM would carry settings from run to run.
|
||||
MAME_STATE=$(mktemp -d)
|
||||
trap 'rm -f "$LUA_PATH"; rm -rf "${MAME_STATE:?}"' EXIT
|
||||
|
||||
# Build the per-segment load lua.
|
||||
LOAD_LUA=""
|
||||
|
|
@ -79,15 +96,41 @@ while IFS='|' read -r img base entry sz; do
|
|||
fi
|
||||
done <<< "$PARSED"
|
||||
|
||||
# Lua fragments for the optional hooks.
|
||||
# Each hook's memory is cleared at load, so stale RAM can neither end the
|
||||
# run early nor pass for program output.
|
||||
DONE_LUA="local done = false"
|
||||
INIT_LUA=""
|
||||
if [ -n "$DONE_ADDR" ]; then
|
||||
DONE_LUA="local done = loaded and mem:read_u16($DONE_ADDR) == $DONE_VAL"
|
||||
INIT_LUA="$INIT_LUA mem:write_u16($DONE_ADDR, 0)"
|
||||
fi
|
||||
TICK_LUA=""
|
||||
if [ -n "$TICK_ADDR" ]; then
|
||||
TICK_LUA="if loaded then mem:write_u32($TICK_ADDR, math.floor(emu.time() * 1000) & 0xFFFFFFFF) end"
|
||||
fi
|
||||
TEXT_LUA=""
|
||||
if [ -n "$TEXT_ADDR" ]; then
|
||||
INIT_LUA="$INIT_LUA mem:write_u8($TEXT_ADDR, 0)"
|
||||
TEXT_LUA="local text = ''
|
||||
for i = 0, 0x0FFF do
|
||||
local c = mem:read_u8($TEXT_ADDR + i)
|
||||
if c == 0 then break end
|
||||
text = text .. string.char(c)
|
||||
end
|
||||
for line in text:gmatch('[^\\n]+') do print('MAME-TEXT ' .. line) end"
|
||||
fi
|
||||
|
||||
cat > "$LUA_PATH" <<EOF
|
||||
local frame = 0
|
||||
local loaded = false
|
||||
emu.register_frame_done(function()
|
||||
frame = frame + 1
|
||||
if frame == 30 and not loaded then
|
||||
local cpu = manager.machine.devices[":maincpu"]
|
||||
local mem = cpu.spaces["program"]
|
||||
if frame == 30 and not loaded then
|
||||
$LOAD_LUA
|
||||
$INIT_LUA
|
||||
loaded = true
|
||||
cpu.state["PC"].value = $ENTRY_BASE + $ENTRY_OFF
|
||||
cpu.state["PB"].value = ($ENTRY_BASE >> 16) & 0xff
|
||||
|
|
@ -98,9 +141,11 @@ $LOAD_LUA
|
|||
cpu.state["S"].value = 0x01FF
|
||||
print('MAME-READY pc=0x' .. string.format('%06x', $ENTRY_BASE + $ENTRY_OFF))
|
||||
end
|
||||
if frame == $CHECK_FRAME then
|
||||
local cpu = manager.machine.devices[":maincpu"]
|
||||
local mem = cpu.spaces["program"]
|
||||
$TICK_LUA
|
||||
$DONE_LUA
|
||||
if done or frame == $CHECK_FRAME then
|
||||
print(string.format('MAME-STOP frame=%d secs=%.3f', frame, emu.time()))
|
||||
$TEXT_LUA
|
||||
$LUA_CHECKS
|
||||
manager.machine:exit()
|
||||
end
|
||||
|
|
@ -114,11 +159,14 @@ fi
|
|||
OUT=$(SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout "${MAME_TIMEOUT:-30}" mame apple2gs \
|
||||
"${RAMSIZE_ARG[@]}" \
|
||||
-rompath "$PROJECT_ROOT/tools/mame/roms" \
|
||||
-cfg_directory "$MAME_STATE" -nvram_directory "$MAME_STATE" \
|
||||
-plugins -autoboot_script "$LUA_PATH" \
|
||||
-video none -sound none -nothrottle -seconds_to_run "$SECS" 2>&1 | /usr/bin/grep -E "^(MAME-|SEG-)")
|
||||
|
||||
echo "$OUT"
|
||||
mapfile -t GOT_LIST < <(printf '%s\n' "$OUT" | /usr/bin/grep -oE 'val=0x[0-9a-f]+' | sed 's/val=0x//')
|
||||
# Only the MAME-READ lines: MAME-TEXT is program output and could contain
|
||||
# anything.
|
||||
mapfile -t GOT_LIST < <(printf '%s\n' "$OUT" | /usr/bin/grep '^MAME-READ' | /usr/bin/grep -oE 'val=0x[0-9a-f]+' | sed 's/val=0x//')
|
||||
ok=1
|
||||
for i in "${!EXPECT_LIST[@]}"; do
|
||||
if [ "${GOT_LIST[$i]:-}" != "${EXPECT_LIST[$i]}" ]; then
|
||||
|
|
|
|||
|
|
@ -1633,6 +1633,107 @@ EOF
|
|||
fi
|
||||
rm -f "$cDblMame" "$oDblMame" "$oSdMame" "$binDblMame"
|
||||
|
||||
# Every ordered float and double predicate (< <= > >= == !=) on eight
|
||||
# values, signed zeros included, as a branch and as a 0/1 value; kExp is
|
||||
# the host-computed expected bit table. Pins two 2026-09-29 fixes: the
|
||||
# backend read the compare helpers' 16-bit int result as an i32 and
|
||||
# tested the sign of X (getCmpLibcallReturnType), and softDouble.c's
|
||||
# __ltdf2 / __ledf2 / __gedf2 returned booleans where LLVM tests a
|
||||
# three-way order. Before them 396 of the 768 answers were wrong.
|
||||
log "check: MAME float/double compares: every ordered predicate, signed zeros"
|
||||
cFpCmpFile="$(mktemp --suffix=.c)"
|
||||
oFpCmpFile="$(mktemp --suffix=.o)"
|
||||
binFpCmpFile="$(mktemp --suffix=.bin)"
|
||||
cat > "$cFpCmpFile" <<'EOF'
|
||||
// Every ordered float and double predicate on 8 values (signed zeros
|
||||
// included) as a branch and as a 0/1 value. kExp is the host-computed
|
||||
// expected bit table. Mailbox: 0x025000 = mismatches, 0x025002 = first
|
||||
// failing case, 0x025004 = 0xC0DE done.
|
||||
typedef unsigned char u8;
|
||||
typedef unsigned short u16;
|
||||
#define NV 8
|
||||
#define NP 6
|
||||
#define BAR() __asm__ volatile("" ::: "memory")
|
||||
#define NOI __attribute__((noinline)) static
|
||||
static const float kF[NV] = { -2.5f, -1.0f, -0.0f, 0.0f, 1.0f, 1.5f, 10.0f, 1e30f };
|
||||
static const double kD[NV] = { -2.5, -1.0, -0.0, 0.0, 1.0, 1.5, 10.0, 1e30 };
|
||||
static const u8 kExp[96] = {
|
||||
0xFE, 0xFC, 0xF0, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0xFF, 0xFE, 0xFC, 0xFC, 0xF0, 0xE0, 0xC0, 0x80,
|
||||
0x00, 0x01, 0x03, 0x03, 0x0F, 0x1F, 0x3F, 0x7F, 0x01, 0x03, 0x0F, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF,
|
||||
0x01, 0x02, 0x0C, 0x0C, 0x10, 0x20, 0x40, 0x80, 0xFE, 0xFD, 0xF3, 0xF3, 0xEF, 0xDF, 0xBF, 0x7F,
|
||||
0xFE, 0xFC, 0xF0, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0xFF, 0xFE, 0xFC, 0xFC, 0xF0, 0xE0, 0xC0, 0x80,
|
||||
0x00, 0x01, 0x03, 0x03, 0x0F, 0x1F, 0x3F, 0x7F, 0x01, 0x03, 0x0F, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF,
|
||||
0x01, 0x02, 0x0C, 0x0C, 0x10, 0x20, 0x40, 0x80, 0xFE, 0xFD, 0xF3, 0xF3, 0xEF, 0xDF, 0xBF, 0x7F,
|
||||
};
|
||||
#define ALL(M, T, p) M(T, p, lt, <) M(T, p, le, <=) M(T, p, gt, >) M(T, p, ge, >=) M(T, p, eq, ==) M(T, p, ne, !=)
|
||||
#define BR(T, p, n, op) NOI u16 br##p##_##n(T a, T b) { if (a op b) { BAR(); return 1u; } return 0u; }
|
||||
#define VA(T, p, n, op) NOI u16 va##p##_##n(T a, T b) { return (u16)(a op b); }
|
||||
ALL(BR, float, f)
|
||||
ALL(VA, float, f)
|
||||
ALL(BR, double, d)
|
||||
ALL(VA, double, d)
|
||||
typedef u16 (*FnF)(float, float);
|
||||
typedef u16 (*FnD)(double, double);
|
||||
#define R(p, k) p##k##_lt, p##k##_le, p##k##_gt, p##k##_ge, p##k##_eq, p##k##_ne
|
||||
static FnF const kBrF[NP] = { R(br, f) };
|
||||
static FnF const kVaF[NP] = { R(va, f) };
|
||||
static FnD const kBrD[NP] = { R(br, d) };
|
||||
static FnD const kVaD[NP] = { R(va, d) };
|
||||
static u16 gBad;
|
||||
static u16 gFirst = 0xFFFFu;
|
||||
static void check(u16 got, u16 want, u16 id) {
|
||||
if (got != want) {
|
||||
if (gBad == 0u) {
|
||||
gFirst = id;
|
||||
}
|
||||
gBad++;
|
||||
}
|
||||
}
|
||||
int main(void) {
|
||||
u16 p;
|
||||
u16 i;
|
||||
u16 j;
|
||||
u16 bit = 0u;
|
||||
for (p = 0u; p < NP; p++) {
|
||||
for (i = 0u; i < NV; i++) {
|
||||
for (j = 0u; j < NV; j++) {
|
||||
u16 want = (u16)((kExp[bit >> 3] >> (bit & 7u)) & 1u);
|
||||
check(kBrF[p](kF[i], kF[j]), want, bit);
|
||||
check(kVaF[p](kF[i], kF[j]) != 0u, want, (u16)(bit | 0x4000u));
|
||||
bit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (p = 0u; p < NP; p++) {
|
||||
for (i = 0u; i < NV; i++) {
|
||||
for (j = 0u; j < NV; j++) {
|
||||
u16 want = (u16)((kExp[bit >> 3] >> (bit & 7u)) & 1u);
|
||||
check(kBrD[p](kD[i], kD[j]), want, bit);
|
||||
check(kVaD[p](kD[i], kD[j]) != 0u, want, (u16)(bit | 0x4000u));
|
||||
bit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
*(volatile u16 *)0x025000UL = gBad;
|
||||
*(volatile u16 *)0x025002UL = gFirst;
|
||||
*(volatile u16 *)0x025004UL = 0xC0DEu;
|
||||
for (;;) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
"$CLANG" --target=w65816 -O2 -ffunction-sections -c \
|
||||
"$cFpCmpFile" -o "$oFpCmpFile"
|
||||
"$PROJECT_ROOT/tools/link816" -o "$binFpCmpFile" --text-base 0x1000 \
|
||||
"$oCrt0File" "$oFpCmpFile" "$PROJECT_ROOT/runtime/softFloat.o" \
|
||||
"$PROJECT_ROOT/runtime/softDouble.o" "$oLibgccFile" >/dev/null 2>&1
|
||||
if ! MAME_CHECK_FRAME=900 MAME_SECS=20 \
|
||||
bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binFpCmpFile" --check \
|
||||
0x025000=0000 0x025002=ffff 0x025004=c0de >/dev/null 2>&1; then
|
||||
die "MAME: float/double compares wrong (mailbox 0x025000 = mismatches, 0x025002 = first case)"
|
||||
fi
|
||||
rm -f "$cFpCmpFile" "$oFpCmpFile" "$binFpCmpFile"
|
||||
|
||||
# Regression: W65816SepRepCleanup's PHI-copy hoist must not move a
|
||||
# `STA slot` above an `AND/ORA/EOR/ADC/SBC/CMP_StackRel slot` read in
|
||||
# the gap. Before the fix (gap check only guarded LDA_StackRel),
|
||||
|
|
@ -3089,6 +3190,410 @@ EOF
|
|||
fi
|
||||
rm -f "$cI32CmpFile" "$oI32CmpFile" "$binI32CmpFile"
|
||||
|
||||
# The 2026-09-29 compare / loop lowering: sign tests on i8 / i16 /
|
||||
# i32 (bmi / bpl, `and #$80`, the `cmp #$8000` carry form under a PHI
|
||||
# copy), i32 == / != split into per-half branches (W65816CmpPrepare,
|
||||
# including an argument's high word, which arrives in X: the
|
||||
# W65816LowerWide32 register-class fix), i32 shifts by 16 to 31, and
|
||||
# loops with constant bounds across 0x7FFF / 0x8000 / 0xFFFF /
|
||||
# 0x10000 whose test moved onto the incremented counter. kExp holds
|
||||
# the same functions' results built for the host by gcc (1663 of
|
||||
# them). Without the LowerWide32 fix the program never finishes.
|
||||
log "check: MAME compare/loop lowering: sign tests, i32 ==/!= split, shifts >= 16, post-increment loop bounds"
|
||||
cLowerFile="$(mktemp --suffix=.c)"
|
||||
oLowerFile="$(mktemp --suffix=.o)"
|
||||
binLowerFile="$(mktemp --suffix=.bin)"
|
||||
cat > "$cLowerFile" <<'EOF'
|
||||
// Sign tests (i8 / i16 / i32 against 0 and -1), i32 == / != split into
|
||||
// per-half branches, i32 shifts by 16 and more, and loops with constant
|
||||
// bounds across 0x7FFF / 0x8000 / 0xFFFF / 0x10000 tested on the
|
||||
// incremented counter. kExp holds the results of the same functions
|
||||
// built for the host by gcc. Mailbox: 0x025000 = mismatches, 0x025002 =
|
||||
// first failing result index, 0x025004 = results checked, 0x025006 =
|
||||
// 0xC0DE done. No <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
|
||||
# malloc could hand out the same memory to two callers.
|
||||
log "check: MAME runs malloc/free coalesce — three blocks freed in alloc order (#100)"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ add_llvm_target(W65816CodeGen
|
|||
W65816I32IncFold.cpp
|
||||
W65816ImgCalleeSave.cpp
|
||||
W65816NarrowI32Mul.cpp
|
||||
W65816CmpPrepare.cpp
|
||||
W65816PromoteFiToImg.cpp
|
||||
W65816StackRelToImg.cpp
|
||||
W65816StackSlotMerge.cpp
|
||||
|
|
@ -58,6 +59,7 @@ add_llvm_target(W65816CodeGen
|
|||
Support
|
||||
Target
|
||||
TargetParser
|
||||
TransformUtils
|
||||
W65816Desc
|
||||
W65816Info
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,12 @@ FunctionPass *createW65816SjLjFinalize();
|
|||
// zext that a SDAG-level combine would key off. See W65816NarrowI32Mul.cpp.
|
||||
FunctionPass *createW65816NarrowI32Mul();
|
||||
|
||||
// IR pass before ISel: loop tests on the incremented counter (`V < C` as
|
||||
// `V+1 < C+1` when V+1 is already computed), and i32 == / != feeding a
|
||||
// branch split into per-half i16 compares SelectionDAGBuilder branches on
|
||||
// one at a time. See W65816CmpPrepare.cpp.
|
||||
FunctionPass *createW65816CmpPrepare();
|
||||
|
||||
// Post-LSR IR pass: detect pointer-walking loops over GlobalAddress
|
||||
// bases and rewrite to forward-counter + indexed GEP form. LSR's
|
||||
// transform is faster for pointer-arg loops (single ptr increment per
|
||||
|
|
@ -213,6 +219,7 @@ void initializeW65816SjLjFinalizePass(PassRegistry &);
|
|||
void initializeW65816LowerWide32Pass(PassRegistry &);
|
||||
void initializeW65816ImgCalleeSavePass(PassRegistry &);
|
||||
void initializeW65816NarrowI32MulPass(PassRegistry &);
|
||||
void initializeW65816CmpPreparePass(PassRegistry &);
|
||||
void initializeW65816PromoteFiToImgPass(PassRegistry &);
|
||||
void initializeW65816StackSlotMergePass(PassRegistry &);
|
||||
void initializeW65816StackRelToImgPass(PassRegistry &);
|
||||
|
|
|
|||
223
src/llvm/lib/Target/W65816/W65816CmpPrepare.cpp
Normal file
223
src/llvm/lib/Target/W65816/W65816CmpPrepare.cpp
Normal 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();
|
||||
}
|
||||
|
|
@ -528,7 +528,6 @@ static void promoteI8Cmp(SDValue &LHS, SDValue &RHS, ISD::CondCode CC,
|
|||
static W65816CC::CondCode normalizeCC(SDValue &LHS, SDValue &RHS,
|
||||
ISD::CondCode &CC, SelectionDAG &DAG,
|
||||
const SDLoc &DL) {
|
||||
promoteI8Cmp(LHS, RHS, CC, DAG, DL);
|
||||
// CMP wants the comparand (constant or memory) on the right. If a DAG
|
||||
// pre-pass put the constant on the left, swap and flip the condition.
|
||||
if (isa<ConstantSDNode>(LHS) && !isa<ConstantSDNode>(RHS)) {
|
||||
|
|
@ -536,6 +535,35 @@ static W65816CC::CondCode normalizeCC(SDValue &LHS, SDValue &RHS,
|
|||
CC = ISD::getSetCCSwappedOperands(CC);
|
||||
}
|
||||
|
||||
// Sign tests: x < 0 and x >= 0, and the x <= -1 / x > -1 forms instcombine
|
||||
// canonicalises some of them to. The sign is a single bit, so no ordered
|
||||
// compare is needed: an i16 is `cmp #0 ; bmi/bpl` (StackSlotCleanup then
|
||||
// drops the cmp when the op that produced x already set N), 6 cycles less
|
||||
// than the sign-bias XOR below; an i8's sign is bit 7, so it is
|
||||
// `and #$80 ; bne/beq` instead of an 11-cycle sign extension.
|
||||
if (auto *KC = dyn_cast<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":
|
||||
// a < b (signed) iff (a ^ 0x8000) < (b ^ 0x8000) (unsigned)
|
||||
// The XOR flips the sign bit, which converts signed-int ordering to
|
||||
|
|
@ -2003,6 +2031,34 @@ SDValue W65816TargetLowering::LowerShift(SDValue Op, SelectionDAG &DAG) const {
|
|||
return buildWide32(DAG, DL, NewLo, NewHi);
|
||||
}
|
||||
}
|
||||
// By 16 or more one half moves into the other and the rest is at most
|
||||
// an i16 shift, which the i16 patterns do inline for 1..15 (SRA only
|
||||
// for 15, its sign fill). `(u16)(x >> 16)`, the high word, was a
|
||||
// __lshrsi3 call; now it is the high half itself.
|
||||
if (N >= 16 && N <= 31) {
|
||||
SDLoc DL(Op);
|
||||
SDValue X = Op.getOperand(0);
|
||||
SDValue Zero = DAG.getConstant(0, DL, MVT::i16);
|
||||
SDValue ShN = DAG.getConstant(N - 16, DL, MVT::i16);
|
||||
if (Op0 == ISD::SHL) {
|
||||
SDValue Lo = extractWide32Lo(DAG, DL, X);
|
||||
SDValue NewHi =
|
||||
N == 16 ? Lo : DAG.getNode(ISD::SHL, DL, MVT::i16, Lo, ShN);
|
||||
return buildWide32(DAG, DL, Zero, NewHi);
|
||||
}
|
||||
if (Op0 == ISD::SRL) {
|
||||
SDValue Hi = extractWide32Hi(DAG, DL, X);
|
||||
SDValue NewLo =
|
||||
N == 16 ? Hi : DAG.getNode(ISD::SRL, DL, MVT::i16, Hi, ShN);
|
||||
return buildWide32(DAG, DL, NewLo, Zero);
|
||||
}
|
||||
if (Op0 == ISD::SRA && (N == 16 || N == 31)) {
|
||||
SDValue Hi = extractWide32Hi(DAG, DL, X);
|
||||
SDValue Fill = DAG.getNode(ISD::SRA, DL, MVT::i16, Hi,
|
||||
DAG.getConstant(15, DL, MVT::i16));
|
||||
return buildWide32(DAG, DL, N == 16 ? Hi : Fill, Fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,15 @@ public:
|
|||
return TargetLoweringBase::getScalarShiftAmountTy(DL, LHSTy);
|
||||
}
|
||||
|
||||
// The soft-float compare helpers (__ltsf2, __gedf2, __unorddf2, ...)
|
||||
// return C `int`, which is 16 bits here: the result is in A alone. The
|
||||
// i32 default read the i32 return pair A:X and tested the sign of X, a
|
||||
// register the helper never sets, so float and double `<`, `<=`, `>`
|
||||
// and `>=` answered with whatever X happened to hold.
|
||||
MVT::SimpleValueType getCmpLibcallReturnType() const override {
|
||||
return MVT::i16;
|
||||
}
|
||||
|
||||
// ptr32-mode hook: with patches/0007-targetlowering-virtual-
|
||||
// gettypeconversion making the base function virtual, this can be
|
||||
// overridden to force i64 to expand directly to i16 halves rather
|
||||
|
|
|
|||
|
|
@ -264,6 +264,51 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
|
|||
|
||||
// Pass 3: rewrite uses.
|
||||
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::iterator It = MBB.begin(); It != MBB.end();) {
|
||||
MachineInstr *MI = &*It++;
|
||||
|
|
@ -276,9 +321,7 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
|
|||
unsigned SubIdx = MI->getOperand(2).getImm();
|
||||
auto [Lo, Hi] = wideMap[Src];
|
||||
Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi;
|
||||
Register Dst = MI->getOperand(0).getReg();
|
||||
MRI.replaceRegWith(Dst, Half);
|
||||
useToErase.push_back(MI);
|
||||
forwardHalf(MI, MI->getOperand(0).getReg(), Half);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -299,8 +342,7 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
|
|||
unsigned SubIdx = SrcOp.getSubReg();
|
||||
auto [Lo, Hi] = wideMap[SrcOp.getReg()];
|
||||
Register Half = (SubIdx == llvm::sub_lo) ? Lo : Hi;
|
||||
MRI.replaceRegWith(DstOp.getReg(), Half);
|
||||
useToErase.push_back(MI);
|
||||
forwardHalf(MI, DstOp.getReg(), Half);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -313,7 +355,8 @@ bool W65816LowerWide32::runOnMachineFunction(MachineFunction &MF) {
|
|||
if (isPtrOp) {
|
||||
Register Ptr = MI->getOperand(1).getReg();
|
||||
if (Ptr.isVirtual() && wideMap.count(Ptr)) {
|
||||
auto [Lo, Hi] = wideMap[Ptr];
|
||||
Register Lo = current(wideMap[Ptr].first);
|
||||
Register Hi = current(wideMap[Ptr].second);
|
||||
unsigned NewOpc = (Opc == W65816::LDAptr32) ? W65816::LDAptr32S
|
||||
: (Opc == W65816::STAptr32) ? W65816::STAptr32S
|
||||
: W65816::STBptr32S;
|
||||
|
|
|
|||
|
|
@ -1085,6 +1085,27 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
if (It == MBB.begin()) break;
|
||||
}
|
||||
if (!Test || !FirstCorrupt) continue;
|
||||
// A sign test needs no wrap: `cmp #0 ; bmi` is `cmp #$8000 ; bcs`
|
||||
// (A >= $8000 unsigned exactly when bit 15 is set), and C survives
|
||||
// the N/Z-only corrupters, typically a PHI copy of a constant. The
|
||||
// CMP costs the same, the wrap's 7 cycles go.
|
||||
if ((BrOpc == W65816::BMI || BrOpc == W65816::BPL) &&
|
||||
Test->getOpcode() == W65816::CMPi16imm &&
|
||||
Test->getOperand(1).isImm() && Test->getOperand(1).getImm() == 0) {
|
||||
bool CarryClobbered = false;
|
||||
for (auto It = FirstCorrupt->getIterator();
|
||||
It != std::next(LastCorrupt->getIterator()); ++It)
|
||||
if (!It->isDebugInstr() && isCarryCorrupting(It->getOpcode()))
|
||||
CarryClobbered = true;
|
||||
if (!CarryClobbered) {
|
||||
const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
|
||||
Test->getOperand(1).setImm(0x8000);
|
||||
Br->setDesc(
|
||||
TII->get(BrOpc == W65816::BMI ? W65816::BCS : W65816::BCC));
|
||||
Changed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Stack-relative ops inside the wrap need one more byte of
|
||||
// displacement to compensate for PHP's S decrement. Without it,
|
||||
// `lda 5,s` between PHP and PLP reads at (orig_S-1)+5 = orig_S+4,
|
||||
|
|
@ -1590,20 +1611,42 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
return false;
|
||||
}
|
||||
};
|
||||
// Returns true iff walking back from `Start` (exclusive) finds an
|
||||
// A-modifier as the first non-skip op. Skips debug ops and
|
||||
// Returns the A-modifier, if walking back from `Start` (exclusive) finds
|
||||
// one as the first non-skip op, else null. Skips debug ops and
|
||||
// A-transparent stores; stops at the first real op. Templated to
|
||||
// accept either iterator or const_iterator (Cmps came from a non-
|
||||
// const iteration; predecessors are walked via const_iterator).
|
||||
auto walkbackBefore = [&](auto Start, auto Begin) -> bool {
|
||||
auto aWriterBefore = [&](auto Start, auto Begin) -> const MachineInstr * {
|
||||
auto It = Start;
|
||||
while (It != Begin) {
|
||||
--It;
|
||||
if (It->isDebugInstr()) continue;
|
||||
if (isATransparent(*It)) continue;
|
||||
return It->modifiesRegister(W65816::A, TRI);
|
||||
return It->modifiesRegister(W65816::A, TRI) ? &*It : nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
// BMI / BPL read N, which the A-writer sets from bit 15 of A only when it
|
||||
// is a full 16-bit op: an 8-bit op sets it from bit 7 and XBA from the new
|
||||
// low byte. (Z, all BEQ / BNE read, comes out of the same instruction as
|
||||
// N, so for the 16-bit writers the two are equally trustworthy.) Keep the
|
||||
// `cmp #0` unless the writer defines the 16-bit accumulator class, or is a
|
||||
// 16-bit copy or pull.
|
||||
auto setsSign16 = [](const MachineInstr &W) {
|
||||
switch (W.getOpcode()) {
|
||||
case TargetOpcode::COPY:
|
||||
case W65816::TXA:
|
||||
case W65816::TYA:
|
||||
case W65816::PLA:
|
||||
return true;
|
||||
case W65816::XBA16:
|
||||
return false;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const MCInstrDesc &D = W.getDesc();
|
||||
return D.getNumDefs() > 0 &&
|
||||
D.operands()[0].RegClass == W65816::Acc16RegClassID;
|
||||
};
|
||||
// Pass 1c can only eliminate CMPi16imm $a, 0 if the preceding
|
||||
// A-modifier reliably sets N/Z to reflect A's final value. LDAfi
|
||||
|
|
@ -1626,8 +1669,9 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
!Cmp->getOperand(1).isImm() ||
|
||||
Cmp->getOperand(1).getImm() != 0)
|
||||
continue;
|
||||
bool Found = walkbackBefore(Cmp->getIterator(), MBB.begin());
|
||||
if (!Found) continue;
|
||||
const MachineInstr *Writer =
|
||||
aWriterBefore(Cmp->getIterator(), MBB.begin());
|
||||
if (!Writer) continue;
|
||||
// Only eliminate if there are NO LdaLike instructions between
|
||||
// this CMP and the next Bxx (or end of MBB). Otherwise the
|
||||
// CMP is the only flag-setting marker between the test value
|
||||
|
|
@ -1653,6 +1697,9 @@ bool W65816StackSlotCleanup::runOnMachineFunction(MachineFunction &MF) {
|
|||
if (BrOpc == W65816::BCC || BrOpc == W65816::BCS ||
|
||||
BrOpc == W65816::BVC || BrOpc == W65816::BVS)
|
||||
SafeToErase = false;
|
||||
if ((BrOpc == W65816::BMI || BrOpc == W65816::BPL) &&
|
||||
!setsSign16(*Writer))
|
||||
SafeToErase = false;
|
||||
break;
|
||||
}
|
||||
// PHP/PLP-wrap-aware: only safe when LDAfi-expansion sets N/Z
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ LLVMInitializeW65816Target() {
|
|||
initializeW65816I32IncFoldPass(PR);
|
||||
initializeW65816ImgCalleeSavePass(PR);
|
||||
initializeW65816NarrowI32MulPass(PR);
|
||||
initializeW65816CmpPreparePass(PR);
|
||||
initializeW65816PromoteFiToImgPass(PR);
|
||||
initializeW65816StackSlotMergePass(PR);
|
||||
initializeW65816StackRelToImgPass(PR);
|
||||
|
|
@ -204,6 +205,11 @@ void W65816PassConfig::addISelPrepare() {
|
|||
// operands are zero. Catches the sumSquares-style `(u32)i * i`
|
||||
// pattern that SDAG-level analysis can't see across BB boundaries.
|
||||
addPass(createW65816NarrowI32Mul());
|
||||
// Compare shaping (post-increment loop tests, i32 == / != as a branch
|
||||
// pair). After CodeGenPrepare so nothing re-merges the compares, and
|
||||
// only when optimizing: -O0 keeps the straight lowering.
|
||||
if (getOptLevel() != CodeGenOptLevel::None)
|
||||
addPass(createW65816CmpPrepare());
|
||||
TargetPassConfig::addISelPrepare();
|
||||
}
|
||||
|
||||
|
|
|
|||
140
src/llvm/test/CodeGen/W65816/cmp-prepare.ll
Normal file
140
src/llvm/test/CodeGen/W65816/cmp-prepare.ll
Normal 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
|
||||
}
|
||||
44
src/llvm/test/CodeGen/W65816/fp-cmp-libcall-i16.ll
Normal file
44
src/llvm/test/CodeGen/W65816/fp-cmp-libcall-i16.ll
Normal 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
|
||||
}
|
||||
|
|
@ -8,10 +8,11 @@
|
|||
; bias folds: 0x1234 -> sbc #0x9234). x <= K / x > K become x < K+1 /
|
||||
; x >= K+1; reg/reg <= and > swap operands. A constant whose low half is
|
||||
; 0 reduces to an i16 compare of the high halves (x < 0x10000 is hi < 1,
|
||||
; signed x < 0 is hi < 0).
|
||||
; signed x < 0 is hi < 0, which is just `lda hi ; bmi`).
|
||||
;
|
||||
; eq / ne: (lo ^ rhs.lo) | (hi ^ rhs.hi) tested against 0, and against a
|
||||
; constant 0 just lo | hi.
|
||||
; eq / ne feeding a branch (W65816CmpPrepare): the low halves, bne, then
|
||||
; the high halves, bne. Against 0 (or a constant with a zero half) it stays
|
||||
; the branch-free lo | hi, (lo ^ K) | hi, ... tested against 0.
|
||||
;
|
||||
; The old lowering built (hiLT | (hiEQ & loLT)) from three i16 setcc
|
||||
; diamonds and branched on the OR: every `i < n` in a 32-bit loop paid for
|
||||
|
|
@ -23,14 +24,16 @@ target datalayout = "e-m:e-p:32:16-i16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16-
|
|||
|
||||
@sink = global i16 0
|
||||
|
||||
; a.hi arrives in X: it must be loaded into A for the second compare.
|
||||
define void @br_eq(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_eq:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor 0xa, s
|
||||
; CHECK: eor 0x8, s
|
||||
; CHECK-NEXT: sta
|
||||
; CHECK-NEXT: ora
|
||||
; CHECK-NEXT: bne
|
||||
; CHECK: stx 0xd0
|
||||
; CHECK-NEXT: cmp 0x4, s
|
||||
; CHECK-NEXT: bne [[F:.LBB[0-9_]+]]
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: cmp 0x6, s
|
||||
; CHECK-NEXT: bne [[F]]
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp eq i32 %a, %b
|
||||
|
|
@ -46,11 +49,12 @@ f:
|
|||
define void @br_ne(i32 %a, i32 %b) {
|
||||
; CHECK-LABEL: br_ne:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor 0xa, s
|
||||
; CHECK: eor 0x8, s
|
||||
; CHECK-NEXT: sta
|
||||
; CHECK-NEXT: ora
|
||||
; CHECK-NEXT: beq
|
||||
; CHECK: stx 0xd0
|
||||
; CHECK-NEXT: cmp 0x4, s
|
||||
; CHECK-NEXT: bne [[T:.LBB[0-9_]+]]
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: cmp 0x6, s
|
||||
; CHECK-NEXT: bne [[T]]
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ne i32 %a, %b
|
||||
|
|
@ -236,11 +240,11 @@ f:
|
|||
define void @brk_eq(i32 %a) {
|
||||
; CHECK-LABEL: brk_eq:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x1234
|
||||
; CHECK: eor #0x5678
|
||||
; CHECK-NEXT: sta
|
||||
; CHECK-NEXT: ora
|
||||
; CHECK-NEXT: bne
|
||||
; CHECK: cmp #0x5678
|
||||
; CHECK-NEXT: bne [[F:.LBB[0-9_]+]]
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: cmp #0x1234
|
||||
; CHECK-NEXT: bne [[F]]
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp eq i32 %a, 305419896
|
||||
|
|
@ -256,11 +260,11 @@ f:
|
|||
define void @brk_ne(i32 %a) {
|
||||
; CHECK-LABEL: brk_ne:
|
||||
; CHECK-NOT: php
|
||||
; CHECK: eor #0x1234
|
||||
; CHECK: eor #0x5678
|
||||
; CHECK-NEXT: sta
|
||||
; CHECK-NEXT: ora
|
||||
; CHECK-NEXT: beq
|
||||
; CHECK: cmp #0x5678
|
||||
; CHECK-NEXT: bne [[T:.LBB[0-9_]+]]
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: cmp #0x1234
|
||||
; CHECK-NEXT: bne [[T]]
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp ne i32 %a, 305419896
|
||||
|
|
@ -452,9 +456,7 @@ define void @brk_slt0(i32 %a) {
|
|||
; CHECK-LABEL: brk_slt0:
|
||||
; CHECK-NOT: sbc
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: eor #0x8000
|
||||
; CHECK-NEXT: cmp #0x8000
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK-NEXT: bmi
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp slt i32 %a, 0
|
||||
|
|
@ -472,9 +474,7 @@ define void @brk_sgtm1(i32 %a) {
|
|||
; CHECK-LABEL: brk_sgtm1:
|
||||
; CHECK-NOT: sbc
|
||||
; CHECK: lda 0xd0
|
||||
; CHECK-NEXT: eor #0x8000
|
||||
; CHECK-NEXT: cmp #0x8000
|
||||
; CHECK-NEXT: bcc
|
||||
; CHECK-NEXT: bmi
|
||||
; CHECK: rtl
|
||||
entry:
|
||||
%c = icmp sgt i32 %a, -1
|
||||
|
|
|
|||
66
src/llvm/test/CodeGen/W65816/i32-shift-16.ll
Normal file
66
src/llvm/test/CodeGen/W65816/i32-shift-16.ll
Normal 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
|
||||
}
|
||||
98
src/llvm/test/CodeGen/W65816/lower-wide32-half-class.mir
Normal file
98
src/llvm/test/CodeGen/W65816/lower-wide32-half-class.mir
Normal 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
|
||||
...
|
||||
107
src/llvm/test/CodeGen/W65816/sign-test-bmi.ll
Normal file
107
src/llvm/test/CodeGen/W65816/sign-test-bmi.ll
Normal 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
|
||||
}
|
||||
131
src/llvm/test/CodeGen/W65816/sign-test-flags.mir
Normal file
131
src/llvm/test/CodeGen/W65816/sign-test-flags.mir
Normal 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
|
||||
...
|
||||
|
|
@ -38,25 +38,33 @@ array memory). It is optional: CoreMark plus the runtime it pulls in
|
|||
Lua test (`--segment-cap`, extra segments at bank 4+) and runs it through
|
||||
`scripts/runMultiSeg.sh`.
|
||||
|
||||
Default iteration count is 1 (smallest valid run). Override for
|
||||
publishable scores:
|
||||
Default iteration count is 3. CoreMark only validates a run of at
|
||||
least 10 seconds, and one iteration takes about 10.8 s of emulated time
|
||||
(2026-09), so 3 leaves room for the compiler to get faster. Override
|
||||
for longer runs:
|
||||
|
||||
```bash
|
||||
ITERATIONS=5 bash tests/coremark/build.sh --layer2
|
||||
ITERATIONS=10 bash tests/coremark/runCoreMark.sh
|
||||
```
|
||||
|
||||
CoreMark spec recommends >= 10 seconds of runtime. At ~1 MHz, expect
|
||||
roughly one iteration per second of in-IIgs time — so iteration counts
|
||||
of 10–60 give a representative score.
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
bash tests/coremark/runCoreMark.sh --layer2
|
||||
```
|
||||
|
||||
The run terminates with `0xC0DE` written to `$025000` on success.
|
||||
Elapsed VBL ticks (60 Hz) are stored at `$025002` (low/hi halves).
|
||||
This is a correctness gate, not just a benchmark. core_portme.c sends
|
||||
CoreMark's console output to `$02:6000` and takes its clock from a
|
||||
millisecond counter at `$02:5010` that the MAME harness
|
||||
(`scripts/runMultiSeg.sh`) updates every frame; bare metal runs with
|
||||
IRQs off, so libc's VBL `clock()` never moves. The script fails unless
|
||||
CoreMark reports "Correct operation validated" (its own CRC checks plus
|
||||
the 10-second rule) and the seed, list, matrix, state and final CRCs
|
||||
match the values a host build of the same sources prints. CoreMark's
|
||||
own "Total time" and "Iterations/Sec" are the score.
|
||||
|
||||
The run ends with `0xC0DE` written to `$025000` (`0xBEEF` there means
|
||||
it started but never finished); elapsed ticks are at `$025002`.
|
||||
|
||||
**Note:** running CoreMark under MAME inside this project's restricted
|
||||
shell crashes MAME (same SIGSEGV as Lua's full interpreter run —
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build CoreMark for W65816. Compiles the 5 core .c files plus our
|
||||
# porting layer (core_portme.c). Iteration count baked at compile
|
||||
# time via -DITERATIONS=N (default 1 — runs ~1 sec at 1 MHz IIgs).
|
||||
# time via -DITERATIONS=N. The default of 3 runs about 32 s of emulated
|
||||
# time (2026-09): CoreMark only validates a run of at least 10 s, so this
|
||||
# leaves room for the compiler to get 3x faster before the count must grow.
|
||||
#
|
||||
# Output: build/*.o for each TU.
|
||||
#
|
||||
|
|
@ -18,7 +20,7 @@ CLANG="$PROJECT_ROOT/tools/llvm-mos-build/bin/clang"
|
|||
# instead of hard-failing the build -- see scripts/ccRegallocFallback.sh.
|
||||
CC="$PROJECT_ROOT/scripts/ccRegallocFallback.sh"
|
||||
|
||||
ITERATIONS="${ITERATIONS:-1}"
|
||||
ITERATIONS="${ITERATIONS:-3}"
|
||||
|
||||
LAYER2=()
|
||||
for arg in "$@"; do
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
"num": 1,
|
||||
"name": "SEG1",
|
||||
"base": "0x001000",
|
||||
"size": 41548,
|
||||
"bss_start": "0x00d000",
|
||||
"bss_size": 6268,
|
||||
"size": 40728,
|
||||
"bss_start": "0x00b700",
|
||||
"bss_size": 2034,
|
||||
"image": "coreMark.bin",
|
||||
"entry_offset": "0x0000"
|
||||
},
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
"num": 2,
|
||||
"name": "SEG2",
|
||||
"base": "0x040000",
|
||||
"size": 16362,
|
||||
"size": 16480,
|
||||
"bss_start": "0x000000",
|
||||
"bss_size": 0,
|
||||
"image": "coreMark.seg2.bin",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// CoreMark porting layer for W65816. See core_portme.h for config.
|
||||
#include "coremark.h"
|
||||
#include "core_portme.h"
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if VALIDATION_RUN
|
||||
|
|
@ -22,15 +21,58 @@ volatile ee_s32 seed3_volatile = 0x8;
|
|||
volatile ee_s32 seed4_volatile = ITERATIONS;
|
||||
volatile ee_s32 seed5_volatile = 0;
|
||||
|
||||
// Timing — uses libc's clock() (VBL counter at 60 Hz).
|
||||
#define GETMYTIME(_t) (*_t = (CORETIMETYPE)clock())
|
||||
// The harness (runCoreMark.sh) owns these addresses. Bank 2 is clear of
|
||||
// the program (bank 0 and banks 4+) and of its static data (bank 0).
|
||||
// CM_TICK_ADDR u32 emulated milliseconds, rewritten by the harness every
|
||||
// video frame. libc's clock() reads the ROM's VBL counter,
|
||||
// which never moves here: bare metal runs with IRQs off.
|
||||
// CM_OUT_ADDR CM_OUT_BYTES of captured console output, NUL-terminated;
|
||||
// the harness checks the CRCs CoreMark prints.
|
||||
#define CM_TICK_ADDR 0x025010UL
|
||||
#define CM_OUT_ADDR 0x026000UL
|
||||
#define CM_OUT_BYTES 0x1000u
|
||||
#define GETMYTIME(_t) (*_t = harnessTicks())
|
||||
#define MYTIMEDIFF(fin, ini) ((fin) - (ini))
|
||||
#define TIMER_RES_DIVIDER 1
|
||||
#define SAMPLE_TIME_IMPLEMENTATION 1
|
||||
#define EE_TICKS_PER_SEC (CLOCKS_PER_SEC / TIMER_RES_DIVIDER)
|
||||
#define EE_TICKS_PER_SEC (1000u / TIMER_RES_DIVIDER)
|
||||
|
||||
static CORETIMETYPE harnessTicks(void);
|
||||
void __putByte(char c);
|
||||
|
||||
static CORETIMETYPE start_time_val;
|
||||
static CORETIMETYPE stop_time_val;
|
||||
static ee_u16 outPos;
|
||||
|
||||
|
||||
// The two 16-bit halves are read separately, so a frame boundary between
|
||||
// them could pair an old low half with a new high half; re-read until the
|
||||
// high half is stable.
|
||||
static CORETIMETYPE harnessTicks(void) {
|
||||
volatile ee_u16 *tick = (volatile ee_u16 *)CM_TICK_ADDR;
|
||||
ee_u16 hi;
|
||||
ee_u16 lo;
|
||||
|
||||
do {
|
||||
hi = tick[1];
|
||||
lo = tick[0];
|
||||
} while (hi != tick[1]);
|
||||
return ((CORETIMETYPE)hi << 16) | lo;
|
||||
}
|
||||
|
||||
|
||||
// Overrides libc's weak console hook (putchar), keeping one byte free for
|
||||
// the terminating NUL the harness stops at.
|
||||
void __putByte(char c) {
|
||||
volatile char *out = (volatile char *)CM_OUT_ADDR;
|
||||
|
||||
if (outPos < CM_OUT_BYTES - 1u) {
|
||||
out[outPos] = c;
|
||||
outPos++;
|
||||
out[outPos] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void start_time(void) {
|
||||
GETMYTIME(&start_time_val);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// CoreMark porting layer for the W65816 / Apple IIgs target.
|
||||
// Adapts the barebones template:
|
||||
// - clock() via VBL counter at 60 Hz (CLOCKS_PER_SEC=60)
|
||||
// - printf via our libc
|
||||
// - time from a millisecond counter runCoreMark.sh's MAME harness keeps
|
||||
// (bare metal runs with interrupts off, so libc's VBL clock() is 0)
|
||||
// - printf via our libc, captured by the harness (see core_portme.c)
|
||||
// - MEM_STATIC (heap-free)
|
||||
// - HAS_FLOAT=1 (soft-double; needed for the score calc)
|
||||
// - Single-threaded (MULTITHREAD=1)
|
||||
|
|
@ -10,8 +11,8 @@
|
|||
#define CORE_PORTME_H
|
||||
|
||||
#define HAS_FLOAT 1
|
||||
#define HAS_TIME_H 1
|
||||
#define USE_CLOCK 1
|
||||
#define HAS_TIME_H 0
|
||||
#define USE_CLOCK 0
|
||||
#define HAS_STDIO 1
|
||||
#define HAS_PRINTF 1
|
||||
|
||||
|
|
@ -33,16 +34,14 @@ typedef unsigned long ee_size_t;
|
|||
|
||||
#define align_mem(x) (void *)(4 + (((ee_ptr_int)(x)-1) & ~3))
|
||||
|
||||
// ITERATIONS — keep small for the 1 MHz IIgs. CoreMark spec wants
|
||||
// >= 10 sec of runtime; at this clock that's order-of-magnitude
|
||||
// 100 iterations. Run with ITERATIONS=1 first to validate, then
|
||||
// scale up. The user can pass -DITERATIONS=N to override.
|
||||
// ITERATIONS: build.sh passes -DITERATIONS (see its default). One
|
||||
// iteration takes about 10.8 s of emulated time at -O2 (2026-09), and
|
||||
// CoreMark only validates a run of at least 10 s.
|
||||
#ifndef ITERATIONS
|
||||
#define ITERATIONS 1
|
||||
#define ITERATIONS 3
|
||||
#endif
|
||||
|
||||
// Timing. CLOCKS_PER_SEC = 60 (VBL). Use TIMER_RES_DIVIDER=1 since
|
||||
// 60 Hz is already coarse.
|
||||
// Timing: emulated milliseconds, updated once per video frame.
|
||||
#define CORETIMETYPE ee_u32
|
||||
typedef ee_u32 CORE_TICKS;
|
||||
typedef double secs_ret;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,58 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build + link + run CoreMark under MAME.
|
||||
# Build + link + run CoreMark under MAME, and fail unless CoreMark's own
|
||||
# report says the results are right.
|
||||
#
|
||||
# Usage:
|
||||
# bash tests/coremark/runCoreMark.sh # default: ITERATIONS=1
|
||||
# ITERATIONS=5 bash tests/coremark/runCoreMark.sh
|
||||
# bash tests/coremark/runCoreMark.sh # ITERATIONS from build.sh
|
||||
# ITERATIONS=10 bash tests/coremark/runCoreMark.sh
|
||||
# bash tests/coremark/runCoreMark.sh --layer2
|
||||
#
|
||||
# The gate reads CoreMark's printed report (core_portme.c captures the
|
||||
# console in bank 2 and runMultiSeg.sh prints it as MAME-TEXT lines) and
|
||||
# requires:
|
||||
# - "Correct operation validated": CoreMark compared its list, matrix and
|
||||
# state CRCs with the known values for these seeds, and the run lasted
|
||||
# at least 10 s by core_portme.c's clock (the harness's emulated
|
||||
# milliseconds);
|
||||
# - the seed, list, matrix, state and final CRCs below, so a failure names
|
||||
# the wrong one. crcfinal chains every iteration's results.
|
||||
# The done sentinel alone only proved that CoreMark returned: a compiler
|
||||
# whose CoreMark printed list 0x9865 / matrix 0xa7f7 / state 0xe10b passed.
|
||||
|
||||
set -eu
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
source "$SCRIPT_DIR/../../scripts/common.sh"
|
||||
CM_OBJ="$SCRIPT_DIR/build"
|
||||
RT="$PROJECT_ROOT/runtime"
|
||||
|
||||
# CoreMark's known values for the 2K performance run (seeds 0, 0, 0x66).
|
||||
WANT_SEEDCRC=0xe9f5
|
||||
WANT_CRCLIST=0xe714
|
||||
WANT_CRCMATRIX=0x1fd7
|
||||
WANT_CRCSTATE=0x8e3a
|
||||
|
||||
# crcfinal by iteration count, from the vendored sources built for the host
|
||||
# (posix port, `coremark.exe 0x0 0x0 0x66 N`).
|
||||
crcFinalFor() {
|
||||
case "$1" in
|
||||
1) echo 0xe714 ;;
|
||||
2) echo 0x72be ;;
|
||||
3) echo 0x2e87 ;;
|
||||
4) echo 0x9f95 ;;
|
||||
5) echo 0xf24c ;;
|
||||
6) echo 0x9db0 ;;
|
||||
7) echo 0x3413 ;;
|
||||
8) echo 0xccd1 ;;
|
||||
9) echo 0x7de8 ;;
|
||||
10) echo 0xfcaf ;;
|
||||
20) echo 0x4983 ;;
|
||||
30) echo 0xf8b3 ;;
|
||||
50) echo 0x0158 ;;
|
||||
100) echo 0x988c ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
rm -rf "$CM_OBJ"
|
||||
bash "$SCRIPT_DIR/build.sh" "$@"
|
||||
|
|
@ -33,9 +74,49 @@ bash "$SCRIPT_DIR/build.sh" "$@"
|
|||
ls -la coreMark*.bin
|
||||
echo ""
|
||||
|
||||
# 0xC0DE sentinel = success; otherwise 0xBEEF = mid-run hang.
|
||||
# Elapsed ticks at 0x025002..0x025005 (low/hi).
|
||||
MAME_CHECK_FRAME=900 MAME_SECS=30 MAME_TIMEOUT=300 MAME_RAMSIZE=4M \
|
||||
# The run ends on portable_fini's 0xC0DE sentinel at $02:5000 ($BEEF there
|
||||
# means CoreMark started but never finished). The tick and text addresses
|
||||
# are core_portme.c's CM_TICK_ADDR and CM_OUT_ADDR. 900 emulated seconds
|
||||
# only caps a hang; the wall time follows CoreMark's own run time.
|
||||
runOk=1
|
||||
OUT=$(MAME_CHECK_FRAME=54000 MAME_SECS=905 MAME_TIMEOUT=900 MAME_RAMSIZE=4M \
|
||||
MAME_DONE_ADDR=0x025000 MAME_DONE_VAL=0xC0DE \
|
||||
MAME_TICK_ADDR=0x025010 MAME_TEXT_ADDR=0x026000 \
|
||||
bash "$PROJECT_ROOT/scripts/runMultiSeg.sh" \
|
||||
"$SCRIPT_DIR/coreMark.manifest.json" \
|
||||
--check 0x025000=c0de
|
||||
--check 0x025000=c0de) || runOk=0
|
||||
echo "$OUT"
|
||||
[ $runOk -eq 1 ] || die "CoreMark: no 0xC0DE done sentinel (hang or crash)"
|
||||
|
||||
report=$(sed -n 's/^MAME-TEXT //p' <<< "$OUT")
|
||||
# One "name : value" field of the report, e.g. field '[0]crclist'.
|
||||
field() {
|
||||
awk -F' *: ' -v k="$1" '$1 == k && !seen { print $2; seen = 1 }' <<< "$report"
|
||||
}
|
||||
|
||||
bad=0
|
||||
expect() {
|
||||
local got
|
||||
got=$(field "$1")
|
||||
if [ "$got" != "$2" ]; then
|
||||
warn "CoreMark: $1 is '${got:-missing}', expected $2"
|
||||
bad=1
|
||||
fi
|
||||
}
|
||||
iterations=$(field "Iterations")
|
||||
wantFinal=$(crcFinalFor "$iterations")
|
||||
expect "seedcrc" "$WANT_SEEDCRC"
|
||||
expect "[0]crclist" "$WANT_CRCLIST"
|
||||
expect "[0]crcmatrix" "$WANT_CRCMATRIX"
|
||||
expect "[0]crcstate" "$WANT_CRCSTATE"
|
||||
if [ -n "$wantFinal" ]; then
|
||||
expect "[0]crcfinal" "$wantFinal"
|
||||
else
|
||||
warn "CoreMark: no known crcfinal for ${iterations:-?} iterations; not checked"
|
||||
fi
|
||||
if ! grep -q '^Correct operation validated' <<< "$report"; then
|
||||
warn "CoreMark did not report \"Correct operation validated\" (its ERROR lines are above)"
|
||||
bad=1
|
||||
fi
|
||||
[ $bad -eq 0 ] || die "CoreMark: results not validated (warnings above)"
|
||||
log "CoreMark OK: $iterations iterations, CRCs match, $(field 'Total time (secs)') s, $(field 'Iterations/Sec') iterations/s"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"num": 1,
|
||||
"name": "SEG1",
|
||||
"base": "0x001000",
|
||||
"size": 44548,
|
||||
"size": 43790,
|
||||
"bss_start": "0x00e200",
|
||||
"bss_size": 4248,
|
||||
"image": "luaTest.bin",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue