65816-llvm-mos/scripts/benchCyclesPrecise.sh
Scott Duensing f542f4fa01 Checkpoint
2026-05-03 21:31:53 -05:00

136 lines
5.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# benchCyclesPrecise.sh — measure per-call cycle counts via the
# emu.time()-based runner (scripts/runInMameCycles.sh).
#
# For each benchmark in benchmarks/, build a wrapper that calls the
# bench function ITERS times between START / DONE markers; the runner
# captures emulated time and converts to cycles assuming the IIgs
# slow-mode clock (1023000 Hz — IIe-compatible default; our binary
# doesn't enable fast mode unless its wrapper does).
#
# Output: markdown table with cycles-per-call. Both clang and the
# Calypsi numbers (from `tools/calypsi/cc65816`) are reported when
# Calypsi is installed.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BENCH_DIR="$PROJECT_ROOT/benchmarks"
CLANG="$PROJECT_ROOT/tools/llvm-mos-build/bin/clang"
LLVM_MC="$PROJECT_ROOT/tools/llvm-mos-build/bin/llvm-mc"
LINK="$PROJECT_ROOT/tools/link816"
RUNNER="$PROJECT_ROOT/scripts/runInMameCycles.sh"
oCrt0=$(mktemp --suffix=.o)
oLibgcc=$(mktemp --suffix=.o)
"$LLVM_MC" -arch=w65816 -filetype=obj "$PROJECT_ROOT/runtime/src/crt0.s" -o "$oCrt0"
"$LLVM_MC" -arch=w65816 -filetype=obj "$PROJECT_ROOT/runtime/src/libgcc.s" -o "$oLibgcc"
# Per-benchmark inputs / extern decls (mirrors benchCycles.sh).
benchInputs() {
case "$1" in
sumOfSquares) echo 'sumOfSquares(50)';;
fib) echo 'fib(10)';;
strcpy) echo 'mystrcpy(dst, "hello world!")';;
memcmp) echo 'mymemcmp("hello", "hello", 5)';;
bsearch) echo 'bsearch(arr, 8, 5)';;
dotProduct) echo 'dotProduct(va, vb, 4)';;
popcount) echo 'popcount(0x12345678UL)';;
crc32) echo 'crc32((const unsigned char *)"hello", 5)';;
*) echo "/* unknown */";;
esac
}
benchExtern() {
case "$1" in
sumOfSquares) echo 'extern unsigned long sumOfSquares(unsigned short n);';;
fib) echo 'extern unsigned short fib(unsigned short n);';;
strcpy) echo 'extern char *mystrcpy(char *d, const char *s); static char dst[16];';;
memcmp) echo 'extern int mymemcmp(const void *a, const void *b, unsigned int n);';;
bsearch) echo 'extern int bsearch(const int *arr, int n, int key); static const int arr[] = {1,2,3,4,5,6,7,8};';;
dotProduct) echo 'extern long dotProduct(const short *a, const short *b, unsigned int n); static const short va[] = {1,2,3,4}; static const short vb[] = {5,6,7,8};';;
popcount) echo 'extern int popcount(unsigned long x);';;
crc32) echo 'extern unsigned long crc32(const unsigned char *p, unsigned int n);';;
*) echo '';;
esac
}
# How many iterations to run each bench for. Bigger = more
# precise (smaller relative measurement noise) but longer runtime.
# Heavy benches get fewer iters; cheap benches get more.
benchIters() {
case "$1" in
sumOfSquares) echo 50;; # ~1600 cyc/call → ~80k cyc total
fib) echo 100;;
strcpy) echo 200;;
memcmp) echo 500;;
bsearch) echo 200;;
dotProduct) echo 200;;
popcount) echo 500;;
crc32) echo 200;;
*) echo 100;;
esac
}
runOneBench() {
local name="$1"
local extern_decl call_expr iters
extern_decl=$(benchExtern "$name")
call_expr=$(benchInputs "$name")
iters=$(benchIters "$name")
if [ -z "$extern_decl" ] || [ "$call_expr" = "/* unknown */" ]; then
echo "(no input config)"; return
fi
local cwrap obench owrap bin
cwrap=$(mktemp --suffix=.c)
owrap=$(mktemp --suffix=.o)
obench=$(mktemp --suffix=.o)
bin=$(mktemp --suffix=.bin)
cat > "$cwrap" <<EOF
$extern_decl
__attribute__((noinline)) static void switchToBank2(void) {
__asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n");
}
volatile unsigned long sink;
#define ITERS $iters
int main(void) {
switchToBank2();
/* warm-up */
for (int w = 0; w < 5; w++) sink = (unsigned long)($call_expr);
*(volatile unsigned short *)0x5000 = 0xa1a1; /* START */
for (int i = 0; i < ITERS; i++) sink = (unsigned long)($call_expr);
*(volatile unsigned short *)0x5002 = 0xa2a2; /* DONE */
while (1) {}
}
EOF
"$CLANG" --target=w65816 -O2 -ffunction-sections -c "$cwrap" -o "$owrap" 2>/dev/null \
|| { echo "compile-fail"; rm -f "$cwrap" "$owrap"; return; }
"$CLANG" --target=w65816 -O2 -ffunction-sections -c "$BENCH_DIR/$name.c" -o "$obench" 2>/dev/null \
|| { echo "compile-fail"; rm -f "$cwrap" "$owrap" "$obench"; return; }
"$LINK" -o "$bin" --text-base 0x1000 "$oCrt0" "$oLibgcc" "$owrap" "$obench" 2>/dev/null \
|| { echo "link-fail"; rm -f "$cwrap" "$owrap" "$obench" "$bin"; return; }
local val
val=$(bash "$RUNNER" "$bin" "$iters" 2>&1 | grep -oE 'cyc_per_call=[0-9.]+' | head -1 | sed 's/cyc_per_call=//')
rm -f "$cwrap" "$owrap" "$obench" "$bin"
if [ -z "$val" ]; then
echo "(no read)"
else
printf '%.0f cyc/call' "$val"
fi
}
printf '| Benchmark | Per-call cycles (clang) |\n'
printf '|-----------|------------------------:|\n'
for src in "$BENCH_DIR"/*.c; do
name=$(basename "$src" .c)
result=$(runOneBench "$name")
printf '| %s | %s |\n' "$name" "$result"
done
rm -f "$oCrt0" "$oLibgcc"