kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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'