kit

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

commit 24f445586a405a0772b5d45a48b55d7c4fc82c00
parent b8876feffff2156a0e73be9e0b48281bf496a370
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 28 May 2026 21:51:44 -0700

test aa64 tail calls

Diffstat:
Atest/opt/aa64_tail_call.sh | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/test.mk | 6+++++-
2 files changed, 98 insertions(+), 1 deletion(-)

diff --git a/test/opt/aa64_tail_call.sh b/test/opt/aa64_tail_call.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# Structural aarch64 tail-call checks. +# +# These cases prove that realized tails are sibling calls: +# - direct stack-arg tail calls reuse the caller's incoming stack-arg window +# - direct tails lower to AARCH64_JUMP26 (`b`), not AARCH64_CALL26 (`bl`) +# - indirect tails lower to `br`, not `blr` +# - the caller frame is restored before the sibling branch +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +CFREE="${CFREE:-$ROOT/build/cfree}" +WORK="$ROOT/build/test/opt/aa64_tail_call" +mkdir -p "$WORK" + +fail() { + printf 'aa64-tail-call check FAILED: %s\n' "$1" >&2 + if [ -n "${2:-}" ] && [ -f "$2" ]; then + sed 's/^/ | /' "$2" >&2 + fi + exit 1 +} + +slice_func() { + local src="$1" func="$2" out="$3" + awk -v name="$func" ' + $0 ~ "^[0-9a-f]+ <" name ">:" { in_fn = 1; print; next } + /^[0-9a-f]+ </ { in_fn = 0 } + in_fn { print } + ' "$src" > "$out" +} + +compile_case() { + local name="$1" src="$2" + "$CFREE" cc -target aarch64-linux-gnu -O1 -c "$src" \ + -o "$WORK/$name.o" > "$WORK/$name.cc.out" 2> "$WORK/$name.cc.err" + "$CFREE" objdump -d "$WORK/$name.o" \ + > "$WORK/$name.dis" 2> "$WORK/$name.objdump.err" + "$CFREE" objdump -r "$WORK/$name.o" \ + > "$WORK/$name.relocs" 2> "$WORK/$name.relocs.err" +} + +compile_case direct_stack "$ROOT/test/toy/cases/25_tail_many_stack_args.toy" +slice_func "$WORK/direct_stack.dis" caller "$WORK/direct_stack.caller.dis" + +if grep -Eq '\bbl\b.*target' "$WORK/direct_stack.caller.dis"; then + fail 'direct stack-arg tail used bl target' "$WORK/direct_stack.caller.dis" +fi +if ! grep -Eq '\bb\b.*target.*AARCH64_JUMP26' "$WORK/direct_stack.caller.dis"; then + fail 'direct stack-arg tail missing b target / AARCH64_JUMP26' \ + "$WORK/direct_stack.caller.dis" +fi +if ! grep -Eq '\bstr\b.*\[x29, #16\]' "$WORK/direct_stack.caller.dis" || + ! grep -Eq '\bstr\b.*\[x29, #24\]' "$WORK/direct_stack.caller.dis"; then + fail 'direct stack-arg tail did not write caller incoming stack window' \ + "$WORK/direct_stack.caller.dis" +fi +if ! awk ' + /add[[:space:]]+sp, sp,/ { restored = 1 } + /[[:space:]]b[[:space:]]+.*target.*AARCH64_JUMP26/ { + found = 1; ok = restored; exit + } + END { exit(found && ok ? 0 : 1) } +' "$WORK/direct_stack.caller.dis"; then + fail 'direct tail branched before restoring sp' "$WORK/direct_stack.caller.dis" +fi +if ! grep -Eq 'AARCH64_JUMP26[[:space:]]+target' "$WORK/direct_stack.relocs"; then + fail 'direct tail relocation is not AARCH64_JUMP26' "$WORK/direct_stack.relocs" +fi +if grep -Eq 'AARCH64_CALL26[[:space:]]+target' "$WORK/direct_stack.relocs"; then + fail 'direct tail emitted AARCH64_CALL26 relocation to target' \ + "$WORK/direct_stack.relocs" +fi + +compile_case indirect "$ROOT/test/toy/cases/32_musttail_indirect.toy" +slice_func "$WORK/indirect.dis" apply "$WORK/indirect.apply.dis" + +if grep -Eq '\bblr\b' "$WORK/indirect.apply.dis"; then + fail 'indirect musttail used blr' "$WORK/indirect.apply.dis" +fi +if ! grep -Eq '\bbr[[:space:]]+x[0-9]+' "$WORK/indirect.apply.dis"; then + fail 'indirect musttail missing br' "$WORK/indirect.apply.dis" +fi +if ! awk ' + /ldp[[:space:]]+x29, x30,/ { restored = 1 } + /[[:space:]]br[[:space:]]+x[0-9]+/ { found = 1; ok = restored; exit } + END { exit(found && ok ? 0 : 1) } +' "$WORK/indirect.apply.dis"; then + fail 'indirect tail branched before restoring frame record' \ + "$WORK/indirect.apply.dis" +fi + +printf 'aa64-tail-call: ok\n' diff --git a/test/test.mk b/test/test.mk @@ -521,7 +521,7 @@ test-macho: lib $(TEST_RT_DEP) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_R OPT_TEST_BIN = build/test/cg_ir_lower_test TINY_INLINE_TEST_BIN = build/test/tiny_inline_test -test-opt: bin $(OPT_TEST_BIN) test-opt-tiny-inline test-opt-inline test-opt-zero-arg test-opt-static-prune-aa64 +test-opt: bin $(OPT_TEST_BIN) test-opt-tiny-inline test-opt-inline test-opt-zero-arg test-opt-static-prune-aa64 test-opt-aa64-tail $(OPT_TEST_BIN) $(OPT_TEST_BIN): test/opt/cg_ir_lower_test.c $(LIB_OBJS) @@ -548,6 +548,10 @@ test-opt-zero-arg: bin test-opt-static-prune-aa64: bin @CFREE=$(abspath $(BIN)) bash test/opt/static_prune_aa64.sh +.PHONY: test-opt-aa64-tail +test-opt-aa64-tail: bin + @CFREE=$(abspath $(BIN)) bash test/opt/aa64_tail_call.sh + test-parse: test-parse-ok test-parse-err test-parse-ok: lib $(TEST_RT_DEP) $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)