boot2

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

run-suite.sh (29488B)


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