kit

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

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'