#!/usr/bin/env bash # bench.sh — compile a benchmark suite with both clang (this toolchain) # and Calypsi cc65816, compare emitted code size. # # Each benchmark is a self-contained .c file under benchmarks/. We # compile each with both toolchains (-O2 / --speed), then count # bytes in the .text + .data sections of the resulting object. # Output is a markdown table on stdout. # # Cycle-time comparison would require running each benchmark in MAME # under both toolchains' produced code, with a wrapper function that # instruments the cycle counter. That's a separate, more involved # tool — left for future work. 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" CALYPSI="$PROJECT_ROOT/tools/calypsi/usr/local/lib/calypsi-65816-5.16/bin/cc65816" [ -x "$CLANG" ] || { echo "ERROR: clang not built" >&2; exit 1; } [ -x "$CALYPSI" ] || { echo "ERROR: Calypsi not installed" >&2; exit 1; } [ -d "$BENCH_DIR" ] || { echo "ERROR: $BENCH_DIR not found" >&2; exit 1; } # Object-size measurement. Different object formats — for clang it's # ELF (use llvm-readobj), for Calypsi it's its own format (use the # binary file size as a proxy, minus header overhead). ELF .text + # .rodata + .data covers code + constants; we report code-only as the # primary metric. clangSize() { local o="$1" "$PROJECT_ROOT/tools/llvm-mos-build/bin/llvm-readobj" --section-headers "$o" \ 2>/dev/null | awk ' /Name: .text/ { intext=1; inrodata=0; indata=0; next } /Name: .rodata/ { intext=0; inrodata=1; indata=0; next } /Name: .data/ { intext=0; inrodata=0; indata=1; next } /Name: / { intext=0; inrodata=0; indata=0; next } /Size:/ { if (intext) text += strtonum($2) if (inrodata) rodata += strtonum($2) if (indata) data += strtonum($2) } END { print text " " rodata " " data } ' } # Calypsi text size: extract the highest farcode offset from the # assembler listing. cc65816 -> .s, then as65816 --list-file # emits "OFFSET hexbytes" columns; we pick the max offset and add # the byte width of the final instruction (1-3 bytes typically). # Approximation but within a byte or two of true text size. calypsiTextSize() { local src="$1" local s lst tmp s=$(mktemp --suffix=.s) lst=$(mktemp --suffix=.lst) tmp=$(mktemp --suffix=.o) "$CALYPSI" -O 2 --speed --assembly-source "$s" -c "$src" -o "$tmp" 2>/dev/null \ || { echo 0; rm -f "$s" "$lst" "$tmp"; return; } "$CALYPSI" -O 2 --speed -c "$src" -o "$tmp" 2>/dev/null "$PROJECT_ROOT/tools/calypsi/usr/local/lib/calypsi-65816-5.16/bin/as65816" \ --list-file "$lst" -o "$tmp" "$s" 2>/dev/null # Highest farcode offset. We skip the +instruction-bytes detail # (rough estimate is fine for relative comparison). local maxOff maxOff=$(grep -oE "^[0-9]+ [0-9a-f]{6}" "$lst" 2>/dev/null \ | awk '{print strtonum("0x"$2)}' | sort -n | tail -1) echo "${maxOff:-0}" rm -f "$s" "$lst" "$tmp" } # Print markdown header. printf '| Benchmark | clang (B) | Calypsi (B) | clang vs Calypsi |\n' printf '|-----------|----------:|------------:|-----------------:|\n' totalClang=0 totalCalypsi=0 for src in "$BENCH_DIR"/*.c; do name=$(basename "$src" .c) cObj=$(mktemp --suffix=.clang.o) "$CLANG" --target=w65816 -O2 -ffunction-sections \ -c "$src" -o "$cObj" 2>/dev/null || { echo "clang failed on $name" >&2; rm -f "$cObj"; continue; } read clangText _ _ < <(clangSize "$cObj") clangText=${clangText:-0} calText=$(calypsiTextSize "$src") if [ "$calText" -gt 0 ]; then ratio=$(awk -v a="$clangText" -v b="$calText" 'BEGIN{printf "%.2fx", a/b}') else ratio="—" fi printf '| %s | %d | %d | %s |\n' "$name" "$clangText" "$calText" "$ratio" totalClang=$((totalClang + clangText)) totalCalypsi=$((totalCalypsi + calText)) rm -f "$cObj" done if [ "$totalCalypsi" -gt 0 ]; then totalRatio=$(awk -v a="$totalClang" -v b="$totalCalypsi" 'BEGIN{printf "%.2fx", a/b}') printf '| **total** | **%d** | **%d** | **%s** |\n' "$totalClang" "$totalCalypsi" "$totalRatio" fi