o1p_aa64.sh (5204B)
1 #!/usr/bin/env bash 2 # Structural checks for two aa64 -O1 code-size follow-ups 3 # (doc/plan/O1-PATTERNS.md L3 + L10). 4 # 5 # L3 -- widen aggregate / memset zero-init. The aa64 AGG_SET expander 6 # (aa_set_bytes) used to emit one `strb` per byte for a whole-struct / array 7 # zero (cjson has a 64-long strb run). It now emits the widest aligned store 8 # covering the remaining run (`str xzr`/`str x`/`str w`/`strh`/`strb` tail), 9 # so a 64-byte zero-init must NOT produce a long `strb` run and MUST use wider 10 # (8/4-byte) stores. 11 # 12 # L10 -- frame elision on no-spill leaf functions. kit used to emit a full 13 # `stp x29,x30,[sp,#-16]!; add x29,sp,#0 ... ldp` frame even for a leaf that 14 # spills nothing and never touches the frame. Such a function must now emit NO 15 # `stp x29,x30` / `add x29,sp` frame, while a frame-needing function (call / 16 # alloca / spill / address-taken local) MUST still keep its frame. 17 set -euo pipefail 18 19 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 20 KIT="${KIT:-$ROOT/build/kit}" 21 WORK="$ROOT/build/test/opt/o1p_aa64" 22 rm -rf "$WORK" 23 mkdir -p "$WORK" 24 25 fail() { 26 printf 'o1p-aa64 check FAILED: %s\n' "$1" >&2 27 if [ -n "${2:-}" ] && [ -f "$2" ]; then 28 sed 's/^/ | /' "$2" >&2 29 fi 30 exit 1 31 } 32 33 slice_func() { 34 local src="$1" func="$2" out="$3" 35 awk -v name="$func" ' 36 $0 ~ "^[0-9a-f]+ <" name ">:" { in_fn = 1; print; next } 37 /^[0-9a-f]+ </ { in_fn = 0 } 38 in_fn { print } 39 ' "$src" > "$out" 40 } 41 42 compile_case() { 43 local name="$1" src="$2" 44 "$KIT" cc -target aarch64-linux-gnu -O1 -std=c11 -c "$src" \ 45 -o "$WORK/$name.o" > "$WORK/$name.cc.out" 2> "$WORK/$name.cc.err" || 46 { cat "$WORK/$name.cc.err" >&2; fail "compile of $name failed"; } 47 "$KIT" objdump -d "$WORK/$name.o" \ 48 > "$WORK/$name.dis" 2> "$WORK/$name.objdump.err" 49 } 50 51 # Count `strb` instructions in a sliced function (the mnemonic is after the 52 # second tab in objdump output). 53 count_strb() { grep -cE $'\t''strb' "$1" || true; } 54 55 # ============================================================================ 56 # L3: widen aggregate zero-init 57 # ============================================================================ 58 # A 64-byte aggregate zeroed via `= {0}` -- the cjson PrintBuffered shape. The 59 # baseline emits ~64 consecutive `strb`; the widened expander must emit far 60 # fewer strb (only an unaligned tail at most) and use 8/4-byte stores. 61 cat > "$WORK/zero64.c" <<'EOF' 62 struct Big { long a,b,c,d,e,f,g,h; }; /* 64 bytes, 8-aligned */ 63 void zero64(struct Big *out) { 64 struct Big z = {0}; 65 *out = z; 66 } 67 char buf64[64]; 68 void zerobuf(void) { 69 char local[64] = {0}; 70 __builtin_memcpy(buf64, local, 64); 71 } 72 EOF 73 compile_case zero64 "$WORK/zero64.c" 74 slice_func "$WORK/zero64.dis" zero64 "$WORK/zero64.fn" 75 [ -s "$WORK/zero64.fn" ] || fail "zero64 not found in disassembly" "$WORK/zero64.dis" 76 nstrb=$(count_strb "$WORK/zero64.fn") 77 # An 8-aligned 64-byte zero should produce ZERO per-byte stores after widening. 78 if [ "$nstrb" -gt 8 ]; then 79 fail "zero64 still emits a long strb run ($nstrb strb); expected wide stores (L3)" "$WORK/zero64.fn" 80 fi 81 # It must use wider stores (8-byte `str x..`, or a 4-byte `str w..`). 82 grep -Eq $'\t''str[[:space:]]+x[0-9]+' "$WORK/zero64.fn" || 83 fail "zero64 did not widen to 8-byte stores (str xN) (L3)" "$WORK/zero64.fn" 84 85 # ============================================================================ 86 # L10: frame elision on no-spill leaf functions 87 # ============================================================================ 88 # A one-op leaf accessor: no call, no spill, no address-taken local, not 89 # varargs. It must NOT establish a frame. 90 cat > "$WORK/leaf.c" <<'EOF' 91 double add(double a, double b) { return a + b; } 92 long getfield(long *p) { return p[3]; } 93 int sum3(int a, int b, int c) { return a + b + c; } 94 EOF 95 compile_case leaf "$WORK/leaf.c" 96 for fn in add getfield sum3; do 97 slice_func "$WORK/leaf.dis" "$fn" "$WORK/leaf.$fn.fn" 98 [ -s "$WORK/leaf.$fn.fn" ] || fail "$fn not found in disassembly" "$WORK/leaf.dis" 99 if grep -Eq $'\t''stp[[:space:]]+x29, x30' "$WORK/leaf.$fn.fn"; then 100 fail "$fn establishes a frame (stp x29,x30) but is a no-spill leaf (L10)" "$WORK/leaf.$fn.fn" 101 fi 102 if grep -Eq $'\t''add[[:space:]]+x29, sp' "$WORK/leaf.$fn.fn"; then 103 fail "$fn anchors x29 but is a no-spill leaf (L10)" "$WORK/leaf.$fn.fn" 104 fi 105 grep -Eq $'\t''ret' "$WORK/leaf.$fn.fn" || 106 fail "$fn has no ret" "$WORK/leaf.$fn.fn" 107 done 108 109 # Frame-NEEDING functions must STILL establish a frame. 110 cat > "$WORK/framed.c" <<'EOF' 111 extern long ext(long); 112 long calls(long x) { return ext(x) + 1; } /* non-leaf: keeps frame */ 113 long takesaddr(long x) { long v = x; long *p = &v; return *p + ext(x); } 114 long recurse(long n) { return n <= 1 ? 1 : n * recurse(n - 1); } 115 EOF 116 compile_case framed "$WORK/framed.c" 117 for fn in calls takesaddr recurse; do 118 slice_func "$WORK/framed.dis" "$fn" "$WORK/framed.$fn.fn" 119 [ -s "$WORK/framed.$fn.fn" ] || fail "$fn not found in disassembly" "$WORK/framed.dis" 120 grep -Eq $'\t''stp[[:space:]]+x29, x30' "$WORK/framed.$fn.fn" || 121 fail "$fn dropped its frame but needs one (call/addr/recursion) (L10)" "$WORK/framed.$fn.fn" 122 done 123 124 printf 'o1p-aa64: ok (L3 wide zero-init, L10 leaf frame elision + framed kept)\n'