perf_identity_gate.sh (6907B)
1 #!/usr/bin/env bash 2 # perf_identity_gate.sh — prove a candidate kit produces BYTE-IDENTICAL output to a 3 # golden kit across every pipeline segment. The gate for all perf changes 4 # (PERF.md invariant): ASan can't see a premature arena reuse or an uninit read, 5 # but the output diff can. 6 # 7 # Usage: GOLDEN=path/to/golden/kit CAND=path/to/candidate/kit bash scripts/perf_identity_gate.sh 8 # optional: GATE_DIR (work dir, default build/perf-gate/work) 9 # GATE_TARGET (kit -target, default host) 10 # GATE_KEEP=1 (keep work dir on success) 11 # 12 # Covers: -O0/-O1 objects, -g DWARF, -S asm, -E preprocessor text, diagnostics 13 # (stderr), the linked executable, and a backslash-newline line-splice battery 14 # (mid-token / mid-string / leading / trailing / consecutive + a diagnostic whose 15 # line crosses a splice — the lexer-specific gate from PERF.md). 16 # 17 # Exit 0 iff every category is byte-identical. Prints a per-category PASS/FAIL table. 18 set -u 19 20 GOLDEN="${GOLDEN:?set GOLDEN=path/to/golden/kit}" 21 CAND="${CAND:?set CAND=path/to/candidate/kit}" 22 GEN="${GEN:-scripts/cc_bench_gen.py}" 23 GATE_DIR="${GATE_DIR:-build/perf-gate/work}" 24 TARGET_FLAG="" 25 [ -n "${GATE_TARGET:-}" ] && TARGET_FLAG="-target ${GATE_TARGET}" 26 27 GOLDEN="$(cd "$(dirname "$GOLDEN")" && pwd)/$(basename "$GOLDEN")" 28 CAND="$(cd "$(dirname "$CAND")" && pwd)/$(basename "$CAND")" 29 30 # Sysroot for linking the host libc (match cc_bench.sh). 31 SYSROOT="${KIT_SYSROOT:-}" 32 if [ -z "$SYSROOT" ] && [ "$(uname -s)" = "Darwin" ] && command -v xcrun >/dev/null 2>&1; then 33 SYSROOT="$(xcrun --show-sdk-path 2>/dev/null || true)" 34 fi 35 SYSROOT_ARGS=(); [ -n "$SYSROOT" ] && SYSROOT_ARGS=(--sysroot "$SYSROOT") 36 37 rm -rf "$GATE_DIR"; mkdir -p "$GATE_DIR" 38 ROOT="$(pwd)" 39 GEN="$ROOT/$GEN" 40 41 fail=0 42 declare -a ROWS 43 44 record() { # name result 45 ROWS+=("$2 $1") 46 [ "$2" = FAIL ] && fail=1 47 } 48 49 # diff one output pair, recording PASS/FAIL 50 cmpfile() { # name a b 51 if cmp -s "$2" "$3"; then record "$1" PASS; else record "$1" FAIL; fi 52 } 53 54 # Run both kits via the `cc` driver, compare a produced artifact (rel path in cwd). 55 # args: name outpath -- ccargs... 56 run_pair() { # name outrel ccargs... 57 local name="$1" outrel="$2"; shift 2 58 ( cd "$GATE_DIR" && "$GOLDEN" cc $TARGET_FLAG "$@" >/dev/null 2>g_$name.err; echo $? >g_$name.rc; [ -f "$outrel" ] && cp "$outrel" "g_$name.out" ) 59 ( cd "$GATE_DIR" && "$CAND" cc $TARGET_FLAG "$@" >/dev/null 2>c_$name.err; echo $? >c_$name.rc; [ -f "$outrel" ] && cp "$outrel" "c_$name.out" ) 60 cmpfile "$name.rc" "$GATE_DIR/g_$name.rc" "$GATE_DIR/c_$name.rc" 61 [ -f "$GATE_DIR/g_$name.out" ] && cmpfile "$name.obj" "$GATE_DIR/g_$name.out" "$GATE_DIR/c_$name.out" 62 } 63 64 # Run both kits via `cc` capturing STDOUT (for -E/-S) or STDERR (diagnostics). 65 run_capture() { # name stream(out|err) ccargs... 66 local name="$1" stream="$2"; shift 2 67 if [ "$stream" = out ]; then 68 ( cd "$GATE_DIR" && "$GOLDEN" cc $TARGET_FLAG "$@" >g_$name.cap 2>/dev/null; echo $? >g_$name.rc ) 69 ( cd "$GATE_DIR" && "$CAND" cc $TARGET_FLAG "$@" >c_$name.cap 2>/dev/null; echo $? >c_$name.rc ) 70 else 71 ( cd "$GATE_DIR" && "$GOLDEN" cc $TARGET_FLAG "$@" >/dev/null 2>g_$name.cap; echo $? >g_$name.rc ) 72 ( cd "$GATE_DIR" && "$CAND" cc $TARGET_FLAG "$@" >/dev/null 2>c_$name.cap; echo $? >c_$name.rc ) 73 fi 74 cmpfile "$name.cap" "$GATE_DIR/g_$name.cap" "$GATE_DIR/c_$name.cap" 75 cmpfile "$name.rc" "$GATE_DIR/g_$name.rc" "$GATE_DIR/c_$name.rc" 76 } 77 78 # ---- 1. compile + preprocess axes (the synthetic corpus) ------------------- 79 # n's chosen to exercise each path broadly while staying fast for repeated runs. 80 gen_axis() { python3 "$GEN" --axis "$1" --n "$2" --out "$GATE_DIR/$3" >/dev/null 2>&1; } 81 82 # compile axes -> object (-O0, -O1, -g), asm (-S) 83 for spec in "fn-count:1500:fn" "body-size:8000:body" "global-decl:3000:glob" \ 84 "type-decl:1500:type" "locals-per-fn:3000:loc" "ref-density:3000:ref"; do 85 IFS=: read -r ax n tag <<<"$spec" 86 gen_axis "$ax" "$n" "$tag" 87 src="$tag/gen.c" 88 run_pair "${tag}_O0" "$tag/gen.o" -O0 -c "$src" -o "$tag/gen.o" 89 run_pair "${tag}_O1" "$tag/gen1.o" -O1 -c "$src" -o "$tag/gen1.o" 90 run_pair "${tag}_g" "$tag/geng.o" -O0 -g -c "$src" -o "$tag/geng.o" 91 run_capture "${tag}_S" out -O0 -S "$src" -o - 92 done 93 94 # preprocess axes -> -E text 95 for spec in "pp-macro:2000:ppm" "pp-include:32:inc"; do 96 IFS=: read -r ax n tag <<<"$spec" 97 gen_axis "$ax" "$n" "$tag" 98 ( cd "$GATE_DIR/$tag" && "$GOLDEN" cc $TARGET_FLAG -E gen.c >../g_${tag}E.cap 2>/dev/null ) 99 ( cd "$GATE_DIR/$tag" && "$CAND" cc $TARGET_FLAG -E gen.c >../c_${tag}E.cap 2>/dev/null ) 100 cmpfile "${tag}E" "$GATE_DIR/g_${tag}E.cap" "$GATE_DIR/c_${tag}E.cap" 101 done 102 103 # ---- 2. line-splice battery (lexer-specific gate) -------------------------- 104 cat >"$GATE_DIR/splice.c" <<'EOF' 105 /* mid-token splice */ 106 int ab\ 107 cd = 1; 108 /* mid-string splice */ 109 const char *s = "he\ 110 llo"; 111 /* leading-continuation */ 112 \ 113 int x = 2; 114 /* trailing whitespace after backslash is NOT a splice; keep plain */ 115 int y = 3; 116 /* consecutive continuations */ 117 int z\ 118 \ 119 zz = 4; 120 #define CAT(a,b) a##b 121 int CAT(foo,\ 122 bar) = 5; 123 EOF 124 run_pair "splice_O0" splice.o -O0 -c splice.c -o splice.o 125 run_capture "spliceE" out -E splice.c -o - 126 127 # diagnostic whose line number falls across a splice (error on the post-splice line) 128 cat >"$GATE_DIR/splicediag.c" <<'EOF' 129 int good = 1; 130 int wi\ 131 dgets = undefined_symbol_here; 132 EOF 133 run_capture "splice_diag" err -O0 -c splicediag.c -o /dev/null 134 135 # ---- 3. general diagnostics (stderr text) ---------------------------------- 136 cat >"$GATE_DIR/diag.c" <<'EOF' 137 int f(void) { return q; } /* undeclared */ 138 void g(void) { int x = "str"; } /* int from ptr */ 139 struct S { int a; }; struct S; int h(struct T*); /* incomplete */ 140 int dup; int dup; /* redef-ok (tentative) */ 141 int bad(void) { 1 + ; } /* parse error */ 142 EOF 143 run_capture "diag" err -O0 -c diag.c -o /dev/null 144 145 # ---- 4. linker (prebuild objects with golden; link with each; cmp exe) ----- 146 gen_axis obj-count 6 link 147 ( cd "$GATE_DIR/link" 148 for c in obj_*.c main.c; do "$GOLDEN" cc $TARGET_FLAG -O0 -c "$c" -o "${c%.c}.o" >/dev/null 2>&1; done 149 "$GOLDEN" ld $TARGET_FLAG obj_*.o main.o -l c "${SYSROOT_ARGS[@]}" -o g.exe >/dev/null 2>&1; echo $? >../g_link.rc 150 "$CAND" ld $TARGET_FLAG obj_*.o main.o -l c "${SYSROOT_ARGS[@]}" -o c.exe >/dev/null 2>&1; echo $? >../c_link.rc ) 151 cmpfile "link.exe" "$GATE_DIR/link/g.exe" "$GATE_DIR/link/c.exe" 152 cmpfile "link.rc" "$GATE_DIR/g_link.rc" "$GATE_DIR/c_link.rc" 153 154 # ---- report ---------------------------------------------------------------- 155 echo "=== perf identity gate ===" 156 echo "golden: $GOLDEN" 157 echo "cand: $CAND" 158 printf '%s\n' "${ROWS[@]}" | sort 159 echo "---" 160 if [ "$fail" = 0 ]; then 161 echo "RESULT: PASS (byte-identical across all categories)" 162 [ -n "${GATE_KEEP:-}" ] || rm -rf "$GATE_DIR" 163 exit 0 164 else 165 echo "RESULT: FAIL — diffs remain in $GATE_DIR (g_*/c_* pairs)" 166 exit 1 167 fi