boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

boot-run-tests.sh (29146B)


      1 #!/bin/sh
      2 ## boot-run-tests.sh — in-container suite runner.
      3 ##
      4 ## One invocation handles every requested fixture in a suite for $ARCH,
      5 ## instead of the host re-entering the container per fixture. The host
      6 ## runner (scripts/run-tests.sh) starts one podman process per arch and
      7 ## lets this script do all the build / execute / diff work.
      8 ##
      9 ## PASS/FAIL lines stream to stdout in the same format the host expects;
     10 ## the host greps them to update its totals, then prints the final
     11 ## summary itself. Fixture-name discovery happens here when no names
     12 ## are passed (so the host can stay agnostic about each suite's layout),
     13 ## except for m1pp: scripts/lint.sh runs python on the host, so the
     14 ## host preflights lint and passes the explicit kept list down.
     15 ##
     16 ## Env: ARCH=aarch64|amd64|riscv64
     17 ## Usage: boot-run-tests.sh --suite=<m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc> [name ...]
     18 
     19 set -eu
     20 
     21 : "${ARCH:?ARCH must be set}"
     22 
     23 SUITE=
     24 NAMES=
     25 
     26 while [ "$#" -gt 0 ]; do
     27     case "$1" in
     28         --suite)   shift; SUITE=$1 ;;
     29         --suite=*) SUITE=${1#--suite=} ;;
     30         --) shift; while [ "$#" -gt 0 ]; do NAMES="$NAMES $1"; shift; done; break ;;
     31         -*) echo "$0: unknown flag '$1'" >&2; exit 2 ;;
     32         *) NAMES="$NAMES $1" ;;
     33     esac
     34     shift
     35 done
     36 
     37 case "$SUITE" in
     38     m1pp|p1|scheme1|cc-util|cc-lex|cc-pp|cc-cg|cc|cc-libc|cc-ext|tcc-cc|tcc-libc) ;;
     39     "") echo "$0: --suite required" >&2; exit 2 ;;
     40     *) echo "$0: unknown suite '$SUITE'" >&2; exit 2 ;;
     41 esac
     42 
     43 CC_EXTRA_FLAGS=
     44 [ "${CC_TRACE_EMIT:-0}" = "1" ] && CC_EXTRA_FLAGS="$CC_EXTRA_FLAGS --cc-trace-emit"
     45 [ "${CC_DEBUG:-0}" = "1" ]      && CC_EXTRA_FLAGS="$CC_EXTRA_FLAGS --cc-debug"
     46 
     47 discover() {
     48     dir=$1; ext=$2
     49     ls "$dir" 2>/dev/null \
     50         | sed -n "s/^\\([^_][^.]*\\)\\.$ext\$/\\1/p" \
     51         | sort -u
     52 }
     53 
     54 report() {
     55     label=$1; status=$2
     56     echo "  $status $label"
     57 }
     58 
     59 show_diff() {
     60     expected=$1; actual=$2
     61     echo "    --- expected ---"
     62     printf '%s\n' "$expected" | sed 's/^/    /'
     63     echo "    --- actual ---"
     64     printf '%s\n' "$actual"   | sed 's/^/    /'
     65 }
     66 
     67 # fail <label> [<heading>] [<log-file>]
     68 # Emit a FAIL row plus an optional indented heading and indented log
     69 # contents. Lets every suite handle a failed sub-step without re-running
     70 # the failing command to capture its stderr.
     71 fail() {
     72     label=$1
     73     report "$label" FAIL
     74     [ -n "${2:-}" ] && echo "    $2" || :
     75     if [ -n "${3:-}" ] && [ -e "${3:-}" ]; then
     76         sed 's/^/    /' "$3" >&2 || true
     77     fi
     78 }
     79 
     80 ## --- m1pp suite ---------------------------------------------------------
     81 ##
     82 ## Single check: run M1pp against tests/M1pp/<name>.M1pp, diff its text
     83 ## output against tests/M1pp/<name>.expected. The suite tests macro
     84 ## expansion only — assembling the result through hex2pp is the job of
     85 ## the p1 / cc-* suites, where the input is a complete program.
     86 run_m1pp_suite() {
     87     if [ -z "$NAMES" ]; then
     88         NAMES=$(discover tests/M1pp M1pp)
     89     fi
     90     for name in $NAMES; do
     91         expected=tests/M1pp/$name.expected
     92         m1pp_src=tests/M1pp/$name.M1pp
     93 
     94         if [ ! -e "$m1pp_src" ]; then
     95             echo "  SKIP $name (no .M1pp)"
     96             continue
     97         fi
     98         if [ ! -e "$expected" ]; then
     99             echo "  SKIP $name (no .expected)"
    100             continue
    101         fi
    102         expected_content=$(cat "$expected")
    103 
    104         label="[$ARCH] $name"
    105         outfile=build/$ARCH/tests/M1pp/$name.hex2pp
    106         mkdir -p "$(dirname "$outfile")"
    107         rm -f "$outfile"
    108         "./build/$ARCH/M1pp/M1pp" "$m1pp_src" "$outfile" >/dev/null 2>&1 || true
    109         if [ -e "$outfile" ]; then
    110             actual=$(cat "$outfile")
    111         else
    112             actual=
    113         fi
    114 
    115         if [ "$actual" != "$expected_content" ]; then
    116             report "$label" FAIL
    117             show_diff "$expected_content" "$actual"
    118             continue
    119         fi
    120 
    121         report "$label" PASS
    122     done
    123 }
    124 
    125 ## --- p1 suite -----------------------------------------------------------
    126 
    127 run_p1_suite() {
    128     if [ -z "$NAMES" ]; then
    129         NAMES=$(discover tests/P1 P1pp)
    130     fi
    131     for name in $NAMES; do
    132         pp_src=tests/P1/$name.P1pp
    133         expected=tests/P1/$name.expected
    134         if [ ! -e "$expected" ]; then echo "  SKIP $name (no .expected)"; continue; fi
    135         if [ ! -e "$pp_src" ]; then echo "  SKIP $name (no .P1pp)"; continue; fi
    136         expected_content=$(cat "$expected")
    137 
    138         label="[$ARCH] $name"
    139         bin=build/$ARCH/tests/P1/$name
    140         log=build/$ARCH/.work/tests/P1/$name/build.log
    141         mkdir -p "$(dirname "$bin")" "$(dirname "$log")"
    142         if ! sh scripts/boot-build-p1pp.sh "$bin" "$pp_src" \
    143                 >"$log" 2>&1; then
    144             fail "$label" "" "$log"
    145             continue
    146         fi
    147         actual=$("./$bin" 2>&1 || true)
    148         if [ "$actual" = "$expected_content" ]; then
    149             report "$label" PASS
    150         else
    151             report "$label" FAIL
    152             show_diff "$expected_content" "$actual"
    153         fi
    154     done
    155 }
    156 
    157 ## --- scheme1 suite ------------------------------------------------------
    158 
    159 run_scheme1_suite() {
    160     if [ -z "$NAMES" ]; then
    161         NAMES=$(discover tests/scheme1 scm)
    162     fi
    163     for name in $NAMES; do
    164         fixture=tests/scheme1/$name.scm
    165         expected_stdout_file=tests/scheme1/$name.expected
    166         expected_exit_file=tests/scheme1/$name.expected-exit
    167 
    168         if [ ! -e "$fixture" ]; then echo "  SKIP $name (no .scm)"; continue; fi
    169         if [ -e "$expected_stdout_file" ]; then
    170             expected_stdout=$(cat "$expected_stdout_file")
    171         else
    172             expected_stdout=
    173         fi
    174         if [ -e "$expected_exit_file" ]; then
    175             expected_exit=$(cat "$expected_exit_file")
    176         else
    177             expected_exit=0
    178         fi
    179 
    180         label="[$ARCH] $name"
    181         bin=build/$ARCH/scheme1/scheme1
    182         if [ ! -x "$bin" ]; then
    183             report "$label" FAIL
    184             echo "    (missing $bin -- run 'make scheme1 ARCH=$ARCH')" >&2
    185             continue
    186         fi
    187 
    188         tmp_stdout=$(mktemp)
    189         if sh scripts/boot-run-scheme1.sh "$fixture" >"$tmp_stdout" 2>&1; then
    190             actual_exit=0
    191         else
    192             actual_exit=$?
    193         fi
    194         actual_stdout=$(cat "$tmp_stdout")
    195         rm -f "$tmp_stdout"
    196 
    197         if [ "$actual_stdout" = "$expected_stdout" ] \
    198            && [ "$actual_exit" = "$expected_exit" ]; then
    199             report "$label" PASS
    200         else
    201             report "$label" FAIL
    202             if [ "$actual_stdout" != "$expected_stdout" ]; then
    203                 show_diff "$expected_stdout" "$actual_stdout"
    204             fi
    205             if [ "$actual_exit" != "$expected_exit" ]; then
    206                 echo "    exit: expected $expected_exit, got $actual_exit"
    207             fi
    208         fi
    209     done
    210 }
    211 
    212 ## --- cc-* suites --------------------------------------------------------
    213 
    214 _cc_check() {
    215     lbl=$1; exp_out=$2; exp_exit=$3; act_out=$4; act_exit=$5
    216     if [ "$act_out" = "$exp_out" ] && [ "$act_exit" = "$exp_exit" ]; then
    217         report "$lbl" PASS
    218     else
    219         report "$lbl" FAIL
    220         if [ "$act_out" != "$exp_out" ]; then show_diff "$exp_out" "$act_out"; fi
    221         if [ "$act_exit" != "$exp_exit" ]; then
    222             echo "    exit: expected $exp_exit, got $act_exit"
    223         fi
    224     fi
    225 }
    226 
    227 # _cc_unit_suite <suite-name> <expected-ext> <layer-list>
    228 _cc_unit_suite() {
    229     suite=$1; ext=$2; layers=$3
    230     [ -n "$NAMES" ] || NAMES=$(discover tests/$suite scm)
    231     for name in $NAMES; do
    232         fixture=tests/$suite/$name.scm
    233         [ -e "$fixture" ] || { echo "  SKIP $name (no .scm)"; continue; }
    234         if [ -e "tests/$suite/$name.$ext" ]; then
    235             expout=$(cat "tests/$suite/$name.$ext")
    236         else
    237             expout=
    238         fi
    239         if [ -e "tests/$suite/$name.expected-exit" ]; then
    240             expexit=$(cat "tests/$suite/$name.expected-exit")
    241         else
    242             expexit=0
    243         fi
    244         tmp=$(mktemp)
    245         # Wrap in `sh -c` so catm failure doesn't abort under set -e and
    246         # scheme1 still runs (matches host runner's prior behavior).
    247         if sh -c "
    248             build/$ARCH/tools/catm /tmp/cc-test.scm $layers $fixture
    249             exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm
    250         " >"$tmp" 2>&1; then
    251             act_exit=0
    252         else
    253             act_exit=$?
    254         fi
    255         act_out=$(cat "$tmp"); rm -f "$tmp"
    256         _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
    257     done
    258 }
    259 
    260 run_cc_util_suite() {
    261     _cc_unit_suite cc-util expected "scheme1/prelude.scm cc/cc.scm"
    262 }
    263 
    264 # _cc_pipeline_suite <suite-name> <expected-ext> <layers>
    265 _cc_pipeline_suite() {
    266     suite=$1; ext=$2; layers=$3
    267     [ -n "$NAMES" ] || NAMES=$(discover tests/$suite c)
    268     for name in $NAMES; do
    269         fixture=tests/$suite/$name.c
    270         [ -e "$fixture" ] || { echo "  SKIP $name (no .c)"; continue; }
    271         if [ -e "tests/$suite/$name.$ext" ]; then
    272             expout=$(grep -v '^;;' "tests/$suite/$name.$ext" || true)
    273         else
    274             expout=
    275         fi
    276         if [ -e "tests/$suite/$name.expected-exit" ]; then
    277             expexit=$(cat "tests/$suite/$name.expected-exit")
    278         else
    279             expexit=0
    280         fi
    281         tmp=$(mktemp)
    282         if sh -c "
    283             build/$ARCH/tools/catm /tmp/cc-test.scm $layers
    284             exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm $fixture
    285         " >"$tmp" 2>&1; then
    286             act_exit=0
    287         else
    288             act_exit=$?
    289         fi
    290         if [ "$expexit" != "0" ]; then
    291             act_out=
    292         else
    293             act_out=$(cat "$tmp")
    294         fi
    295         rm -f "$tmp"
    296         _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
    297     done
    298 }
    299 
    300 run_cc_lex_suite() {
    301     _cc_pipeline_suite cc-lex expected-toks \
    302         "scheme1/prelude.scm cc/cc.scm tests/cc-lex/_run-lex.scm"
    303 }
    304 
    305 # Two passes: .c fixtures via the lex+pp pipeline; the lone .scm fixture
    306 # (22-initial-defines) covers the -D mechanism the driver doesn't expose,
    307 # so it stays on the unit-suite path. NAMES is restored between passes
    308 # because _cc_pipeline_suite may have populated it via discovery.
    309 run_cc_pp_suite() {
    310     saved=$NAMES
    311     _cc_pipeline_suite cc-pp expected-toks \
    312         "scheme1/prelude.scm cc/cc.scm tests/cc-pp/_run-pp.scm"
    313     NAMES=$saved
    314     _cc_unit_suite cc-pp expected \
    315         "scheme1/prelude.scm cc/cc.scm"
    316 }
    317 
    318 # _cc_runtime_suite <suite-name> <fixture-ext> <layers> [<fixture-as-arg?>]
    319 _cc_runtime_suite() {
    320     suite=$1; fext=$2; layers=$3; arg_pass=${4:-0}
    321     [ -n "$NAMES" ] || NAMES=$(discover tests/$suite "$fext")
    322     for name in $NAMES; do
    323         fixture=tests/$suite/$name.$fext
    324         [ -e "$fixture" ] || { echo "  SKIP $name (no .$fext)"; continue; }
    325 
    326         if [ -e tests/$suite/$name.expected ]; then
    327             expout=$(cat tests/$suite/$name.expected)
    328         else
    329             expout=
    330         fi
    331         if [ -e tests/$suite/$name.expected-exit ]; then
    332             expexit=$(cat tests/$suite/$name.expected-exit)
    333         else
    334             expexit=0
    335         fi
    336 
    337         elf=build/$ARCH/tests/$suite/$name
    338         workdir=build/$ARCH/.work/tests/$suite/$name
    339         p1pp=$workdir/$name.P1pp
    340         mkdir -p "$(dirname "$elf")" "$workdir"
    341 
    342         if [ "$arg_pass" = "1" ]; then
    343             cmd="
    344                 build/$ARCH/tools/catm /tmp/cc-test.scm $layers
    345                 exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm $fixture
    346             "
    347         else
    348             cmd="
    349                 build/$ARCH/tools/catm /tmp/cc-test.scm $layers $fixture
    350                 exec build/$ARCH/scheme1/scheme1 /tmp/cc-test.scm
    351             "
    352         fi
    353         label="[$ARCH] $suite/$name"
    354         cg_log=$workdir/cg.log
    355         if ! sh -c "$cmd" >"$p1pp" 2>"$cg_log"; then
    356             fail "$label" "cg emission failed:" "$cg_log"
    357             continue
    358         fi
    359 
    360         p1pp_log=$workdir/p1pp.log
    361         if ! WORK_SUBPATH=tests/$suite/$name \
    362                 sh scripts/boot-build-p1pp.sh "$elf" "$p1pp" \
    363                 >"$p1pp_log" 2>&1; then
    364             fail "$label" "P1pp assemble failed:" "$p1pp_log"
    365             continue
    366         fi
    367 
    368         tmp=$(mktemp)
    369         if "./$elf" >"$tmp" 2>&1; then
    370             act_exit=0
    371         else
    372             act_exit=$?
    373         fi
    374         act_out=$(cat "$tmp"); rm -f "$tmp"
    375         _cc_check "[$ARCH] $suite/$name" "$expout" "$expexit" "$act_out" "$act_exit"
    376     done
    377 }
    378 
    379 run_cc_cg_suite() {
    380     _cc_runtime_suite cc-cg scm \
    381         "scheme1/prelude.scm cc/cc.scm" 0
    382 }
    383 
    384 run_cc_suite() {
    385     [ -n "$NAMES" ] || NAMES=$(discover tests/cc c)
    386     for name in $NAMES; do
    387         src=tests/cc/$name.c
    388         [ -e "$src" ] || { echo "  SKIP $name (no .c)"; continue; }
    389         if [ -e tests/cc/$name.expected ]; then
    390             expout=$(cat tests/cc/$name.expected)
    391         else
    392             expout=
    393         fi
    394         if [ -e tests/cc/$name.expected-exit ]; then
    395             expexit=$(cat tests/cc/$name.expected-exit)
    396         else
    397             expexit=0
    398         fi
    399         elf=build/$ARCH/tests/cc/$name
    400         workdir=build/$ARCH/.work/tests/cc/$name
    401         p1pp=$workdir/$name.P1pp
    402         label="[$ARCH] cc/$name"
    403         mkdir -p "$(dirname "$elf")" "$workdir"
    404 
    405         cc_log=$workdir/cc.log
    406         # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split.
    407         if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \
    408                 "$src" "$p1pp" >"$cc_log" 2>&1; then
    409             fail "$label" "cc compile failed:" "$cc_log"
    410             continue
    411         fi
    412 
    413         p1pp_log=$workdir/p1pp.log
    414         if ! WORK_SUBPATH=tests/cc/$name \
    415                 sh scripts/boot-build-p1pp.sh "$elf" "$p1pp" \
    416                 >"$p1pp_log" 2>&1; then
    417             fail "$label" "P1pp assemble failed:" "$p1pp_log"
    418             continue
    419         fi
    420 
    421         tmp=$(mktemp)
    422         if "./$elf" >"$tmp" 2>&1; then
    423             act_exit=0
    424         else
    425             act_exit=$?
    426         fi
    427         act_out=$(cat "$tmp"); rm -f "$tmp"
    428         _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit"
    429     done
    430 }
    431 
    432 ## --- cc-libc suite ------------------------------------------------------
    433 ##
    434 ## Mirrors run_cc_suite but links the prepended libc.P1pp into every
    435 ## fixture. Targeted red-green TDD on the cc.scm + libc combination —
    436 ## each .c is small (one feature: printf with %d, malloc round-trip,
    437 ## getenv lookup, …) so a failure isolates the bug to one symbol path.
    438 run_cc_libc_suite() {
    439     [ -n "$NAMES" ] || NAMES=$(discover tests/cc-libc c)
    440     for name in $NAMES; do
    441         src=tests/cc-libc/$name.c
    442         [ -e "$src" ] || { echo "  SKIP $name (no .c)"; continue; }
    443         if [ -e tests/cc-libc/$name.expected ]; then
    444             expout=$(cat tests/cc-libc/$name.expected)
    445         else
    446             expout=
    447         fi
    448         if [ -e tests/cc-libc/$name.expected-exit ]; then
    449             expexit=$(cat tests/cc-libc/$name.expected-exit)
    450         else
    451             expexit=0
    452         fi
    453         elf=build/$ARCH/tests/cc-libc/$name
    454         workdir=build/$ARCH/.work/tests/cc-libc/$name
    455         client_p1pp=$workdir/$name.client.P1pp
    456         label="[$ARCH] cc-libc/$name"
    457         mkdir -p "$(dirname "$elf")" "$workdir"
    458 
    459         # Compile the client TU in lib mode so it doesn't emit its
    460         # own :p1_main / :ELF_end and namespaces its anonymous string
    461         # labels under app__cc__str_N — distinct from libc.P1pp's
    462         # libc__cc__str_N.
    463         cc_log=$workdir/cc.log
    464         # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split.
    465         if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \
    466                 --lib=app__ "$src" "$client_p1pp" \
    467                 >"$cc_log" 2>&1; then
    468             fail "$label" "cc compile failed:" "$cc_log"
    469             continue
    470         fi
    471 
    472         # catm chain: entry-libc supplies :p1_main (calls __libc_init
    473         # then main), libc.P1pp supplies the libc routines, the client
    474         # supplies :main, elf-end supplies the :ELF_end terminator.
    475         p1pp_log=$workdir/p1pp.log
    476         if ! WORK_SUBPATH=tests/cc-libc/$name \
    477                 sh scripts/boot-build-p1pp.sh "$elf" \
    478                 P1/entry-libc.P1pp build/$ARCH/vendor/mes-libc/libc.P1pp \
    479                 "$client_p1pp" P1/elf-end.P1pp \
    480                 >"$p1pp_log" 2>&1; then
    481             fail "$label" "P1pp assemble failed:" "$p1pp_log"
    482             continue
    483         fi
    484 
    485         tmp=$(mktemp)
    486         if "./$elf" >"$tmp" 2>&1; then
    487             act_exit=0
    488         else
    489             act_exit=$?
    490         fi
    491         act_out=$(cat "$tmp"); rm -f "$tmp"
    492         _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit"
    493     done
    494 }
    495 
    496 ## --- cc-ext suite -------------------------------------------------------
    497 ##
    498 ## External C subset coverage via the vendored c-testsuite single-exec
    499 ## fixtures (vendor/c-testsuite/single-exec/NNNNN.c). This complements
    500 ## tests/cc, which is hand-curated; the goal here is breadth, to surface
    501 ## bugs in cc.scm against programs we did not write ourselves.
    502 ##
    503 ## Fixture spec: each program returns 0 on success and non-zero on
    504 ## failure; the .expected file pins stdout+stderr. We honest-report:
    505 ## PASS = runs + matches expected + exit 0, anything else = FAIL.
    506 ## Compile/assemble errors count as FAIL too — every regression in the
    507 ## supported subset shows up. As cc.scm grows the FAIL count drops.
    508 ##
    509 ## Pipeline switches on the .tags file: tests with `needs-libc` are
    510 ## linked through the same chain as the cc-libc suite (entry-libc +
    511 ## mes-libc + client + elf-end, with --lib=app__ to namespace string
    512 ## labels); plain tests use the bare cc -> P1pp -> ELF flow.
    513 ##
    514 ## Selection: with no name args, runs every fixture. Otherwise the args
    515 ## are basenames (e.g. 00001) under vendor/c-testsuite/single-exec/.
    516 run_cc_ext_suite() {
    517     dir=vendor/c-testsuite/single-exec
    518     [ -n "$NAMES" ] || NAMES=$(discover "$dir" c)
    519     for name in $NAMES; do
    520         src=$dir/$name.c
    521         tags=$dir/$name.c.tags
    522         expected=$dir/$name.c.expected
    523         label="[$ARCH] cc-ext/$name"
    524 
    525         [ -e "$src" ] || { echo "  SKIP $name (no .c)"; continue; }
    526 
    527         needs_libc=0
    528         if [ -e "$tags" ] && grep -q '^needs-libc$' "$tags"; then
    529             needs_libc=1
    530         fi
    531 
    532         expout=
    533         [ -e "$expected" ] && expout=$(cat "$expected")
    534         expexit=0
    535 
    536         elf=build/$ARCH/tests/cc-ext/$name
    537         workdir=build/$ARCH/.work/tests/cc-ext/$name
    538         client_p1pp=$workdir/$name.P1pp
    539         mkdir -p "$(dirname "$elf")" "$workdir"
    540 
    541         cc_log=$workdir/cc.log
    542         if [ "$needs_libc" = "1" ]; then
    543             # Lib mode: client TU compiled with --lib=app__ so it doesn't
    544             # emit its own :p1_main / :ELF_end and namespaces anonymous
    545             # string labels under app__cc__str_N (libc supplies its own).
    546             # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split.
    547             if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \
    548                     --lib=app__ "$src" "$client_p1pp" \
    549                     >"$cc_log" 2>&1; then
    550                 fail "$label" "cc compile failed:" "$cc_log"
    551                 continue
    552             fi
    553         else
    554             # shellcheck disable=SC2086 # CC_EXTRA_FLAGS is intentionally word-split.
    555             if ! build/$ARCH/scheme1/scheme1 build/$ARCH/cc/cc.scm $CC_EXTRA_FLAGS \
    556                     "$src" "$client_p1pp" \
    557                     >"$cc_log" 2>&1; then
    558                 fail "$label" "cc compile failed:" "$cc_log"
    559                 continue
    560             fi
    561         fi
    562 
    563         p1pp_log=$workdir/p1pp.log
    564         if [ "$needs_libc" = "1" ]; then
    565             # catm chain matches run_cc_libc_suite: entry-libc supplies
    566             # :p1_main (calls __libc_init then main), libc.P1pp the libc
    567             # routines, the client :main, elf-end the :ELF_end terminator.
    568             if ! WORK_SUBPATH=tests/cc-ext/$name \
    569                     sh scripts/boot-build-p1pp.sh "$elf" \
    570                     P1/entry-libc.P1pp build/$ARCH/vendor/mes-libc/libc.P1pp \
    571                     "$client_p1pp" P1/elf-end.P1pp \
    572                     >"$p1pp_log" 2>&1; then
    573                 fail "$label" "P1pp assemble failed:" "$p1pp_log"
    574                 continue
    575             fi
    576         else
    577             if ! WORK_SUBPATH=tests/cc-ext/$name \
    578                     sh scripts/boot-build-p1pp.sh "$elf" "$client_p1pp" \
    579                     >"$p1pp_log" 2>&1; then
    580                 fail "$label" "P1pp assemble failed:" "$p1pp_log"
    581                 continue
    582             fi
    583         fi
    584 
    585         tmp=$(mktemp)
    586         if "./$elf" >"$tmp" 2>&1; then
    587             act_exit=0
    588         else
    589             act_exit=$?
    590         fi
    591         act_out=$(cat "$tmp"); rm -f "$tmp"
    592         _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit"
    593     done
    594 }
    595 
    596 ## --- tcc-cc suite -------------------------------------------------------
    597 ##
    598 ## Runs the plain tests/cc fixtures through a self-built tcc. STAGE
    599 ## selects the compiler — STAGE=2 uses tcc-tcc (twice-compiled, built
    600 ## by tcc-boot2 which was itself built by cc.scm), STAGE=3 uses
    601 ## tcc-tcc-tcc (thrice-compiled, built by tcc-tcc — the README
    602 ## endpoint, the first tcc whose machine code an actual tcc emitted).
    603 ## start.o / mem.o come from the tcc-cc tree (cross-asm and
    604 ## tcc-boot2-built respectively); they don't change between stages.
    605 run_tcc_cc_suite() {
    606     case "$ARCH" in
    607         aarch64) tcc_target=ARM64;   tcc_banner='AArch64' ;;
    608         amd64)   tcc_target=X86_64;  tcc_banner='x86_64'  ;;
    609         riscv64) tcc_target=RISCV64; tcc_banner='riscv64' ;;
    610         *)
    611             echo "  FAIL [$ARCH] tcc-cc"
    612             echo "    tcc-cc supports ARCH in {aarch64, amd64, riscv64} only" >&2
    613             return
    614             ;;
    615     esac
    616 
    617     case "${STAGE:-2}" in
    618         2) tcc=build/$ARCH/tcc-tcc/tcc-tcc;         stage_tag=stage2 ;;
    619         3) tcc=build/$ARCH/tcc-tcc-tcc/tcc-tcc-tcc; stage_tag=stage3 ;;
    620         *)
    621             echo "  FAIL [$ARCH] tcc-cc"
    622             echo "    unknown STAGE='$STAGE' (expected 2 or 3)" >&2
    623             return
    624             ;;
    625     esac
    626     start=build/$ARCH/tcc-cc/start.o
    627     mem=build/$ARCH/tcc-cc/mem.o
    628     tcc_include=build/$ARCH/vendor/tcc/tcc-0.9.26-1147-gee75a10c/include
    629     # x86_64 only: __va_start / __va_arg intrinsics for variadic
    630     # functions. Other arches lower va_arg without out-of-line helpers.
    631     if [ "$ARCH" = "amd64" ]; then
    632         va_list=build/$ARCH/tcc-cc/va_list.o
    633     else
    634         va_list=
    635     fi
    636     if [ ! -x "$tcc" ]; then
    637         echo "  FAIL [$ARCH] tcc-cc"
    638         echo "    missing $tcc -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2
    639         return
    640     fi
    641     if [ ! -e "$start" ]; then
    642         echo "  FAIL [$ARCH] tcc-cc"
    643         echo "    missing $start -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2
    644         return
    645     fi
    646     if [ ! -e "$mem" ]; then
    647         echo "  FAIL [$ARCH] tcc-cc"
    648         echo "    missing $mem -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2
    649         return
    650     fi
    651     if [ -n "$va_list" ] && [ ! -e "$va_list" ]; then
    652         echo "  FAIL [$ARCH] tcc-cc"
    653         echo "    missing $va_list -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2
    654         return
    655     fi
    656     if ! "$tcc" -version 2>/dev/null | grep "$tcc_banner" >/dev/null; then
    657         echo "  FAIL [$ARCH] tcc-cc"
    658         echo "    $tcc is not a $tcc_banner-targeted tcc; rebuild with TCC_TARGET=$tcc_target" >&2
    659         return
    660     fi
    661 
    662     [ -n "$NAMES" ] || NAMES=$(discover tests/cc c)
    663     for name in $NAMES; do
    664         src=tests/cc/$name.c
    665         [ -e "$src" ] || { echo "  SKIP $name (no .c)"; continue; }
    666         if [ -e tests/cc/$name.expected ]; then
    667             expout=$(cat tests/cc/$name.expected)
    668         else
    669             expout=
    670         fi
    671         if [ -e tests/cc/$name.expected-exit ]; then
    672             expexit=$(cat tests/cc/$name.expected-exit)
    673         else
    674             expexit=0
    675         fi
    676 
    677         elf=build/$ARCH/tests/tcc-cc/$stage_tag/$name
    678         workdir=build/$ARCH/.work/tests/tcc-cc/$stage_tag/$name
    679         label="[$ARCH] tcc-cc[$stage_tag]/$name"
    680         mkdir -p "$(dirname "$elf")" "$workdir"
    681 
    682         tcc_log=$workdir/tcc.log
    683         # shellcheck disable=SC2086 # $va_list is intentionally word-split (may be empty).
    684         if ! "$tcc" -nostdlib -I "$tcc_include" \
    685                 "$start" "$mem" $va_list "$src" -o "$elf" \
    686                 >"$tcc_log" 2>&1; then
    687             fail "$label" "tcc compile/link failed:" "$tcc_log"
    688             continue
    689         fi
    690 
    691         tmp=$(mktemp)
    692         if "./$elf" >"$tmp" 2>&1; then
    693             act_exit=0
    694         else
    695             act_exit=$?
    696         fi
    697         act_out=$(cat "$tmp"); rm -f "$tmp"
    698         _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit"
    699     done
    700 }
    701 
    702 ## --- tcc-libc suite -----------------------------------------------------
    703 ##
    704 ## End-to-end "tcc as a real compiler" check, run through a self-built
    705 ## tcc. STAGE selects the compiler — STAGE=2 uses tcc-tcc (twice-
    706 ## compiled), STAGE=3 uses tcc-tcc-tcc (thrice-compiled, README
    707 ## endpoint). tcc-boot2 already compiled mes-libc into libc.o; for each
    708 ## tests/cc-libc fixture, the selected tcc compiles + links it against
    709 ##   start.o          per-arch entry stub: __libc_init then main then exit
    710 ##   sys_stubs.o      per-arch raw-syscall sys_* implementations
    711 ##   mem.o            mem* compiler-builtin runtime (memcpy/memmove/memset/memcmp)
    712 ##   libc.o           tcc-boot2-built mes-libc
    713 ## and runs the resulting ELF natively in the per-arch container.
    714 run_tcc_libc_suite() {
    715     case "$ARCH" in
    716         aarch64) tcc_target=ARM64;   tcc_banner='AArch64' ;;
    717         amd64)   tcc_target=X86_64;  tcc_banner='x86_64'  ;;
    718         riscv64) tcc_target=RISCV64; tcc_banner='riscv64' ;;
    719         *)
    720             echo "  FAIL [$ARCH] tcc-libc"
    721             echo "    tcc-libc supports ARCH in {aarch64, amd64, riscv64} only" >&2
    722             return
    723             ;;
    724     esac
    725 
    726     case "${STAGE:-2}" in
    727         2) tcc=build/$ARCH/tcc-tcc/tcc-tcc;         stage_tag=stage2 ;;
    728         3) tcc=build/$ARCH/tcc-tcc-tcc/tcc-tcc-tcc; stage_tag=stage3 ;;
    729         *)
    730             echo "  FAIL [$ARCH] tcc-libc"
    731             echo "    unknown STAGE='$STAGE' (expected 2 or 3)" >&2
    732             return
    733             ;;
    734     esac
    735     start=build/$ARCH/tcc-libc/start.o
    736     sys_stubs=build/$ARCH/tcc-libc/sys_stubs.o
    737     mem=build/$ARCH/tcc-libc/mem.o
    738     libc=build/$ARCH/tcc-libc/libc.o
    739     tcc_include=build/$ARCH/vendor/tcc/tcc-0.9.26-1147-gee75a10c/include
    740     # x86_64 only: __va_start / __va_arg intrinsics for variadic
    741     # functions. mes-libc's printf family hits this directly.
    742     if [ "$ARCH" = "amd64" ]; then
    743         va_list=build/$ARCH/tcc-cc/va_list.o
    744     else
    745         va_list=
    746     fi
    747     for f in "$tcc" "$start" "$sys_stubs" "$mem" "$libc"; do
    748         if [ ! -e "$f" ]; then
    749             echo "  FAIL [$ARCH] tcc-libc"
    750             echo "    missing $f -- run 'make test SUITE=tcc-libc ARCH=$ARCH'" >&2
    751             return
    752         fi
    753     done
    754     if [ -n "$va_list" ] && [ ! -e "$va_list" ]; then
    755         echo "  FAIL [$ARCH] tcc-libc"
    756         echo "    missing $va_list -- run 'make test SUITE=tcc-libc ARCH=$ARCH'" >&2
    757         return
    758     fi
    759     if ! "$tcc" -version 2>/dev/null | grep "$tcc_banner" >/dev/null; then
    760         echo "  FAIL [$ARCH] tcc-libc"
    761         echo "    $tcc is not a $tcc_banner-targeted tcc; rebuild with TCC_TARGET=$tcc_target" >&2
    762         return
    763     fi
    764 
    765     [ -n "$NAMES" ] || NAMES=$(discover tests/cc-libc c)
    766     for name in $NAMES; do
    767         src=tests/cc-libc/$name.c
    768         [ -e "$src" ] || { echo "  SKIP $name (no .c)"; continue; }
    769         if [ -e tests/cc-libc/$name.expected ]; then
    770             expout=$(cat tests/cc-libc/$name.expected)
    771         else
    772             expout=
    773         fi
    774         if [ -e tests/cc-libc/$name.expected-exit ]; then
    775             expexit=$(cat tests/cc-libc/$name.expected-exit)
    776         else
    777             expexit=0
    778         fi
    779 
    780         elf=build/$ARCH/tests/tcc-libc/$stage_tag/$name
    781         workdir=build/$ARCH/.work/tests/tcc-libc/$stage_tag/$name
    782         label="[$ARCH] tcc-libc[$stage_tag]/$name"
    783         mkdir -p "$(dirname "$elf")" "$workdir"
    784 
    785         tcc_log=$workdir/tcc.log
    786         # shellcheck disable=SC2086 # $va_list is intentionally word-split (may be empty).
    787         if ! "$tcc" -nostdlib -I "$tcc_include" \
    788                 "$start" "$sys_stubs" "$mem" "$libc" $va_list "$src" -o "$elf" \
    789                 >"$tcc_log" 2>&1; then
    790             fail "$label" "tcc compile/link failed:" "$tcc_log"
    791             continue
    792         fi
    793 
    794         tmp=$(mktemp)
    795         if "./$elf" >"$tmp" 2>&1; then
    796             act_exit=0
    797         else
    798             act_exit=$?
    799         fi
    800         act_out=$(cat "$tmp"); rm -f "$tmp"
    801         _cc_check "$label" "$expout" "$expexit" "$act_out" "$act_exit"
    802     done
    803 }
    804 
    805 case "$SUITE" in
    806     m1pp)     run_m1pp_suite ;;
    807     p1)       run_p1_suite ;;
    808     scheme1)  run_scheme1_suite ;;
    809     cc-util)  run_cc_util_suite ;;
    810     cc-lex)   run_cc_lex_suite ;;
    811     cc-pp)    run_cc_pp_suite ;;
    812     cc-cg)    run_cc_cg_suite ;;
    813     cc)       run_cc_suite ;;
    814     cc-libc)  run_cc_libc_suite ;;
    815     cc-ext)   run_cc_ext_suite ;;
    816     tcc-cc)   run_tcc_cc_suite ;;
    817     tcc-libc) run_tcc_libc_suite ;;
    818 esac