kit

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

prologue_tier.sh (7899B)


      1 #!/usr/bin/env bash
      2 # Structural checks for the -O1 known-frame prologue cost-model tiers.
      3 #
      4 # aa64 is the reference (it already selects a minimal frame shape per function);
      5 # this pins that behaviour and asserts the ported equivalents on x64 and rv64:
      6 #
      7 #   x64 slim        leaf-ish frame (no callee-saves/locals/outgoing) keeps
      8 #                   `push rbp; mov rbp,rsp` but drops the `sub rsp` reservation.
      9 #   x64 red-zone    SysV leaf with a small frame keeps its locals/saves at
     10 #                   rbp-relative offsets in the 128-byte red zone, no `sub rsp`.
     11 #   rv64 leaf       a true leaf with no frame needs (no callee-saves, no slots,
     12 #                   no outgoing, register-only params) emits NO prologue and a
     13 #                   bare `ret` -- it never saves ra / sets up s0.
     14 #
     15 # A non-leaf (calls something) must NOT take the leaf tiers, since the call
     16 # clobbers ra (rv64) / the red zone (x64): those guards are asserted too.
     17 set -euo pipefail
     18 
     19 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     20 KIT="${KIT:-$ROOT/build/kit}"
     21 WORK="$ROOT/build/test/opt/prologue_tier"
     22 rm -rf "$WORK"
     23 mkdir -p "$WORK"
     24 
     25 fail() {
     26   printf 'prologue-tier 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 triple="$1" name="$2" src="$3"
     44   shift 3
     45   "$KIT" cc -target "$triple" -O1 "$@" -c "$src" \
     46     -o "$WORK/$name.o" > "$WORK/$name.cc.out" 2> "$WORK/$name.cc.err"
     47   "$KIT" objdump -d "$WORK/$name.o" \
     48     > "$WORK/$name.dis" 2> "$WORK/$name.objdump.err"
     49 }
     50 
     51 # ---- fixtures ----
     52 cat > "$WORK/leaf.c" <<'EOF'
     53 int leaf(int x) { return x + 1; }
     54 EOF
     55 
     56 cat > "$WORK/leaf_call.c" <<'EOF'
     57 extern int g(int);
     58 int leaf_call(int x) { return g(x) + 1; }
     59 EOF
     60 
     61 # 16 bytes of locals, leaf, no calls -> fits the SysV red zone on x64.
     62 cat > "$WORK/small_locals.c" <<'EOF'
     63 int small_locals(int x) {
     64   volatile int a[4];
     65   a[0] = x; a[1] = x + 1; a[2] = x + 2; a[3] = x + 3;
     66   return a[0] + a[1] + a[2] + a[3];
     67 }
     68 EOF
     69 
     70 # Inline asm can clobber the return-address register (rv64 ra) or the red zone
     71 # (x64 SysV), so a function containing ANY asm block must NOT take the
     72 # frame-eliding leaf/red-zone tiers, even though it makes no call.
     73 cat > "$WORK/asm_fn.c" <<'EOF'
     74 int asm_fn(int x) { __asm__ volatile("" ::: "memory"); return x + 1; }
     75 EOF
     76 cat > "$WORK/asm_locals.c" <<'EOF'
     77 int asm_locals(int x) {
     78   volatile int a[4];
     79   __asm__ volatile("" ::: "memory");
     80   a[0] = x; a[1] = x + 1; a[2] = x + 2; a[3] = x + 3;
     81   return a[0] + a[1] + a[2] + a[3];
     82 }
     83 EOF
     84 
     85 # ===================== aa64 reference (characterization) =====================
     86 # L10 (O1-PATTERNS): a no-spill LEAF (no call, no callee-saves, no slots, no
     87 # outgoing args, no asm, no frame-chain read) is now FRAMELESS -- it emits no
     88 # `stp x29,x30` record at all, just the op + `ret`. A non-leaf (calls g) must
     89 # still establish the frame record so x30/LR survives the call.
     90 compile_case aarch64-linux-gnu aa64_leaf "$WORK/leaf.c"
     91 slice_func "$WORK/aa64_leaf.dis" leaf "$WORK/aa64_leaf.fn"
     92 grep -Eq 'stp[[:space:]]+x29, x30' "$WORK/aa64_leaf.fn" &&
     93   fail 'aa64 no-spill leaf still emits a frame record (L10 should elide it)' "$WORK/aa64_leaf.fn"
     94 grep -Eq '\bret\b' "$WORK/aa64_leaf.fn" ||
     95   fail 'aa64 frameless leaf is missing its ret' "$WORK/aa64_leaf.fn"
     96 
     97 # Guard: a non-leaf (calls g) must KEEP the frame so x30/LR survives the call.
     98 compile_case aarch64-linux-gnu aa64_leaf_call "$WORK/leaf_call.c"
     99 slice_func "$WORK/aa64_leaf_call.dis" leaf_call "$WORK/aa64_leaf_call.fn"
    100 grep -Eq 'stp[[:space:]]+x29, x30' "$WORK/aa64_leaf_call.fn" ||
    101   fail 'aa64 non-leaf dropped the frame record (L10 over-fired across a call)' "$WORK/aa64_leaf_call.fn"
    102 
    103 # Guard: inline asm may clobber x30/LR, so a leaf with asm must KEEP the frame.
    104 compile_case aarch64-linux-gnu aa64_asm "$WORK/asm_fn.c"
    105 slice_func "$WORK/aa64_asm.dis" asm_fn "$WORK/aa64_asm.fn"
    106 grep -Eq 'stp[[:space:]]+x29, x30' "$WORK/aa64_asm.fn" ||
    107   fail 'aa64 leaf with inline asm dropped the frame record (frameless must not fire with asm)' "$WORK/aa64_asm.fn"
    108 
    109 # ===================== x64 slim (no sub rsp on empty frame) ==================
    110 compile_case x86_64-linux-gnu x64_leaf "$WORK/leaf.c"
    111 slice_func "$WORK/x64_leaf.dis" leaf "$WORK/x64_leaf.fn"
    112 grep -Eq 'push[[:space:]]+%rbp' "$WORK/x64_leaf.fn" ||
    113   fail 'x64 leaf dropped the rbp frame record (slim should keep push rbp)' "$WORK/x64_leaf.fn"
    114 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_leaf.fn" &&
    115   fail 'x64 leaf still reserves stack (slim tier should skip sub rsp)' "$WORK/x64_leaf.fn"
    116 
    117 # A register-only call still leaves max_outgoing==0 and an empty frame -> slim.
    118 compile_case x86_64-linux-gnu x64_leaf_call "$WORK/leaf_call.c"
    119 slice_func "$WORK/x64_leaf_call.dis" leaf_call "$WORK/x64_leaf_call.fn"
    120 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_leaf_call.fn" &&
    121   fail 'x64 register-only call still reserves stack (slim should skip sub rsp)' "$WORK/x64_leaf_call.fn"
    122 
    123 # ===================== x64 red-zone leaf (locals, no sub rsp) ================
    124 compile_case x86_64-linux-gnu x64_redzone "$WORK/small_locals.c"
    125 slice_func "$WORK/x64_redzone.dis" small_locals "$WORK/x64_redzone.fn"
    126 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_redzone.fn" &&
    127   fail 'x64 red-zone leaf still reserves stack (should use the red zone, no sub rsp)' "$WORK/x64_redzone.fn"
    128 grep -Eq '\(%rbp\)' "$WORK/x64_redzone.fn" ||
    129   fail 'x64 red-zone leaf does not address locals rbp-relative' "$WORK/x64_redzone.fn"
    130 
    131 compile_case x86_64-linux-gnu x64_no_redzone "$WORK/small_locals.c" \
    132   -mno-red-zone
    133 slice_func "$WORK/x64_no_redzone.dis" small_locals "$WORK/x64_no_redzone.fn"
    134 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_no_redzone.fn" ||
    135   fail 'x64 -mno-red-zone leaf skipped stack reservation' "$WORK/x64_no_redzone.fn"
    136 
    137 # ===================== rv64 leaf (no frame at all) ==========================
    138 compile_case riscv64-linux-gnu rv64_leaf "$WORK/leaf.c"
    139 slice_func "$WORK/rv64_leaf.dis" leaf "$WORK/rv64_leaf.fn"
    140 grep -Eq '\bsd[[:space:]]+ra,' "$WORK/rv64_leaf.fn" &&
    141   fail 'rv64 leaf saved ra (leaf tier should emit no frame)' "$WORK/rv64_leaf.fn"
    142 grep -Eq 'addi[[:space:]]+sp, sp, -' "$WORK/rv64_leaf.fn" &&
    143   fail 'rv64 leaf adjusted sp (leaf tier should keep sp untouched)' "$WORK/rv64_leaf.fn"
    144 grep -Eq '\bret\b' "$WORK/rv64_leaf.fn" ||
    145   fail 'rv64 leaf is missing its ret' "$WORK/rv64_leaf.fn"
    146 
    147 # Guard: a non-leaf (calls g) must KEEP the frame so ra survives the call.
    148 compile_case riscv64-linux-gnu rv64_leaf_call "$WORK/leaf_call.c"
    149 slice_func "$WORK/rv64_leaf_call.dis" leaf_call "$WORK/rv64_leaf_call.fn"
    150 grep -Eq '\bsd[[:space:]]+ra,' "$WORK/rv64_leaf_call.fn" ||
    151   fail 'rv64 non-leaf dropped the ra save (leaf tier over-fired across a call)' "$WORK/rv64_leaf_call.fn"
    152 
    153 # Guard: inline asm may clobber ra, so a leaf with asm must KEEP the frame.
    154 compile_case riscv64-linux-gnu rv64_asm "$WORK/asm_fn.c"
    155 slice_func "$WORK/rv64_asm.dis" asm_fn "$WORK/rv64_asm.fn"
    156 grep -Eq '\bsd[[:space:]]+ra,' "$WORK/rv64_asm.fn" ||
    157   fail 'rv64 leaf with inline asm dropped the ra save (slim tier must not fire with asm)' "$WORK/rv64_asm.fn"
    158 
    159 # ===================== x64 red-zone must not fire with inline asm ===========
    160 # Inline asm may clobber the red zone (e.g. a `call`), so a red-zone leaf with
    161 # asm must keep its reserved stack.
    162 compile_case x86_64-linux-gnu x64_asm_locals "$WORK/asm_locals.c"
    163 slice_func "$WORK/x64_asm_locals.dis" asm_locals "$WORK/x64_asm_locals.fn"
    164 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_asm_locals.fn" ||
    165   fail 'x64 red-zone leaf with inline asm skipped sub rsp (must reserve, asm may clobber the red zone)' "$WORK/x64_asm_locals.fn"
    166 
    167 printf 'prologue-tier: ok\n'