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'