run.sh (31579B)
1 #!/usr/bin/env bash 2 # test/toy/run.sh — .toy frontend end-to-end tests, on the shared corpus 3 # harness (test/lib/kit_corpus.sh). Lanes (KIT_TEST_PATHS, default RLCWI): 4 # R kit run -O{level} case.toy (JIT native) 5 # I kit run --no-jit -O{level} case.toy (IR interpreter) 6 # Ops the interpreter does not yet implement SKIP (greppable 7 # "interp: <feature> not supported"), like paths C/W. 8 # L kit cc -O{level} -c -> kit ld -> native exec. Has a <name>.objdump 9 # sidecar substring check (separate :objdump verdict) and a 10 # <name>.link.skip sidecar. 11 # X cross-arch: kit cc -O{level} -target -> kit ld -> exec_target for the 12 # aa64/x64/rv64 Linux targets. The freestanding _start/TLS stub is also 13 # built by kit cc -target (no host toolchain), so the whole path — case 14 # object, startup stub, and link — is kit end to end. Exec is deferred to 15 # the engine's batched exec_target flush (kit_queue_e). Opt-in (not in the 16 # default paths). 17 # C kit cc --emit=c case.toy -> host cc -> native exec. Exercises the 18 # --emit=c C-source backend driven by a non-C frontend (validates that the 19 # CGTarget seam is frontend-agnostic). Phased-rollout panics from the C 20 # target report as SKIP. Host cc runs under -Wall -Wextra -Werror; fixtures 21 # wrap their i64 main in a small i32 thunk so the emitted 22 # `int32_t main(void)` satisfies the standard. opt-0 only. 23 # W kit cc -target wasm32-none -c case.toy -> .wasm; then kit run on the 24 # .wasm (routes through the lang/wasm frontend back to native CG, JITs, and 25 # invokes main). Exercises the Wasm backend (Toy -> wasm). opt-0 only. 26 # Most cases hit unimplemented Wasm lowerings -> phased-rollout SKIP. 27 # 28 # Sidecars: 29 # <name>.expected expected process exit code, default 0 30 # <name>.objdump fixed substrings expected in `kit objdump -h -t` 31 # after the linked-object compile path (lane L) 32 # <name>.cbackend.skip opts the case out of path C (with reason) 33 # <name>.wasm.skip opts the case out of path W (with reason) 34 # <name>.link.skip opts the case out of path L (with reason) 35 # <name>.link.hosted forces path L to link hosted (kit cc -lc) for this 36 # case regardless of platform default (with reason) 37 # err/<name>.expected expected diagnostic substring for compile-fail cases 38 # err/<name>.ccargs extra `kit cc -c` flags for that err case (e.g. a 39 # -target triple for a target-specific diagnostic) 40 # 41 # Filtering: 42 # ./run.sh [name_filter] [paths] 43 # KIT_TEST_FILTER / KIT_TEST_PATHS, where paths is a subset of "RLXCWI". 44 # X is opt-in cross-arch cc+ld+exec for aa64, x64, and rv64. 45 # C and W run only at O0 even when included with other opt levels. 46 # Default paths are "RLCWI"; override with KIT_TEST_PATHS. 47 # KIT_OPT_LEVELS selects optimization levels. 48 # 49 # Every lane hook writes only under KIT_WORK and records via kit_*, so the runner 50 # is parallel-safe; KIT_TOY_PARALLEL flips dispatch. 51 52 set -u 53 54 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 55 export KIT_LIB_DIR="$ROOT/test/lib" 56 . "$ROOT/test/lib/kit_corpus.sh" 57 58 TEST_DIR="$ROOT/test/toy" 59 BUILD_DIR="$ROOT/build/test/toy" 60 KIT="${KIT:-$ROOT/build/kit}" 61 62 FILTER="${1:-${KIT_TEST_FILTER:-}}" 63 PATHS="${2:-${KIT_TEST_PATHS:-RLCWI}}" 64 case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac 65 case "$PATHS" in *L*) RUN_L=1;; *) RUN_L=0;; esac 66 case "$PATHS" in *X*) RUN_X=1;; *) RUN_X=0;; esac 67 case "$PATHS" in *C*) RUN_C=1;; *) RUN_C=0;; esac 68 case "$PATHS" in *W*) RUN_W=1;; *) RUN_W=0;; esac 69 case "$PATHS" in *I*) RUN_I=1;; *) RUN_I=0;; esac 70 TOY_CROSS_ARCHS="${KIT_TOY_CROSS_ARCHS:-aa64 x64 rv64 rv32}" 71 TOY_OPT_LEVELS="${KIT_OPT_LEVELS:-0 1}" 72 HOST_CC="${CC:-cc}" 73 PAR="${KIT_TOY_PARALLEL:-1}" 74 75 # Is the host C compiler gcc (vs clang)? GCC's tail-call checker rejects the 76 # c_target's `__attribute__((musttail)) return <sret-call>` ("cannot tail-call: 77 # return value used after call") where clang honors it, so musttail+sret cases 78 # carry a `.cbackend.gcc.skip` sidecar that the C lane respects only on gcc. 79 case "$($HOST_CC --version 2>&1 | head -n1)" in 80 *clang*) TOY_HOST_CC_IS_GCC=0 ;; 81 *) TOY_HOST_CC_IS_GCC=1 ;; 82 esac 83 84 # Lane L links the kit object and runs the result natively. A freestanding 85 # `kit ld` (no crt) is runnable where the format drives the entry to `main` 86 # directly (Mach-O's LC_MAIN), but a native ELF executable needs a crt-provided 87 # `_start`, so on ELF hosts we drive the link through `kit cc -lc` (kit's hosted 88 # profile pulls the crt that calls main). kit ld is still exercised -- cc just 89 # invokes it with the crt/libc inputs. Override with KIT_TOY_L_HOSTED=0|1. 90 case "$(uname -s)" in 91 Darwin) TOY_L_HOSTED_DEFAULT=0 ;; 92 *) TOY_L_HOSTED_DEFAULT=1 ;; 93 esac 94 TOY_L_HOSTED="${KIT_TOY_L_HOSTED:-$TOY_L_HOSTED_DEFAULT}" 95 96 # The engine's KIT_TEST_FILTER drives discovery; honor the positional filter. 97 export KIT_TEST_FILTER="$FILTER" 98 99 shopt -s nullglob 100 mkdir -p "$BUILD_DIR" 101 102 if [ ! -x "$KIT" ]; then 103 printf 'missing kit binary: %s\n' "$KIT" >&2 104 exit 2 105 fi 106 107 # ---- shared rc oracle (KIT_WORK-confined -> parallel-safe) ------------------- 108 # Toy's pass rule is stricter than wasm's: rc must match AND stderr must be 109 # empty. Diagnostics (expected/got + the stderr body) are surfaced on FAIL. 110 tf_check_rc() { # LABEL GOT EXPECTED STDERR_FILE 111 local label="$1" got="$2" expected="$3" stderr_file="$4" 112 expected=$((expected & 255)) 113 if [ "$got" -eq "$expected" ] && [ ! -s "$stderr_file" ]; then 114 kit_pass "$label" 115 else 116 kit_fail "$label" "expected rc $expected, got $got" 117 if [ -s "$stderr_file" ]; then 118 sed 's/^/ | /' "$stderr_file" 119 fi 120 fi 121 } 122 123 # ---- lanes (cases corpus) -------------------------------------------------- 124 kit_lane_R() { 125 local label="$KIT_BASE/R-O$KIT_OPT" rc 126 "$KIT" run "-O$KIT_OPT" "$KIT_SRC" > "$KIT_WORK/run.out" 2> "$KIT_WORK/run.err" 127 rc=$? 128 tf_check_rc "$label" "$rc" "$KIT_EXPECTED" "$KIT_WORK/run.err" 129 } 130 131 # Path I: kit run --no-jit — execute through the IR interpreter instead of 132 # JIT-compiled native code, and assert the same exit code as the golden. Ops the 133 # interpreter does not yet implement emit a greppable 134 # "interp: <feature> not supported" diagnostic and SKIP rather than FAIL. 135 kit_lane_I() { 136 local label="$KIT_BASE/I-O$KIT_OPT" rc missing 137 "$KIT" run --no-jit "-O$KIT_OPT" "$KIT_SRC" \ 138 > "$KIT_WORK/interp.out" 2> "$KIT_WORK/interp.err" 139 rc=$? 140 missing=$(grep -oE 'interp: .*not supported' "$KIT_WORK/interp.err" 2>/dev/null | head -n1 || true) 141 if [ -n "$missing" ]; then 142 kit_skip "$label" "$missing" 143 return 144 fi 145 tf_check_rc "$label" "$rc" "$KIT_EXPECTED" "$KIT_WORK/interp.err" 146 } 147 148 kit_lane_L() { 149 local label="$KIT_BASE/L-O$KIT_OPT" rc 150 local obj="$KIT_WORK/$KIT_BASE.o" exe="$KIT_WORK/$KIT_BASE.exe" 151 local cc_err="$KIT_WORK/cc.err" ld_err="$KIT_WORK/ld.err" 152 local dump="$KIT_WORK/objdump.out" dump_err="$KIT_WORK/objdump.err" 153 local dump_exp="${KIT_SRC%.toy}.objdump" 154 # The .objdump goldens encode section/symbol spellings, which differ by 155 # object format (Mach-O `__TEXT,__const` vs ELF `.rodata`...). On non-Darwin 156 # (ELF) hosts, prefer a `.objdump.elf` sidecar when present; fall back to the 157 # Mach-O golden otherwise so cases without an ELF variant still run. 158 if [ "$(uname -s)" != "Darwin" ] && [ -f "${KIT_SRC%.toy}.objdump.elf" ]; then 159 dump_exp="${KIT_SRC%.toy}.objdump.elf" 160 fi 161 local link_skip="${KIT_SRC%.toy}.link.skip" 162 local link_hosted="${KIT_SRC%.toy}.link.hosted" 163 local pattern missing 164 165 if [ -e "$link_skip" ]; then 166 kit_skip "$label" "$(head -n1 "$link_skip")" 167 return 168 fi 169 170 # A <name>.link.hosted sidecar forces this case through the hosted link 171 # (kit cc -lc) regardless of the platform default. Used for cases that need 172 # the libc/dyld runtime to link at all (e.g. thread-locals, whose Mach-O TLV 173 # / ELF static-TLS setup is supplied by the hosted crt, not bare `kit ld`). 174 local case_hosted="$TOY_L_HOSTED" 175 if [ -e "$link_hosted" ]; then 176 case_hosted=1 177 fi 178 179 if ! "$KIT" cc "-O$KIT_OPT" -c "$KIT_SRC" -o "$obj" \ 180 > "$KIT_WORK/cc.out" 2> "$cc_err"; then 181 kit_fail "$label" "kit cc -O$KIT_OPT -c failed" 182 sed 's/^/ | /' "$cc_err" 183 return 184 fi 185 if [ -s "$cc_err" ]; then 186 kit_fail "$label" "kit cc -O$KIT_OPT -c wrote stderr" 187 sed 's/^/ | /' "$cc_err" 188 return 189 fi 190 191 if [ -f "$dump_exp" ]; then 192 if ! "$KIT" objdump -h -t "$obj" > "$dump" 2> "$dump_err"; then 193 kit_fail "$label:objdump" "kit objdump failed" 194 sed 's/^/ | /' "$dump_err" 195 return 196 fi 197 missing=0 198 while IFS= read -r pattern || [ -n "$pattern" ]; do 199 [ -z "$pattern" ] && continue 200 if ! grep -F -q -- "$pattern" "$dump"; then 201 missing=1 202 printf '%s\n' "$pattern" >> "$KIT_WORK/objdump.missing" 203 fi 204 done < "$dump_exp" 205 if [ "$missing" -eq 0 ]; then 206 kit_pass "$label:objdump" 207 else 208 kit_fail "$label:objdump" "missing objdump substring(s)" 209 printf ' missing objdump substring(s):\n' 210 sed 's/^/ > /' "$KIT_WORK/objdump.missing" 211 printf ' actual objdump:\n' 212 sed 's/^/ | /' "$dump" 213 fi 214 fi 215 216 if [ "$case_hosted" = "1" ]; then 217 if ! "$KIT" cc "$obj" -lc -o "$exe" > "$KIT_WORK/ld.out" 2> "$ld_err"; then 218 kit_fail "$label" "kit cc -lc link failed" 219 sed 's/^/ | /' "$ld_err" 220 return 221 fi 222 else 223 if ! "$KIT" ld "$obj" -o "$exe" > "$KIT_WORK/ld.out" 2> "$ld_err"; then 224 kit_fail "$label" "kit ld failed" 225 sed 's/^/ | /' "$ld_err" 226 return 227 fi 228 fi 229 if [ -s "$ld_err" ]; then 230 kit_fail "$label" "kit link wrote stderr" 231 sed 's/^/ | /' "$ld_err" 232 return 233 fi 234 235 chmod +x "$exe" 2>/dev/null || true 236 "$exe" > "$KIT_WORK/exe.out" 2> "$KIT_WORK/exe.err" 237 rc=$? 238 tf_check_rc "$label" "$rc" "$KIT_EXPECTED" "$KIT_WORK/exe.err" 239 } 240 241 # ---- cross-arch (path X) helpers ------------------------------------------- 242 cross_triple_for() { 243 case "$1" in 244 aa64|aarch64) printf 'aarch64-linux-gnu' ;; 245 x64|x86_64) printf 'x86_64-linux-gnu' ;; 246 rv64|riscv64) printf 'riscv64-linux-gnu' ;; 247 *) return 1 ;; 248 esac 249 } 250 251 cross_tag_for() { 252 case "$1" in 253 aa64|aarch64) printf 'aarch64-linux' ;; 254 x64|x86_64) printf 'x64-linux' ;; 255 rv64|riscv64) printf 'rv64-linux' ;; 256 *) return 1 ;; 257 esac 258 } 259 260 cross_start_triple_for() { 261 local arch="$1" triple="$2" 262 case "$arch" in 263 rv64|riscv64) 264 # The X lane links a no-libc static image with this custom _start. 265 # Compile the rv64 startup object as freestanding so kit ld keeps 266 # the same +16 TCB-biased TLS convention that the stub installs, 267 # while the Toy object itself still exercises the Linux target. 268 printf 'riscv64-none-elf' ;; 269 *) 270 printf '%s' "$triple" ;; 271 esac 272 } 273 274 cross_make_start_obj() { 275 local arch="$1" triple="$2" work="$3" start_triple 276 local start_c="$work/$arch.start.c" start_o="$work/$arch.start.o" 277 start_triple="$(cross_start_triple_for "$arch" "$triple")" 278 cat > "$start_c" <<'EOF_START' 279 extern int main(void); 280 281 /* kit-ld synthesizes the .tdata template boundary symbols for a static 282 * image. The per-thread TLS image size is their difference, computed as a 283 * pointer subtraction so each symbol's PC-relative bias cancels — a lone 284 * kit boundary symbol doesn't resolve cleanly under -fno-pic. The toy 285 * corpus has no uninitialized (.tbss) thread-locals, so the image is 286 * exactly .tdata; any .tbss tail is covered by g_tls_block's zero-fill. */ 287 extern char __tdata_start[]; 288 extern char __tdata_end[]; 289 290 /* Per-thread static-TLS image. kit resolves a local-exec var at .tdata 291 * image offset `o` to a thread-pointer-relative `o + 16` on aarch64/riscv64 292 * (a 16-byte TCB sits ahead of .tdata; see src/obj/elf/link.c) and to a 293 * negative offset on x86_64 (variant II, TCB after the image). The 294 * freestanding stub must seed this block and set the thread pointer ITSELF: 295 * the cross runtimes don't do it for a no-libc static binary — notably 296 * qemu-riscv64 user-mode leaves tp pointing at uninitialized memory. The 297 * block is zero-initialized (.bss); single-threaded, so file scope is fine. */ 298 static char g_tls_block[4096] __attribute__((aligned(16))); 299 300 static void tls_init(void) { 301 unsigned long td_n = (unsigned long)(__tdata_end - __tdata_start); 302 unsigned long i; 303 #if defined(__aarch64__) 304 /* Variant I: tp -> [TCB(16) | tdata]; var at tp + (off + 16). */ 305 char* dst = g_tls_block + 16; 306 for (i = 0; i < td_n; ++i) dst[i] = __tdata_start[i]; 307 __asm__ volatile("msr tpidr_el0, %0" ::"r"(g_tls_block) : "memory"); 308 #elif defined(__riscv) && __riscv_xlen == 64 309 /* Variant I: tp -> [TCB(16) | tdata]; var at tp + (off + 16). */ 310 char* dst = g_tls_block + 16; 311 for (i = 0; i < td_n; ++i) dst[i] = __tdata_start[i]; 312 __asm__ volatile("mv tp, %0" ::"r"(g_tls_block) : "memory"); 313 #elif defined(__x86_64__) 314 /* Variant II: TLS bytes at negative offsets from %fs, which points at the 315 * TCB whose first word is a self-pointer (kit reads it via %fs:0). Lay out 316 * [tdata | TCB] and arch_prctl(ARCH_SET_FS, &TCB) so a var at offset `o` 317 * lands at fs + (o - td_n) = &tdata[o]. */ 318 char* tcb = g_tls_block + td_n; 319 for (i = 0; i < td_n; ++i) g_tls_block[i] = __tdata_start[i]; 320 *(void**)tcb = tcb; 321 register long rax __asm__("rax") = 158; /* arch_prctl */ 322 register long rdi __asm__("rdi") = 0x1002; /* ARCH_SET_FS */ 323 register long rsi __asm__("rsi") = (long)tcb; 324 __asm__ volatile("syscall" : "+r"(rax) : "r"(rdi), "r"(rsi) 325 : "rcx", "r11", "memory"); 326 #else 327 #error unsupported target 328 #endif 329 } 330 331 __attribute__((noreturn)) static void do_exit(int code) { 332 #if defined(__aarch64__) 333 register long x8 __asm__("x8") = 94; 334 register long x0 __asm__("x0") = code; 335 __asm__ volatile("svc #0" ::"r"(x8), "r"(x0) : "memory"); 336 #elif defined(__x86_64__) 337 register long rax __asm__("rax") = 231; 338 register long rdi __asm__("rdi") = code; 339 __asm__ volatile("syscall" ::"r"(rax), "r"(rdi) : "memory"); 340 #elif defined(__riscv) && __riscv_xlen == 64 341 register long a7 __asm__("a7") = 94; 342 register long a0 __asm__("a0") = code; 343 __asm__ volatile("ecall" ::"r"(a7), "r"(a0) : "memory"); 344 #else 345 #error unsupported target 346 #endif 347 __builtin_unreachable(); 348 } 349 350 #if defined(__x86_64__) 351 __attribute__((force_align_arg_pointer)) 352 #endif 353 void _start(void) { 354 tls_init(); 355 do_exit(main()); 356 } 357 EOF_START 358 # Compile the startup stub with kit cc (the tool under test), not a host 359 # toolchain: the X path exercises kit cc + kit ld end to end, stub 360 # included. -fno-PIC/-fno-pie give absolute (non-GOT) relocations for the 361 # kit-ld-synthesized __tdata_{start,end} boundary symbols, matching the 362 # static ET_EXEC the link produces. (kit cc has no -fno-stack-protector.) 363 if ! "$KIT" cc -O1 -target "$start_triple" -ffreestanding \ 364 -fno-PIC -fno-pie -c "$start_c" -o "$start_o" \ 365 > "$work/$arch.start.out" 2> "$work/$arch.start.err"; then 366 return 1 367 fi 368 printf '%s' "$start_o" 369 } 370 371 # rv32 cross arch: freestanding (`-none-elf`), so it cannot use the Linux 372 # qemu-user / exec_target path the other cross arches share. Compile for 373 # riscv32-none-elf and run the bare-metal image under qemu-system-riscv32 via the 374 # shared helper. Runs inline (one qemu-system boot per image) rather than through 375 # the deferred exec_target queue. 376 # 377 # A `<name>.rv32.skip` sidecar opts a single case out (the standard per-lane 378 # mechanism, like `.link.skip` / `.wasm.skip`) — but NONE are committed: the real 379 # rv32 gaps surfaced by this lane (i64 atomics, the 64-bit overflow intrinsic, 380 # i64 varargs, thread-local storage, a toy soft-float compare lowering) are left 381 # RED on purpose, so they are not silently hidden. The sidecar exists only for 382 # cases that are genuinely inapplicable to rv32 (and may be added later). The 383 # shared asmnop skip (an aa64-only construct, already skipped for every non-aa64 384 # cross arch) and the env-unavailable skip are the only built-in non-run verdicts. 385 cross_one_rv32() { 386 local label="$KIT_BASE/X-O$KIT_OPT:rv32" rc reason exp obj cc_err 387 local skip="${KIT_SRC%.toy}.rv32.skip" 388 if [ -e "$skip" ]; then kit_skip "$label" "$(head -n1 "$skip")"; return; fi 389 if [ "${RV32_BARE_OK:-0}" -ne 1 ]; then 390 kit_skip "$label" "no rv32 runner (qemu-system-riscv32)"; return; fi 391 if grep -q 'asmnop' "$KIT_SRC" 2>/dev/null; then 392 kit_skip "$label" "asmnop is target-specific before toy asm selectors"; return; fi 393 obj="$KIT_WORK/$KIT_BASE.O$KIT_OPT.rv32.o"; cc_err="$KIT_WORK/rv32.cc.err" 394 if ! "$KIT" cc "-O$KIT_OPT" -target riscv32-none-elf \ 395 -march=rv32imafc_zicsr_zifencei -mabi=ilp32f -ffreestanding \ 396 -c "$KIT_SRC" -o "$obj" > "$KIT_WORK/rv32.cc.out" 2> "$cc_err"; then 397 kit_fail "$label" "kit cc -target riscv32-none-elf failed" 398 sed 's/^/ | /' "$cc_err"; return 399 fi 400 if [ -s "$cc_err" ]; then 401 kit_fail "$label" "kit cc rv32 wrote stderr"; sed 's/^/ | /' "$cc_err"; return; fi 402 reason="$(rv32_bare_run "$obj" "$KIT_WORK" "$KIT_WORK/rv32.rc")" 403 if [ $? -eq 2 ]; then kit_skip "$label" "$reason"; return; fi 404 rc="$(cat "$KIT_WORK/rv32.rc" 2>/dev/null || echo 99)"; exp=$(( KIT_EXPECTED & 255 )) 405 # Bare-metal has no stderr channel; the finisher maps main()'s return onto the 406 # qemu exit code, so compare it directly to the expected exit code. 407 if [ "$rc" -eq "$exp" ]; then kit_pass "$label" 408 else kit_fail "$label" "expected rc $exp, got $rc (qemu-system-riscv32)"; fi 409 } 410 411 cross_one() { 412 local arch="$1" 413 local triple tag obj exe start_obj cc_err ld_err out err label 414 if [ "$arch" = "rv32" ]; then cross_one_rv32; return; fi 415 triple="$(cross_triple_for "$arch")" || { 416 kit_skip "$KIT_BASE/X-O$KIT_OPT:$arch" "unknown cross arch" 417 return 418 } 419 tag="$(cross_tag_for "$arch")" || { 420 kit_skip "$KIT_BASE/X-O$KIT_OPT:$arch" "unknown cross arch" 421 return 422 } 423 label="$KIT_BASE/X-O$KIT_OPT:$arch" 424 if [ "$arch" != "aa64" ] && [ "$arch" != "aarch64" ] && 425 grep -q 'asmnop' "$KIT_SRC"; then 426 kit_skip "$label" "asmnop is target-specific before toy asm selectors" 427 return 428 fi 429 # Arch-specific cases name themselves with an _aa64/_x64/_rv64 basename 430 # suffix and use intrinsics that only lower on that arch (e.g. the aa64 431 # privileged wfi/wfe/DAIF pair has no x64/rv64 lowering). Cross-compiling 432 # them onto a sibling arch is expected to fail, so skip the mismatches. 433 case "$KIT_BASE" in 434 *_aa64) case "$arch" in aa64|aarch64) ;; *) 435 kit_skip "$label" "aa64-only case (intrinsics have no $arch lowering)"; return ;; esac ;; 436 *_x64) case "$arch" in x64|x86_64) ;; *) 437 kit_skip "$label" "x64-only case (intrinsics have no $arch lowering)"; return ;; esac ;; 438 *_rv64) case "$arch" in rv64|riscv64) ;; *) 439 kit_skip "$label" "rv64-only case (intrinsics have no $arch lowering)"; return ;; esac ;; 440 esac 441 if ! exec_target_supported "$tag"; then 442 kit_skip "$label" "no runner for $tag" 443 return 444 fi 445 446 obj="$KIT_WORK/$KIT_BASE.O$KIT_OPT.$arch.o" 447 exe="$KIT_WORK/$KIT_BASE.O$KIT_OPT.$arch.exe" 448 cc_err="$KIT_WORK/$arch.cc.err" 449 ld_err="$KIT_WORK/$arch.ld.err" 450 out="$KIT_WORK/$arch.out" 451 err="$KIT_WORK/$arch.err" 452 453 if ! "$KIT" cc "-O$KIT_OPT" -target "$triple" -c "$KIT_SRC" -o "$obj" \ 454 > "$KIT_WORK/$arch.cc.out" 2> "$cc_err"; then 455 kit_fail "$label" "kit cc -O$KIT_OPT -target $triple -c failed" 456 sed 's/^/ | /' "$cc_err" 457 return 458 fi 459 if [ -s "$cc_err" ]; then 460 kit_fail "$label" "kit cc -O$KIT_OPT -target $triple -c wrote stderr" 461 sed 's/^/ | /' "$cc_err" 462 return 463 fi 464 465 # The stub is compiled by kit cc (the tool under test), so a failure here 466 # is a real codegen bug, not a missing host toolchain — surface it as FAIL. 467 start_obj="$(cross_make_start_obj "$arch" "$triple" "$KIT_WORK")" || { 468 kit_fail "$label" "kit cc failed on startup stub" 469 sed 's/^/ | /' "$KIT_WORK/$arch.start.err" 470 return 471 } 472 473 # kit ld links a non-PIE static ET_EXEC by default (no -pie here), so the 474 # freestanding image lands at IMAGE_BASE_STATIC (0x400000): a PIE/ET_DYN 475 # would be based at vaddr 0, putting its writable segments below the 476 # loader's mmap_min_addr where qemu-user maps them non-writable and the 477 # stub's TLS-block store faults. 478 if ! "$KIT" ld "$obj" "$start_obj" -o "$exe" \ 479 > "$KIT_WORK/$arch.ld.out" 2> "$ld_err"; then 480 kit_fail "$label" "kit ld failed" 481 sed 's/^/ | /' "$ld_err" 482 return 483 fi 484 if [ -s "$ld_err" ]; then 485 kit_fail "$label" "kit ld wrote stderr" 486 sed 's/^/ | /' "$ld_err" 487 return 488 fi 489 490 chmod +x "$exe" 2>/dev/null || true 491 # Defer execution: the engine batches one container run per arch and checks 492 # rc == expected after exec_target_flush; tf_flush_verify (KIT_FLUSH_VERIFY) 493 # adds the empty-stderr check after filtering the platform-mismatch warning. 494 kit_queue_e "$label" "$exe" "$out" "$err" "$KIT_WORK/$arch.rc" "$KIT_EXPECTED" "$tag" "$err" 495 } 496 497 kit_lane_X() { 498 local arch 499 for arch in $TOY_CROSS_ARCHS; do 500 cross_one "$arch" 501 done 502 } 503 504 # KIT_FLUSH_VERIFY: called per queued-X case after flush with (label, payload, 505 # rc). The engine already verified rc == expected; here we replicate the 506 # original's "stderr must be empty (after filtering the image-platform warning)" 507 # rule. payload is the err file path. Returns 0 to keep pass, 1 to fail. 508 tf_flush_verify() { 509 local errf="$2" 510 [ -n "$errf" ] || return 0 511 if [ -s "$errf" ]; then 512 grep -v '^WARNING: image platform .* does not match the expected platform' \ 513 "$errf" > "$errf.clean" 2>/dev/null || true 514 mv "$errf.clean" "$errf" 2>/dev/null || true 515 fi 516 [ ! -s "$errf" ] 517 } 518 519 # ---- C-source backend (path C, opt-0 only) --------------------------------- 520 kit_lane_C() { 521 local label="$KIT_BASE/C-O$KIT_OPT" rc missing 522 local cbackend_skip="${KIT_SRC%.toy}.cbackend.skip" 523 if [ -e "$cbackend_skip" ]; then 524 kit_skip "$label" "$(head -n1 "$cbackend_skip")" 525 return 526 fi 527 local cbackend_gcc_skip="${KIT_SRC%.toy}.cbackend.gcc.skip" 528 if [ "$TOY_HOST_CC_IS_GCC" = 1 ] && [ -e "$cbackend_gcc_skip" ]; then 529 kit_skip "$label" "$(head -n1 "$cbackend_gcc_skip")" 530 return 531 fi 532 local out_c="$KIT_WORK/$KIT_BASE.kit.c" 533 local out_bin="$KIT_WORK/$KIT_BASE.cbackend.bin" 534 local emit_err="$KIT_WORK/c.emit.err" 535 local cc_err="$KIT_WORK/c.cc.err" 536 local run_err="$KIT_WORK/c.run.err" 537 538 # --emit=c forces opt_level=0 internally; pass -O$KIT_OPT anyway so the 539 # driver flag parsing stays exercised. 540 if ! "$KIT" cc "-O$KIT_OPT" --emit=c "$KIT_SRC" -o "$out_c" \ 541 > "$KIT_WORK/c.emit.out" 2> "$emit_err"; then 542 # Phased-rollout panic from the C target -> SKIP, not FAIL. 543 missing=$(grep -oE 'C target: .*(not implemented|not yet supported)' \ 544 "$emit_err" 2>/dev/null | head -n1 || true) 545 if [ -n "$missing" ]; then 546 kit_skip "$label" "$missing" 547 return 548 fi 549 kit_fail "$label" "kit cc --emit=c failed" 550 sed 's/^/ | /' "$emit_err" 551 return 552 fi 553 # Fixtures wrap their i64 main in an `fn main(): i32` thunk so the emitted 554 # `int32_t main(void)` satisfies the host C compiler's main-return-type check. 555 # -Wno-tentative-definition-incomplete-type: the c_target forward-declares a 556 # data global as `static const struct __kit_data_g g;` before defining that 557 # per-global struct later in the same TU. It is valid C (g is completed by 558 # end of TU; clang>=15 and gcc accept it), but clang 14's pedantic warning 559 # rejects the forward decl under -Werror. clang has the flag; gcc ignores the 560 # unknown -Wno- silently, so this is safe across host toolchains. 561 if ! $HOST_CC -std=gnu99 -Wall -Wextra -Werror \ 562 -Wno-tentative-definition-incomplete-type "$out_c" -o "$out_bin" \ 563 > "$KIT_WORK/c.cc.out" 2> "$cc_err"; then 564 kit_fail "$label" "host cc rejected emitted source" 565 sed 's/^/ | /' "$cc_err" 566 return 567 fi 568 "$out_bin" > "$KIT_WORK/c.run.out" 2> "$run_err" 569 rc=$? 570 tf_check_rc "$label" "$rc" "$KIT_EXPECTED" "$run_err" 571 } 572 573 # ---- Wasm roundtrip (path W, opt-0 only) ----------------------------------- 574 kit_lane_W() { 575 local label="$KIT_BASE/W-O$KIT_OPT" rc missing 576 local wasm_skip="${KIT_SRC%.toy}.wasm.skip" 577 if [ -e "$wasm_skip" ]; then 578 kit_skip "$label" "$(head -n1 "$wasm_skip")" 579 return 580 fi 581 local wasm="$KIT_WORK/$KIT_BASE.wasm" 582 local cc_err="$KIT_WORK/wasm.cc.err" 583 local run_err="$KIT_WORK/wasm.run.err" 584 if ! "$KIT" cc "-O$KIT_OPT" -target wasm32-none -c "$KIT_SRC" -o "$wasm" \ 585 > "$KIT_WORK/wasm.cc.out" 2> "$cc_err"; then 586 # Phased-rollout panic from the Wasm target -> SKIP, not FAIL. The Wasm 587 # target emits "wasm: <feature> not yet implemented" plus a handful of 588 # "unsupported X" / "too many X (max N supported in v1)" / 589 # "max N supported in v1" variants — all meaning this case needs more 590 # wasm CGTarget work. 591 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)' \ 592 "$cc_err" 2>/dev/null | head -n1 || true) 593 if [ -n "$missing" ]; then 594 kit_skip "$label" "$missing" 595 return 596 fi 597 kit_fail "$label" "kit cc -target wasm32-none failed" 598 sed 's/^/ | /' "$cc_err" 599 return 600 fi 601 if [ -s "$cc_err" ]; then 602 kit_fail "$label" "kit cc -target wasm32-none wrote stderr" 603 sed 's/^/ | /' "$cc_err" 604 return 605 fi 606 # kit run on the .wasm routes through the lang/wasm frontend, lowers to 607 # native CG, links via the JIT, and invokes main. 608 "$KIT" run "$wasm" > "$KIT_WORK/wasm.run.out" 2> "$run_err" 609 rc=$? 610 tf_check_rc "$label" "$rc" "$KIT_EXPECTED" "$run_err" 611 } 612 613 # ---- err corpus (compile-fail cases) --------------------------------------- 614 # cc MUST fail; the .expected file holds diagnostic substring(s) that must 615 # appear in stderr (grep -F -f). cc is invoked native (no -target) by default, 616 # matching the original which used `kit cc -c` on the host arch. A case that 617 # needs a non-host target (e.g. a 32-bit-only diagnostic) supplies the extra cc 618 # flags in an err/<name>.ccargs sidecar; its contents are word-split onto the 619 # `kit cc -c` command line. 620 kit_lane_ERR() { 621 local label="$KIT_BASE/E" 622 local obj="$KIT_WORK/$KIT_BASE.o" 623 local err="$KIT_WORK/cc.err" 624 local exp="${KIT_SRC%.toy}.expected" 625 local ccargs_file="${KIT_SRC%.toy}.ccargs" 626 local ccargs="" 627 [ -f "$ccargs_file" ] && ccargs="$(cat "$ccargs_file")" 628 if "$KIT" cc -c $ccargs "$KIT_SRC" -o "$obj" > "$KIT_WORK/cc.out" 2> "$err"; then 629 kit_fail "$label" "expected compile failure, got success" 630 return 631 fi 632 if [ ! -f "$exp" ]; then 633 kit_fail "$label" "missing expected diagnostic: $exp" 634 return 635 fi 636 if grep -F -q -f "$exp" "$err"; then 637 kit_pass "$label" 638 else 639 kit_fail "$label" "expected diagnostic substring not found" 640 printf ' expected diagnostic substring:\n' 641 sed 's/^/ > /' "$exp" 642 printf ' actual stderr:\n' 643 sed 's/^/ | /' "$err" 644 fi 645 } 646 647 # ---- exec_target wiring (path X uses the engine's deferred-exec flush) ------ 648 if [ "$RUN_X" -eq 1 ]; then 649 have_podman=0 650 command -v podman >/dev/null 2>&1 && have_podman=1 651 QEMU_BIN="${QEMU_BIN:-$(command -v qemu-aarch64 2>/dev/null || true)}" 652 have_qemu=0 653 [ -n "$QEMU_BIN" ] && have_qemu=1 654 case "$(uname -m 2>/dev/null)" in 655 aarch64|arm64) is_aarch64=1 ;; 656 *) is_aarch64=0 ;; 657 esac 658 export have_qemu QEMU_BIN have_podman is_aarch64 659 # Every queued exe/out/err/rc path lives under BUILD_DIR; bind-mount it once 660 # so a single batched container drains the whole path-X queue. 661 EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR" 662 export EXEC_TARGET_MOUNT_ROOT 663 # shellcheck source=../lib/exec_target.sh 664 . "$ROOT/test/lib/exec_target.sh" 665 # rv32 is a cross arch in path X, but freestanding (qemu-system bare-metal), 666 # so it uses its own exec helper rather than exec_target's qemu-user path. 667 case " $TOY_CROSS_ARCHS " in 668 *" rv32 "*) 669 # shellcheck source=../lib/exec_rv32_bare.sh 670 . "$ROOT/test/lib/exec_rv32_bare.sh" 671 rv32_bare_setup "$BUILD_DIR/rv32" ;; 672 esac 673 fi 674 675 # ---- drive the corpora ----------------------------------------------------- 676 printf 'toy: KIT=%s\n' "$KIT" 677 printf 'toy: PATHS=%s OPT_LEVELS=%s\n' "$PATHS" "$TOY_OPT_LEVELS" 678 if [ "$RUN_X" -eq 1 ]; then 679 printf 'toy: cross archs=%s (path X)\n' "$TOY_CROSS_ARCHS" 680 fi 681 682 # cases corpus — active lanes in PATHS canonical order R I L X C W. 683 CASE_LANES= 684 [ "$RUN_R" -eq 1 ] && CASE_LANES="$CASE_LANES R" 685 [ "$RUN_I" -eq 1 ] && CASE_LANES="$CASE_LANES I" 686 [ "$RUN_L" -eq 1 ] && CASE_LANES="$CASE_LANES L" 687 [ "$RUN_X" -eq 1 ] && CASE_LANES="$CASE_LANES X" 688 [ "$RUN_C" -eq 1 ] && CASE_LANES="$CASE_LANES C" 689 [ "$RUN_W" -eq 1 ] && CASE_LANES="$CASE_LANES W" 690 691 KIT_LABEL=toy KIT_BUILD_DIR="$BUILD_DIR" \ 692 KIT_CORPUS_GLOBS="$TEST_DIR/cases/*.toy" KIT_CORPUS_EXT=toy \ 693 KIT_SIDECAR_DIR="$TEST_DIR/cases" \ 694 KIT_LANES="$CASE_LANES" KIT_OPT_LEVELS="$TOY_OPT_LEVELS" \ 695 KIT_TUPLES="host-host" KIT_TARGETS_EXT="" KIT_OPT0ONLY="C W" \ 696 KIT_PARALLELIZABLE="$PAR" KIT_FLUSH_VERIFY=tf_flush_verify \ 697 kit_corpus_run 698 699 # err cases exercise compile-failure paths; they aren't relevant to path C/W 700 # (which go through the same cc invocation). Only run them when at least one of 701 # the native compile paths (R/L/X) is enabled. 702 if [ "$RUN_R" -eq 1 ] || [ "$RUN_L" -eq 1 ] || [ "$RUN_X" -eq 1 ]; then 703 err_cases=("$TEST_DIR"/err/*.toy) 704 err_have_match=1 705 if [ -n "${KIT_TEST_FILTER:-}" ]; then 706 err_have_match=0 707 for src in "${err_cases[@]}"; do 708 base="$(basename "${src%.toy}")" 709 case "$base" in *"$KIT_TEST_FILTER"*) err_have_match=1; break ;; esac 710 done 711 fi 712 if [ "${#err_cases[@]}" -gt 0 ] && [ "$err_have_match" -eq 1 ]; then 713 KIT_LABEL=toy KIT_BUILD_DIR="$BUILD_DIR/err" \ 714 KIT_CORPUS_GLOBS="$TEST_DIR/err/*.toy" KIT_CORPUS_EXT=toy \ 715 KIT_SIDECAR_DIR="$TEST_DIR/err" \ 716 KIT_LANES="ERR" KIT_OPT_LEVELS="" KIT_TUPLES="host-host" \ 717 KIT_TARGETS_EXT="" KIT_PARALLELIZABLE="$PAR" \ 718 kit_corpus_run 719 fi 720 fi 721 722 # toy treats skips (interp-unsupported ops, no cross-arch runner, the 723 # intentionally-red musttail cases, ...) as non-fatal — matching the original 724 # runner, which only ever exited nonzero on a real FAIL. 725 KIT_SKIP_IS_FAILURE=0 726 kit_summary toy 727 kit_exit