aa64_tail_call.sh (4535B)
1 #!/usr/bin/env bash 2 # Structural aarch64 tail-call checks. 3 # 4 # These cases prove that realized tails are sibling calls: 5 # - direct stack-arg tail calls reuse the caller's incoming stack-arg window 6 # - direct tails lower to AARCH64_JUMP26 (`b`), not AARCH64_CALL26 (`bl`) 7 # - indirect tails lower to `br`, not `blr` 8 # - the caller frame is restored before the sibling branch 9 set -euo pipefail 10 11 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 12 KIT="${KIT:-$ROOT/build/kit}" 13 WORK="$ROOT/build/test/opt/aa64_tail_call" 14 mkdir -p "$WORK" 15 16 fail() { 17 printf 'aa64-tail-call check FAILED: %s\n' "$1" >&2 18 if [ -n "${2:-}" ] && [ -f "$2" ]; then 19 sed 's/^/ | /' "$2" >&2 20 fi 21 exit 1 22 } 23 24 slice_func() { 25 local src="$1" func="$2" out="$3" 26 awk -v name="$func" ' 27 $0 ~ "^[0-9a-f]+ <" name ">:" { in_fn = 1; print; next } 28 /^[0-9a-f]+ </ { in_fn = 0 } 29 in_fn { print } 30 ' "$src" > "$out" 31 } 32 33 compile_case() { 34 local name="$1" src="$2" 35 "$KIT" cc -target aarch64-linux-gnu -O1 -c "$src" \ 36 -o "$WORK/$name.o" > "$WORK/$name.cc.out" 2> "$WORK/$name.cc.err" 37 "$KIT" objdump -d "$WORK/$name.o" \ 38 > "$WORK/$name.dis" 2> "$WORK/$name.objdump.err" 39 "$KIT" objdump -r "$WORK/$name.o" \ 40 > "$WORK/$name.relocs" 2> "$WORK/$name.relocs.err" 41 } 42 43 compile_case direct_stack "$ROOT/test/toy/cases/25_tail_many_stack_args.toy" 44 slice_func "$WORK/direct_stack.dis" caller "$WORK/direct_stack.caller.dis" 45 46 if grep -Eq '\bbl\b.*target' "$WORK/direct_stack.caller.dis"; then 47 fail 'direct stack-arg tail used bl target' "$WORK/direct_stack.caller.dis" 48 fi 49 if ! grep -Eq '\bb\b.*target.*AARCH64_JUMP26' "$WORK/direct_stack.caller.dis"; then 50 fail 'direct stack-arg tail missing b target / AARCH64_JUMP26' \ 51 "$WORK/direct_stack.caller.dis" 52 fi 53 # The tail's two stack-arg stores must land in the caller's incoming stack-arg 54 # window -- the same [x29,#K] / [x29,#K+8] addresses the caller LOADS its own 55 # 9th/10th incoming args from (physically the caller's CFA window). With the 56 # W1.1 uniform x29-at-bottom layout that window is at the CFA-relative offset 57 # x29 + (frame_size - out_stack), not the old top-record fixed [x29,#16]/[#24]; 58 # pinning the literal offset would be brittle, so assert that some [x29,#K] is 59 # both an incoming-arg load and a tail-arg store, with [x29,#K+8] the same. 60 if ! awk ' 61 function off(s, t) { 62 if (!match(s, /#[0-9]+\]/)) return -1 63 t = substr(s, RSTART + 1, RLENGTH - 2) 64 return t + 0 65 } 66 /ldr[[:space:]]+x[0-9]+, \[x29, #[0-9]+\]/ { loaded[off($0)] = 1 } 67 /str[[:space:]]+x[0-9]+, \[x29, #[0-9]+\]/ { stored[off($0)] = 1 } 68 END { 69 for (k in stored) 70 if (loaded[k] && stored[k+8] && loaded[k+8]) { ok = 1 } 71 exit(ok ? 0 : 1) 72 } 73 ' "$WORK/direct_stack.caller.dis"; then 74 fail 'direct stack-arg tail did not write caller incoming stack window' \ 75 "$WORK/direct_stack.caller.dis" 76 fi 77 # The caller frame must be torn down before the sibling branch. The W1.1 general 78 # bottom-record teardown recovers sp from the x29 anchor (`add sp, x16, #...`); 79 # the os==0 small-frame fold uses a post-indexed `ldp ...,[sp],#N`. Accept either. 80 if ! awk ' 81 /add[[:space:]]+sp, (sp|x1[0-9]),/ { restored = 1 } 82 /ldp[[:space:]]+x29, x30, \[sp\], #/ { restored = 1 } 83 /[[:space:]]b[[:space:]]+.*target.*AARCH64_JUMP26/ { 84 found = 1; ok = restored; exit 85 } 86 END { exit(found && ok ? 0 : 1) } 87 ' "$WORK/direct_stack.caller.dis"; then 88 fail 'direct tail branched before restoring sp' "$WORK/direct_stack.caller.dis" 89 fi 90 if ! grep -Eq 'AARCH64_JUMP26[[:space:]]+target' "$WORK/direct_stack.relocs"; then 91 fail 'direct tail relocation is not AARCH64_JUMP26' "$WORK/direct_stack.relocs" 92 fi 93 if grep -Eq 'AARCH64_CALL26[[:space:]]+target' "$WORK/direct_stack.relocs"; then 94 fail 'direct tail emitted AARCH64_CALL26 relocation to target' \ 95 "$WORK/direct_stack.relocs" 96 fi 97 98 compile_case indirect "$ROOT/test/toy/cases/32_musttail_indirect.toy" 99 slice_func "$WORK/indirect.dis" apply "$WORK/indirect.apply.dis" 100 101 if grep -Eq '\bblr\b' "$WORK/indirect.apply.dis"; then 102 fail 'indirect musttail used blr' "$WORK/indirect.apply.dis" 103 fi 104 if ! grep -Eq '\bbr[[:space:]]+x[0-9]+' "$WORK/indirect.apply.dis"; then 105 fail 'indirect musttail missing br' "$WORK/indirect.apply.dis" 106 fi 107 if ! awk ' 108 /ldp[[:space:]]+x29, x30,/ { restored = 1 } 109 /[[:space:]]br[[:space:]]+x[0-9]+/ { found = 1; ok = restored; exit } 110 END { exit(found && ok ? 0 : 1) } 111 ' "$WORK/indirect.apply.dis"; then 112 fail 'indirect tail branched before restoring frame record' \ 113 "$WORK/indirect.apply.dis" 114 fi 115 116 printf 'aa64-tail-call: ok\n'