kit

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

run.sh (29013B)


      1 #!/usr/bin/env bash
      2 # test/parse/run.sh — file-driven C-parser test harness, on the shared corpus
      3 # harness (test/lib/kit_corpus.sh).
      4 #
      5 # For each test/parse/cases/*.c, runs up to six lanes (KIT_TEST_PATHS,
      6 # default DREJ — C and W are opt-in):
      7 #
      8 #   D  in-process JIT — parse-runner --jit FILE.c → exit code matches
      9 #                       expected. No file I/O. Host arch must match cross
     10 #                       target.
     11 #   R  ELF roundtrip  — parse-runner --emit + kit-roundtrip + readelf
     12 #                       normalize diff. Validates emitter+reader fidelity.
     13 #   E  exec via qemu  — parse-runner --emit + start.o → link-exe-runner →
     14 #                       qemu/podman → exit code. Cross-host friendly. Deferred
     15 #                       to a batched exec_target flush (kit_queue_e).
     16 #   J  jit-via-file   — parse-runner --emit + jit-runner. Host arch must match
     17 #                       cross target.
     18 #   C  emit-c host    — parse-runner --emit-c + host cc + test_main wrapper,
     19 #                       run native. Validates the --emit=c C-source backend.
     20 #                       Host arch must match cross target. opt=0 only. Cases
     21 #                       that hit an unimplemented C-target method are reported
     22 #                       as SKIP (not FAIL) so phased backend rollout is
     23 #                       tolerated.
     24 #   W  wasm roundtrip — kit cc -target wasm32-none -c case.c -> .wasm, then
     25 #                       kit run -e test_main on it (the lang/wasm frontend
     26 #                       re-lowers to native CG, JITs, calls test_main).
     27 #                       Exercises the Wasm CGTarget (C -> wasm). Opt-in and
     28 #                       opt=0 only; host arch must match cross target (the
     29 #                       re-lowering JITs for the host). Phased-rollout panics
     30 #                       ("wasm: ... not yet implemented") report SKIP.
     31 #
     32 # Reuses the test/link harness binaries (kit-roundtrip, link-exe-runner,
     33 # jit-runner) and test/link/harness/start.c verbatim.
     34 #
     35 # Sidecar conventions (each missing file uses the documented default):
     36 #   <name>.expected   — integer; default 0. Compared mod 256 to test_main.
     37 #   <name>.skip       — single-line reason. Whole-case skip on every arch.
     38 #   <name>.<arch>.skip— single-line reason; whole-case skip on that arch only
     39 #                       (e.g. asm_01_grammar.rv64.skip).
     40 #   <name>.cbackend.skip — single-line reason; opts the case out of lane C only.
     41 #   <name>.wasm.skip  — single-line reason; opts the case out of lane W only.
     42 # Skips are treated as failure unless KIT_TEST_ALLOW_SKIP=1 (matching the
     43 # rest of the test suite).
     44 #
     45 # Filtering:
     46 #   ./run.sh [name_filter] [paths]
     47 #     name_filter   substring match against case basename
     48 #     paths         subset of "DREJCW" (default "DREJ" — C and W opt-in)
     49 #   Equivalent env vars: KIT_TEST_FILTER, KIT_TEST_PATHS.
     50 #
     51 # Optimization levels:
     52 #   KIT_OPT_LEVELS="0 1"  whitespace-separated levels to test.
     53 #   KIT_OPT_LEVEL=1       compatibility shorthand for one level.
     54 #   Default is "0 1".
     55 #
     56 # Parallelism:
     57 #   default                 run in parallel with a capped CPU-count default.
     58 #   KIT_TEST_JOBS=N       run up to N cases concurrently.
     59 #   KIT_PARSE_PARALLEL=0  force serial dispatch.
     60 #   KIT_CORPUS_TRACE=1    force serial dispatch and print each item/lane before
     61 #                         running it.
     62 # All lane hooks write only under KIT_WORK and record via kit_*, so the runner is
     63 # parallel-safe by construction.
     64 
     65 set -u
     66 
     67 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     68 export KIT_LIB_DIR="$ROOT/test/lib"
     69 . "$ROOT/test/lib/kit_corpus.sh"
     70 
     71 TEST_DIR="$ROOT/test/parse"
     72 LINK_TEST_DIR="$ROOT/test/link"
     73 BUILD_DIR="$ROOT/build/test"
     74 LIB_AR="$ROOT/build/libkit.a"
     75 
     76 KIT="${KIT:-$ROOT/build/kit}"
     77 PARSE_RUNNER="$BUILD_DIR/parse-runner"
     78 ROUNDTRIP_BIN="$BUILD_DIR/kit-roundtrip"
     79 LINK_EXE_RUNNER="$BUILD_DIR/link-exe-runner"
     80 JIT_RUNNER="$BUILD_DIR/jit-runner"
     81 NORMALIZE="$ROOT/test/elf/normalize.py"
     82 
     83 # KIT_TEST_ARCH selects the cross-target. Default aa64 preserves the
     84 # pre-multiarch behavior. The C runners read the same env via
     85 # test/lib/kit_test_target.h.
     86 KIT_TEST_ARCH="${KIT_TEST_ARCH:-aa64}"
     87 case "$KIT_TEST_ARCH" in
     88     aa64|aarch64|arm64)   TEST_ARCH=aa64;   CLANG_TRIPLE=aarch64-linux-gnu;  EXEC_ARCH=aarch64 ;;
     89     x64|x86_64|amd64)     TEST_ARCH=x64;    CLANG_TRIPLE=x86_64-linux-gnu;   EXEC_ARCH=x64 ;;
     90     rv64|riscv64)         TEST_ARCH=rv64;   CLANG_TRIPLE=riscv64-linux-gnu;  EXEC_ARCH=rv64 ;;
     91     # rv32 is freestanding: the E lane runs bare-metal under qemu-system-riscv32
     92     # via exec_bare (rv32), not exec_target's qemu-user path. CLANG_TRIPLE is
     93     # only for clang probes; the kit target comes from KIT_TEST_ARCH.
     94     rv32|riscv32)         TEST_ARCH=rv32;   CLANG_TRIPLE=riscv32-unknown-elf; EXEC_ARCH=rv32 ;;
     95     # arm32 is freestanding too: the E lane runs bare-metal under qemu-system-arm
     96     # via exec_bare (arm32), mirroring rv32.
     97     arm32|arm)            TEST_ARCH=arm32;  CLANG_TRIPLE=arm-none-eabi;      EXEC_ARCH=arm32 ;;
     98     *) printf 'unknown KIT_TEST_ARCH=%s\n' "$KIT_TEST_ARCH" >&2; exit 2 ;;
     99 esac
    100 export KIT_TEST_ARCH
    101 
    102 case "$TEST_ARCH" in
    103     aa64) RT_AR="$ROOT/build/rt/aarch64-linux/libkit_rt.a" ;;
    104     x64)  RT_AR="$ROOT/build/rt/x86_64-linux/libkit_rt.a" ;;
    105     rv64) RT_AR="$ROOT/build/rt/riscv64-linux/libkit_rt.a" ;;
    106     rv32) RT_AR="$ROOT/build/rt/riscv32-elf-hardfloat/libkit_rt.a" ;;  # used by exec_bare (rv32)
    107     arm32) RT_AR="$ROOT/build/rt/arm-eabi-thumb2/libkit_rt.a" ;;       # used by exec_bare (arm32)
    108 esac
    109 RT_LINK_ARGS=()
    110 if [ -f "$RT_AR" ]; then
    111     RT_LINK_ARGS=(--archive "$RT_AR")
    112 fi
    113 
    114 CLANG_TARGET="--target=$CLANG_TRIPLE"
    115 CC="${CC:-cc}"
    116 
    117 FILTER="${1:-${KIT_TEST_FILTER:-}}"
    118 PATHS="${2:-${KIT_TEST_PATHS:-DREJ}}"
    119 export KIT_TEST_FILTER="$FILTER"
    120 if [ -n "${KIT_OPT_LEVELS:-}" ]; then
    121     OPT_LEVELS="$KIT_OPT_LEVELS"
    122 elif [ -n "${KIT_OPT_LEVEL:-}" ]; then
    123     OPT_LEVELS="$KIT_OPT_LEVEL"
    124 else
    125     OPT_LEVELS="0 1"
    126 fi
    127 for opt in $OPT_LEVELS; do
    128     case "$opt" in
    129         0|1|2) ;;
    130         *) printf 'parse: invalid opt level %s in KIT_OPT_LEVELS\n' "$opt" >&2; exit 2 ;;
    131     esac
    132 done
    133 case "$PATHS" in *D*) RUN_D=1;; *) RUN_D=0;; esac
    134 case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac
    135 case "$PATHS" in *E*) RUN_E=1;; *) RUN_E=0;; esac
    136 case "$PATHS" in *J*) RUN_J=1;; *) RUN_J=0;; esac
    137 case "$PATHS" in *C*) RUN_C=1;; *) RUN_C=0;; esac
    138 case "$PATHS" in *W*) RUN_W=1;; *) RUN_W=0;; esac
    139 
    140 mkdir -p "$BUILD_DIR" "$BUILD_DIR/parse"
    141 
    142 # ---- tool detection (mirrors test/cg/run.sh) -------------------------------
    143 
    144 have_clang_cross=0
    145 have_readelf=0
    146 have_python3=0
    147 have_qemu=0
    148 have_podman=0
    149 have_roundtrip=0
    150 have_exe_runner=0
    151 have_jit_runner=0
    152 is_aarch64=0
    153 
    154 if clang $CLANG_TARGET -c -x c - -o /dev/null < /dev/null 2>/dev/null; then
    155     have_clang_cross=1
    156 fi
    157 command -v llvm-readelf >/dev/null 2>&1 && have_readelf=1
    158 command -v readelf      >/dev/null 2>&1 && have_readelf=1
    159 command -v python3      >/dev/null 2>&1 && have_python3=1
    160 
    161 QEMU_BIN="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)"
    162 [ -n "$QEMU_BIN" ] && have_qemu=1
    163 command -v podman >/dev/null 2>&1 && have_podman=1
    164 
    165 arch_raw="$(uname -m 2>/dev/null || true)"
    166 { [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1
    167 
    168 # Host object format for path C: the emitted C is target-locked, so the
    169 # C-target must use the host's obj format (controls ELF-vs-Mach-O choices
    170 # like `__attribute__((alias))` vs a thunk fallback).
    171 case "$(uname -s 2>/dev/null)" in
    172     Darwin) HOST_OBJ_FMT=macho ;;
    173     *)      HOST_OBJ_FMT=elf   ;;
    174 esac
    175 
    176 # is_native_target=1 when the cross-target arch matches the host arch.
    177 # Required for in-process JIT (path D) and the jit-runner (path J).
    178 is_native_target=0
    179 case "$TEST_ARCH" in
    180     aa64) [ $is_aarch64 -eq 1 ] && is_native_target=1 ;;
    181     x64)  { [ "$arch_raw" = "x86_64" ] || [ "$arch_raw" = "amd64" ]; } && is_native_target=1 ;;
    182     rv64) [ "$arch_raw" = "riscv64" ] && is_native_target=1 ;;
    183 esac
    184 
    185 READELF_BIN="$(command -v llvm-readelf 2>/dev/null || command -v readelf 2>/dev/null || true)"
    186 
    187 # Shared per-arch exec helper — see test/lib/exec_target.sh.
    188 EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR"
    189 export have_qemu have_podman is_aarch64 QEMU_BIN EXEC_TARGET_MOUNT_ROOT
    190 # shellcheck source=../lib/exec_target.sh
    191 source "$ROOT/test/lib/exec_target.sh"
    192 
    193 # rv32 is freestanding: the E lane runs bare-metal under qemu-system-riscv32 via
    194 # the exec_bare backend (sourced by exec_target.sh).
    195 if [ "$TEST_ARCH" = "rv32" ]; then
    196     # The parse corpus's entry is test_main() (path C bridges main->test_main).
    197     if exec_bare_setup rv32 "$BUILD_DIR/rv32" test_main; then RV32_BARE_OK=1; else RV32_BARE_OK=0; fi
    198 fi
    199 if [ "$TEST_ARCH" = "arm32" ]; then
    200     if exec_bare_setup arm32 "$BUILD_DIR/arm32" test_main; then ARM32_BARE_OK=1; else ARM32_BARE_OK=0; fi
    201 fi
    202 
    203 # ---- harness binaries ------------------------------------------------------
    204 
    205 printf 'Checking harness...\n'
    206 
    207 if [ ! -f "$LIB_AR" ]; then
    208     printf '  FATAL: %s not found — run "make lib" first\n' "$LIB_AR" >&2
    209     exit 1
    210 fi
    211 
    212 # parse-runner
    213 if [ -x "$PARSE_RUNNER" ]; then
    214     printf '  found parse-runner\n'
    215 else
    216     printf '  FATAL parse-runner missing — run "make build/test/parse-runner"\n' >&2
    217     exit 1
    218 fi
    219 
    220 # kit-roundtrip — for path R.
    221 if [ -x "$ROUNDTRIP_BIN" ]; then
    222     have_roundtrip=1
    223     printf '  found kit-roundtrip\n'
    224 else
    225     printf '  warn kit-roundtrip missing — path R will skip\n' >&2
    226 fi
    227 
    228 # link-exe-runner — for path E.
    229 if [ -x "$LINK_EXE_RUNNER" ]; then
    230     have_exe_runner=1
    231     printf '  found link-exe-runner\n'
    232 else
    233     printf '  warn link-exe-runner missing — path E will skip\n' >&2
    234 fi
    235 
    236 # jit-runner — for path J. Only when host arch matches the cross-target.
    237 if [ $is_native_target -eq 1 ]; then
    238     if [ -x "$JIT_RUNNER" ]; then
    239         have_jit_runner=1
    240         printf '  found jit-runner\n'
    241     else
    242         printf '  warn jit-runner missing — path J will skip\n' >&2
    243     fi
    244 fi
    245 
    246 # Cached start.o — build once for the harness run rather than per case.
    247 # This harness is routinely run for several architectures at once (for
    248 # example, independent cross-test workers).  Keep the cached startup object
    249 # architecture-owned: sharing parse_start.o lets one worker replace another's
    250 # object between the probe above and the per-case link below.
    251 START_OBJ="$BUILD_DIR/parse_start.$TEST_ARCH.o"
    252 have_start_obj=0
    253 if [ $have_clang_cross -eq 1 ]; then
    254     if clang $CLANG_TARGET -O1 -ffreestanding -fno-stack-protector \
    255             -fno-PIC -fno-pie \
    256             -c "$LINK_TEST_DIR/harness/start.c" -o "$START_OBJ" 2>/dev/null; then
    257         have_start_obj=1
    258     fi
    259 fi
    260 
    261 # Cached test_main main-wrapper.o — used by path C to link the emitted C
    262 # source against a host-cc-compiled main() that returns test_main()'s value.
    263 # Phase 1 C target only supports native host arch (no cross-emit), so this
    264 # wrapper is built with the host CC, not the cross clang.
    265 C_WRAPPER_SRC="$BUILD_DIR/parse_c_wrapper.c"
    266 C_WRAPPER_OBJ="$BUILD_DIR/parse_c_wrapper.o"
    267 have_c_wrapper=0
    268 if [ ! -f "$C_WRAPPER_SRC" ] || [ ! -s "$C_WRAPPER_SRC" ]; then
    269     cat > "$C_WRAPPER_SRC" <<'EOF'
    270 /* Generated by test/parse/run.sh — bridges main() to test_main() for path C. */
    271 extern int test_main(void);
    272 int main(void) { return test_main(); }
    273 EOF
    274 fi
    275 if $CC -std=c11 -c "$C_WRAPPER_SRC" -o "$C_WRAPPER_OBJ" 2>/dev/null; then
    276     have_c_wrapper=1
    277     printf '  built c-wrapper\n'
    278 else
    279     printf '  warn c-wrapper (host CC failed)\n' >&2
    280 fi
    281 
    282 # Probe whether the host C compiler treats `long double` as 128-bit
    283 # (IEEE binary128). The ldbl128_* fixtures early-return 0 unless
    284 # __LDBL_MANT_DIG__ == 113, and the kit target the C path uses
    285 # (host arch + host OS) matches the host compiler, so a mismatch means
    286 # the test cannot exercise its 128-bit code path and silently turns
    287 # into a return-0 — which then fails the non-zero expected. Skip path C
    288 # for these tests when the host doesn't provide 128-bit ldbl.
    289 HOST_LDBL128=0
    290 LDBL_PROBE_SRC="$BUILD_DIR/parse_ldbl_probe.c"
    291 LDBL_PROBE_BIN="$BUILD_DIR/parse_ldbl_probe"
    292 cat > "$LDBL_PROBE_SRC" <<'EOF'
    293 int main(void) { return __LDBL_MANT_DIG__ == 113 ? 0 : 1; }
    294 EOF
    295 if $CC -std=c11 "$LDBL_PROBE_SRC" -o "$LDBL_PROBE_BIN" 2>/dev/null \
    296         && "$LDBL_PROBE_BIN" 2>/dev/null; then
    297     HOST_LDBL128=1
    298 fi
    299 
    300 # ---- per-lane oracle hooks (KIT_WORK-confined -> parallel-safe) -------------
    301 # Each hook records via kit_pass/kit_fail/kit_skip (or kit_queue_e for E). The
    302 # expected exit code is KIT_EXPECTED (the engine read <name>.expected); compared
    303 # mod 256 to the program's exit code.
    304 
    305 # Build the .o the R/E/J lanes share, once per (case,opt). Sets PARSE_OBJ.
    306 # Returns 0 on success, 1 on failure.
    307 #
    308 # The original harness emitted the .o ONCE up front (before R/E/J) and, on a
    309 # failure, recorded a single FAIL "<name>/emit" and skipped R/E/J entirely. To
    310 # preserve that exact verdict (one FAIL, not one per lane), the first lane that
    311 # needs the object reports "<name>/emit"; a failure marker makes every later
    312 # lane in the same item return silently without re-reporting.
    313 _parse_emit_obj() {
    314     PARSE_OBJ="$KIT_WORK/$KIT_BASE.o"
    315     [ -f "$PARSE_OBJ" ] && return 0
    316     [ -f "$KIT_WORK/.emit.failed" ] && return 1
    317     if ! KIT_OPT_LEVEL="$KIT_OPT" "$PARSE_RUNNER" --emit "$KIT_SRC" "$PARSE_OBJ" \
    318             2>"$KIT_WORK/emit.err"; then
    319         : > "$KIT_WORK/.emit.failed"
    320         kit_fail "$KIT_NAME/emit" "parse-runner --emit failed; see $KIT_WORK/emit.err"
    321         return 1
    322     fi
    323     return 0
    324 }
    325 
    326 kit_lane_D() {
    327     if [ $is_native_target -eq 0 ]; then
    328         kit_skip "$KIT_NAME/D" "host arch != $TEST_ARCH (no native JIT)"
    329         return
    330     fi
    331     local exp_byte t0 dt d_rc
    332     exp_byte=$(( KIT_EXPECTED & 0xff ))
    333     t0=$(kit_now_ms)
    334     KIT_OPT_LEVEL="$KIT_OPT" "$PARSE_RUNNER" --jit "$KIT_SRC" \
    335         >"$KIT_WORK/d.out" 2>"$KIT_WORK/d.err"
    336     d_rc=$?
    337     dt=$(( $(kit_now_ms) - t0 ))
    338     kit_time D "$dt"
    339     if [ "$d_rc" -eq "$exp_byte" ]; then
    340         kit_pass "$KIT_NAME/D (${dt}ms)"
    341     else
    342         kit_fail "$KIT_NAME/D" "expected $exp_byte got $d_rc, ${dt}ms"
    343     fi
    344 }
    345 
    346 kit_lane_R() {
    347     if [ $have_roundtrip -ne 1 ] || [ $have_readelf -ne 1 ] || [ $have_python3 -ne 1 ]; then
    348         kit_skip "$KIT_NAME/R" "missing roundtrip/readelf/python3"
    349         return
    350     fi
    351     _parse_emit_obj || return
    352     local t0 dt rt r_ok r_msg
    353     t0=$(kit_now_ms)
    354     rt="$KIT_WORK/$KIT_BASE.rt.o"
    355     r_ok=1; r_msg=""
    356     if ! "$ROUNDTRIP_BIN" "$PARSE_OBJ" "$rt" 2>"$KIT_WORK/rt.err"; then
    357         r_ok=0; r_msg="roundtrip failed"
    358     else
    359         "$READELF_BIN" -aW "$PARSE_OBJ" | python3 "$NORMALIZE" >"$KIT_WORK/golden.norm" 2>/dev/null
    360         "$READELF_BIN" -aW "$rt"        | python3 "$NORMALIZE" >"$KIT_WORK/rt.norm"     2>/dev/null
    361         diff -u "$KIT_WORK/golden.norm" "$KIT_WORK/rt.norm" >"$KIT_WORK/r.diff" 2>&1 || r_ok=0
    362     fi
    363     dt=$(( $(kit_now_ms) - t0 ))
    364     kit_time R "$dt"
    365     if [ $r_ok -eq 1 ]; then kit_pass "$KIT_NAME/R (${dt}ms)"
    366     else kit_fail "$KIT_NAME/R" "${r_msg} ${dt}ms"; fi
    367 }
    368 
    369 kit_lane_E() {
    370     # The ldbl128_* execution fixtures assert IEEE binary128 semantics. x64
    371     # targets use either x87 extended precision (SysV/macOS) or double-aliased
    372     # long double (Win64), so their deliberate return-0 guard cannot match the
    373     # nonzero binary128 oracle. Keep the layout/macro probe, whose expected
    374     # result is already zero, and classify the semantic fixtures as inapplicable.
    375     if [ "$TEST_ARCH" = "x64" ] && \
    376             [[ "$KIT_BASE" == ldbl128_* ]] && \
    377             [[ "$KIT_BASE" != ldbl128_01_* ]]; then
    378         kit_skip "$KIT_NAME/E" "x64 long double is not IEEE binary128"
    379         return
    380     fi
    381     # rv32: freestanding bare-metal. parse-runner --emit -> kit ld with a startup
    382     # that calls main() and reports its return via a SiFive finisher -> run under
    383     # qemu-system-riscv32 (test/lib/exec_bare.sh). The qemu exit equals
    384     # main()'s return, so the corpus rc==expected oracle applies. Gaps stay RED.
    385     if [ "$TEST_ARCH" = "rv32" ]; then
    386         local exp_byte rc reason t0 dt run_rc
    387         if [ "${RV32_BARE_OK:-0}" -ne 1 ]; then
    388             kit_skip "$KIT_NAME/E" "no rv32 runner (qemu-system-riscv32)"
    389             return
    390         fi
    391         _parse_emit_obj || return
    392         exp_byte=$(( KIT_EXPECTED & 0xff ))
    393         t0=$(kit_now_ms)
    394         reason="$(exec_bare_run rv32 "$PARSE_OBJ" "$KIT_WORK" "$KIT_WORK/exec.rc")"
    395         run_rc=$?
    396         dt=$(( $(kit_now_ms) - t0 ))
    397         kit_time E "$dt"
    398         if [ "$run_rc" -eq 2 ]; then kit_fail "$KIT_NAME/E" "$reason, ${dt}ms"; return; fi
    399         rc="$(cat "$KIT_WORK/exec.rc" 2>/dev/null || echo 99)"
    400         if [ "$rc" -eq "$exp_byte" ]; then kit_pass "$KIT_NAME/E (${dt}ms)"
    401         else kit_fail "$KIT_NAME/E" "expected $exp_byte got $rc (qemu-system-riscv32), ${dt}ms"; fi
    402         return
    403     fi
    404     if [ "$TEST_ARCH" = "arm32" ]; then
    405         # arm32: freestanding bare-metal, same shape as rv32 but under
    406         # qemu-system-arm (ARM semihosting exit). Gaps stay RED.
    407         local exp_byte rc reason t0 dt run_rc
    408         if [ "${ARM32_BARE_OK:-0}" -ne 1 ]; then
    409             kit_skip "$KIT_NAME/E" "no arm32 runner (qemu-system-arm)"
    410             return
    411         fi
    412         _parse_emit_obj || return
    413         exp_byte=$(( KIT_EXPECTED & 0xff ))
    414         t0=$(kit_now_ms)
    415         reason="$(exec_bare_run arm32 "$PARSE_OBJ" "$KIT_WORK" "$KIT_WORK/exec.rc")"
    416         run_rc=$?
    417         dt=$(( $(kit_now_ms) - t0 ))
    418         kit_time E "$dt"
    419         if [ "$run_rc" -eq 2 ]; then kit_fail "$KIT_NAME/E" "$reason, ${dt}ms"; return; fi
    420         rc="$(cat "$KIT_WORK/exec.rc" 2>/dev/null || echo 99)"
    421         if [ "$rc" -eq "$exp_byte" ]; then kit_pass "$KIT_NAME/E (${dt}ms)"
    422         else kit_fail "$KIT_NAME/E" "expected $exp_byte got $rc (qemu-system-arm), ${dt}ms"; fi
    423         return
    424     fi
    425     if [ $have_exe_runner -ne 1 ] || [ $have_clang_cross -ne 1 ] || [ $have_start_obj -ne 1 ]; then
    426         kit_skip "$KIT_NAME/E" "no link-exe-runner, $TEST_ARCH clang, or start.o"
    427         return
    428     fi
    429     _parse_emit_obj || return
    430     local t0 dt exe exp_byte
    431     exp_byte=$(( KIT_EXPECTED & 0xff ))
    432     t0=$(kit_now_ms)
    433     exe="$KIT_WORK/linked.exe"
    434     if ! "$LINK_EXE_RUNNER" -o "$exe" "$PARSE_OBJ" "$START_OBJ" "${RT_LINK_ARGS[@]}" \
    435             >"$KIT_WORK/exec_link.out" 2>"$KIT_WORK/exec_link.err"; then
    436         dt=$(( $(kit_now_ms) - t0 ))
    437         kit_time E "$dt"
    438         kit_fail "$KIT_NAME/E" "link failed, ${dt}ms"
    439     elif exec_target_supported "$EXEC_ARCH"; then
    440         dt=$(( $(kit_now_ms) - t0 ))
    441         kit_time E "$dt"
    442         # Deferred batched exec: the engine flush runs the exe and verifies
    443         # rc == exp_byte (both masked & 255) at the end.
    444         kit_queue_e "$KIT_NAME/E (link ${dt}ms)" "$exe" \
    445             "$KIT_WORK/exec.out" "$KIT_WORK/exec.err" "$KIT_WORK/exec.rc" \
    446             "$exp_byte" "$EXEC_ARCH"
    447     else
    448         dt=$(( $(kit_now_ms) - t0 ))
    449         kit_time E "$dt"
    450         kit_skip "$KIT_NAME/E" "no runner for $EXEC_ARCH"
    451     fi
    452 }
    453 
    454 kit_lane_J() {
    455     if [ $have_jit_runner -ne 1 ]; then
    456         kit_skip "$KIT_NAME/J" "no jit-runner (host arch != $TEST_ARCH)"
    457         return
    458     fi
    459     _parse_emit_obj || return
    460     local t0 dt j_rc exp_byte
    461     exp_byte=$(( KIT_EXPECTED & 0xff ))
    462     t0=$(kit_now_ms)
    463     "$JIT_RUNNER" "$PARSE_OBJ" "${RT_LINK_ARGS[@]}" >"$KIT_WORK/jit.out" 2>"$KIT_WORK/jit.err"
    464     j_rc=$?
    465     dt=$(( $(kit_now_ms) - t0 ))
    466     kit_time J "$dt"
    467     if [ "$j_rc" -eq "$exp_byte" ]; then
    468         kit_pass "$KIT_NAME/J (${dt}ms)"
    469     else
    470         kit_fail "$KIT_NAME/J" "expected $exp_byte got $j_rc, ${dt}ms"
    471     fi
    472 }
    473 
    474 # Path C: --emit=c + host cc + run. Phase 1 of the C-source backend only
    475 # handles a slice of the CGTarget vtable (doc/CBACKEND.md). Cases that hit an
    476 # unimplemented method panic, surfaced here as SKIP so the pass/fail signal
    477 # reflects the implemented surface. The .cbackend.skip sidecar (handled by the
    478 # engine via the per-lane sidecar with LANE=cbackend) opts a case out of C only.
    479 kit_lane_C() {
    480     # Per-case opt-out for path C: <name>.cbackend.skip. The engine's per-lane
    481     # sidecar check keys on the lane id ("C"), so the cbackend-named sidecar is
    482     # handled here instead.
    483     local reason
    484     if reason=$(kit_skip_sidecar "$KIT_SIDECAR_DIR" "$KIT_BASE" "" cbackend); then
    485         kit_skip "$KIT_NAME/C" "$reason"
    486         return
    487     fi
    488     # ldbl128_* tests assert 128-bit long-double semantics. Skip them on
    489     # path C when the host C compiler doesn't provide 128-bit ldbl (the
    490     # test's `if (__LDBL_MANT_DIG__ != 113) return 0;` early-out can't be
    491     # reconciled with a non-zero expected). ldbl128_01_* are exempt.
    492     if [ $HOST_LDBL128 -eq 0 ] && \
    493             [[ "$KIT_BASE" == ldbl128_* ]] && \
    494             [[ "$KIT_BASE" != ldbl128_01_* ]]; then
    495         kit_skip "$KIT_NAME/C" "host long double is not 128-bit"
    496         return
    497     fi
    498     # Mach-O's static linker rejects unresolved weak undef refs that aren't
    499     # backed by a dylib. ELF lets them resolve to 0 at link time, which is
    500     # what the test expects.
    501     if [ "$HOST_OBJ_FMT" = "macho" ] && [[ "$KIT_BASE" == attr_p2_08_weak_undef ]]; then
    502         kit_skip "$KIT_NAME/C" "Mach-O static link rejects weak undef ref without dylib"
    503         return
    504     fi
    505     # File-scope asm that defines C-visible symbols re-emits verbatim, so it
    506     # defines the bare name (global_x). On Mach-O the C reference picks up the
    507     # leading-underscore (_global_x), so the link can't resolve — a name-mangling
    508     # mismatch the C backend can't bridge without parsing the opaque asm. ELF has
    509     # no such prefix, so the emitted C links and runs there.
    510     # asm_04_register_callee_saved hits the same wall: its file-scope asm defines
    511     # write_saved_reg/read_saved_reg as bare names, but the C calls reference the
    512     # underscored _write_saved_reg/_read_saved_reg on Mach-O, so the link fails.
    513     # Verified otherwise-correct: underscoring the asm labels links clean under
    514     # -Wall -Wextra -Werror and returns the expected 77.
    515     if [ "$HOST_OBJ_FMT" = "macho" ] && \
    516             { [[ "$KIT_BASE" == asm_02_file_scope ]] || \
    517               [[ "$KIT_BASE" == asm_04_register_callee_saved ]]; }; then
    518         kit_skip "$KIT_NAME/C" "Mach-O underscores C symbol refs; verbatim file-scope asm defines the bare name"
    519         return
    520     fi
    521     # The C backend replays IR as portable C and has no arch register model, so
    522     # it cannot bind asm operands to machine register classes (aa64 "x"/"y",
    523     # rv64 "cr"/"cf"); cg/asm.c correctly rejects them. Native-only feature.
    524     if [[ "$KIT_BASE" == cg_native_inline_asm_machine_constraints ]]; then
    525         kit_skip "$KIT_NAME/C" "C backend has no machine register model for arch asm constraints"
    526         return
    527     fi
    528     if [ $have_c_wrapper -eq 0 ]; then
    529         kit_skip "$KIT_NAME/C" "no c-wrapper (host CC failed)"
    530         return
    531     fi
    532     if [ $is_native_target -eq 0 ]; then
    533         kit_skip "$KIT_NAME/C" "host arch != $TEST_ARCH (C target is target-locked)"
    534         return
    535     fi
    536     local t0 dt c_src c_bin c_rc missing exp_byte
    537     exp_byte=$(( KIT_EXPECTED & 0xff ))
    538     t0=$(kit_now_ms)
    539     c_src="$KIT_WORK/$KIT_BASE.kit.c"
    540     c_bin="$KIT_WORK/$KIT_BASE.cbackend.bin"
    541     # Emitted C is target-locked, so we override KIT_TEST_OBJ to the host's
    542     # object format for the --emit-c invocation — otherwise ELF-only constructs
    543     # like __attribute__((alias("x"))) leak into source compiled by a
    544     # Mach-O-targeting host cc and fail at compile time.
    545     if ! KIT_TEST_OBJ="$HOST_OBJ_FMT" "$PARSE_RUNNER" \
    546          --emit-c "$KIT_SRC" "$c_src" \
    547             >"$KIT_WORK/c.emit.out" 2>"$KIT_WORK/c.emit.err"; then
    548         dt=$(( $(kit_now_ms) - t0 ))
    549         kit_time C "$dt"
    550         # Recognize "C target: ... not implemented" and "... not yet supported"
    551         # as phased-rollout skips. Anything else is a real failure.
    552         missing=$(grep -oE 'C target: .*(not implemented|not yet supported)' \
    553                   "$KIT_WORK/c.emit.err" 2>/dev/null | head -n1 || true)
    554         if [ -n "$missing" ]; then
    555             kit_skip "$KIT_NAME/C" "$missing"
    556         else
    557             kit_fail "$KIT_NAME/C" "parse-runner --emit-c failed; see $KIT_WORK/c.emit.err"
    558         fi
    559     elif ! $CC -std=c11 -Wall -Wextra -Werror "$c_src" "$C_WRAPPER_OBJ" -o "$c_bin" \
    560             >"$KIT_WORK/c.cc.out" 2>"$KIT_WORK/c.cc.err"; then
    561         dt=$(( $(kit_now_ms) - t0 ))
    562         kit_time C "$dt"
    563         kit_fail "$KIT_NAME/C" "host cc rejected emitted source; see $KIT_WORK/c.cc.err"
    564     else
    565         "$c_bin" >"$KIT_WORK/c.run.out" 2>"$KIT_WORK/c.run.err"
    566         c_rc=$?
    567         dt=$(( $(kit_now_ms) - t0 ))
    568         kit_time C "$dt"
    569         if [ "$c_rc" -eq "$exp_byte" ]; then
    570             kit_pass "$KIT_NAME/C (${dt}ms)"
    571         else
    572             kit_fail "$KIT_NAME/C" "expected $exp_byte got $c_rc, ${dt}ms"
    573         fi
    574     fi
    575 }
    576 
    577 # Path W: cc -target wasm32-none + kit run. Compile the case straight to a
    578 # .wasm via the Wasm CGTarget, then run it with `kit run -e test_main` (the
    579 # lang/wasm frontend re-lowers to native CG, JITs it, and calls test_main).
    580 # Target-agnostic like C, but the re-lowering JITs for the host, so it only
    581 # runs when the host arch matches the cross target. opt=0 only. Phased-rollout
    582 # panics surface as SKIP. The .wasm.skip sidecar (engine per-lane, LANE=wasm)
    583 # opts a case out of W only.
    584 kit_lane_W() {
    585     # Per-case opt-out for path W: <name>.wasm.skip. The engine's per-lane
    586     # sidecar check keys on the lane id ("W"), so the wasm-named sidecar is
    587     # handled here instead.
    588     local reason
    589     if reason=$(kit_skip_sidecar "$KIT_SIDECAR_DIR" "$KIT_BASE" "" wasm); then
    590         kit_skip "$KIT_NAME/W" "$reason"
    591         return
    592     fi
    593     if [ $is_native_target -eq 0 ]; then
    594         kit_skip "$KIT_NAME/W" "host arch != $TEST_ARCH (no native JIT for re-lowering)"
    595         return
    596     fi
    597     local t0 dt wasm w_cc_err w_run_err w_rc w_missing exp_byte
    598     exp_byte=$(( KIT_EXPECTED & 0xff ))
    599     wasm="$KIT_WORK/$KIT_BASE.wasm"
    600     w_cc_err="$KIT_WORK/w.cc.err"
    601     w_run_err="$KIT_WORK/w.run.err"
    602     t0=$(kit_now_ms)
    603     if ! "$KIT" cc -O0 -target wasm32-none -c "$KIT_SRC" -o "$wasm" \
    604             >"$KIT_WORK/w.cc.out" 2>"$w_cc_err"; then
    605         dt=$(( $(kit_now_ms) - t0 )); kit_time W "$dt"
    606         w_missing=$(grep -oE 'wasm(32 ABI| target)?: .*(not yet implemented|not (yet )?supported|unsupported [a-z_0-9]+|max [0-9]+ supported|supported in v1)' \
    607                     "$w_cc_err" 2>/dev/null | head -n1 || true)
    608         if [ -n "$w_missing" ]; then
    609             kit_skip "$KIT_NAME/W" "$w_missing"
    610         else
    611             kit_fail "$KIT_NAME/W" "cc -target wasm32-none failed; see $w_cc_err"
    612         fi
    613     else
    614         # Validate by exit code only (like D/J/C). cc stderr is not a failure
    615         # on its own: legitimate non-fatal diagnostics such as `#warning` print
    616         # there while compilation succeeds.
    617         "$KIT" run -e test_main "$wasm" >"$KIT_WORK/w.run.out" 2>"$w_run_err"
    618         w_rc=$?
    619         dt=$(( $(kit_now_ms) - t0 )); kit_time W "$dt"
    620         if [ "$w_rc" -eq "$exp_byte" ]; then
    621             kit_pass "$KIT_NAME/W (${dt}ms)"
    622         else
    623             kit_fail "$KIT_NAME/W" "expected $exp_byte got $w_rc, ${dt}ms"
    624         fi
    625     fi
    626 }
    627 
    628 # ---- drive the corpus ------------------------------------------------------
    629 
    630 # Active lanes in DREJCW order.
    631 LANES=
    632 [ $RUN_D -eq 1 ] && LANES="$LANES D"
    633 [ $RUN_R -eq 1 ] && LANES="$LANES R"
    634 [ $RUN_E -eq 1 ] && LANES="$LANES E"
    635 [ $RUN_J -eq 1 ] && LANES="$LANES J"
    636 [ $RUN_C -eq 1 ] && LANES="$LANES C"
    637 [ $RUN_W -eq 1 ] && LANES="$LANES W"
    638 
    639 # Paths C/W force opt_level=0 internally; running them at every requested opt
    640 # level would duplicate identical work. When C and/or W are the ONLY enabled
    641 # lanes, collapse the opt axis to "0" so the matrix isn't padded with items
    642 # that produce no records. (The engine already gates C/W to opt 0 via
    643 # KIT_OPT0ONLY; this just trims the redundant opt=1 expansion.)
    644 CASE_OPT_LEVELS="$OPT_LEVELS"
    645 if [ $RUN_D -eq 0 ] && [ $RUN_R -eq 0 ] && [ $RUN_E -eq 0 ] && [ $RUN_J -eq 0 ] \
    646         && { [ $RUN_C -eq 1 ] || [ $RUN_W -eq 1 ]; }; then
    647     CASE_OPT_LEVELS="0"
    648 fi
    649 
    650 PAR="${KIT_PARSE_PARALLEL:-1}"
    651 
    652 printf 'test-parse-ok target=%s obj=%s arch=%s\n' "$CLANG_TRIPLE" "$HOST_OBJ_FMT" "$TEST_ARCH"
    653 
    654 KIT_LABEL=test-parse-ok KIT_BUILD_DIR="$BUILD_DIR/parse" \
    655     KIT_CORPUS_GLOBS="$TEST_DIR/cases/*.c" KIT_CORPUS_EXT=c KIT_SIDECAR_DIR="$TEST_DIR/cases" \
    656     KIT_LANES="$LANES" KIT_OPT_LEVELS="$CASE_OPT_LEVELS" KIT_TUPLES="$TEST_ARCH-noobj" \
    657     KIT_OPT0ONLY="C W" KIT_TARGETS_EXT="" KIT_PARALLELIZABLE="$PAR" \
    658     kit_corpus_run
    659 
    660 # Treat skips (cross-target without a runner, the data-model-bound .skip
    661 # sidecars — i128_*/ldbl128_* under ILP32, long-double-aliased targets, ...) as
    662 # non-fatal, matching the toy runner: only a real FAIL (wrong exit code) or an
    663 # XPASS (stale skip) gates the exit. Per-lane opt-ins like KIT_TEST_ALLOW_SKIP=1
    664 # are now redundant but harmless.
    665 KIT_SKIP_IS_FAILURE=0
    666 kit_summary test-parse-ok
    667 kit_exit