run.sh (30904B)
1 #!/usr/bin/env bash 2 # test/link/run.sh — linker and JIT test harness, on the shared corpus 3 # harness (test/lib/kit_corpus.sh). 4 # 5 # Cases are DIRECTORIES: test/link/cases/<name>/ and bad/<name>/, each holding 6 # source(s) + marker files. Three lanes per case (KIT_TEST_PATHS, default REJ): 7 # 8 # R test/link/cases/*/ clang-c → .o → kit-roundtrip → structural diff. 9 # Validates obj reader + writer fidelity. 10 # E clang-c → .o → link-exe-runner (kit_link_exe) → exe → qemu/podman 11 # → check exit code (DEFERRED batched exec, except 12 # kernel_image cases which run a SYNCHRONOUS 13 # qemu-system-* via exec_kernel.sh inline). 14 # Validates linker layout and reloc application. 15 # J clang-c → .o → jit-runner (kit_link_jit) → call test_main() 16 # → check return value (host-native arch only). 17 # Validates JIT path. 18 # 19 # Negative tests live in test/link/bad/<name>/ — sources that compile 20 # cleanly but should cause the linker (E) and JIT (J) to reject. Each bad/ 21 # case requires an `expect` file containing a substring that must appear in 22 # stderr. The bad/ corpus is a SECOND kit_corpus_run with negative E/J lanes. 23 # 24 # Case markers (files in the case directory): 25 # expected — expected exit/return value (default 0) 26 # jit_only — skip R and E paths; run J only 27 # use_resolver — pass --use-resolver to jit-runner (case 28) 28 # linker_flags — one flag per line; passed to link-exe-runner and jit-runner 29 # cflags — extra clang -c flags appended to every TU compile 30 # gc_absent — one symbol per line; verified absent post-link 31 # (jit_runner --check-absent for J; llvm-nm scan for E) 32 # gc_present — one symbol per line; verified present post-link 33 # (jit_runner --check-present for J; llvm-nm scan for E) 34 # archive_b — package b.o into b.a; content "demand" or "whole" 35 # linker_script — basename of an .lds file in the case dir; passed via 36 # --linker-script to both runners. The harness first looks 37 # for a per-arch variant (foo.<arch>.lds) before the literal. 38 # kernel_image — empty marker; case is a freestanding kernel image. Skips 39 # R and J; on E runs the linked exe via a per-arch 40 # qemu-system-* invocation (semihosting on aa64; SIFIVE_TEST 41 # MMIO exit on rv64) SYNCHRONOUSLY. 42 # targets — whole-case applicability: listed <arch>-<obj> tuples (one 43 # per line) run; others print SKIP-NA and don't count. 44 # j_targets — per-lane applicability for J only. Listed tuples run J; 45 # others SKIP-NA for J and continue running R/E. 46 # e_targets — peer of j_targets for lane E. 47 # 48 # Per-arch source variants: for each candidate (entry.S, a.S, b.S, a.c, b.c, 49 # c.c), prefer <name>.<TEST_ARCH>.<ext> if present, else the bare <name>.<ext>. 50 # Same for any file referenced by linker_script. 51 # 52 # Filtering: 53 # ./run.sh [name_filter] [paths] 54 # name_filter substring match against case name (e.g. "02", "rodata") 55 # paths subset of "REJ" (default "REJ") 56 # Equivalent env vars: KIT_TEST_FILTER, KIT_TEST_PATHS. 57 # 58 # All lane hooks write only under KIT_WORK and record via kit_*, so the runner 59 # is parallel-safe by construction; KIT_LINK_PARALLEL flips dispatch. The 60 # default is serial (0) because of the cached start.o + synchronous kernel 61 # path; flipping to 1 is a one-line change since every hook is KIT_WORK-confined. 62 63 set -u 64 65 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 66 export KIT_LIB_DIR="$ROOT/test/lib" 67 . "$ROOT/test/lib/kit_corpus.sh" 68 69 TEST_DIR="$ROOT/test/link" 70 BUILD_DIR="$ROOT/build/test" 71 LIB_AR="$ROOT/build/libkit.a" 72 ROUNDTRIP_BIN_ELF="$ROOT/build/test/kit-roundtrip" 73 ROUNDTRIP_BIN_MACHO="$ROOT/build/test/kit-roundtrip-macho" 74 NORMALIZE_ELF="$ROOT/test/elf/normalize.py" 75 NORMALIZE_MACHO="$ROOT/test/macho/normalize.py" 76 77 LINK_EXE_RUNNER="$BUILD_DIR/link-exe-runner" 78 JIT_RUNNER="$BUILD_DIR/jit-runner" 79 80 # KIT_TEST_ARCH and KIT_TEST_OBJ select the cross-target. Defaults 81 # aa64+elf preserve the pre-multiarch behavior. The C runners read the 82 # same env vars via test/lib/kit_test_target.h. 83 KIT_TEST_ARCH="${KIT_TEST_ARCH:-aa64}" 84 KIT_TEST_OBJ="${KIT_TEST_OBJ:-elf}" 85 case "$KIT_TEST_ARCH" in 86 aa64|aarch64|arm64) TEST_ARCH=aa64; EXEC_ARCH=aarch64 ;; 87 x64|x86_64|amd64) TEST_ARCH=x64; EXEC_ARCH=x64 ;; 88 rv64|riscv64) TEST_ARCH=rv64; EXEC_ARCH=rv64 ;; 89 *) printf 'unknown KIT_TEST_ARCH=%s\n' "$KIT_TEST_ARCH" >&2; exit 2 ;; 90 esac 91 case "$KIT_TEST_OBJ" in 92 elf) 93 EXEC_OS=linux 94 case "$TEST_ARCH" in 95 aa64) CLANG_TRIPLE=aarch64-linux-gnu ;; 96 x64) CLANG_TRIPLE=x86_64-linux-gnu ;; 97 rv64) CLANG_TRIPLE=riscv64-linux-gnu ;; 98 esac 99 ROUNDTRIP_BIN="$ROUNDTRIP_BIN_ELF" 100 NORMALIZE="$NORMALIZE_ELF" 101 ;; 102 macho) 103 EXEC_OS=macos 104 case "$TEST_ARCH" in 105 aa64) CLANG_TRIPLE=arm64-apple-macos ;; 106 x64) CLANG_TRIPLE=x86_64-apple-macos ;; 107 rv64) printf 'KIT_TEST_OBJ=macho has no rv64 target\n' >&2; exit 2 ;; 108 esac 109 ROUNDTRIP_BIN="$ROUNDTRIP_BIN_MACHO" 110 NORMALIZE="$NORMALIZE_MACHO" 111 ;; 112 *) printf 'unknown KIT_TEST_OBJ=%s\n' "$KIT_TEST_OBJ" >&2; exit 2 ;; 113 esac 114 EXEC_TAG="${EXEC_ARCH}-${EXEC_OS}" 115 export KIT_TEST_ARCH KIT_TEST_OBJ 116 117 CLANG_TARGET="--target=$CLANG_TRIPLE" 118 119 # Filters (env vars or positional args; args win): 120 # $1 / KIT_TEST_FILTER — substring match against case name (e.g. "02") 121 # $2 / KIT_TEST_PATHS — subset of "REJ" (default "REJ"); selects paths 122 FILTER="${1:-${KIT_TEST_FILTER:-}}" 123 PATHS="${2:-${KIT_TEST_PATHS:-REJ}}" 124 # kit_corpus_discover filters discovery by KIT_TEST_FILTER (substring vs base). 125 export KIT_TEST_FILTER="$FILTER" 126 case "$PATHS" in *R*) RUN_R=1;; *) RUN_R=0;; esac 127 case "$PATHS" in *E*) RUN_E=1;; *) RUN_E=0;; esac 128 case "$PATHS" in *J*) RUN_J=1;; *) RUN_J=0;; esac 129 130 mkdir -p "$BUILD_DIR" 131 mkdir -p "$BUILD_DIR/link" 132 133 # ---- tool detection -------------------------------------------------------- 134 135 have_clang_cross=0 136 have_readelf=0 137 have_python3=0 138 have_qemu=0 139 have_podman=0 140 have_runner=0 141 have_ar=0 142 have_roundtrip=0 143 is_aarch64=0 144 145 if clang $CLANG_TARGET -c -x c - -o /dev/null < /dev/null 2>/dev/null; then 146 have_clang_cross=1 147 fi 148 command -v llvm-readelf >/dev/null 2>&1 && have_readelf=1 149 command -v readelf >/dev/null 2>&1 && have_readelf=1 150 command -v python3 >/dev/null 2>&1 && have_python3=1 151 have_readobj=0 152 command -v llvm-readobj >/dev/null 2>&1 && have_readobj=1 153 command -v readobj >/dev/null 2>&1 && have_readobj=1 154 # Path R needs the right dump tool for the obj format. ELF wants 155 # llvm-readelf; Mach-O wants llvm-readobj. The harness exposes a single 156 # have_dump flag so per-case skip logic doesn't have to branch on the obj. 157 have_dump=0 158 case "$KIT_TEST_OBJ" in 159 elf) [ $have_readelf -eq 1 ] && have_dump=1 ;; 160 macho) [ $have_readobj -eq 1 ] && have_dump=1 ;; 161 esac 162 # Prefer llvm-ar for archive creation: Apple's /usr/bin/ar requires Mach-O 163 # members and silently drops ELF objects, breaking the cross-target archive 164 # cases here. 165 AR_BIN="$(command -v llvm-ar 2>/dev/null || command -v ar 2>/dev/null || true)" 166 [ -n "$AR_BIN" ] && have_ar=1 167 [ -f "$ROUNDTRIP_BIN" ] && have_roundtrip=1 168 169 QEMU_BIN="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)" 170 [ -n "$QEMU_BIN" ] && have_qemu=1 171 command -v podman >/dev/null 2>&1 && have_podman=1 172 { [ $have_qemu -eq 1 ] || [ $have_podman -eq 1 ]; } && have_runner=1 173 174 arch_raw="$(uname -m 2>/dev/null || true)" 175 { [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1 176 177 # is_native_target=1 when the cross-target arch matches the host arch. 178 # Required for the jit-runner (path J). 179 is_native_target=0 180 case "$TEST_ARCH" in 181 aa64) [ $is_aarch64 -eq 1 ] && is_native_target=1 ;; 182 x64) { [ "$arch_raw" = "x86_64" ] || [ "$arch_raw" = "amd64" ]; } && is_native_target=1 ;; 183 rv64) [ "$arch_raw" = "riscv64" ] && is_native_target=1 ;; 184 esac 185 186 READELF_BIN="$(command -v llvm-readelf 2>/dev/null || command -v readelf 2>/dev/null || true)" 187 # llvm-nm works on both ELF and Mach-O; format-agnostic tool for the 188 # gc_present / gc_absent symbol-presence checks. Falls back to plain `nm`. 189 NM_BIN="$(command -v llvm-nm 2>/dev/null || command -v nm 2>/dev/null || true)" 190 have_nm=0 191 [ -n "$NM_BIN" ] && have_nm=1 192 193 # Shared per-arch exec helpers. Path E queues each linked.exe and the engine 194 # drains all cases in one batched flush per arch (amortizes the ~150 ms 195 # per-launch podman client overhead). Kernel images run synchronously via 196 # exec_kernel.sh. 197 EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR" 198 export have_qemu have_podman is_aarch64 QEMU_BIN EXEC_TARGET_MOUNT_ROOT 199 # shellcheck source=../lib/exec_target.sh 200 . "$ROOT/test/lib/exec_target.sh" 201 # shellcheck source=../lib/exec_kernel.sh 202 . "$ROOT/test/lib/exec_kernel.sh" 203 204 # ---- locate harness binaries ----------------------------------------------- 205 # The Makefile's `test-link` target builds these as proper Make targets so 206 # they pick up libkit.a changes. Running this script directly without 207 # `make test-link` is supported but requires the binaries to already exist. 208 209 printf 'Locating harness...\n' 210 211 if [ ! -f "$LIB_AR" ]; then 212 printf ' FATAL: %s not found — run "make lib" first\n' "$LIB_AR" >&2 213 exit 1 214 fi 215 216 have_exe_runner=0 217 have_jit_runner=0 218 219 if [ -x "$LINK_EXE_RUNNER" ]; then 220 have_exe_runner=1 221 printf ' found link-exe-runner\n' 222 else 223 printf ' warn link-exe-runner missing (run "make %s")\n' "$LINK_EXE_RUNNER" >&2 224 fi 225 226 if [ $is_native_target -eq 1 ]; then 227 if [ -x "$JIT_RUNNER" ]; then 228 have_jit_runner=1 229 printf ' found jit-runner\n' 230 else 231 printf ' warn jit-runner missing (run "make %s")\n' "$JIT_RUNNER" >&2 232 fi 233 fi 234 235 # Cached start.o — every case used to recompile this from the same source 236 # (~40 ms × N cases). Build it once for the whole harness run. Hooks only 237 # READ it, so this stays parallel-safe. 238 START_OBJ="$BUILD_DIR/link_start.$TEST_ARCH.$KIT_TEST_OBJ.o" 239 have_start_obj=0 240 if [ $have_clang_cross -eq 1 ]; then 241 if clang $CLANG_TARGET -O1 -ffreestanding -fno-stack-protector \ 242 -fno-PIC -fno-pie \ 243 -c "$TEST_DIR/harness/start.c" -o "$START_OBJ" 2>/dev/null; then 244 have_start_obj=1 245 fi 246 fi 247 248 # Mach-O Path E needs libSystem.tbd for the `exit` import in start.c (and any 249 # libc calls user TUs make). Resolve it via xcrun on Darwin hosts. On non-Darwin 250 # hosts there is no SDK to point at and Mach-O exec is SKIP anyway, so leaving 251 # these empty is fine. 252 MACHO_LIBSYSTEM="" 253 MACHO_DSO_ARGS=() 254 if [ "$KIT_TEST_OBJ" = "macho" ]; then 255 if command -v xcrun >/dev/null 2>&1; then 256 sdk="$(xcrun --show-sdk-path 2>/dev/null || true)" 257 if [ -n "$sdk" ] && [ -f "$sdk/usr/lib/libSystem.tbd" ]; then 258 MACHO_LIBSYSTEM="$sdk/usr/lib/libSystem.tbd" 259 MACHO_DSO_ARGS=(--dso "$MACHO_LIBSYSTEM") 260 fi 261 fi 262 fi 263 264 CUR_TUPLE="${TEST_ARCH}-${KIT_TEST_OBJ}" 265 266 # ---- per-case marker reading (KIT_READ_CASE) -------------------------------- 267 # Runs once per item, before lanes. Reads markers + per-arch source variants 268 # into shell vars the lane hooks consume, then compiles the TUs (shared by all 269 # lanes within the item) into $KIT_WORK. On compile failure, records one FAIL 270 # for the whole case and sets KIT_SKIP_NA_CASE to suppress all lanes (mirrors 271 # the original "note_fail; continue"). 272 link_read_case() { 273 CASE_DIR="${KIT_SRC%/}" # KIT_SRC is the glob match "cases/<name>/" 274 NAME="$KIT_BASE" # case name (== dir basename) 275 276 # Whole-case applicability via `targets` (literal marker name, no base/ext). 277 if [ -f "$CASE_DIR/targets" ]; then 278 applicable=0 279 for tuple in $(cat "$CASE_DIR/targets"); do 280 [ "$tuple" = "$CUR_TUPLE" ] && applicable=1 281 done 282 if [ $applicable -eq 0 ]; then 283 # SKIP-NA whole case (uncounted) — target-specific feature with no 284 # analogue on this tuple. 285 printf ' SKIP-NA %s — N/A on %s\n' "$NAME" "$CUR_TUPLE" 286 KIT_SKIP_NA_CASE=1 287 return 288 fi 289 fi 290 291 # Markers 292 KIT_EXPECTED=0 293 [ -f "$CASE_DIR/expected" ] && KIT_EXPECTED="$(tr -d '[:space:]' < "$CASE_DIR/expected")" 294 JIT_ONLY=0; [ -f "$CASE_DIR/jit_only" ] && JIT_ONLY=1 295 USE_RESOLVER=0; [ -f "$CASE_DIR/use_resolver" ] && USE_RESOLVER=1 296 297 # Per-lane applicability — j_targets / e_targets list tuples on which the 298 # J / E lane can run. R and the other lane still run; only the gated lane 299 # prints SKIP-NA. 300 J_APPLICABLE=1 301 if [ -f "$CASE_DIR/j_targets" ]; then 302 J_APPLICABLE=0 303 for tuple in $(cat "$CASE_DIR/j_targets"); do 304 [ "$tuple" = "$CUR_TUPLE" ] && J_APPLICABLE=1 305 done 306 fi 307 E_APPLICABLE=1 308 if [ -f "$CASE_DIR/e_targets" ]; then 309 E_APPLICABLE=0 310 for tuple in $(cat "$CASE_DIR/e_targets"); do 311 [ "$tuple" = "$CUR_TUPLE" ] && E_APPLICABLE=1 312 done 313 fi 314 315 ARCHIVE_MODE="none" 316 [ -f "$CASE_DIR/archive_b" ] && ARCHIVE_MODE="$(tr -d '[:space:]' < "$CASE_DIR/archive_b")" 317 318 # Extra linker flags (one per line). 319 EXTRA_FLAGS=() 320 if [ -f "$CASE_DIR/linker_flags" ]; then 321 while IFS= read -r flag; do 322 [ -n "$flag" ] && EXTRA_FLAGS+=("$flag") 323 done < "$CASE_DIR/linker_flags" 324 fi 325 326 # GC-absent / GC-present symbols. 327 GC_ABSENT_SYMS=() 328 if [ -f "$CASE_DIR/gc_absent" ]; then 329 while IFS= read -r sym; do 330 [ -n "$sym" ] && GC_ABSENT_SYMS+=("$sym") 331 done < "$CASE_DIR/gc_absent" 332 fi 333 GC_PRESENT_SYMS=() 334 if [ -f "$CASE_DIR/gc_present" ]; then 335 while IFS= read -r sym; do 336 [ -n "$sym" ] && GC_PRESENT_SYMS+=("$sym") 337 done < "$CASE_DIR/gc_present" 338 fi 339 340 # Per-case extra clang cflags (one token per whitespace). 341 CASE_CFLAGS=() 342 if [ -f "$CASE_DIR/cflags" ]; then 343 while IFS= read -r line; do 344 for tok in $line; do 345 [ -n "$tok" ] && CASE_CFLAGS+=("$tok") 346 done 347 done < "$CASE_DIR/cflags" 348 fi 349 350 KERNEL_IMAGE=0 351 [ -f "$CASE_DIR/kernel_image" ] && KERNEL_IMAGE=1 352 353 # Source files (.c and .S). For each candidate, prefer the per-arch variant 354 # (entry.aa64.S beats entry.S when TEST_ARCH=aa64); the bare name is the 355 # fallback. 356 TU_SRCS=() 357 for spec in entry:S a:S b:S a:c b:c c:c; do 358 base="${spec%%:*}"; ext="${spec##*:}" 359 if [ -f "$CASE_DIR/${base}.${TEST_ARCH}.${ext}" ]; then 360 TU_SRCS+=("$CASE_DIR/${base}.${TEST_ARCH}.${ext}") 361 elif [ -f "$CASE_DIR/${base}.${ext}" ]; then 362 TU_SRCS+=("$CASE_DIR/${base}.${ext}") 363 fi 364 done 365 366 # Linker script (marker content is a basename; derive per-arch variant 367 # first, fall back to literal). 368 LINKER_SCRIPT_FILE="" 369 if [ -f "$CASE_DIR/linker_script" ]; then 370 ls_base="$(tr -d '[:space:]' < "$CASE_DIR/linker_script")" 371 ls_stem="${ls_base%.*}" 372 ls_ext="${ls_base##*.}" 373 if [ -f "$CASE_DIR/${ls_stem}.${TEST_ARCH}.${ls_ext}" ]; then 374 LINKER_SCRIPT_FILE="$CASE_DIR/${ls_stem}.${TEST_ARCH}.${ls_ext}" 375 elif [ -f "$CASE_DIR/${ls_base}" ]; then 376 LINKER_SCRIPT_FILE="$CASE_DIR/${ls_base}" 377 fi 378 fi 379 380 # kernel_image cases need an arch-specific entry stub. If the case ships no 381 # entry.<arch>.S (and no bare entry.S), it's structurally inapplicable here. 382 if [ $KERNEL_IMAGE -eq 1 ] && \ 383 [ ! -f "$CASE_DIR/entry.${TEST_ARCH}.S" ] && \ 384 [ ! -f "$CASE_DIR/entry.S" ]; then 385 KIT_SKIP_CASE="kernel_image: no entry.${TEST_ARCH}.S in case" 386 return 387 fi 388 389 # ---- compile with clang cross ----------------------------------------- 390 COMPILE_OK=1 391 if [ $have_clang_cross -eq 0 ]; then 392 # Original emits per-path SKIP lines (R, optionally E, J) and continues. 393 kit_skip "$NAME/R" "no $TEST_ARCH clang" 394 [ $JIT_ONLY -eq 0 ] && kit_skip "$NAME/E" "no $TEST_ARCH clang" 395 kit_skip "$NAME/J" "no $TEST_ARCH clang" 396 KIT_SKIP_NA_CASE=1 397 return 398 fi 399 400 OBJ_FILES=() 401 for src in "${TU_SRCS[@]}"; do 402 base="$(basename "$src")"; base="${base%.c}"; base="${base%.S}" 403 obj="$KIT_WORK/${base}.o" 404 if ! clang $CLANG_TARGET -O1 -fno-inline -ffreestanding -fno-stack-protector \ 405 -fno-PIC -fno-pie -fcommon \ 406 "${CASE_CFLAGS[@]}" \ 407 -c "$src" -o "$obj" 2>"$KIT_WORK/compile_${base}.err"; then 408 COMPILE_OK=0; break 409 fi 410 OBJ_FILES+=("$obj") 411 done 412 413 if [ $COMPILE_OK -eq 0 ]; then 414 kit_fail "$NAME (compile failed)" 415 KIT_SKIP_NA_CASE=1 416 return 417 fi 418 419 # ---- build archive from b.o if requested ------------------------------ 420 # Roundtrip always uses raw .o files; exec/JIT use the archive. 421 RT_OBJ_FILES=("${OBJ_FILES[@]}") # for roundtrip: all .o files 422 LINK_OBJ_FILES=() # for exec/JIT 423 LINK_ARC_FLAGS=() 424 for o in "${OBJ_FILES[@]}"; do 425 base="$(basename "$o" .o)" 426 if [ "$base" = "b" ] && [ "$ARCHIVE_MODE" != "none" ]; then 427 if [ $have_ar -eq 1 ]; then 428 arc="$KIT_WORK/b.a" 429 "$AR_BIN" rcs "$arc" "$o" 2>/dev/null 430 if [ "$ARCHIVE_MODE" = "whole" ]; then 431 LINK_ARC_FLAGS+=(--whole-archive --archive "$arc") 432 else 433 LINK_ARC_FLAGS+=(--archive "$arc") 434 fi 435 else 436 LINK_OBJ_FILES+=("$o") 437 fi 438 else 439 LINK_OBJ_FILES+=("$o") 440 fi 441 done 442 } 443 444 # ---- cases-corpus lanes ---------------------------------------------------- 445 446 # R — roundtrip + golden structural diff. Skipped (no record) for jit_only and 447 # kernel_image cases, matching the original guards. 448 kit_lane_R() { 449 [ $JIT_ONLY -eq 1 ] && return 450 [ $KERNEL_IMAGE -eq 1 ] && return 451 if [ $have_roundtrip -eq 1 ] && [ $have_dump -eq 1 ] && [ $have_python3 -eq 1 ]; then 452 local t0; t0=$(kit_now_ms) 453 local r_ok=1 obj base rt 454 for obj in "${RT_OBJ_FILES[@]}"; do 455 base="$(basename "$obj" .o)" 456 rt="$KIT_WORK/${base}_rt.o" 457 if ! "$ROUNDTRIP_BIN" "$obj" "$rt" 2>"$KIT_WORK/rt_${base}.err"; then 458 r_ok=0; break 459 fi 460 if [ "$KIT_TEST_OBJ" = "macho" ]; then 461 python3 "$NORMALIZE" "$obj" >"$KIT_WORK/${base}_golden.norm" 2>/dev/null 462 python3 "$NORMALIZE" "$rt" >"$KIT_WORK/${base}_rt.norm" 2>/dev/null 463 else 464 "$READELF_BIN" -aW "$obj" | python3 "$NORMALIZE" filter \ 465 >"$KIT_WORK/${base}_golden.norm" 2>/dev/null 466 "$READELF_BIN" -aW "$rt" | python3 "$NORMALIZE" filter \ 467 >"$KIT_WORK/${base}_rt.norm" 2>/dev/null 468 fi 469 if ! diff -u "$KIT_WORK/${base}_golden.norm" "$KIT_WORK/${base}_rt.norm" \ 470 >"$KIT_WORK/${base}_diff.txt" 2>&1; then 471 r_ok=0; break 472 fi 473 done 474 kit_time R "$(( $(kit_now_ms) - t0 ))" 475 if [ $r_ok -eq 1 ]; then kit_pass "$NAME/R" 476 else kit_fail "$NAME/R"; fi 477 else 478 kit_skip "$NAME/R" "missing roundtrip/dump-tool/python3" 479 fi 480 } 481 482 # E — link now; defer exec (batched) or run kernel synchronously. Branch 483 # precedence mirrors the original if/elif/elif: the e_targets SKIP-NA check 484 # (non-kernel, inapplicable tuple) fires first regardless of jit_only or runner 485 # availability; then jit_only cases produce no E record; then the link path. 486 kit_lane_E() { 487 # e_targets SKIP-NA (uncounted) for non-kernel cases on inapplicable tuples. 488 if [ $E_APPLICABLE -eq 0 ] && [ $KERNEL_IMAGE -eq 0 ]; then 489 printf ' SKIP-NA %s/E — N/A on %s\n' "$NAME" "$CUR_TUPLE" 490 kit_skip_na "$NAME/E" 491 return 492 fi 493 # jit_only cases skip E entirely (no record). 494 [ $JIT_ONLY -eq 1 ] && return 495 if [ $have_exe_runner -eq 0 ]; then 496 kit_skip "$NAME/E" "no link-exe-runner" 497 return 498 fi 499 500 local t0; t0=$(kit_now_ms) 501 local script_flags=() 502 [ -n "$LINKER_SCRIPT_FILE" ] && script_flags=(--linker-script "$LINKER_SCRIPT_FILE") 503 504 local exe="$KIT_WORK/linked.exe" 505 local link_cmd 506 if [ $KERNEL_IMAGE -eq 1 ]; then 507 # Freestanding kernel image: no harness start.o; the case's own entry.S 508 # is the program entry. 509 link_cmd=("$LINK_EXE_RUNNER" "${EXTRA_FLAGS[@]}" \ 510 "${script_flags[@]}" -o "$exe" \ 511 "${LINK_OBJ_FILES[@]}" "${LINK_ARC_FLAGS[@]}") 512 elif [ $have_start_obj -eq 1 ]; then 513 link_cmd=("$LINK_EXE_RUNNER" "${EXTRA_FLAGS[@]}" \ 514 "${script_flags[@]}" -o "$exe") 515 [ ${#MACHO_DSO_ARGS[@]} -gt 0 ] && link_cmd+=("${MACHO_DSO_ARGS[@]}") 516 link_cmd+=("${LINK_OBJ_FILES[@]}" "$START_OBJ" "${LINK_ARC_FLAGS[@]}") 517 else 518 kit_skip "$NAME/E" "no cached start.o" 519 return 520 fi 521 522 if ! "${link_cmd[@]}" >"$KIT_WORK/exec_link.out" 2>"$KIT_WORK/exec_link.err"; then 523 kit_time E "$(( $(kit_now_ms) - t0 ))" 524 kit_fail "$NAME/E" "link failed" 525 elif [ $KERNEL_IMAGE -eq 1 ]; then 526 if ! exec_kernel_supported "$TEST_ARCH"; then 527 kit_time E "$(( $(kit_now_ms) - t0 ))" 528 kit_skip "$NAME/E" "no qemu-system-* for $TEST_ARCH" 529 else 530 exec_kernel_run "$TEST_ARCH" "$exe" "$KIT_WORK/exec.out" "$KIT_WORK/exec.err" 531 kit_time E "$(( $(kit_now_ms) - t0 ))" 532 if [ "$RUN_RC" -eq "$KIT_EXPECTED" ]; then kit_pass "$NAME/E" 533 else kit_fail "$NAME/E" "expected $KIT_EXPECTED got $RUN_RC"; fi 534 fi 535 elif [ $have_runner -eq 1 ]; then 536 kit_time E "$(( $(kit_now_ms) - t0 ))" 537 # Defer to the batched flush. Payload carries the gc symbol-check sets, 538 # space-joined per list and '|'-separated (absent|present), verified 539 # post-flush by link_flush_verify. 540 local gca="" gcp="" s 541 for s in "${GC_ABSENT_SYMS[@]:-}"; do [ -n "$s" ] && gca="${gca:+$gca }$s"; done 542 for s in "${GC_PRESENT_SYMS[@]:-}"; do [ -n "$s" ] && gcp="${gcp:+$gcp }$s"; done 543 kit_queue_e "$NAME/E" "$exe" "$KIT_WORK/exec.out" "$KIT_WORK/exec.err" \ 544 "$KIT_WORK/exec.rc" "$KIT_EXPECTED" "$EXEC_TAG" "${gca}|${gcp}|$exe" 545 else 546 kit_skip "$NAME/E" "no runner (qemu/podman)" 547 fi 548 } 549 550 # J — JIT runner + inline gc symbol checks. Skipped (no record) for kernel. 551 kit_lane_J() { 552 [ $KERNEL_IMAGE -eq 1 ] && return 553 # j_targets SKIP-NA (uncounted) on inapplicable tuples. 554 if [ $J_APPLICABLE -eq 0 ]; then 555 printf ' SKIP-NA %s/J — N/A on %s\n' "$NAME" "$CUR_TUPLE" 556 kit_skip_na "$NAME/J" 557 return 558 fi 559 if [ $have_jit_runner -eq 0 ]; then 560 kit_skip "$NAME/J" "no jit-runner (host arch != $TEST_ARCH or build failed)" 561 return 562 fi 563 564 local t0; t0=$(kit_now_ms) 565 local jit_cmd=("$JIT_RUNNER" "${EXTRA_FLAGS[@]}") 566 [ $USE_RESOLVER -eq 1 ] && jit_cmd+=(--use-resolver) 567 [ -n "$LINKER_SCRIPT_FILE" ] && jit_cmd+=(--linker-script "$LINKER_SCRIPT_FILE") 568 jit_cmd+=("${LINK_OBJ_FILES[@]}" "${LINK_ARC_FLAGS[@]}") 569 570 "${jit_cmd[@]}" >"$KIT_WORK/jit.out" 2>"$KIT_WORK/jit.err" 571 local j_rc=$? sym 572 # gc_absent / gc_present checks via the jit_runner's --check-* flags. 573 for sym in "${GC_ABSENT_SYMS[@]:-}"; do 574 [ -z "$sym" ] && continue 575 if "${jit_cmd[@]}" --check-absent "$sym" \ 576 >"$KIT_WORK/jit_gc.out" 2>"$KIT_WORK/jit_gc.err"; then 577 : 578 else 579 j_rc=$? 580 break 581 fi 582 done 583 if [ "$j_rc" -eq "$KIT_EXPECTED" ]; then 584 for sym in "${GC_PRESENT_SYMS[@]:-}"; do 585 [ -z "$sym" ] && continue 586 if "${jit_cmd[@]}" --check-present "$sym" \ 587 >"$KIT_WORK/jit_gc.out" 2>"$KIT_WORK/jit_gc.err"; then 588 : 589 else 590 j_rc=$? 591 break 592 fi 593 done 594 fi 595 kit_time J "$(( $(kit_now_ms) - t0 ))" 596 if [ "$j_rc" -eq "$KIT_EXPECTED" ]; then kit_pass "$NAME/J" 597 else kit_fail "$NAME/J" "expected $KIT_EXPECTED got $j_rc"; fi 598 } 599 600 # ---- deferred-E flush verification (gc symbol presence/absence) ------------ 601 # Called per queued-E item after the batched exec flush, ONLY when rc==expected 602 # (the engine's rc check passed). Runs the gc symbol presence/absence checks via 603 # llvm-nm (format-agnostic). Payload = "<absent syms>|<present syms>|<exe>". 604 # Returns 0 to keep the engine's PASS, 1 to flip it to FAIL — we do NOT call 605 # kit_fail ourselves (the engine records the single verdict). 606 link_flush_verify() { 607 local payload="$2" 608 local gca="${payload%%|*}"; payload="${payload#*|}" 609 local gcp="${payload%%|*}"; local exe="${payload#*|}" 610 { [ -z "$gca" ] && [ -z "$gcp" ]; } && return 0 611 [ $have_nm -eq 1 ] || return 0 612 613 # Write the symbol dump next to the exe (its dir is the persistent per-item 614 # KIT_WORK; parallel-safe). 615 local syms="$exe.syms" 616 "$NM_BIN" "$exe" >"$syms" 2>/dev/null 617 [ -s "$syms" ] || return 0 618 619 # llvm-nm format: "<addr> <type> <name>". `U` = undefined. On Mach-O the 620 # asm form of source `name` is `_name`; accept either. 621 local sym 622 for sym in $gca; do 623 if awk -v s="$sym" -v u="_$sym" \ 624 '($NF==s || $NF==u) && $(NF-1)!="U" {found=1} END{exit !found}' "$syms"; then 625 return 1 # gc_absent symbol unexpectedly present 626 fi 627 done 628 for sym in $gcp; do 629 if ! awk -v s="$sym" -v u="_$sym" \ 630 '($NF==s || $NF==u) && $(NF-1)!="U" {found=1} END{exit !found}' "$syms"; then 631 return 1 # gc_present symbol missing 632 fi 633 done 634 return 0 635 } 636 637 # ---- bad/ negative lanes --------------------------------------------------- 638 # Sources compile cleanly but the linker (E) and JIT (J) must reject: non-zero 639 # exit with no signal AND stderr contains the substring from `expect`. 640 641 bad_read_case() { 642 CASE_DIR="${KIT_SRC%/}" 643 NAME="bad/$KIT_BASE" 644 645 # Whole-case applicability via `targets` (same mechanism as link_read_case): 646 # a negative case may be format-specific (e.g. an ELF-only TLS scenario with 647 # no Mach-O/COFF analogue). Listed <arch>-<obj> tuples run; others SKIP-NA. 648 if [ -f "$CASE_DIR/targets" ]; then 649 applicable=0 650 for tuple in $(cat "$CASE_DIR/targets"); do 651 [ "$tuple" = "$CUR_TUPLE" ] && applicable=1 652 done 653 if [ $applicable -eq 0 ]; then 654 printf ' SKIP-NA %s — N/A on %s\n' "$NAME" "$CUR_TUPLE" 655 KIT_SKIP_NA_CASE=1 656 return 657 fi 658 fi 659 660 EXPECT_FILE="$CASE_DIR/expect" 661 if [ ! -f "$EXPECT_FILE" ]; then 662 kit_fail "$NAME (missing $EXPECT_FILE)" 663 KIT_SKIP_NA_CASE=1 664 return 665 fi 666 EXPECT="$(cat "$EXPECT_FILE")" 667 668 if [ $have_clang_cross -eq 0 ]; then 669 kit_skip "$NAME" "no $TEST_ARCH clang" 670 KIT_SKIP_NA_CASE=1 671 return 672 fi 673 674 TU_SRCS=() 675 for f in "$CASE_DIR/a.c" "$CASE_DIR/b.c" "$CASE_DIR/c.c"; do 676 [ -f "$f" ] && TU_SRCS+=("$f") 677 done 678 679 OBJ_FILES=(); COMPILE_OK=1 680 for src in "${TU_SRCS[@]}"; do 681 base="$(basename "$src" .c)" 682 obj="$KIT_WORK/${base}.o" 683 if ! clang $CLANG_TARGET -O1 -fno-inline -ffreestanding -fno-stack-protector \ 684 -fno-PIC -fno-pie -fcommon \ 685 -c "$src" -o "$obj" 2>"$KIT_WORK/compile_${base}.err"; then 686 COMPILE_OK=0; break 687 fi 688 OBJ_FILES+=("$obj") 689 done 690 if [ $COMPILE_OK -eq 0 ]; then 691 kit_fail "$NAME (compile failed; sources should compile, only link is expected to fail)" 692 KIT_SKIP_NA_CASE=1 693 return 694 fi 695 } 696 697 kit_lane_BE() { # bad/ Path E (negative) 698 if [ $have_exe_runner -eq 0 ]; then 699 kit_skip "$NAME/E" "no link-exe-runner" 700 return 701 fi 702 if [ $have_start_obj -eq 0 ]; then 703 kit_skip "$NAME/E" "no cached start.o" 704 return 705 fi 706 local exe="$KIT_WORK/linked.exe" 707 if "$LINK_EXE_RUNNER" -o "$exe" "${OBJ_FILES[@]}" "$START_OBJ" \ 708 >"$KIT_WORK/link.out" 2>"$KIT_WORK/link.err"; then 709 kit_fail "$NAME/E" "linker succeeded; expected non-zero exit" 710 else 711 local rc=$? 712 if [ $rc -ge 128 ]; then 713 kit_fail "$NAME/E" "linker died via signal $((rc-128))" 714 elif ! grep -qF -- "$EXPECT" "$KIT_WORK/link.err"; then 715 kit_fail "$NAME/E" "stderr did not contain: $EXPECT" 716 sed 's/^/ | /' "$KIT_WORK/link.err" 717 else 718 kit_pass "$NAME/E" 719 fi 720 fi 721 } 722 723 kit_lane_BJ() { # bad/ Path J (negative) 724 if [ $have_jit_runner -eq 0 ]; then 725 kit_skip "$NAME/J" "no jit-runner (host arch != $TEST_ARCH or build failed)" 726 return 727 fi 728 if "$JIT_RUNNER" "${OBJ_FILES[@]}" >"$KIT_WORK/jit.out" 2>"$KIT_WORK/jit.err"; then 729 kit_fail "$NAME/J" "jit-runner succeeded; expected non-zero exit" 730 else 731 local rc=$? 732 if [ $rc -ge 128 ]; then 733 kit_fail "$NAME/J" "jit-runner died via signal $((rc-128))" 734 elif ! grep -qF -- "$EXPECT" "$KIT_WORK/jit.err"; then 735 kit_fail "$NAME/J" "stderr did not contain: $EXPECT" 736 sed 's/^/ | /' "$KIT_WORK/jit.err" 737 else 738 kit_pass "$NAME/J" 739 fi 740 fi 741 } 742 743 # ---- drive the corpora ----------------------------------------------------- 744 printf 'Running cases...\n' 745 746 PAR="${KIT_LINK_PARALLEL:-0}" 747 748 # Active lanes for the cases corpus, in REJ order. 749 CASE_LANES= 750 [ "$RUN_R" -eq 1 ] && CASE_LANES="$CASE_LANES R" 751 [ "$RUN_E" -eq 1 ] && CASE_LANES="$CASE_LANES E" 752 [ "$RUN_J" -eq 1 ] && CASE_LANES="$CASE_LANES J" 753 754 KIT_LABEL=test-link KIT_BUILD_DIR="$BUILD_DIR/link" \ 755 KIT_CORPUS_GLOBS="$TEST_DIR/cases/*/" KIT_CORPUS_EXT="" KIT_SIDECAR_DIR="$TEST_DIR/cases" \ 756 KIT_LANES="$CASE_LANES" KIT_OPT_LEVELS="" KIT_TUPLES="$CUR_TUPLE" \ 757 KIT_TARGETS_EXT="" KIT_READ_CASE=link_read_case KIT_FLUSH_VERIFY=link_flush_verify \ 758 KIT_PARALLELIZABLE="$PAR" \ 759 kit_corpus_run 760 761 # bad/ negative corpus — E and J only (no R). 762 BAD_LANES= 763 [ "$RUN_E" -eq 1 ] && BAD_LANES="$BAD_LANES BE" 764 [ "$RUN_J" -eq 1 ] && BAD_LANES="$BAD_LANES BJ" 765 766 if [ -n "$BAD_LANES" ]; then 767 KIT_LABEL=test-link KIT_BUILD_DIR="$BUILD_DIR/link/bad" \ 768 KIT_CORPUS_GLOBS="$TEST_DIR/bad/*/" KIT_CORPUS_EXT="" KIT_SIDECAR_DIR="$TEST_DIR/bad" \ 769 KIT_LANES="$BAD_LANES" KIT_OPT_LEVELS="" KIT_TUPLES="$CUR_TUPLE" \ 770 KIT_TARGETS_EXT="" KIT_READ_CASE=bad_read_case \ 771 KIT_PARALLELIZABLE="$PAR" \ 772 kit_corpus_run 773 fi 774 775 kit_summary test-link 776 kit_exit