prologue_tier.sh (6394B)
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 "$KIT" cc -target "$triple" -O1 -c "$src" \ 45 -o "$WORK/$name.o" > "$WORK/$name.cc.out" 2> "$WORK/$name.cc.err" 46 "$KIT" objdump -d "$WORK/$name.o" \ 47 > "$WORK/$name.dis" 2> "$WORK/$name.objdump.err" 48 } 49 50 # ---- fixtures ---- 51 cat > "$WORK/leaf.c" <<'EOF' 52 int leaf(int x) { return x + 1; } 53 EOF 54 55 cat > "$WORK/leaf_call.c" <<'EOF' 56 extern int g(int); 57 int leaf_call(int x) { return g(x) + 1; } 58 EOF 59 60 # 16 bytes of locals, leaf, no calls -> fits the SysV red zone on x64. 61 cat > "$WORK/small_locals.c" <<'EOF' 62 int small_locals(int x) { 63 volatile int a[4]; 64 a[0] = x; a[1] = x + 1; a[2] = x + 2; a[3] = x + 3; 65 return a[0] + a[1] + a[2] + a[3]; 66 } 67 EOF 68 69 # Inline asm can clobber the return-address register (rv64 ra) or the red zone 70 # (x64 SysV), so a function containing ANY asm block must NOT take the 71 # frame-eliding leaf/red-zone tiers, even though it makes no call. 72 cat > "$WORK/asm_fn.c" <<'EOF' 73 int asm_fn(int x) { __asm__ volatile("" ::: "memory"); return x + 1; } 74 EOF 75 cat > "$WORK/asm_locals.c" <<'EOF' 76 int asm_locals(int x) { 77 volatile int a[4]; 78 __asm__ volatile("" ::: "memory"); 79 a[0] = x; a[1] = x + 1; a[2] = x + 2; a[3] = x + 3; 80 return a[0] + a[1] + a[2] + a[3]; 81 } 82 EOF 83 84 # ===================== aa64 reference (characterization) ===================== 85 compile_case aarch64-linux-gnu aa64_leaf "$WORK/leaf.c" 86 slice_func "$WORK/aa64_leaf.dis" leaf "$WORK/aa64_leaf.fn" 87 grep -Eq 'stp[[:space:]]+x29, x30, \[sp, #-16\]!' "$WORK/aa64_leaf.fn" || 88 fail 'aa64 leaf is not using the slim_prologue frame record' "$WORK/aa64_leaf.fn" 89 90 # ===================== x64 slim (no sub rsp on empty frame) ================== 91 compile_case x86_64-linux-gnu x64_leaf "$WORK/leaf.c" 92 slice_func "$WORK/x64_leaf.dis" leaf "$WORK/x64_leaf.fn" 93 grep -Eq 'push[[:space:]]+%rbp' "$WORK/x64_leaf.fn" || 94 fail 'x64 leaf dropped the rbp frame record (slim should keep push rbp)' "$WORK/x64_leaf.fn" 95 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_leaf.fn" && 96 fail 'x64 leaf still reserves stack (slim tier should skip sub rsp)' "$WORK/x64_leaf.fn" 97 98 # A register-only call still leaves max_outgoing==0 and an empty frame -> slim. 99 compile_case x86_64-linux-gnu x64_leaf_call "$WORK/leaf_call.c" 100 slice_func "$WORK/x64_leaf_call.dis" leaf_call "$WORK/x64_leaf_call.fn" 101 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_leaf_call.fn" && 102 fail 'x64 register-only call still reserves stack (slim should skip sub rsp)' "$WORK/x64_leaf_call.fn" 103 104 # ===================== x64 red-zone leaf (locals, no sub rsp) ================ 105 compile_case x86_64-linux-gnu x64_redzone "$WORK/small_locals.c" 106 slice_func "$WORK/x64_redzone.dis" small_locals "$WORK/x64_redzone.fn" 107 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_redzone.fn" && 108 fail 'x64 red-zone leaf still reserves stack (should use the red zone, no sub rsp)' "$WORK/x64_redzone.fn" 109 grep -Eq '\(%rbp\)' "$WORK/x64_redzone.fn" || 110 fail 'x64 red-zone leaf does not address locals rbp-relative' "$WORK/x64_redzone.fn" 111 112 # ===================== rv64 leaf (no frame at all) ========================== 113 compile_case riscv64-linux-gnu rv64_leaf "$WORK/leaf.c" 114 slice_func "$WORK/rv64_leaf.dis" leaf "$WORK/rv64_leaf.fn" 115 grep -Eq '\bsd[[:space:]]+ra,' "$WORK/rv64_leaf.fn" && 116 fail 'rv64 leaf saved ra (leaf tier should emit no frame)' "$WORK/rv64_leaf.fn" 117 grep -Eq 'addi[[:space:]]+sp, sp, -' "$WORK/rv64_leaf.fn" && 118 fail 'rv64 leaf adjusted sp (leaf tier should keep sp untouched)' "$WORK/rv64_leaf.fn" 119 grep -Eq '\bret\b' "$WORK/rv64_leaf.fn" || 120 fail 'rv64 leaf is missing its ret' "$WORK/rv64_leaf.fn" 121 122 # Guard: a non-leaf (calls g) must KEEP the frame so ra survives the call. 123 compile_case riscv64-linux-gnu rv64_leaf_call "$WORK/leaf_call.c" 124 slice_func "$WORK/rv64_leaf_call.dis" leaf_call "$WORK/rv64_leaf_call.fn" 125 grep -Eq '\bsd[[:space:]]+ra,' "$WORK/rv64_leaf_call.fn" || 126 fail 'rv64 non-leaf dropped the ra save (leaf tier over-fired across a call)' "$WORK/rv64_leaf_call.fn" 127 128 # Guard: inline asm may clobber ra, so a leaf with asm must KEEP the frame. 129 compile_case riscv64-linux-gnu rv64_asm "$WORK/asm_fn.c" 130 slice_func "$WORK/rv64_asm.dis" asm_fn "$WORK/rv64_asm.fn" 131 grep -Eq '\bsd[[:space:]]+ra,' "$WORK/rv64_asm.fn" || 132 fail 'rv64 leaf with inline asm dropped the ra save (slim tier must not fire with asm)' "$WORK/rv64_asm.fn" 133 134 # ===================== x64 red-zone must not fire with inline asm =========== 135 # Inline asm may clobber the red zone (e.g. a `call`), so a red-zone leaf with 136 # asm must keep its reserved stack. 137 compile_case x86_64-linux-gnu x64_asm_locals "$WORK/asm_locals.c" 138 slice_func "$WORK/x64_asm_locals.dis" asm_locals "$WORK/x64_asm_locals.fn" 139 grep -Eq 'sub[ql]?[[:space:]]+\$[0-9]+, %rsp' "$WORK/x64_asm_locals.fn" || 140 fail 'x64 red-zone leaf with inline asm skipped sub rsp (must reserve, asm may clobber the red zone)' "$WORK/x64_asm_locals.fn" 141 142 printf 'prologue-tier: ok\n'