kit

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

aa64_x29_bottom.sh (6572B)


      1 #!/usr/bin/env bash
      2 # Structural checks for the -O1 aa64 uniform x29-at-bottom known-frame layout
      3 # (doc/plan/O1.md W1.1).
      4 #
      5 # At -O1 the frame is fully known before the body emits, so aa64 anchors x29 at
      6 # the BOTTOM of the static slots (just above the outgoing-arg area). Every spill
      7 # / local slot is then a one-instruction POSITIVE `ldr/str [x29,#k]` within the
      8 # 32 KB scaled reach (`add x16,x29,#hi ; ldr [x16,#lo]` only past it). This
      9 # replaces the old `sub x17,x29,#k ; ldur` two-instruction fallback that was 36%
     10 # of lvm / 12% of sqlite instructions.
     11 #
     12 # The saved x29/x30 pair sits AT [x29]/[x29+8] (the frame-pointer chain kit's
     13 # unwinder / __kit_backtrace walks) and outgoing args stay sp-relative
     14 # ([sp,#k]) so calls after an alloca still address their arg area at current sp.
     15 #
     16 # These checks pin the resulting disassembly on aarch64 (the reference backend):
     17 #  1. a big-frame, high-pressure function spills via `ldr/str [x29,#k]` and
     18 #     emits NO `sub xN,x29,#k` for spill addressing;
     19 #  2. an alloca function (sp floats) still addresses spills via [x29,#k] and
     20 #     never recomputes a slot address with `sub xN,x29,#k`;
     21 #  3. a >32 KB-frame function uses the add-build (`add x16,x29,#... ; ldr/str`)
     22 #     and never `sub xN,x29,#`;
     23 #  4. a signed-narrow-load function never uses `sub xN,x29,#` for spills.
     24 set -euo pipefail
     25 
     26 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     27 KIT="${KIT:-$ROOT/build/kit}"
     28 WORK="$ROOT/build/test/opt/aa64_x29_bottom"
     29 rm -rf "$WORK"
     30 mkdir -p "$WORK"
     31 
     32 fail() {
     33   printf 'aa64-x29-bottom check FAILED: %s\n' "$1" >&2
     34   if [ -n "${2:-}" ] && [ -f "$2" ]; then
     35     sed 's/^/  | /' "$2" >&2
     36   fi
     37   exit 1
     38 }
     39 
     40 slice_func() {
     41   local src="$1" func="$2" out="$3"
     42   awk -v name="$func" '
     43     $0 ~ "^[0-9a-f]+ <" name ">:" { in_fn = 1; print; next }
     44     /^[0-9a-f]+ </ { in_fn = 0 }
     45     in_fn { print }
     46   ' "$src" > "$out"
     47 }
     48 
     49 compile_case() {
     50   local name="$1" src="$2"
     51   "$KIT" cc -target aarch64-linux-gnu -O1 -std=c11 -c "$src" \
     52     -o "$WORK/$name.o" > "$WORK/$name.cc.out" 2> "$WORK/$name.cc.err" ||
     53     { cat "$WORK/$name.cc.err" >&2; fail "compile of $name failed"; }
     54   "$KIT" objdump -d "$WORK/$name.o" \
     55     > "$WORK/$name.dis" 2> "$WORK/$name.objdump.err"
     56 }
     57 
     58 # A spill never recomputes its address with `sub xN, x29, #k` on the known-frame
     59 # path -- that is exactly the 2-insn pattern W1.1 removes.
     60 no_frame_sub() { # $1 = sliced fn file, $2 = label
     61   if grep -Eq 'sub[[:space:]]+x[0-9]+, x29, #' "$1"; then
     62     fail "$2 recomputes a frame-slot address with 'sub xN, x29, #' (W1.1 should address [x29,#k] positively)" "$1"
     63   fi
     64 }
     65 
     66 # ---- fixtures ----
     67 
     68 # 1. Big frame (>256-byte spill area) + outgoing stack args (>8-arg calls). This
     69 #    is the case W1.1 targets: a large frame means spill offsets escape `ldur`'s
     70 #    +-256 range, and out_stack>0 disqualifies the old folded bottom-record. The
     71 #    baseline emits `sub x17,x29,#k ; ldur` for each such spill; x29-at-bottom
     72 #    must address every one as a positive [x29,#k] with no frame-address `sub`.
     73 cat > "$WORK/pressure.c" <<'EOF'
     74 extern long sink(long,long,long,long,long,long,long,long,long,long);
     75 long pressure(long *p) {
     76   long v[48];
     77   for (int i = 0; i < 48; i++) v[i] = p[i] + i;
     78   long s = 0;
     79   s += sink(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7],v[8],v[9]);
     80   s += sink(v[10],v[11],v[12],v[13],v[14],v[15],v[16],v[17],v[18],v[19]);
     81   s += sink(v[20],v[21],v[22],v[23],v[24],v[25],v[26],v[27],v[28],v[29]);
     82   s += sink(v[30],v[31],v[32],v[33],v[34],v[35],v[36],v[37],v[38],v[39]);
     83   for (int i = 0; i < 48; i++) s += v[i];
     84   return s;
     85 }
     86 EOF
     87 compile_case pressure "$WORK/pressure.c"
     88 slice_func "$WORK/pressure.dis" pressure "$WORK/pressure.fn"
     89 [ -s "$WORK/pressure.fn" ] || fail "pressure not found in disassembly" "$WORK/pressure.dis"
     90 no_frame_sub "$WORK/pressure.fn" pressure
     91 grep -Eq '(ldr|str)[[:space:]]+[wx][0-9]+, \[x29, #[0-9]+\]' "$WORK/pressure.fn" ||
     92   fail "pressure has no positive [x29,#k] spill access (expected x29-at-bottom)" "$WORK/pressure.fn"
     93 # The prologue must anchor x29 = sp + os (saved fp/lr pair at [x29]/[x29+8]).
     94 grep -Eq 'add[[:space:]]+x29, sp' "$WORK/pressure.fn" ||
     95   fail "pressure prologue did not anchor x29 = sp + os" "$WORK/pressure.fn"
     96 
     97 # 2. alloca: sp floats during the body, but x29 stays put, so spills are still
     98 #    [x29,#k] and outgoing args are [sp,#k]. (The call below takes stack args.)
     99 cat > "$WORK/alloca_fn.c" <<'EOF'
    100 extern long use(long *, long, long, long, long, long, long, long, long, long);
    101 long alloca_fn(long n, long x) {
    102   long *p = __builtin_alloca(n * sizeof(long));
    103   for (long i = 0; i < n; i++) p[i] = x + i;
    104   long r = use(p, x, x+1, x+2, x+3, x+4, x+5, x+6, x+7, x+8);
    105   return r + p[0];
    106 }
    107 EOF
    108 compile_case alloca_fn "$WORK/alloca_fn.c"
    109 slice_func "$WORK/alloca_fn.dis" alloca_fn "$WORK/alloca_fn.fn"
    110 [ -s "$WORK/alloca_fn.fn" ] || fail "alloca_fn not found in disassembly" "$WORK/alloca_fn.dis"
    111 no_frame_sub "$WORK/alloca_fn.fn" alloca_fn
    112 grep -Eq 'add[[:space:]]+x29, sp' "$WORK/alloca_fn.fn" ||
    113   fail "alloca_fn prologue did not anchor x29 = sp + os" "$WORK/alloca_fn.fn"
    114 
    115 # 3. >32 KB frame: the scaled `ldr/str [x29,#k]` reach is exceeded, so the slot
    116 #    address is built with `add x16/x17, x29, #hi` -- never `sub xN, x29, #k`.
    117 cat > "$WORK/huge.c" <<'EOF'
    118 extern void use_buf(volatile char *, volatile char *);
    119 long huge(long x) {
    120   volatile char a[40000];
    121   volatile char b[8];
    122   a[0] = (char)x;
    123   b[0] = (char)(x + 1);
    124   use_buf(a, b);
    125   return a[0] + b[0];
    126 }
    127 EOF
    128 compile_case huge "$WORK/huge.c"
    129 slice_func "$WORK/huge.dis" huge "$WORK/huge.fn"
    130 [ -s "$WORK/huge.fn" ] || fail "huge not found in disassembly" "$WORK/huge.dis"
    131 no_frame_sub "$WORK/huge.fn" huge
    132 grep -Eq 'add[[:space:]]+x1[67], x29, #' "$WORK/huge.fn" ||
    133   fail "huge >32KB frame did not build a far slot address with 'add x16/x17, x29, #'" "$WORK/huge.fn"
    134 
    135 # 4. signed narrow loads in a big frame: spilled narrow values still address
    136 #    [x29,#k] (ldrsb when reloaded as signed), never a sub-x29 recompute.
    137 cat > "$WORK/snl.c" <<'EOF'
    138 extern long sink2(long,long,long,long,long,long,long,long,long,long);
    139 long snl(signed char *p) {
    140   signed char v[400];
    141   for (int i = 0; i < 400; i++) v[i] = p[i];
    142   long s = sink2(v[0],v[40],v[80],v[120],v[160],v[200],v[240],v[280],v[320],v[360]);
    143   for (int i = 0; i < 400; i += 17) s += v[i];
    144   return s;
    145 }
    146 EOF
    147 compile_case snl "$WORK/snl.c"
    148 slice_func "$WORK/snl.dis" snl "$WORK/snl.fn"
    149 [ -s "$WORK/snl.fn" ] || fail "snl not found in disassembly" "$WORK/snl.dis"
    150 no_frame_sub "$WORK/snl.fn" snl
    151 
    152 printf 'aa64-x29-bottom: ok (pressure, alloca, >32KB add-build, signed narrow)\n'