kit

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

run.sh (47351B)


      1 #!/bin/sh
      2 # Driver-level behavior checks for the kit multitool CLI.
      3 
      4 set -u
      5 
      6 script_dir=$(cd "$(dirname "$0")" && pwd)
      7 repo_root=$(cd "$script_dir/../.." && pwd)
      8 
      9 KIT="${KIT:-$repo_root/build/kit}"
     10 
     11 if [ ! -x "$KIT" ]; then
     12     echo "driver: kit binary not found at $KIT" >&2
     13     exit 2
     14 fi
     15 
     16 work=$(mktemp -d "${TMPDIR:-/tmp}/kit-driver-test.XXXXXX")
     17 trap 'rm -rf "$work"' EXIT
     18 
     19 # Type-K mode-P kit: ok/run_ok/run_fail/contains/same_file/is_executable/
     20 # assert_file_exists/check_mode, all recording through the unified kit_* counters
     21 # over $work. check_mode replaces the inline stat -f/-c permission probe; the
     22 # driver-specific scenarios that need shell control flow (umask, cd, stdin
     23 # piping, runtime auto-build via nm) stay inline but route verdicts through
     24 # ok/not_ok. Mode-P suites are SERIAL — fixtures under $work are shared/mutated.
     25 KIT_KIT_DIR="$repo_root/test/lib"
     26 . "$repo_root/test/lib/kit_sh_kit.sh"
     27 kit_report_init
     28 
     29 cat > "$work/main.c" <<'SRC'
     30 int main(void) { return 0; }
     31 int _start(void) { return 0; }
     32 SRC
     33 
     34 cat > "$work/other.c" <<'SRC'
     35 int other(void) { return 0; }
     36 SRC
     37 
     38 # ---- executable permission bits (cc/ld) ----
     39 if (umask 077; "$KIT" cc "$work/main.c" -o "$work/cc-exe") \
     40     > "$work/cc.out" 2> "$work/cc.err"; then
     41     check_mode "cc-executable-mode" "$work/cc-exe" 700
     42 else
     43     not_ok "cc-executable-mode" "$work/cc.err"
     44 fi
     45 
     46 rm -f "$work/a.out"
     47 if (cd "$work" && umask 077 && "$KIT" cc main.c) \
     48     > "$work/cc-link-default.out" 2> "$work/cc-link-default.err"; then
     49     if [ -f "$work/a.out" ]; then
     50         check_mode "cc-link-default-output" "$work/a.out" 700
     51     else
     52         echo "a.out not created" > "$work/cc-link-default.diag"
     53         not_ok "cc-link-default-output" "$work/cc-link-default.diag"
     54     fi
     55 else
     56     not_ok "cc-link-default-output" "$work/cc-link-default.err"
     57 fi
     58 
     59 if (umask 077; "$KIT" cc -c "$work/main.c" -o "$work/main.o") \
     60     > "$work/cc-c.out" 2> "$work/cc-c.err"; then
     61     : > "$work/ld-exe"
     62     chmod 0644 "$work/ld-exe"
     63     if (umask 077; "$KIT" ld "$work/main.o" -o "$work/ld-exe") \
     64         > "$work/ld.out" 2> "$work/ld.err"; then
     65         check_mode "ld-executable-mode" "$work/ld-exe" 700
     66     else
     67         not_ok "ld-executable-mode" "$work/ld.err"
     68     fi
     69 else
     70     not_ok "ld-executable-mode" "$work/cc-c.err"
     71 fi
     72 
     73 # ---- stdin piping: -x asm/s/asm-cpp/S → object ----
     74 for xlang in asm s asm-cpp S; do
     75     if printf '.globl asm_stdin\nasm_stdin:\n  ret\n' |
     76             "$KIT" cc -target x86_64-linux -x "$xlang" -c - \
     77                 -o "$work/stdin-$xlang.o" \
     78                 > "$work/stdin-$xlang.out" 2> "$work/stdin-$xlang.err" &&
     79        [ -f "$work/stdin-$xlang.o" ]; then
     80         ok "cc-stdin-x-$xlang"
     81     else
     82         not_ok "cc-stdin-x-$xlang" "$work/stdin-$xlang.err"
     83     fi
     84 done
     85 
     86 # ---- stdin piping: -x wasm/wat → emitted C ----
     87 for xlang in wasm wat; do
     88     if printf '(module (func (export "test_main") (result i32) i32.const 0))\n' |
     89             "$KIT" cc --emit=c -x "$xlang" - \
     90                 -o "$work/stdin-$xlang.c" \
     91                 > "$work/stdin-$xlang.out" 2> "$work/stdin-$xlang.err" &&
     92        [ -s "$work/stdin-$xlang.c" ]; then
     93         ok "cc-stdin-x-$xlang"
     94     else
     95         not_ok "cc-stdin-x-$xlang" "$work/stdin-$xlang.err"
     96     fi
     97 done
     98 
     99 # ---- ld -r partial link: output mode + relinkability ----
    100 cat > "$work/partial-main.c" <<'SRC'
    101 int foo(void);
    102 int _start(void) { return foo(); }
    103 SRC
    104 cat > "$work/partial-foo.c" <<'SRC'
    105 int foo(void) { return 0; }
    106 SRC
    107 
    108 if "$KIT" cc -target x86_64-linux -c "$work/partial-main.c" \
    109         -o "$work/partial-main.o" > "$work/partial-main.out" \
    110         2> "$work/partial-main.err" &&
    111    "$KIT" cc -target x86_64-linux -c "$work/partial-foo.c" \
    112         -o "$work/partial-foo.o" > "$work/partial-foo.out" \
    113         2> "$work/partial-foo.err"; then
    114     : > "$work/partial.o"
    115     chmod 0644 "$work/partial.o"
    116     if (umask 077; "$KIT" ld -r "$work/partial-main.o" \
    117             "$work/partial-foo.o" -o "$work/partial.o") \
    118         > "$work/ld-r.out" 2> "$work/ld-r.err"; then
    119         check_mode "ld-r-output-mode" "$work/partial.o" 644
    120         if "$KIT" ld "$work/partial.o" -o "$work/partial-exe" \
    121             > "$work/ld-r-final.out" 2> "$work/ld-r-final.err"; then
    122             ok "ld-r-final-link"
    123         else
    124             not_ok "ld-r-final-link" "$work/ld-r-final.err"
    125         fi
    126     else
    127         not_ok "ld-r-output-mode" "$work/ld-r.err"
    128     fi
    129 else
    130     { sed 's/^/main: /' "$work/partial-main.err"
    131       sed 's/^/foo:  /' "$work/partial-foo.err"; } > "$work/ld-r-setup.diag"
    132     not_ok "ld-r-output-mode" "$work/ld-r-setup.diag"
    133 fi
    134 
    135 # ---- default output names (cc -c) ----
    136 rm -f "$work/main.o"
    137 if (cd "$work" && "$KIT" cc -c main.c) \
    138     > "$work/cc-c-default.out" 2> "$work/cc-c-default.err"; then
    139     if [ -f "$work/main.o" ]; then
    140         ok "cc-c-default-output"
    141     else
    142         echo "main.o not created" > "$work/cc-c-default.diag"
    143         not_ok "cc-c-default-output" "$work/cc-c-default.diag"
    144     fi
    145 else
    146     not_ok "cc-c-default-output" "$work/cc-c-default.err"
    147 fi
    148 
    149 rm -f "$work/main.o" "$work/other.o"
    150 if (cd "$work" && "$KIT" cc -c main.c other.c) \
    151     > "$work/cc-c-multi.out" 2> "$work/cc-c-multi.err"; then
    152     if [ -f "$work/main.o" ] && [ -f "$work/other.o" ]; then
    153         ok "cc-c-multi-default-output"
    154     else
    155         echo "expected main.o and other.o" > "$work/cc-c-multi.diag"
    156         not_ok "cc-c-multi-default-output" "$work/cc-c-multi.diag"
    157     fi
    158 else
    159     not_ok "cc-c-multi-default-output" "$work/cc-c-multi.err"
    160 fi
    161 
    162 # ---- cc -E → stdout ----
    163 if "$KIT" cc -E "$work/main.c" > "$work/cc-E-default.out" 2> "$work/cc-E-default.err"; then
    164     if [ -s "$work/cc-E-default.out" ]; then
    165         ok "cc-E-default-stdout"
    166     else
    167         echo "stdout was empty" > "$work/cc-E-default.diag"
    168         not_ok "cc-E-default-stdout" "$work/cc-E-default.diag"
    169     fi
    170 else
    171     not_ok "cc-E-default-stdout" "$work/cc-E-default.err"
    172 fi
    173 
    174 # ---- cc -dumpmachine probe ----
    175 if "$KIT" cc -target riscv64-linux -dumpmachine \
    176     > "$work/cc-dumpmachine.out" 2> "$work/cc-dumpmachine.err" &&
    177    [ "$(cat "$work/cc-dumpmachine.out")" = "riscv64-linux" ]; then
    178     ok "cc-dumpmachine-probe"
    179 else
    180     not_ok "cc-dumpmachine-probe" "$work/cc-dumpmachine.err"
    181 fi
    182 
    183 # ---- cc -print-file-name probe ----
    184 if "$KIT" cc -print-file-name=crt1.o \
    185     > "$work/cc-print-file-name.out" 2> "$work/cc-print-file-name.err" &&
    186    [ "$(cat "$work/cc-print-file-name.out")" = "crt1.o" ]; then
    187     ok "cc-print-file-name-probe"
    188 else
    189     not_ok "cc-print-file-name-probe" "$work/cc-print-file-name.err"
    190 fi
    191 
    192 # ---- cc -print-resource-dir is non-empty (the freestanding header root) ----
    193 if "$KIT" cc -print-resource-dir \
    194     > "$work/cc-resdir.out" 2> "$work/cc-resdir.err" &&
    195    [ -s "$work/cc-resdir.out" ]; then
    196     ok "cc-print-resource-dir"
    197 else
    198     { echo "expected a non-empty resource dir"; cat "$work/cc-resdir.err"; } \
    199         > "$work/cc-resdir.diag"
    200     not_ok "cc-print-resource-dir" "$work/cc-resdir.diag"
    201 fi
    202 
    203 # ---- cc -print-search-dirs surfaces the hosted sysroot dirs ----
    204 # KIT_SYSROOT + a cross target makes the output deterministic on any host
    205 # (independent of a native SDK). The Linux expansion must surface <sysroot>/lib,
    206 # <sysroot>/include, and the arch multiarch include subdir.
    207 if KIT_SYSROOT="$work/sr" "$KIT" cc -print-search-dirs -lc -target x86_64-linux \
    208     > "$work/cc-searchdirs.out" 2> "$work/cc-searchdirs.err" &&
    209    grep -q "$work/sr/lib" "$work/cc-searchdirs.out" &&
    210    grep -q "$work/sr/include" "$work/cc-searchdirs.out" &&
    211    grep -q "x86_64-linux-gnu" "$work/cc-searchdirs.out"; then
    212     ok "cc-print-search-dirs-hosted"
    213 else
    214     cp "$work/cc-searchdirs.out" "$work/cc-searchdirs.diag"
    215     not_ok "cc-print-search-dirs-hosted" "$work/cc-searchdirs.diag"
    216 fi
    217 
    218 # ---- cc -print-sysroot echoes the effective explicit sysroot ----
    219 if [ "$("$KIT" cc -print-sysroot --sysroot /opt/sdkx 2>/dev/null)" = "/opt/sdkx" ] &&
    220    [ "$(KIT_SYSROOT=/opt/sdky "$KIT" cc -print-sysroot 2>/dev/null)" = "/opt/sdky" ]; then
    221     ok "cc-print-sysroot"
    222 else
    223     "$KIT" cc -print-sysroot --sysroot /opt/sdkx > "$work/cc-psysroot.diag" 2>&1
    224     not_ok "cc-print-sysroot" "$work/cc-psysroot.diag"
    225 fi
    226 
    227 # ---- accepted-but-ignored compatibility flags are silently accepted ----
    228 if "$KIT" cc -Wall -Wextra -std=c11 -ffreestanding -c "$work/main.c" \
    229     -o "$work/ignored.o" > "$work/cc-ignored.out" 2> "$work/cc-ignored.err" &&
    230    [ -f "$work/ignored.o" ] && [ ! -s "$work/cc-ignored.err" ]; then
    231     ok "cc-ignored-compat-flags"
    232 else
    233     cp "$work/cc-ignored.err" "$work/cc-ignored.diag"
    234     not_ok "cc-ignored-compat-flags" "$work/cc-ignored.diag"
    235 fi
    236 
    237 # ---- --output= long form (cc) ----
    238 if "$KIT" cc -c "$work/main.c" --output="$work/cc-long-output.o" \
    239     > "$work/cc-long-output.out" 2> "$work/cc-long-output.err" &&
    240    [ -f "$work/cc-long-output.o" ]; then
    241     ok "cc-long-output"
    242 else
    243     not_ok "cc-long-output" "$work/cc-long-output.err"
    244 fi
    245 
    246 # ---- -iquote include path ----
    247 mkdir -p "$work/quote-inc"
    248 cat > "$work/quote-inc/q.h" <<'SRC'
    249 #define Q_VALUE 7
    250 SRC
    251 cat > "$work/quote-include.c" <<'SRC'
    252 #include "q.h"
    253 int q(void) { return Q_VALUE; }
    254 SRC
    255 run_ok "cc-iquote-include" "$KIT" cc -c "$work/quote-include.c" \
    256     -iquote "$work/quote-inc" -o "$work/quote-include.o"
    257 
    258 # ---- implicit freestanding headers ----
    259 cat > "$work/implicit-header.c" <<'SRC'
    260 #include <stddef.h>
    261 #include <stdint.h>
    262 int f(void) { return (int)sizeof(size_t) + (int)UINT8_MAX; }
    263 SRC
    264 run_ok "cc-implicit-freestanding-headers" "$KIT" cc -target aarch64-linux \
    265     -c "$work/implicit-header.c" -o "$work/implicit-header.o"
    266 
    267 # ---- runtime auto-build + link via nm (aarch64) ----
    268 mkdir -p "$work/rt-support/rt"
    269 cp -R "$repo_root/rt/include" "$work/rt-support/rt/include"
    270 cp -R "$repo_root/rt/lib" "$work/rt-support/rt/lib"
    271 cat > "$work/rt-div.c" <<'SRC'
    272 #include <stdint.h>
    273 typedef unsigned __int128 u128;
    274 u128 div128(u128 a, u128 b) { return a / b; }
    275 void _start(void) {
    276     volatile u128 x = div128((u128)9, (u128)3);
    277     (void)x;
    278     for (;;) {}
    279 }
    280 SRC
    281 # Verify the runtime was auto-built AND linked by checking the linked
    282 # executable actually defines the runtime symbol the source needs
    283 # (__udivti3 for the u128 division). This is location-independent: the rt
    284 # archive is cached under DriverEnv's cache_dir, not the support-dir.
    285 if "$KIT" cc --support-dir "$work/rt-support" -target aarch64-linux \
    286     -e _start "$work/rt-div.c" -o "$work/rt-div" \
    287     > "$work/rt-div.out" 2> "$work/rt-div.err" &&
    288    "$KIT" nm "$work/rt-div" 2> "$work/rt-div-nm.err" \
    289     | grep -qE '[Tt] __udivti3'; then
    290     ok "cc-auto-builds-and-links-libkit-rt"
    291 else
    292     not_ok "cc-auto-builds-and-links-libkit-rt" "$work/rt-div.err"
    293 fi
    294 
    295 cat > "$work/rt-x64-start.c" <<'SRC'
    296 extern int test_main(void);
    297 void _start(void) {
    298     volatile int rc = test_main();
    299     (void)rc;
    300     for (;;) {}
    301 }
    302 SRC
    303 # freestanding_lib.c uses vsnprintf etc., so a defined `vsnprintf` in the
    304 # linked image proves the runtime's printf.c (a libc source, not just
    305 # compiler-rt) was auto-built and linked — what the old `ar t | grep printf.c`
    306 # member check verified, but location-independent of the rt cache dir.
    307 # The rt's libc symbols are weak (a user libc may override them), so accept a
    308 # weak (W/w) definition as well as a strong (T/t) one.
    309 if "$KIT" cc --support-dir "$work/rt-support" -target x86_64-linux \
    310     -e _start "$repo_root/test/rt/cases/freestanding_lib.c" \
    311     "$work/rt-x64-start.c" \
    312     -o "$work/rt-x64" > "$work/rt-x64.out" 2> "$work/rt-x64.err" &&
    313    "$KIT" nm "$work/rt-x64" 2> "$work/rt-x64-nm.err" \
    314     | grep -qE '[TtWw] vsnprintf'; then
    315     ok "cc-auto-builds-and-links-libkit-rt-x64"
    316 else
    317     { sed 's/^/cc: /' "$work/rt-x64.err"
    318       sed 's/^/nm: /' "$work/rt-x64-nm.err" 2>/dev/null; } > "$work/rt-x64.diag"
    319     not_ok "cc-auto-builds-and-links-libkit-rt-x64" "$work/rt-x64.diag"
    320 fi
    321 
    322 # ---- ld auto-builds + links the runtime (rt archive cached under support-dir) ----
    323 mkdir -p "$work/ld-rt-support/rt"
    324 cp -R "$repo_root/rt/include" "$work/ld-rt-support/rt/include"
    325 cp -R "$repo_root/rt/lib" "$work/ld-rt-support/rt/lib"
    326 if "$KIT" cc --support-dir "$work/ld-rt-support" -target aarch64-linux \
    327     -c "$work/rt-div.c" -o "$work/ld-rt-div.o" \
    328     > "$work/ld-rt-div-cc.out" 2> "$work/ld-rt-div-cc.err" &&
    329    "$KIT" ld --support-dir "$work/ld-rt-support" \
    330     -e _start "$work/ld-rt-div.o" -o "$work/ld-rt-div" \
    331     > "$work/ld-rt-div.out" 2> "$work/ld-rt-div.err" &&
    332    [ -f "$work/ld-rt-support/build/rt/aarch64-linux/libkit_rt.a" ]; then
    333     ok "ld-auto-builds-and-links-libkit-rt"
    334 else
    335     { sed 's/^/cc: /' "$work/ld-rt-div-cc.err"
    336       sed 's/^/ld: /' "$work/ld-rt-div.err" 2>/dev/null; } > "$work/ld-rt.diag"
    337     not_ok "ld-auto-builds-and-links-libkit-rt" "$work/ld-rt.diag"
    338 fi
    339 
    340 # ---- --output= long form (ld) ----
    341 if "$KIT" ld "$work/main.o" --output="$work/ld-long-output" \
    342     > "$work/ld-long-output.out" 2> "$work/ld-long-output.err" &&
    343    [ -f "$work/ld-long-output" ]; then
    344     ok "ld-long-output"
    345 else
    346     not_ok "ld-long-output" "$work/ld-long-output.err"
    347 fi
    348 
    349 # ---- ld --no-undefined rejects unresolved refs in a shared object ----
    350 cat > "$work/shared-undef.c" <<'SRC'
    351 int missing(void);
    352 int f(void) { return missing(); }
    353 SRC
    354 if "$KIT" cc -target x86_64-linux -fPIC -c "$work/shared-undef.c" \
    355     -o "$work/shared-undef.o" > "$work/shared-undef-cc.out" \
    356     2> "$work/shared-undef-cc.err"; then
    357     run_fail "ld-no-undefined" "$KIT" ld -shared --no-undefined \
    358         "$work/shared-undef.o" -o "$work/shared-undef.so"
    359 else
    360     not_ok "ld-no-undefined" "$work/shared-undef-cc.err"
    361 fi
    362 
    363 # ---- ld -lc expands hosted CRT/libc from --sysroot ----
    364 mkdir -p "$work/ld-hosted-sr/lib" "$work/ld-hosted-sr/include"
    365 cat > "$work/ld-hosted-main.c" <<'SRC'
    366 int main(void) { return 0; }
    367 SRC
    368 cat > "$work/ld-hosted-crt.c" <<'SRC'
    369 extern int main(void);
    370 void _start(void) { (void)main(); for (;;) {} }
    371 SRC
    372 cat > "$work/ld-hosted-crti.c" <<'SRC'
    373 void __kit_fake_crti(void) {}
    374 SRC
    375 cat > "$work/ld-hosted-crtn.c" <<'SRC'
    376 void __kit_fake_crtn(void) {}
    377 SRC
    378 cat > "$work/ld-hosted-libc.c" <<'SRC'
    379 int libc_marker(void) { return 7; }
    380 SRC
    381 if "$KIT" cc -target x86_64-linux -fPIE -c "$work/ld-hosted-main.c" \
    382         -o "$work/ld-hosted-main.o" > "$work/ld-hosted-main.out" \
    383         2> "$work/ld-hosted-main.err" &&
    384    "$KIT" cc -target x86_64-linux -fPIE -c "$work/ld-hosted-crt.c" \
    385         -o "$work/ld-hosted-sr/lib/Scrt1.o" > "$work/ld-hosted-scrt.out" \
    386         2> "$work/ld-hosted-scrt.err" &&
    387    "$KIT" cc -target x86_64-linux -fno-PIC -c "$work/ld-hosted-crt.c" \
    388         -o "$work/ld-hosted-sr/lib/crt1.o" > "$work/ld-hosted-crt1.out" \
    389         2> "$work/ld-hosted-crt1.err" &&
    390    "$KIT" cc -target x86_64-linux -c "$work/ld-hosted-crti.c" \
    391         -o "$work/ld-hosted-sr/lib/crti.o" > "$work/ld-hosted-crti.out" \
    392         2> "$work/ld-hosted-crti.err" &&
    393    "$KIT" cc -target x86_64-linux -c "$work/ld-hosted-crtn.c" \
    394         -o "$work/ld-hosted-sr/lib/crtn.o" > "$work/ld-hosted-crtn.out" \
    395         2> "$work/ld-hosted-crtn.err" &&
    396    "$KIT" cc -target x86_64-linux -fPIC -c "$work/ld-hosted-libc.c" \
    397         -o "$work/ld-hosted-libc-pic.o" > "$work/ld-hosted-libc-pic.out" \
    398         2> "$work/ld-hosted-libc-pic.err" &&
    399    "$KIT" ld -shared -nostdlib -e libc_marker "$work/ld-hosted-libc-pic.o" \
    400         -o "$work/ld-hosted-sr/lib/libc.so" > "$work/ld-hosted-so.out" \
    401         2> "$work/ld-hosted-so.err" &&
    402    "$KIT" cc -target x86_64-linux -fno-PIC -c "$work/ld-hosted-libc.c" \
    403         -o "$work/ld-hosted-libc-static.o" > "$work/ld-hosted-libc-static.out" \
    404         2> "$work/ld-hosted-libc-static.err" &&
    405    "$KIT" ar rc "$work/ld-hosted-sr/lib/libc.a" \
    406         "$work/ld-hosted-libc-static.o" > "$work/ld-hosted-ar.out" \
    407         2> "$work/ld-hosted-ar.err"; then
    408     if "$KIT" ld --support-dir "$work/ld-rt-support" \
    409             --sysroot "$work/ld-hosted-sr" -pie -lc \
    410             "$work/ld-hosted-main.o" -o "$work/ld-hosted" \
    411             > "$work/ld-hosted.out" 2> "$work/ld-hosted.err" &&
    412        "$KIT" objdump -p "$work/ld-hosted" > "$work/ld-hosted-p.out" \
    413             2> "$work/ld-hosted-p.err" &&
    414        grep -q "interpreter  /lib/ld-musl-x86_64.so.1" \
    415             "$work/ld-hosted-p.out" &&
    416        grep -q "NEEDED       libc.so" "$work/ld-hosted-p.out"; then
    417         ok "ld-hosted-lc-sysroot"
    418     else
    419         { sed 's/^/ld: /' "$work/ld-hosted.err" 2>/dev/null
    420           sed 's/^/dump: /' "$work/ld-hosted-p.err" 2>/dev/null
    421           sed 's/^/    | /' "$work/ld-hosted-p.out" 2>/dev/null; } \
    422             > "$work/ld-hosted.diag"
    423         not_ok "ld-hosted-lc-sysroot" "$work/ld-hosted.diag"
    424     fi
    425 else
    426     { sed 's/^/main: /' "$work/ld-hosted-main.err" 2>/dev/null
    427       sed 's/^/scrt: /' "$work/ld-hosted-scrt.err" 2>/dev/null
    428       sed 's/^/crt1: /' "$work/ld-hosted-crt1.err" 2>/dev/null
    429       sed 's/^/crti: /' "$work/ld-hosted-crti.err" 2>/dev/null
    430       sed 's/^/crtn: /' "$work/ld-hosted-crtn.err" 2>/dev/null
    431       sed 's/^/so:   /' "$work/ld-hosted-so.err" 2>/dev/null
    432       sed 's/^/ar:   /' "$work/ld-hosted-ar.err" 2>/dev/null; } \
    433         > "$work/ld-hosted-setup.diag"
    434     not_ok "ld-hosted-lc-sysroot" "$work/ld-hosted-setup.diag"
    435 fi
    436 
    437 # ---- ld PIE without dynamic deps has no INTERP/DYNAMIC program headers ----
    438 cat > "$work/ld-static-pie.c" <<'SRC'
    439 void _start(void) { for (;;) {} }
    440 SRC
    441 if "$KIT" cc -target x86_64-linux -fPIE -c "$work/ld-static-pie.c" \
    442         -o "$work/ld-static-pie.o" > "$work/ld-static-pie-cc.out" \
    443         2> "$work/ld-static-pie-cc.err" &&
    444    "$KIT" ld -pie -nostdlib "$work/ld-static-pie.o" \
    445         -o "$work/ld-static-pie" > "$work/ld-static-pie.out" \
    446         2> "$work/ld-static-pie.err" &&
    447    "$KIT" objdump -p "$work/ld-static-pie" \
    448         > "$work/ld-static-pie-p.out" 2> "$work/ld-static-pie-p.err"; then
    449     if ! grep -q "interpreter  " "$work/ld-static-pie-p.out" &&
    450        ! grep -q "^  DYNAMIC" "$work/ld-static-pie-p.out" &&
    451        ! grep -q "NEEDED" "$work/ld-static-pie-p.out"; then
    452         ok "ld-static-pie-no-dynamic-headers"
    453     else
    454         sed 's/^/    | /' "$work/ld-static-pie-p.out" \
    455             > "$work/ld-static-pie.diag"
    456         not_ok "ld-static-pie-no-dynamic-headers" \
    457             "$work/ld-static-pie.diag"
    458     fi
    459 else
    460     { sed 's/^/cc: /' "$work/ld-static-pie-cc.err" 2>/dev/null
    461       sed 's/^/ld: /' "$work/ld-static-pie.err" 2>/dev/null
    462       sed 's/^/dump: /' "$work/ld-static-pie-p.err" 2>/dev/null; } \
    463         > "$work/ld-static-pie-setup.diag"
    464     not_ok "ld-static-pie-no-dynamic-headers" \
    465         "$work/ld-static-pie-setup.diag"
    466 fi
    467 
    468 # ---- ld -no-pie cancels an earlier -pie and emits ET_EXEC ----
    469 if "$KIT" ld -pie -no-pie -nostdlib "$work/ld-static-pie.o" \
    470         -o "$work/ld-no-pie" > "$work/ld-no-pie.out" \
    471         2> "$work/ld-no-pie.err"; then
    472     e_type=$(od -An -tx1 -j 16 -N 2 "$work/ld-no-pie" | tr -d ' \n')
    473     if [ "$e_type" = "0200" ]; then
    474         ok "ld-no-pie-cancels-pie"
    475     else
    476         printf 'e_type bytes=%s want=0200\n' "$e_type" \
    477             > "$work/ld-no-pie.diag"
    478         not_ok "ld-no-pie-cancels-pie" "$work/ld-no-pie.diag"
    479     fi
    480 else
    481     not_ok "ld-no-pie-cancels-pie" "$work/ld-no-pie.err"
    482 fi
    483 
    484 # ---- -no-pie cancels an earlier -pie and emits ET_EXEC ----
    485 cat > "$work/cc-no-pie.c" <<'SRC'
    486 void _start(void) { for (;;) {} }
    487 SRC
    488 if "$KIT" cc -target x86_64-linux -nostdlib -pie -no-pie \
    489         "$work/cc-no-pie.c" -o "$work/cc-no-pie" \
    490         > "$work/cc-no-pie.out" 2> "$work/cc-no-pie.err"; then
    491     e_type=$(od -An -tx1 -j 16 -N 2 "$work/cc-no-pie" | tr -d ' \n')
    492     if [ "$e_type" = "0200" ]; then
    493         ok "cc-no-pie-cancels-pie"
    494     else
    495         printf 'e_type bytes=%s want=0200\n' "$e_type" \
    496             > "$work/cc-no-pie.diag"
    497         not_ok "cc-no-pie-cancels-pie" "$work/cc-no-pie.diag"
    498     fi
    499 else
    500     not_ok "cc-no-pie-cancels-pie" "$work/cc-no-pie.err"
    501 fi
    502 
    503 # ---- objdump -x aggregate (sections + symbol table) ----
    504 if "$KIT" objdump -x "$work/main.o" \
    505     > "$work/objdump-x.out" 2> "$work/objdump-x.err" &&
    506    grep -q "Sections:" "$work/objdump-x.out" &&
    507    grep -q "SYMBOL TABLE:" "$work/objdump-x.out"; then
    508     ok "objdump-x-aggregate"
    509 else
    510     not_ok "objdump-x-aggregate" "$work/objdump-x.err"
    511 fi
    512 
    513 # ---- Mach-O -ffunction-sections atom coalescing ----
    514 {
    515     printf 'int live(void) { return 0; }\n'
    516     i=0
    517     while [ "$i" -lt 300 ]; do
    518         printf 'int f%s(void) { return %s; }\n' "$i" "$i"
    519         i=$((i + 1))
    520     done
    521 } > "$work/macho-many.c"
    522 if "$KIT" cc -target arm64-apple-macos -O1 -ffunction-sections \
    523         -c "$work/macho-many.c" -o "$work/macho-many.o" \
    524         > "$work/macho-many-cc.out" 2> "$work/macho-many-cc.err" &&
    525    "$KIT" objdump -h -t "$work/macho-many.o" \
    526         > "$work/macho-many-dump.out" 2> "$work/macho-many-dump.err"; then
    527     macho_flags=$(od -An -tx4 -j 24 -N 4 "$work/macho-many.o" 2>/dev/null |
    528         tr -d '[:space:]')
    529     macho_sec_count=$(awk '
    530         /^SYMBOL TABLE:/ { in_sec = 0 }
    531         in_sec && /^ *[0-9]+ / { n++ }
    532         /^Sections:/ { in_sec = 1 }
    533         END { print n + 0 }
    534     ' "$work/macho-many-dump.out")
    535     if [ "$macho_sec_count" -le 8 ] &&
    536        ! grep -q '\*UND\*' "$work/macho-many-dump.out" &&
    537        [ "$macho_flags" = "00002000" ]; then
    538         ok "macho-function-sections-atoms"
    539     else
    540         { printf 'sections=%s flags=%s; unexpected UND/flags/fanout\n' \
    541             "$macho_sec_count" "$macho_flags"
    542           sed 's/^/    | /' "$work/macho-many-dump.out"; } \
    543             > "$work/macho-many.diag"
    544         not_ok "macho-function-sections-atoms" "$work/macho-many.diag"
    545     fi
    546 else
    547     { sed 's/^/cc:   /' "$work/macho-many-cc.err"
    548       sed 's/^/dump: /' "$work/macho-many-dump.err"; } > "$work/macho-many-setup.diag"
    549     not_ok "macho-function-sections-atoms" "$work/macho-many-setup.diag"
    550 fi
    551 
    552 # ---- run: JIT compile a source + archive on demand, exit status is the result ----
    553 cat > "$work/run-main.c" <<'SRC'
    554 int add42(int);
    555 int main(void) { return add42(0); }
    556 SRC
    557 cat > "$work/run-lib.c" <<'SRC'
    558 int add42(int x) { return x + 42; }
    559 SRC
    560 
    561 if "$KIT" cc -I"$repo_root/rt/include" \
    562         -c "$work/run-lib.c" -o "$work/run-lib.o" \
    563         > "$work/run-lib.out" 2> "$work/run-lib.err" &&
    564    "$KIT" ar rcs "$work/librun.a" "$work/run-lib.o" \
    565         > "$work/run-ar.out" 2> "$work/run-ar.err"; then
    566     "$KIT" run -I"$repo_root/rt/include" \
    567         "$work/run-main.c" "$work/librun.a" \
    568         > "$work/run-archive.out" 2> "$work/run-archive.err"
    569     run_status=$?
    570     if [ "$run_status" -eq 42 ]; then
    571         ok "run-source-archive-demand"
    572     else
    573         { printf 'status %s, want 42\n' "$run_status"
    574           sed 's/^/    | /' "$work/run-archive.err"; } > "$work/run-archive.diag"
    575         not_ok "run-source-archive-demand" "$work/run-archive.diag"
    576     fi
    577 else
    578     { sed 's/^/lib: /' "$work/run-lib.err"
    579       sed 's/^/ar:  /' "$work/run-ar.err"; } > "$work/run-setup.diag"
    580     not_ok "run-source-archive-demand" "$work/run-setup.diag"
    581 fi
    582 
    583 # ---- run --script: #! shebang interpreter, argv passthrough, implicit -lc ----
    584 # Make a .c file executable with a `#!` line and run it directly. The kernel
    585 # launches the interpreter and appends the script path + the user's args, so
    586 # `--script` names the sole source and routes everything after it to the
    587 # program's argv. `--script` implies -lc; under the JIT that only needs a libc
    588 # sysroot for #include resolution, so probe for a usable one and skip if none.
    589 shebang_sysroot=""
    590 if command -v xcrun >/dev/null 2>&1; then
    591     shebang_sysroot="$(xcrun --show-sdk-path 2>/dev/null || true)"
    592 fi
    593 shebang_sysroot="${KIT_TEST_SYSROOT:-$shebang_sysroot}"
    594 
    595 cat > "$work/shebang-probe.c" <<'SRC'
    596 #include <stdio.h>
    597 int main(void) { return 0; }
    598 SRC
    599 shebang_ok=0
    600 if [ -n "$shebang_sysroot" ] &&
    601    "$KIT" run --sysroot "$shebang_sysroot" --script "$work/shebang-probe.c" \
    602        > "$work/shebang-probe.out" 2> "$work/shebang-probe.err"; then
    603     shebang_ok=1
    604 fi
    605 
    606 if [ "$shebang_ok" -eq 1 ]; then
    607     cat > "$work/greet.c" <<SHEBANG
    608 #!/usr/bin/env -S $KIT run --sysroot $shebang_sysroot --script
    609 #include <stdio.h>
    610 #include <stdlib.h>
    611 int main(int argc, char** argv) {
    612   if (argc < 2) { fprintf(stderr, "usage: greet N\n"); return 2; }
    613   printf("greet:%d\n", atoi(argv[1]) + 1);
    614   return 0;
    615 }
    616 SHEBANG
    617     chmod +x "$work/greet.c"
    618 
    619     # Execute the C file directly. The arg "41" reaches the program (not
    620     # `kit run`); -lc is implied so <stdio.h>/<stdlib.h> resolve.
    621     if "$work/greet.c" 41 > "$work/greet.out" 2> "$work/greet.err"; then
    622         contains "run-shebang-arg" "$work/greet.out" "greet:42"
    623     else
    624         not_ok "run-shebang-arg" "$work/greet.err"
    625     fi
    626 
    627     # A flag-shaped program arg after the script must pass through to the
    628     # program, not be parsed as a `kit run` option. atoi("-5")+1 = -4.
    629     "$work/greet.c" -5 > "$work/greet-flag.out" 2> "$work/greet-flag.err"
    630     greet_flag_rc=$?
    631     if [ "$greet_flag_rc" -eq 0 ] && grep -q "greet:-4" "$work/greet-flag.out"; then
    632         ok "run-shebang-flaglike-arg"
    633     else
    634         { printf 'rc=%s\n' "$greet_flag_rc"
    635           sed 's/^/out: /' "$work/greet-flag.out"
    636           sed 's/^/err: /' "$work/greet-flag.err"; } > "$work/greet-flag.diag"
    637         not_ok "run-shebang-flaglike-arg" "$work/greet-flag.diag"
    638     fi
    639 else
    640     skip_test "run-shebang-arg" "no usable libc sysroot (set KIT_TEST_SYSROOT)"
    641     skip_test "run-shebang-flaglike-arg" "no usable libc sysroot (set KIT_TEST_SYSROOT)"
    642 fi
    643 
    644 # ---- hosted sysroot defaulting: --sysroot is not always required ----------
    645 # When hosted libc is engaged (-lc) but no --sysroot is given, the sysroot is
    646 # resolved from KIT_SYSROOT (the single, portable env override — any target) or
    647 # an on-disk host probe (auto-discovery; native target only, never cross).
    648 cat > "$work/hosted-min.c" <<'SRC'
    649 int main(void) { return 0; }
    650 SRC
    651 cat > "$work/hosted-hello.c" <<'SRC'
    652 #include <stdio.h>
    653 int main(void) { puts("hosted-autoprobe"); return 0; }
    654 SRC
    655 
    656 # Tier 1 — KIT_SYSROOT supplies the sysroot. A Darwin cross-target with -c needs
    657 # only the profile to resolve (defines/includes, no link inputs), so a bare
    658 # existing directory suffices and this runs on every host.
    659 mkdir -p "$work/fake-sdk/usr/include"
    660 if KIT_SYSROOT="$work/fake-sdk" "$KIT" cc -c -target arm64-macos -lc \
    661        "$work/hosted-min.c" -o "$work/hosted-kitsysroot.o" \
    662        > "$work/hosted-kitsysroot.out" 2> "$work/hosted-kitsysroot.err"; then
    663     ok "cc-hosted-kit-sysroot-env"
    664 else
    665     not_ok "cc-hosted-kit-sysroot-env" "$work/hosted-kitsysroot.err"
    666 fi
    667 
    668 # The host probe must NOT fire for a cross-compile: a foreign-OS target with no
    669 # --sysroot/KIT_SYSROOT must still error rather than borrow the host's libc.
    670 # Pick a target whose OS differs from the host so the native-only gate always
    671 # blocks the probe; scrub the env override so only an (incorrect) auto-probe
    672 # could apply.
    673 case "$(uname -s)" in
    674   Linux) cross_target="arm64-macos" ;;   # macOS is never the Linux host's OS
    675   *)     cross_target="x86_64-linux" ;;  # Linux is never the macOS/other host's OS
    676 esac
    677 if env -u KIT_SYSROOT "$KIT" cc -c -target "$cross_target" -lc \
    678        "$work/hosted-min.c" -o "$work/hosted-cross.o" \
    679        > "$work/hosted-cross.out" 2> "$work/hosted-cross.err"; then
    680     { echo "cross-compile unexpectedly resolved a sysroot via auto-probe"
    681       sed 's/^/out: /' "$work/hosted-cross.out"; } > "$work/hosted-cross.diag"
    682     not_ok "cc-hosted-cross-no-autoprobe" "$work/hosted-cross.diag"
    683 else
    684     ok "cc-hosted-cross-no-autoprobe"
    685 fi
    686 
    687 # Tier 2 — host probe end-to-end: no --sysroot, no KIT_SYSROOT, native target.
    688 # Compile + link + run a real hosted program so crt/libc binding is exercised,
    689 # not just header/define resolution. Runs where a host libc is discoverable
    690 # (a macOS host with the CLT/Xcode SDK, or a Linux host with libc headers);
    691 # skip otherwise (e.g. FreeBSD here is untested).
    692 autoprobe_ok=0
    693 case "$(uname -s)" in
    694   Darwin)
    695     { [ -d /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk ] ||
    696       [ -d "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" ]; } &&
    697       autoprobe_ok=1 ;;
    698   Linux)
    699     [ -e /usr/include/stdio.h ] && autoprobe_ok=1 ;;
    700 esac
    701 if [ "$autoprobe_ok" -eq 1 ]; then
    702     if env -u KIT_SYSROOT "$KIT" cc -lc "$work/hosted-hello.c" \
    703            -o "$work/hosted-autoprobe" \
    704            > "$work/hosted-autoprobe.out" 2> "$work/hosted-autoprobe.err" &&
    705        "$work/hosted-autoprobe" > "$work/hosted-autoprobe.run" 2>&1 &&
    706        grep -q "hosted-autoprobe" "$work/hosted-autoprobe.run"; then
    707         ok "cc-hosted-sysroot-autoprobe"
    708     else
    709         { sed 's/^/err: /' "$work/hosted-autoprobe.err"
    710           [ -f "$work/hosted-autoprobe.run" ] &&
    711               sed 's/^/run: /' "$work/hosted-autoprobe.run"; } \
    712             > "$work/hosted-autoprobe.diag"
    713         not_ok "cc-hosted-sysroot-autoprobe" "$work/hosted-autoprobe.diag"
    714     fi
    715 else
    716     skip_test "cc-hosted-sysroot-autoprobe" "no discoverable host libc to probe"
    717 fi
    718 
    719 # ---- archive link order is enforced (def after ref vs ref after def) ----
    720 cat > "$work/order-main.c" <<'SRC'
    721 int foo(void);
    722 int _start(void) { return foo(); }
    723 SRC
    724 cat > "$work/order-foo.c" <<'SRC'
    725 int foo(void) { return 0; }
    726 SRC
    727 
    728 if "$KIT" cc -target x86_64-linux -c "$work/order-main.c" -o "$work/order-main.o" \
    729     > "$work/order-main.out" 2> "$work/order-main.err" &&
    730    "$KIT" cc -target x86_64-linux -c "$work/order-foo.c" -o "$work/order-foo.o" \
    731     > "$work/order-foo.out" 2> "$work/order-foo.err" &&
    732    "$KIT" ar rc "$work/libfoo.a" "$work/order-foo.o" \
    733     > "$work/order-ar.out" 2> "$work/order-ar.err"; then
    734     if "$KIT" cc -target x86_64-linux -L"$work" "$work/order-main.o" -lfoo \
    735         -o "$work/order-right" > "$work/order-right.out" 2> "$work/order-right.err" &&
    736        ! "$KIT" cc -target x86_64-linux -L"$work" -lfoo "$work/order-main.o" \
    737         -o "$work/order-wrong" > "$work/order-wrong.out" 2> "$work/order-wrong.err"; then
    738         ok "cc-link-archive-order"
    739     else
    740         { sed 's/^/right| /' "$work/order-right.err"
    741           sed 's/^/wrong| /' "$work/order-wrong.err"; } > "$work/order.diag"
    742         not_ok "cc-link-archive-order" "$work/order.diag"
    743     fi
    744 else
    745     { sed 's/^/main: /' "$work/order-main.err"
    746       sed 's/^/foo:  /' "$work/order-foo.err"
    747       sed 's/^/ar:   /' "$work/order-ar.err"; } > "$work/order-setup.diag"
    748     not_ok "cc-link-archive-order" "$work/order-setup.diag"
    749 fi
    750 
    751 # ---- rv64 cross-target end-to-end (as, cc, ld, objdump) ----
    752 # Exercises the rv64 lane of each tool the toolchain claims to support.
    753 # Cross-compile-only; no qemu/native exec required.
    754 cat > "$work/rv64-asm.S" <<'SRC'
    755     .text
    756     .globl rv64_entry
    757 rv64_entry:
    758     li      a0, 7
    759     ret
    760 SRC
    761 if "$KIT" as -target riscv64-linux "$work/rv64-asm.S" -o "$work/rv64-asm.o" \
    762     > "$work/rv64-as.out" 2> "$work/rv64-as.err"; then
    763     if "$KIT" objdump -h "$work/rv64-asm.o" \
    764         > "$work/rv64-as-h.out" 2> "$work/rv64-as-h.err" &&
    765        grep -q "elf64-riscv64" "$work/rv64-as-h.out"; then
    766         ok "rv64-as-cc-objdump-elf"
    767     else
    768         cp "$work/rv64-as-h.out" "$work/rv64-as-h.diag"
    769         not_ok "rv64-as-cc-objdump-elf" "$work/rv64-as-h.diag"
    770     fi
    771 else
    772     not_ok "rv64-as-cc-objdump-elf" "$work/rv64-as.err"
    773 fi
    774 
    775 cat > "$work/rv64-cc.c" <<'SRC'
    776 int rv64_main(int x) { return x + 1; }
    777 SRC
    778 if "$KIT" cc -target riscv64-linux -c "$work/rv64-cc.c" -o "$work/rv64-cc.o" \
    779     > "$work/rv64-cc.out" 2> "$work/rv64-cc.err"; then
    780     if "$KIT" objdump -d "$work/rv64-cc.o" \
    781         > "$work/rv64-cc-d.out" 2> "$work/rv64-cc-d.err" &&
    782        grep -q "ret" "$work/rv64-cc-d.out"; then
    783         ok "rv64-cc-emits-ret"
    784     else
    785         cp "$work/rv64-cc-d.out" "$work/rv64-cc-d.diag"
    786         not_ok "rv64-cc-emits-ret" "$work/rv64-cc-d.diag"
    787     fi
    788 else
    789     not_ok "rv64-cc-emits-ret" "$work/rv64-cc.err"
    790 fi
    791 
    792 cat > "$work/rv64-ld-start.c" <<'SRC'
    793 void _start(void) { for (;;) {} }
    794 SRC
    795 if "$KIT" cc -target riscv64-linux -ffreestanding -fno-PIC \
    796        -c "$work/rv64-ld-start.c" -o "$work/rv64-ld-start.o" \
    797     > "$work/rv64-ld-cc.out" 2> "$work/rv64-ld-cc.err"; then
    798     if "$KIT" ld -static -e _start "$work/rv64-ld-start.o" \
    799            -o "$work/rv64-ld.exe" \
    800         > "$work/rv64-ld.out" 2> "$work/rv64-ld.err"; then
    801         # ELF e_machine == EM_RISCV (243 = 0xF3) at byte offset 0x12,
    802         # little-endian 16-bit field. Validates the linker emitted an
    803         # rv64 ELF executable without needing objdump to parse ET_EXEC.
    804         em_byte=$(od -An -tx1 -j 18 -N 1 "$work/rv64-ld.exe" | tr -d ' \n')
    805         if [ "$em_byte" = "f3" ]; then
    806             ok "rv64-ld-static-exe"
    807         else
    808             printf 'e_machine byte=%s want=f3\n' "$em_byte" \
    809                 > "$work/rv64-ld.diag"
    810             not_ok "rv64-ld-static-exe" "$work/rv64-ld.diag"
    811         fi
    812     else
    813         not_ok "rv64-ld-static-exe" "$work/rv64-ld.err"
    814     fi
    815 else
    816     not_ok "rv64-ld-static-exe" "$work/rv64-ld-cc.err"
    817 fi
    818 
    819 # ---- check: frontend-only, emits no output files ----
    820 rm -f "$work/check.o" "$work/a.out"
    821 if "$KIT" check "$work/main.c" > "$work/check.out" 2> "$work/check.err"; then
    822     if [ ! -e "$work/check.o" ] && [ ! -e "$work/a.out" ]; then
    823         ok "check-no-output"
    824     else
    825         echo "unexpected output file" > "$work/check.diag"
    826         not_ok "check-no-output" "$work/check.diag"
    827     fi
    828 else
    829     not_ok "check-no-output" "$work/check.err"
    830 fi
    831 
    832 cat > "$work/check-bad.c" <<'SRC'
    833 int broken( { return 0; }
    834 SRC
    835 if "$KIT" check "$work/check-bad.c" \
    836         > "$work/check-bad.out" 2> "$work/check-bad.err"; then
    837     echo "bad source passed" > "$work/check-bad.diag"
    838     not_ok "check-reports-errors" "$work/check-bad.diag"
    839 else
    840     if grep -q "fatal:" "$work/check-bad.err"; then
    841         ok "check-reports-errors"
    842     else
    843         cp "$work/check-bad.err" "$work/check-bad-missing.diag"
    844         not_ok "check-reports-errors" "$work/check-bad-missing.diag"
    845     fi
    846 fi
    847 
    848 # ---- nm ----
    849 # Compile a fresh object for the nm/size/addr2line tests.
    850 # Use aarch64-linux ELF so DWARF works for addr2line and symbols
    851 # have predictable names (no Mach-O underscore prefix).
    852 rm -f "$work/nm-main.o"
    853 nm_obj_ok=1
    854 if ! "$KIT" cc -target aarch64-linux -c "$work/main.c" -o "$work/nm-main.o" \
    855     > "$work/nm-cc.out" 2> "$work/nm-cc.err"; then
    856     not_ok "nm-setup" "$work/nm-cc.err"
    857     nm_obj_ok=0
    858 fi
    859 
    860 # basic: symbols in an object file
    861 if [ "$nm_obj_ok" = 1 ] &&
    862    "$KIT" nm "$work/nm-main.o" > "$work/nm-basic.out" 2> "$work/nm-basic.err" &&
    863    grep -q "main" "$work/nm-basic.out" &&
    864    grep -q "_start" "$work/nm-basic.out"; then
    865     ok "nm-basic"
    866 elif [ "$nm_obj_ok" = 1 ]; then
    867     not_ok "nm-basic" "$work/nm-basic.err"
    868 else
    869     not_ok "nm-basic"
    870 fi
    871 
    872 # nm -g: only global symbols.
    873 # Source with a local (static) function and a global one.
    874 cat > "$work/nm-global.c" <<'SRC'
    875 static int hidden(void) { return 1; }
    876 int visible(void) { return hidden(); }
    877 SRC
    878 if [ "$nm_obj_ok" = 1 ] &&
    879    "$KIT" cc -target aarch64-linux -c "$work/nm-global.c" \
    880         -o "$work/nm-global.o" > "$work/nm-global-cc.out" \
    881         2> "$work/nm-global-cc.err" &&
    882    "$KIT" nm "$work/nm-global.o" > "$work/nm-global-all.out" 2>/dev/null &&
    883    grep -q "hidden" "$work/nm-global-all.out" &&
    884    "$KIT" nm -g "$work/nm-global.o" > "$work/nm-global-g.out" 2>/dev/null &&
    885    grep -q "visible" "$work/nm-global-g.out" &&
    886    ! grep -q "hidden" "$work/nm-global-g.out"; then
    887     ok "nm-global-only"
    888 else
    889     not_ok "nm-global-only"
    890 fi
    891 
    892 # nm -u: undefined only (ELF objects may have no undefined)
    893 if [ "$nm_obj_ok" = 1 ] &&
    894    "$KIT" nm -u "$work/nm-main.o" > "$work/nm-undef.out" 2>"$work/nm-undef.err"; then
    895     ok "nm-undefined-only"
    896 elif [ "$nm_obj_ok" = 1 ]; then
    897     not_ok "nm-undefined-only" "$work/nm-undef.err"
    898 else
    899     not_ok "nm-undefined-only"
    900 fi
    901 
    902 # nm on an archive
    903 if [ "$nm_obj_ok" = 1 ] &&
    904    "$KIT" ar rcs "$work/libnmtest.a" "$work/nm-main.o" \
    905         > "$work/ar-nm.out" 2> "$work/ar-nm.err" &&
    906    "$KIT" nm "$work/libnmtest.a" > "$work/nm-archive.out" \
    907         2> "$work/nm-archive.err" &&
    908    grep -q "main" "$work/nm-archive.out"; then
    909     ok "nm-archive"
    910 elif [ "$nm_obj_ok" = 1 ]; then
    911     not_ok "nm-archive" "$work/nm-archive.err"
    912 else
    913     not_ok "nm-archive"
    914 fi
    915 
    916 # nm -h (help)
    917 if "$KIT" nm --help > "$work/nm-help.out" 2> "$work/nm-help.err" &&
    918    grep -q "USAGE" "$work/nm-help.out"; then
    919     ok "nm-help"
    920 else
    921     not_ok "nm-help" "$work/nm-help.err"
    922 fi
    923 
    924 # ---- size ----
    925 # basic: section sizes of an object file (use nm-main.o)
    926 if [ "$nm_obj_ok" = 1 ] &&
    927    "$KIT" size "$work/nm-main.o" > "$work/size-basic.out" 2> "$work/size-basic.err" &&
    928    grep -q "text" "$work/size-basic.out"; then
    929     ok "size-basic"
    930 elif [ "$nm_obj_ok" = 1 ]; then
    931     not_ok "size-basic" "$work/size-basic.err"
    932 else
    933     not_ok "size-basic"
    934 fi
    935 
    936 # size -A: SysV format
    937 if [ "$nm_obj_ok" = 1 ] &&
    938    "$KIT" size -A "$work/nm-main.o" > "$work/size-sysv.out" 2> "$work/size-sysv.err" &&
    939    grep -q "Total" "$work/size-sysv.out"; then
    940     ok "size-sysv"
    941 elif [ "$nm_obj_ok" = 1 ]; then
    942     not_ok "size-sysv" "$work/size-sysv.err"
    943 else
    944     not_ok "size-sysv"
    945 fi
    946 
    947 # size on an archive (uses libnmtest.a from nm test)
    948 if [ "$nm_obj_ok" = 1 ] && [ -f "$work/libnmtest.a" ] &&
    949    "$KIT" size "$work/libnmtest.a" > "$work/size-archive.out" \
    950         2> "$work/size-archive.err" &&
    951    grep -q "text" "$work/size-archive.out"; then
    952     ok "size-archive"
    953 elif [ "$nm_obj_ok" = 1 ]; then
    954     not_ok "size-archive" "$work/size-archive.err"
    955 else
    956     not_ok "size-archive"
    957 fi
    958 
    959 # size -h (help)
    960 if "$KIT" size --help > "$work/size-help.out" 2> "$work/size-help.err" &&
    961    grep -q "USAGE" "$work/size-help.out"; then
    962     ok "size-help"
    963 else
    964     not_ok "size-help" "$work/size-help.err"
    965 fi
    966 
    967 # ---- addr2line ----
    968 cat > "$work/a2l.c" <<'SRC'
    969 int calc(int x) { return x * 2; }
    970 int main(void) { return calc(42); }
    971 SRC
    972 
    973 if [ "$nm_obj_ok" != 1 ]; then
    974     not_ok "addr2line-basic"
    975     not_ok "addr2line-nonzero"
    976 elif "$KIT" cc -g -target aarch64-linux -c "$work/a2l.c" -o "$work/a2l.o" \
    977     > "$work/a2l-cc.out" 2> "$work/a2l-cc.err"; then
    978     calc_addr=$("$KIT" nm "$work/a2l.o" 2>/dev/null | \
    979                 grep " calc$" | awk '{print $1}')
    980     if [ -n "$calc_addr" ] &&
    981        "$KIT" addr2line -e "$work/a2l.o" "$calc_addr" \
    982            > "$work/a2l-hit.out" 2> "$work/a2l-hit.err" &&
    983        grep -q "a2l.c" "$work/a2l-hit.out"; then
    984         ok "addr2line-basic"
    985     else
    986         { printf 'calc_addr=%s\n' "$calc_addr"
    987           sed 's/^/    | /' "$work/a2l-hit.err"; } > "$work/a2l-hit.diag"
    988         not_ok "addr2line-basic" "$work/a2l-hit.diag"
    989     fi
    990     # addr2line should produce output (not crash) on any address
    991     if "$KIT" addr2line -e "$work/a2l.o" 0x0 \
    992         > "$work/a2l-miss.out" 2> "$work/a2l-miss.err" &&
    993        [ -s "$work/a2l-miss.out" ]; then
    994         ok "addr2line-nonzero"
    995     else
    996         not_ok "addr2line-nonzero" "$work/a2l-miss.err"
    997     fi
    998 else
    999     not_ok "addr2line-basic" "$work/a2l-cc.err"
   1000     not_ok "addr2line-nonzero"
   1001 fi
   1002 
   1003 # addr2line over Mach-O DWARF: the producer emits .debug_* into the
   1004 # __DWARF segment as __debug_* (16-char-truncated, Apple spelling) and
   1005 # the reader maps the requested ELF name onto that form, so addr2line
   1006 # resolves file:line on a Mach-O object too (not just ELF).
   1007 if "$KIT" cc -g -target arm64-apple-macos -c "$work/a2l.c" \
   1008        -o "$work/a2l.macho.o" > "$work/a2l-m-cc.out" 2> "$work/a2l-m-cc.err"; then
   1009     m_addr=$("$KIT" nm "$work/a2l.macho.o" 2>/dev/null | \
   1010              grep " _\{0,1\}calc$" | awk '{print $1}')
   1011     if [ -n "$m_addr" ] &&
   1012        "$KIT" addr2line -e "$work/a2l.macho.o" "$m_addr" \
   1013            > "$work/a2l-m.out" 2> "$work/a2l-m.err" &&
   1014        grep -q "a2l.c" "$work/a2l-m.out"; then
   1015         ok "addr2line-macho"
   1016     else
   1017         { printf 'm_addr=%s\n' "$m_addr"
   1018           sed 's/^/out| /' "$work/a2l-m.out"
   1019           sed 's/^/err| /' "$work/a2l-m.err"; } > "$work/a2l-m.diag"
   1020         not_ok "addr2line-macho" "$work/a2l-m.diag"
   1021     fi
   1022 else
   1023     not_ok "addr2line-macho" "$work/a2l-m-cc.err"
   1024 fi
   1025 
   1026 # Debug-info retention through the Mach-O linker: link a self-contained
   1027 # image (no SDK/libSystem needed), then resolve file:line via the same
   1028 # `nm | addr2line` flow as the ELF case — exercising the __DWARF segment
   1029 # the linker carried plus the reader on a linked image. nm reports
   1030 # absolute vaddrs for linked Mach-O images, so its value feeds addr2line
   1031 # directly.
   1032 cat > "$work/a2lm.c" <<'SRC'
   1033 int helper(int x) { return x * 3; }
   1034 int compute(int n) { return helper(n) + n; }
   1035 void mainx(void) { compute(5); }
   1036 SRC
   1037 if "$KIT" cc -g -target arm64-apple-macos -c "$work/a2lm.c" \
   1038        -o "$work/a2lm.o" > "$work/a2lm-cc.out" 2> "$work/a2lm-cc.err" &&
   1039    "$KIT" ld -o "$work/a2lm.exe" "$work/a2lm.o" -e mainx \
   1040        > "$work/a2lm-ld.out" 2> "$work/a2lm-ld.err"; then
   1041     cm_addr=$("$KIT" nm "$work/a2lm.exe" 2>/dev/null | \
   1042               grep " _\{0,1\}compute$" | awk '{print $1}')
   1043     if "$KIT" objdump -h "$work/a2lm.exe" 2>/dev/null \
   1044            | grep -q '__DWARF,__debug_info' &&
   1045        [ -n "$cm_addr" ] &&
   1046        "$KIT" addr2line -e "$work/a2lm.exe" "0x$cm_addr" \
   1047            > "$work/a2lm-a2l.out" 2> "$work/a2lm-a2l.err" &&
   1048        grep -q "a2lm.c" "$work/a2lm-a2l.out"; then
   1049         ok "addr2line-macho-linked"
   1050     else
   1051         printf 'compute=%s: %s\n' "$cm_addr" \
   1052             "$(cat "$work/a2lm-a2l.out" 2>/dev/null)" > "$work/a2lm.diag"
   1053         not_ok "addr2line-macho-linked" "$work/a2lm.diag"
   1054     fi
   1055 else
   1056     not_ok "addr2line-macho-linked" "$work/a2lm-ld.err"
   1057 fi
   1058 
   1059 # addr2line -h (help)
   1060 if "$KIT" addr2line --help > "$work/a2l-help.out" 2> "$work/a2l-help.err" &&
   1061    grep -q "USAGE" "$work/a2l-help.out"; then
   1062     ok "addr2line-help"
   1063 else
   1064     not_ok "addr2line-help" "$work/a2l-help.err"
   1065 fi
   1066 
   1067 # --emit=ir: dump the semantic CG IR tape (requires -O1+).
   1068 cat > "$work/ir.c" <<'SRC'
   1069 int add(int a, int b) {
   1070   int c = a + b;
   1071   return c * 2;
   1072 }
   1073 SRC
   1074 
   1075 if "$KIT" cc -O1 --emit=ir -c "$work/ir.c" -o "$work/ir.out" \
   1076     > "$work/ir-emit.out" 2> "$work/ir-emit.err" &&
   1077    grep -q "^func sym#" "$work/ir.out" &&
   1078    grep -q "binop" "$work/ir.out" &&
   1079    grep -q "iadd" "$work/ir.out" &&
   1080    grep -q "ret values=\[" "$work/ir.out"; then
   1081     ok "cc-emit-ir"
   1082 else
   1083     not_ok "cc-emit-ir" "$work/ir-emit.err"
   1084 fi
   1085 
   1086 # --emit=ir without -O1 must be rejected (no IR tape is recorded at -O0).
   1087 if "$KIT" cc --emit=ir -c "$work/ir.c" -o "$work/ir-o0.out" \
   1088     > "$work/ir-o0.out.log" 2> "$work/ir-o0.err"; then
   1089     echo "expected failure at -O0" > "$work/ir-o0.diag"
   1090     not_ok "cc-emit-ir-requires-opt" "$work/ir-o0.diag"
   1091 elif grep -q "requires -O1" "$work/ir-o0.err"; then
   1092     ok "cc-emit-ir-requires-opt"
   1093 else
   1094     cp "$work/ir-o0.err" "$work/ir-o0-wrong.diag"
   1095     not_ok "cc-emit-ir-requires-opt" "$work/ir-o0-wrong.diag"
   1096 fi
   1097 
   1098 # ---- install: symlink the kit tools into a dir ----
   1099 # Default set: toolchain + standard-named byte utils, each link -> the binary.
   1100 inst_dir="$work/inst"
   1101 run_ok "install-default" "$KIT" install "$inst_dir"
   1102 for t in cc cpp as ld ar ranlib strip objcopy objdump nm size addr2line \
   1103          strings xxd cmp sha256sum b2sum crc32 gzip gunzip lz4 lz4c; do
   1104     assert_file_exists "install-has-$t" "$inst_dir/$t"
   1105 done
   1106 is_executable "install-cc-executable" "$inst_dir/cc"
   1107 # A symlink to the binary has identical bytes, and dispatches by basename.
   1108 same_file "install-cc-points-at-kit" "$KIT" "$inst_dir/cc"
   1109 run_ok "install-symlink-dispatches" "$inst_dir/cc" --help
   1110 
   1111 # Non-default tools (e.g. mc) are excluded unless --all selects everything.
   1112 if [ -e "$inst_dir/mc" ]; then
   1113     echo "mc present in the default set" > "$work/install-mc.diag"
   1114     not_ok "install-default-excludes-mc" "$work/install-mc.diag"
   1115 else
   1116     ok "install-default-excludes-mc"
   1117 fi
   1118 inst_all="$work/inst-all"
   1119 run_ok "install-all" "$KIT" install --all "$inst_all"
   1120 assert_file_exists "install-all-has-mc" "$inst_all/mc"
   1121 
   1122 # Existing entries are an error without -f, replaced with it.
   1123 run_fail "install-existing-without-force" "$KIT" install "$inst_dir"
   1124 run_ok "install-existing-with-force" "$KIT" install -f "$inst_dir"
   1125 
   1126 # An explicit tool list installs exactly those names; an unknown name and a
   1127 # missing directory are usage errors.
   1128 inst_pick="$work/inst-pick"
   1129 run_ok "install-explicit" "$KIT" install "$inst_pick" nm ld
   1130 assert_file_exists "install-explicit-nm" "$inst_pick/nm"
   1131 if [ -e "$inst_pick/cc" ]; then
   1132     echo "cc installed despite an explicit 'nm ld' list" \
   1133         > "$work/install-pick.diag"
   1134     not_ok "install-explicit-only-listed" "$work/install-pick.diag"
   1135 else
   1136     ok "install-explicit-only-listed"
   1137 fi
   1138 run_fail "install-unknown-tool" "$KIT" install "$work/inst-bad" bogustool
   1139 run_fail "install-missing-dir" "$KIT" install
   1140 
   1141 # A hard link shares the binary's inode (the `-ef` test compares st_dev+st_ino).
   1142 inst_hard="$work/inst-hard"
   1143 run_ok "install-hardlink" "$KIT" install -H "$inst_hard" nm
   1144 if [ "$inst_hard/nm" -ef "$KIT" ]; then
   1145     ok "install-hardlink-same-inode"
   1146 else
   1147     echo "hard link does not share the binary's inode" \
   1148         > "$work/install-hard.diag"
   1149     not_ok "install-hardlink-same-inode" "$work/install-hard.diag"
   1150 fi
   1151 
   1152 # Dry-run reports intent but changes nothing (the target dir stays absent).
   1153 inst_dry="$work/inst-dry"
   1154 run_ok "install-dry-run" "$KIT" install -n "$inst_dry"
   1155 if [ -e "$inst_dry" ]; then
   1156     echo "dry-run created $inst_dry" > "$work/install-dry.diag"
   1157     not_ok "install-dry-run-no-changes" "$work/install-dry.diag"
   1158 else
   1159     ok "install-dry-run-no-changes"
   1160 fi
   1161 
   1162 # ---- kit run: auto-backtrace on a fatal fault ----------------------------
   1163 # `kit run` executes the JITed entry in-process; on a fatal fault it installs a
   1164 # guard that walks the frame-pointer chain at the crash point and prints a
   1165 # symbolized backtrace to stderr, then exits non-zero (128 + signal). Gated on
   1166 # the host being able to natively `kit run` at all (cross-arch hosts can't), so
   1167 # probe with a trivial program first.
   1168 cat > "$work/run-probe.c" <<'SRC'
   1169 int main(void) { return 0; }
   1170 SRC
   1171 if "$KIT" run "$work/run-probe.c" > "$work/run-probe.out" 2> "$work/run-probe.err"; then
   1172     cat > "$work/run-crash.c" <<'SRC'
   1173 __attribute__((noinline)) void bt_leaf(int* p) { *p = 42; }
   1174 __attribute__((noinline)) void bt_mid(void) { bt_leaf((int*)0); }
   1175 __attribute__((noinline)) void bt_root(void) { bt_mid(); }
   1176 int main(void) { bt_root(); return 0; }
   1177 SRC
   1178     "$KIT" run -g "$work/run-crash.c" \
   1179         > "$work/run-crash.out" 2> "$work/run-crash.err"
   1180     run_crash_rc=$?
   1181     # A clean exit would mean the guard missed the fault.
   1182     if [ "$run_crash_rc" -ne 0 ]; then
   1183         ok "run-backtrace-nonzero-exit"
   1184     else
   1185         { printf 'rc=%s\n' "$run_crash_rc"; cat "$work/run-crash.err"; } \
   1186             > "$work/run-crash.diag"
   1187         not_ok "run-backtrace-nonzero-exit" "$work/run-crash.diag"
   1188     fi
   1189     # The symbolized chain (innermost first) must reach every kit frame with a
   1190     # source location; outer host-runtime frames are truncated at the image edge.
   1191     contains "run-backtrace-leaf" "$work/run-crash.err" "bt_leaf"
   1192     contains "run-backtrace-mid" "$work/run-crash.err" "bt_mid"
   1193     contains "run-backtrace-root" "$work/run-crash.err" "bt_root"
   1194     contains "run-backtrace-source" "$work/run-crash.err" "run-crash.c"
   1195 else
   1196     skip_test "run-backtrace-nonzero-exit" "host cannot natively kit-run"
   1197     skip_test "run-backtrace-leaf" "host cannot natively kit-run"
   1198     skip_test "run-backtrace-mid" "host cannot natively kit-run"
   1199     skip_test "run-backtrace-root" "host cannot natively kit-run"
   1200     skip_test "run-backtrace-source" "host cannot natively kit-run"
   1201 fi
   1202 
   1203 kit_summary driver-cc
   1204 kit_exit