kit

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

test.mk (39374B)


      1 # Data-driven tests. Included from the top-level Makefile.
      2 #
      3 # - test-driver: aggregate alias that runs all test-driver-* targets.
      4 # - test-driver-cc: narrow CLI behavior checks that do not belong to a specific
      5 #   frontend/linker corpus. Depends on the kit driver binary.
      6 # - test-pp: aggregate alias that runs test-pp-ok and test-pp-err.
      7 # - test-pp-ok: C preprocessor success cases; depends on the kit driver binary.
      8 # - test-elf: ELF roundtrip harness in test/elf/; depends only on
      9 #   libkit.a and compiles its own test binaries against it. Skipped
     10 #   layers are reported (set KIT_TEST_ALLOW_SKIP=1 to allow skips).
     11 # - test-ar:  in-process ar reader/writer tests; depends only on
     12 #   libkit.a. Set KIT_AR_TEST_HOST=1 to also dump produced bytes
     13 #   to /tmp and run the host's `ar t` / `nm --print-armap` as a
     14 #   cross-check.
     15 # - test-driver-ar: scenario-driven CLI harness for `kit ar`. Each
     16 #   case under test/ar/cases/ runs a small script and diffs stdout.
     17 #   Depends on the kit driver binary.
     18 # - test-link: linker + JIT behavioral harness in test/link/; three paths
     19 #   per case (roundtrip R, ELF exec E, JIT J). Depends only on libkit.a.
     20 #   Set KIT_TEST_ALLOW_SKIP=1 to allow skipped layers.
     21 # - test-macho: Mach-O variant of test-link; defaults to roundtrip+JIT
     22 #   paths because hosted Mach-O executable execution is target/SDK-specific.
     23 # - test-parse: aggregate alias that runs test-parse-ok and test-parse-err.
     24 # - test-parse-ok: file-driven C parser success harness in test/parse/; each
     25 #   case is a .c source file. Built against the public kit.h surface;
     26 #   reuses kit-roundtrip, link-exe-runner, and jit-runner.
     27 # - test-asm: aggregate alias over the per-arch asm lanes test-asm-aa64,
     28 #   test-asm-x64 and test-asm-rv64. The file-driven assembler/disassembler
     29 #   harness in test/asm/ runs one arch per invocation (KIT_TEST_ARCH) over
     30 #   three sub-corpora (encode/, decode/, listing/), one mode per sub-dir.
     31 #   aa64 runs the full path set (incl. native exec on aa64 hosts); x64/rv64
     32 #   run the host-independent encode/decode/listing lanes only. See doc/ASM.md.
     33 
     34 TEST_TARGETS = \
     35     test-cf-corpus-selftest \
     36     test-aa64-inline \
     37     test-abi-classify \
     38     test-ar \
     39     test-asm \
     40     test-asm-aa64 \
     41     test-asm-x64 \
     42     test-asm-rv64 \
     43     test-disasm-complete \
     44     test-asm-roundtrip \
     45     test-asm-roundtrip-exec \
     46     test-asm-symmetry \
     47     test-asm-roundtrip-toy \
     48     test-hostas-toy \
     49     test-hostas-cross \
     50     test-diff-llvm \
     51     test-bootstrap-toy \
     52     test-bootstrap-toy-debug \
     53     test-bootstrap-toy-release \
     54     test-bounce \
     55     test-cbackend \
     56     test-cg-api \
     57     test-coff \
     58     test-coff-mingw-import \
     59     test-coff-windows-ucrt \
     60     test-debug \
     61     test-dbg \
     62     test-driver \
     63     test-driver-ar \
     64     test-driver-cas \
     65     test-driver-cc \
     66     test-driver-compile \
     67     test-driver-objcopy \
     68     test-driver-objdump \
     69     test-driver-pkg \
     70     test-driver-strings \
     71     test-driver-tools \
     72     test-hash \
     73     test-driver-strip \
     74     test-dwarf \
     75     test-elf \
     76     test-emu \
     77     test-emu-unit \
     78     test-interp \
     79     test-interp-emu \
     80     test-interp-toy \
     81     test-ir-recorder \
     82     test-isa \
     83     test-lib-deps \
     84     test-libc \
     85     test-libc-glibc \
     86     test-libc-glibc-rv64 \
     87     test-libc-musl \
     88     test-libc-musl-rv64 \
     89     test-link \
     90     test-link-reloc-uleb128 \
     91     test-macho \
     92     test-native-direct-target \
     93     test-opt \
     94     test-parse \
     95     test-parse-err \
     96     test-parse-ok \
     97     test-parse-rv64-wide \
     98     test-pp \
     99     test-pp-err \
    100     test-pp-ok \
    101     test-rt-headers \
    102     test-rt-runtime \
    103     test-link-x64 \
    104     test-rv64-inline \
    105     test-rv64-jit \
    106     test-rv64-tls-link \
    107     test-smoke-rv64 \
    108     test-smoke-x64 \
    109     test-toy \
    110     test-wasm \
    111     test-wasm-c \
    112     test-wasm-front \
    113     test-wasm-target \
    114     test-wasm-toy \
    115     test-x64-dbg \
    116     test-x64-inline
    117 
    118 DEFAULT_TEST_TARGETS = \
    119     test-cf-corpus-selftest \
    120     test-driver \
    121     test-pp \
    122     test-elf \
    123     test-coff \
    124     test-ar \
    125     test-link \
    126     test-toy \
    127     test-dwarf \
    128     test-debug \
    129     test-parse \
    130     test-asm \
    131     test-asm-roundtrip \
    132     test-isa \
    133     test-aa64-inline \
    134     test-rv64-inline \
    135     test-rv64-jit \
    136     test-rv64-tls-link \
    137     test-emu \
    138     test-emu-unit \
    139     test-interp \
    140     test-interp-emu \
    141     test-x64-inline \
    142     test-x64-dbg \
    143     test-rt-headers \
    144     test-lib-deps \
    145     test-cg-api \
    146     test-abi-classify \
    147     test-ir-recorder \
    148     test-native-direct-target \
    149     test-opt \
    150     test-asm-symmetry \
    151     test-link-reloc-uleb128 \
    152     test-dbg \
    153     test-disasm-complete \
    154     test-macho \
    155     test-interp-toy \
    156     test-wasm \
    157 		test-libc \
    158 		test-link-x64 \
    159 		test-rt-runtime \
    160 		test-bounce \
    161 		bootstrap \
    162 		test-bootstrap-toy
    163 
    164 .PHONY: test $(TEST_TARGETS)
    165 
    166 test: $(DEFAULT_TEST_TARGETS)
    167 
    168 # Unit-test binary build rules: two regimes (public vs internal interface).
    169 include test/lib/unit.mk
    170 
    171 # Provision the pinned per-arch container rootfs images that exec_target.sh runs
    172 # kit-emitted binaries inside. This is the ONLY test step that touches the
    173 # network: the cross-arch exec harnesses (toy/parse/link ... path X/E/L) run with
    174 # `podman run --pull=never`, so without these images those paths SKIP. Run once,
    175 # and again only after the pin in test/lib/test_images.sh changes. FORCE=1
    176 # re-pulls. The images are pinned per-arch by content digest, so they coexist in
    177 # local storage and can never clobber one another.
    178 .PHONY: test-images
    179 test-images:
    180 	@bash test/lib/pull_test_images.sh
    181 
    182 # Hermetic self-test of the shared corpus harness engine (test/lib/kit_corpus.sh):
    183 # asserts serial==parallel determinism, SKIP-NA, and the parallel-safety
    184 # invariant (exec_target queued only on the parent, never in a worker). No
    185 # kit binary / podman / qemu needed.
    186 test-cf-corpus-selftest:
    187 	@bash test/lib/kit_corpus_selftest.sh
    188 
    189 test-driver: test-driver-cc test-driver-compile test-driver-ar test-driver-cas test-driver-strip test-driver-objcopy test-driver-objdump test-driver-pkg test-driver-strings test-driver-tools
    190 
    191 test-driver-cc: bin
    192 	@KIT=$(abspath $(BIN)) sh test/driver/run.sh
    193 
    194 test-driver-compile: bin
    195 	@KIT=$(abspath $(BIN)) sh test/compile/run.sh
    196 
    197 # test-cbackend: --emit=c C-source backend, driven through three
    198 # frontends — parse-runner (C), toy-runner (toy), wasm-runner (wat/wasm).
    199 # Each invokes its existing runner with paths=C so a single corpus per
    200 # frontend exercises both the existing backends and the C backend.
    201 # Together they prove the CGTarget seam is frontend-agnostic.
    202 # Unimplemented CGTarget methods report as SKIP; see doc/CBACKEND.md.
    203 KIT_CBACKEND_TEST_JOBS ?= $(if $(KIT_TEST_JOBS),$(KIT_TEST_JOBS),1)
    204 test-cbackend: bin
    205 	@KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT_TEST_ALLOW_SKIP=1 sh test/parse/run.sh
    206 	@KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT=$(abspath $(BIN)) sh test/toy/run.sh
    207 	@KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT=$(abspath $(BIN)) bash test/wasm/run.sh
    208 
    209 # test-wasm-toy: opt-in Toy -> Wasm -> JIT roundtrip. Runs the toy corpus
    210 # under the W path (compile -target wasm32-none, then `kit run` the .wasm,
    211 # which routes back through the lang/wasm frontend to native CG). Most cases
    212 # will fail or skip today; the target exists so progress on the Wasm CGTarget
    213 # is visible without putting noise into the default `test` summary.
    214 # Drop `KIT_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
    215 test-wasm-toy: bin
    216 	@KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) sh test/toy/run.sh
    217 
    218 # test-wasm-c: opt-in C -> Wasm -> JIT roundtrip, the C-frontend analogue of
    219 # test-wasm-toy. Runs the test/parse corpus under the W path (compile
    220 # -target wasm32-none, then `kit run -e test_main` the .wasm, which routes
    221 # back through the lang/wasm frontend to native CG). The C corpus exercises
    222 # far more of the language than the toy corpus, so expect many SKIPs for
    223 # not-yet-implemented Wasm lowerings; the target makes that progress visible
    224 # without adding noise to the default `test` summary. Drop
    225 # `KIT_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
    226 test-wasm-c: bin $(PARSE_RUNNER)
    227 	@KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) bash test/parse/run.sh
    228 
    229 test-pp: test-pp-ok test-pp-err
    230 
    231 test-pp-ok: bin
    232 	@KIT=$(abspath $(BIN)) test/pp/run.sh
    233 
    234 test-pp-err: bin
    235 	@KIT=$(abspath $(BIN)) test/pp/run_errors.sh
    236 
    237 # Best-effort kit binary build: Layer D needs build/kit, but the
    238 # binary may not link until enough libkit symbols exist. The harness
    239 # detects a missing binary and skips that layer; don't break test-elf
    240 # when bin fails.
    241 .PHONY: bin-soft
    242 bin-soft:
    243 	-@$(MAKE) bin 2>/dev/null || true
    244 
    245 AR_TEST_BIN = build/test/ar_test
    246 
    247 test-ar: $(AR_TEST_BIN)
    248 	$(AR_TEST_BIN)
    249 
    250 
    251 test-driver-ar: bin
    252 	@KIT=$(abspath $(BIN)) test/ar/run.sh
    253 
    254 test-driver-cas: bin
    255 	@KIT=$(abspath $(BIN)) sh test/cas/run.sh
    256 
    257 test-driver-strip: bin
    258 	@KIT=$(abspath $(BIN)) test/strip/run.sh
    259 
    260 test-driver-objcopy: bin
    261 	@KIT=$(abspath $(BIN)) test/objcopy/run.sh
    262 
    263 test-driver-objdump: bin
    264 	@KIT=$(abspath $(BIN)) sh test/objdump/run.sh
    265 
    266 test-driver-pkg: bin
    267 	@KIT=$(abspath $(BIN)) sh test/pkg/run.sh
    268 
    269 test-driver-strings: bin
    270 	@KIT=$(abspath $(BIN)) sh test/strings/run.sh
    271 
    272 test-driver-tools: bin
    273 	@KIT=$(abspath $(BIN)) sh test/tools/run.sh
    274 
    275 # DWARF consumer unit test: builds a hand-crafted DWARF-bearing ELF in
    276 # memory and exercises every kit_dwarf_* entry. It reaches into the
    277 # internal object builder to synthesize the fixture, so link individual
    278 # lib objects rather than libkit.a (which hides internal symbols).
    279 DWARF_TEST_BIN = build/test/dwarf_test
    280 
    281 test-dwarf: $(DWARF_TEST_BIN)
    282 	$(DWARF_TEST_BIN)
    283 
    284 
    285 # DWARF producer self-roundtrip unit test. Drives Debug directly, calls
    286 # debug_emit, asserts the produced sections have valid DWARF 5 structure
    287 # (length fields, version, address sizes, expected relocations against
    288 # function symbol). Deliberately bypasses the consumer (kit_dwarf_open)
    289 # so encoder bugs aren't masked by matching decoder bugs.
    290 DEBUG_TEST_BIN = build/test/debug_roundtrip_unit
    291 CFI_TEST_BIN = build/test/debug_cfi_unit
    292 
    293 test-debug: $(DEBUG_TEST_BIN) $(CFI_TEST_BIN)
    294 	$(DEBUG_TEST_BIN)
    295 	$(CFI_TEST_BIN)
    296 
    297 
    298 # CFI/.eh_frame producer roundtrip for aa64/rv64/x64 (validates the per-arch
    299 # CIE template: code/data-align, return-address reg, CFA-init reg).
    300 
    301 test-dbg: bin
    302 	@KIT=$(abspath $(BIN)) sh test/dbg/run.sh
    303 
    304 .PHONY: test-dbg-red
    305 test-dbg-red: bin
    306 	@KIT=$(abspath $(BIN)) DBG_STRICT_XFAIL=1 sh test/dbg/run.sh
    307 
    308 # aa64 ISA descriptor-table unit test (doc/ASM.md phase 2). Covers
    309 # every AA64Format the table maps and the alias-precedence invariant
    310 # (first-match disasm picks the alias spelling over the canonical
    311 # form). Internal arch/ surface — needs -Isrc.
    312 AA64_ISA_TEST_BIN = build/test/aa64_isa_test
    313 RV64_DECODE_TEST_BIN = build/test/rv64_decode_test
    314 
    315 test-isa: $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN)
    316 	$(AA64_ISA_TEST_BIN)
    317 	$(RV64_DECODE_TEST_BIN)
    318 
    319 
    320 
    321 # aa64_sweep_gen: emits one representative encoding per disasm-table row for the
    322 # asm<->disasm self-symmetry sweep (test/asm/symmetry.sh). Needs the internal
    323 # arch/aa64/isa.h surface, so -Isrc + LIB_OBJS like the ISA unit test.
    324 AA64_SWEEP_GEN = build/test/aa64_sweep_gen
    325 
    326 # test-emu: emulator end-to-end integration test. Builds tiny in-memory rv64
    327 # ELFs and runs them to their exit syscall, asserting the exit code — entirely
    328 # through the PUBLIC kit_emu_* API, so it links the public archive ($(LIB_AR),
    329 # whose `ld -r` step localizes internal symbols). -Isrc is only for the
    330 # header-only rv64 encoders / ELF constants the in-memory ELF builders use.
    331 EMU_RV64_TEST_BIN = build/test/emu_rv64_test
    332 
    333 test-emu: $(EMU_RV64_TEST_BIN)
    334 	$(EMU_RV64_TEST_BIN)
    335 
    336 $(EMU_RV64_TEST_BIN): test/emu/rv64_smoke_test.c $(UNIT_HDR_DEPS) $(LIB_AR)
    337 	@mkdir -p $(dir $@)
    338 	$(CC) $(HOST_CFLAGS) -Iinclude -Isrc -Itest test/emu/rv64_smoke_test.c $(LIB_AR) -o $@
    339 
    340 # RISC-V ULEB128 diff-reloc application unit test. link_reloc_apply is an
    341 # internal (hidden) symbol, so link the raw lib objects like the other
    342 # internal-surface unit tests rather than libkit.a.
    343 RELOC_ULEB128_TEST_BIN = build/test/reloc_uleb128_unit
    344 
    345 test-link-reloc-uleb128: $(RELOC_ULEB128_TEST_BIN)
    346 	$(RELOC_ULEB128_TEST_BIN)
    347 
    348 # test-emu-unit: white-box unit tests for the emulator's INTERNAL units (rv64
    349 # decoder, EmuAddrSpace, Linux syscall handler) that have no public API. Reaches
    350 # internal symbols -> links $(LIB_OBJS) (mirrors test-interp), not the archive.
    351 EMU_RV64_UNIT_TEST_BIN = build/test/emu_rv64_unit_test
    352 
    353 test-emu-unit: $(EMU_RV64_UNIT_TEST_BIN)
    354 	$(EMU_RV64_UNIT_TEST_BIN)
    355 
    356 
    357 # test-interp: threaded-bytecode interpreter unit smoke test. Builds tiny CG IR
    358 # by hand, runs opt_run_o1_interp + interp_lower + the engine, asserts the
    359 # returned value. Reaches internal opt/interp symbols -> links $(LIB_OBJS) and
    360 # needs -Isrc (mirrors test-opt).
    361 INTERP_SMOKE_TEST_BIN = build/test/interp_smoke_test
    362 
    363 test-interp: $(INTERP_SMOKE_TEST_BIN)
    364 	$(INTERP_SMOKE_TEST_BIN)
    365 
    366 
    367 # test-interp-emu: differential test of the emulator's INTERP execution mode
    368 # (doc/INTERPRETER.md Phase 4). Builds a tiny rv64 ELF with SD/LD/ecall and runs
    369 # it through kit_emu_run in JIT and INTERP modes (both -O1), asserting the exit
    370 # codes match. Public-API only, but links $(LIB_OBJS) like test-interp to dodge
    371 # the visibility-hidden archive's localized internals.
    372 INTERP_EMU_TEST_BIN = build/test/rv64_interp_smoke_test
    373 
    374 test-interp-emu: $(INTERP_EMU_TEST_BIN)
    375 	$(INTERP_EMU_TEST_BIN)
    376 
    377 
    378 # test-interp-toy: run the toy suite's interpreter (--no-jit) path only,
    379 # asserting it matches the golden exit codes (and SKIPping unimplemented ops).
    380 test-interp-toy: bin
    381 	@KIT=$(abspath $(BIN)) KIT_TEST_PATHS=I bash test/toy/run.sh
    382 
    383 CG_API_TEST_BIN = build/test/cg_api_test
    384 CG_SWITCH_TEST_BIN = build/test/cg_switch_test
    385 CG_FP_CMP_TEST_BIN = build/test/cg_fp_cmp_test
    386 STRENGTH_REDUCE_TEST_BIN = build/test/strength_reduce_test
    387 HASH_TEST_BIN = build/test/hash_test
    388 ABI_CLASSIFY_TEST_BIN = build/test/abi_classify_test
    389 IR_RECORDER_TEST_BIN = build/test/ir_recorder_test
    390 NATIVE_DIRECT_TARGET_TEST_BIN = build/test/native_direct_target_test
    391 
    392 test-cg-api: $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) $(CG_FP_CMP_TEST_BIN) \
    393              $(STRENGTH_REDUCE_TEST_BIN)
    394 	$(CG_API_TEST_BIN)
    395 	$(CG_SWITCH_TEST_BIN)
    396 	$(CG_FP_CMP_TEST_BIN)
    397 	$(STRENGTH_REDUCE_TEST_BIN)
    398 
    399 test-hash: $(HASH_TEST_BIN)
    400 	$(HASH_TEST_BIN)
    401 
    402 test-abi-classify: $(ABI_CLASSIFY_TEST_BIN)
    403 	$(ABI_CLASSIFY_TEST_BIN)
    404 
    405 
    406 
    407 
    408 test-ir-recorder: $(IR_RECORDER_TEST_BIN)
    409 	$(IR_RECORDER_TEST_BIN)
    410 
    411 
    412 test-native-direct-target: $(NATIVE_DIRECT_TARGET_TEST_BIN)
    413 	$(NATIVE_DIRECT_TARGET_TEST_BIN)
    414 
    415 
    416 test-toy: bin
    417 	@KIT=$(abspath $(BIN)) test/toy/run.sh
    418 
    419 # test-bootstrap-toy: run the Toy corpus through the bootstrapped (self-built)
    420 # stage3 kit instead of the host-built binary, so the self-hosted compiler is
    421 # exercised on real codegen (not just self-reproduction like `make bootstrap`).
    422 # Split per build mode: -debug runs the debug stage3, -release the release
    423 # (-O1) stage3; the aggregate target runs both.
    424 test-bootstrap-toy: test-bootstrap-toy-debug test-bootstrap-toy-release
    425 
    426 test-bootstrap-toy-debug: bootstrap-debug
    427 	@KIT='$(abspath $(BUILD_DIR)/debug/bootstrap/stage3/kit)' test/toy/run.sh
    428 
    429 test-bootstrap-toy-release: bootstrap-release
    430 	@KIT='$(abspath $(BUILD_DIR)/release/bootstrap/stage3/kit)' test/toy/run.sh
    431 
    432 
    433 # Public-API inline-asm backend tests. These emit a tiny function through CG,
    434 # reopen the object through the public object reader, and assert the expected
    435 # instruction bytes are present in .text.
    436 AA64_INLINE_TEST_BIN = build/test/aa64_inline_test
    437 
    438 test-aa64-inline: $(AA64_INLINE_TEST_BIN)
    439 	$(AA64_INLINE_TEST_BIN)
    440 
    441 
    442 RV64_INLINE_TEST_BIN = build/test/rv64_inline_test
    443 
    444 test-rv64-inline: $(RV64_INLINE_TEST_BIN)
    445 	$(RV64_INLINE_TEST_BIN)
    446 
    447 
    448 # rv64 JIT smoke test. Builds a tiny rv64 ELF .o in memory, runs it
    449 # through kit_link_session in JIT-output mode, and skips native execution
    450 # on non-riscv64 hosts after exercising the JIT mapping/reloc path.
    451 RV64_JIT_TEST_BIN = build/test/rv64_jit_test
    452 
    453 test-rv64-jit: $(RV64_JIT_TEST_BIN)
    454 	@$(RV64_JIT_TEST_BIN); rc=$$?; \
    455 	  if [ $$rc -eq 77 ]; then \
    456 	    echo "  (rv64_jit_test SKIPPED on non-rv64 host)"; \
    457 	    exit 0; \
    458 	  else \
    459 	    exit $$rc; \
    460 	  fi
    461 
    462 
    463 # Link-only regression for rv64 TLS Local-Exec lowering (runs on any host).
    464 test-rv64-tls-link: bin
    465 	@KIT='$(abspath $(BIN))' bash test/smoke/rv64_tls_link.sh
    466 
    467 X64_INLINE_TEST_BIN = build/test/x64_inline_test
    468 
    469 test-x64-inline: $(X64_INLINE_TEST_BIN)
    470 	$(X64_INLINE_TEST_BIN)
    471 
    472 
    473 X64_DBG_TEST_BIN = build/test/x64_dbg_test
    474 
    475 test-x64-dbg: $(X64_DBG_TEST_BIN)
    476 	$(X64_DBG_TEST_BIN)
    477 
    478 # Reaches the internal arch/arch.h surface (arch_lookup, ArchDbgOps) -> links
    479 # $(LIB_OBJS), not the archive, whose relocatable merge localizes non-public
    480 # symbols (mirrors the aa64/rv64 arch unit tests above).
    481 
    482 RT_HEADER_TEST_TARGETS = \
    483     aarch64-linux-gnu        \
    484     x86_64-linux-gnu         \
    485     riscv64-linux-gnu        \
    486     aarch64-apple-darwin     \
    487     x86_64-apple-darwin
    488 
    489 test-rt-headers: bin
    490 	@set -e; \
    491 	for target in $(RT_HEADER_TEST_TARGETS); do \
    492 	  out="build/test/rt-headers/$$target/smoke.o"; \
    493 	  mkdir -p "$$(dirname "$$out")"; \
    494 	  $(BIN) cc -target "$$target" -Werror -c test/rt/smoke.c -o "$$out"; \
    495 	done
    496 
    497 # Arch token -> linux rt variant target. The cross-target test harnesses link
    498 # the matching LINUX runtime archive (compiler-rt-style helpers like
    499 # __kit_sext64ti, __kit_assert_fail) rather than the native `rt` target,
    500 # which builds only the host's own variant. Shared by the single-arch
    501 # parse/asm/macho/link tests (via KIT_TEST_ARCH/TEST_RT_DEP) and by
    502 # test-rt-runtime, which sweeps several arches at once.
    503 KIT_TEST_ARCH ?= aa64
    504 _TEST_RT_aa64    = rt-aarch64-linux
    505 _TEST_RT_aarch64 = rt-aarch64-linux
    506 _TEST_RT_arm64   = rt-aarch64-linux
    507 _TEST_RT_x64     = rt-x86_64-linux
    508 _TEST_RT_x86_64  = rt-x86_64-linux
    509 _TEST_RT_amd64   = rt-x86_64-linux
    510 _TEST_RT_rv64    = rt-riscv64-linux
    511 _TEST_RT_riscv64 = rt-riscv64-linux
    512 TEST_RT_DEP      = $(_TEST_RT_$(KIT_TEST_ARCH))
    513 
    514 # test-rt-runtime compiles each case for the configured arches and links it
    515 # against that arch's LINUX runtime archive, so it must depend on each variant
    516 # (not the host `rt`). A stale per-arch archive otherwise silently breaks the
    517 # link — e.g. a dropped __kit_assert_fail weak def surfaces as an undefined
    518 # reference. KIT_RT_RUNTIME_ARCHES mirrors the default in test/rt/run.sh.
    519 KIT_RT_RUNTIME_ARCHES ?= aa64 x64 rv64
    520 RT_RUNTIME_DEPS := $(foreach a,$(KIT_RT_RUNTIME_ARCHES),$(_TEST_RT_$(a)))
    521 
    522 test-rt-runtime: bin $(RT_RUNTIME_DEPS) $(LINK_EXE_RUNNER)
    523 	@bash test/rt/run.sh
    524 
    525 # Test harness binaries shared by test-elf and test-link.
    526 # Declared as Make targets (not built by the run.sh scripts) so they pick
    527 # up libkit.a changes deterministically.
    528 #
    529 # HARNESS_CFLAGS drops -Wpedantic from the normal host flags; the runners cast
    530 # kit_jit_lookup's void* to a function pointer, which pedantic rejects under
    531 # C11.
    532 HARNESS_CFLAGS = $(filter-out -Wpedantic,$(HOST_CFLAGS)) -Iinclude -Itest
    533 
    534 ROUNDTRIP_BIN         = build/test/kit-roundtrip
    535 ROUNDTRIP_BIN_MACHO   = build/test/kit-roundtrip-macho
    536 ROUNDTRIP_BIN_COFF    = build/test/kit-roundtrip-coff
    537 COFF_IMPORT_SMOKE_BIN = build/test/pe-import-smoke
    538 COFF_IMPORT_MINGW_BIN = build/test/pe-import-mingw
    539 COFF_DSO_FORWARDER_BIN = build/test/pe-dso-forwarder
    540 COFF_MIXED_ARCHIVE_BIN = build/test/pe-mixed-archive
    541 LINK_EXE_RUNNER     = build/test/link-exe-runner
    542 JIT_RUNNER          = build/test/jit-runner
    543 PARSE_RUNNER        = build/test/parse-runner
    544 ASM_RUNNER          = build/test/asm-runner
    545 WASM_TOOL           = build/test/wasm-tool
    546 
    547 $(ROUNDTRIP_BIN): test/elf/kit-roundtrip.c $(LIB_AR)
    548 	@mkdir -p $(dir $@)
    549 	$(CC) $(HARNESS_CFLAGS) test/elf/kit-roundtrip.c $(LIB_AR) -o $@
    550 
    551 # Mach-O peer of kit-roundtrip — read_macho + emit_macho. Used by
    552 # test-link's path R when KIT_TEST_OBJ=macho.
    553 $(ROUNDTRIP_BIN_MACHO): test/macho/kit-roundtrip-macho.c $(LIB_AR)
    554 	@mkdir -p $(dir $@)
    555 	$(CC) $(HARNESS_CFLAGS) -Isrc test/macho/kit-roundtrip-macho.c $(LIB_AR) -o $@
    556 
    557 # PE/COFF round-trip harness (test/coff/). All-in-one binary: builds
    558 # hand-crafted ObjBuilders and asserts emit_coff/read_coff round-trip
    559 # stability for both x86_64-windows and aarch64-windows.
    560 $(ROUNDTRIP_BIN_COFF): test/coff/kit-roundtrip-coff.c $(LIB_OBJS)
    561 	@mkdir -p $(dir $@)
    562 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/kit-roundtrip-coff.c $(LIB_OBJS) -o $@
    563 
    564 # PE import-directory smoke test (test/coff/pe-import-smoke.c).
    565 # Exercises the full chain: short-import shim bytes -> link_add_obj_bytes
    566 # (reclassified as DSO) -> link_resolve -> link_emit_coff. Verifies the
    567 # produced PE32+ via x86_64-w64-mingw32-objdump; skips cleanly if absent.
    568 $(COFF_IMPORT_SMOKE_BIN): test/coff/pe-import-smoke.c $(LIB_OBJS)
    569 	@mkdir -p $(dir $@)
    570 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-smoke.c $(LIB_OBJS) -o $@
    571 
    572 # PE import test against a real mingw archive (test/coff/pe-import-mingw.c).
    573 # Exercises the long-form import-archive absorption path
    574 # (link_add_archive_bytes -> classify_coff_archive_member). Skips cleanly
    575 # when the mingw toolchain isn't installed.
    576 $(COFF_IMPORT_MINGW_BIN): test/coff/pe-import-mingw.c $(LIB_OBJS)
    577 	@mkdir -p $(dir $@)
    578 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-mingw.c $(LIB_OBJS) -o $@
    579 
    580 # read_coff_dso forwarder-export contract (test/coff/pe-dso-forwarder.c).
    581 # Synthesizes a tiny PE32+ DLL with one direct and one forwarder export
    582 # and asserts both surface as OBJ_SEC_NONE globals on the ObjBuilder.
    583 $(COFF_DSO_FORWARDER_BIN): test/coff/pe-dso-forwarder.c $(LIB_OBJS)
    584 	@mkdir -p $(dir $@)
    585 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-dso-forwarder.c $(LIB_OBJS) -o $@
    586 
    587 # Mixed-member archive (test/coff/pe-mixed-archive.c).  Verifies that
    588 # one archive containing both a short-import member and a long-form
    589 # COFF object with a defined data symbol satisfies references through
    590 # both shapes — the same composition libucrt.a uses (API-set imports
    591 # alongside lib64_libucrt_extra_a-*.o helpers).
    592 $(COFF_MIXED_ARCHIVE_BIN): test/coff/pe-mixed-archive.c $(LIB_OBJS)
    593 	@mkdir -p $(dir $@)
    594 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-mixed-archive.c $(LIB_OBJS) -o $@
    595 
    596 $(LINK_EXE_RUNNER): test/link/harness/link_exe_runner.c $(LIB_AR)
    597 	@mkdir -p $(dir $@)
    598 	$(CC) $(HARNESS_CFLAGS) test/link/harness/link_exe_runner.c $(LIB_AR) -o $@
    599 
    600 $(JIT_RUNNER): test/link/harness/jit_runner.c $(LIB_AR)
    601 	@mkdir -p $(dir $@)
    602 	$(CC) $(HARNESS_CFLAGS) test/link/harness/jit_runner.c $(LIB_AR) -o $@
    603 
    604 $(PARSE_RUNNER): test/parse/harness/parse_runner.c $(LIB_AR)
    605 	@mkdir -p $(dir $@)
    606 	$(CC) $(HARNESS_CFLAGS) test/parse/harness/parse_runner.c $(LIB_AR) -o $@
    607 
    608 $(ASM_RUNNER): test/asm/harness/asm_runner.c $(LIB_AR)
    609 	@mkdir -p $(dir $@)
    610 	$(CC) $(HARNESS_CFLAGS) test/asm/harness/asm_runner.c $(LIB_AR) -o $@
    611 
    612 $(WASM_TOOL): test/wasm/harness/wasm_tool.c $(LIB_AR)
    613 	@mkdir -p $(dir $@)
    614 	$(CC) $(HARNESS_CFLAGS) -I. test/wasm/harness/wasm_tool.c $(LIB_AR) -o $@
    615 
    616 test-elf: lib bin-soft $(ROUNDTRIP_BIN)
    617 	KIT_ELF_UNIT_CFLAGS='$(HOST_MODE_CFLAGS)' \
    618 	KIT_ELF_UNIT_LDFLAGS='$(HOST_MODE_LDFLAGS)' \
    619 	bash test/elf/run.sh
    620 
    621 # PE/COFF round-trip harness plus optional hosted Windows smoke. The
    622 # UCRT smoke self-skips when llvm-mingw is not installed.
    623 test-coff: lib bin rt-aarch64-windows $(ROUNDTRIP_BIN_COFF) $(COFF_IMPORT_SMOKE_BIN) $(COFF_DSO_FORWARDER_BIN) $(COFF_MIXED_ARCHIVE_BIN)
    624 	$(ROUNDTRIP_BIN_COFF)
    625 	$(COFF_IMPORT_SMOKE_BIN)
    626 	$(COFF_DSO_FORWARDER_BIN)
    627 	$(COFF_MIXED_ARCHIVE_BIN)
    628 	bash test/coff/windows-ucrt-hosted-smoke.sh
    629 	bash test/coff/windows-system-dlls-smoke.sh
    630 
    631 # Separate target so it can be skipped gracefully if mingw isn't
    632 # installed. The test itself self-skips on missing tooling, but the
    633 # build target only fires when explicitly requested.
    634 test-coff-mingw-import: lib $(COFF_IMPORT_MINGW_BIN)
    635 	$(COFF_IMPORT_MINGW_BIN)
    636 
    637 test-coff-windows-ucrt: bin rt-aarch64-windows
    638 	bash test/coff/windows-ucrt-hosted-smoke.sh
    639 
    640 # The parse/asm/macho harnesses select a cross-target via KIT_TEST_ARCH
    641 # (default aa64); the link rt dependency is resolved through the shared
    642 # _TEST_RT_<arch> map defined above (near test-rt-runtime).
    643 test-link: lib $(ROUNDTRIP_BIN) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    644 	bash test/link/run.sh
    645 
    646 # x64 ELF link/reloc-application coverage. test-link defaults to aa64, so
    647 # kit's x64 static-link reloc fixups (R_X64_PLT32/GOTPCREL/TPOFF/...) were
    648 # only ever run via a manual KIT_TEST_ARCH=x64 override. Opt-in (not in the
    649 # default set) because the E path links + runs under podman/qemu-x86_64; the R
    650 # path (roundtrip + reloc layout) runs on any host.
    651 test-link-x64: lib rt-x86_64-linux $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    652 	@KIT_TEST_ARCH=x64 KIT_TEST_PATHS=RE bash test/link/run.sh
    653 
    654 test-macho: lib $(TEST_RT_DEP) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    655 	KIT_TEST_OBJ=macho \
    656 	KIT_TEST_ARCH=$${KIT_TEST_ARCH:-aa64} \
    657 	KIT_TEST_PATHS=$${KIT_TEST_PATHS:-RJ} \
    658 	KIT_TEST_ALLOW_SKIP=$${KIT_TEST_ALLOW_SKIP:-1} \
    659 	bash test/link/run.sh
    660 
    661 OPT_TEST_BIN = build/test/cg_ir_lower_test
    662 TINY_INLINE_TEST_BIN = build/test/tiny_inline_test
    663 
    664 test-opt: bin $(OPT_TEST_BIN) test-opt-tiny-inline test-opt-inline test-opt-zero-arg test-opt-static-prune-aa64 test-opt-aa64-tail test-opt-prologue-tier
    665 	$(OPT_TEST_BIN)
    666 
    667 
    668 test-opt-tiny-inline: bin $(TINY_INLINE_TEST_BIN)
    669 	$(TINY_INLINE_TEST_BIN)
    670 
    671 
    672 # Behavioral disasm check: tiny callee `bl` disappears from its caller at -O1.
    673 test-opt-inline: bin
    674 	@KIT=$(abspath $(BIN)) bash test/opt/run.sh
    675 
    676 # Behavioral disasm check: a pointer-typed null call arg is not routed through
    677 # a scratch temp at -O1 (PERCALL.md item 3, "zero through a temp").
    678 test-opt-zero-arg: bin
    679 	@KIT=$(abspath $(BIN)) bash test/opt/zero_arg.sh
    680 
    681 .PHONY: test-opt-static-prune-aa64
    682 test-opt-static-prune-aa64: bin
    683 	@KIT=$(abspath $(BIN)) bash test/opt/static_prune_aa64.sh
    684 
    685 .PHONY: test-opt-aa64-tail
    686 test-opt-aa64-tail: bin
    687 	@KIT=$(abspath $(BIN)) bash test/opt/aa64_tail_call.sh
    688 
    689 # Structural disasm check: the -O1 known-frame prologue cost-model tiers
    690 # (aa64 reference + ported x64 slim/red-zone and rv64 leaf shapes).
    691 .PHONY: test-opt-prologue-tier
    692 test-opt-prologue-tier: bin
    693 	@KIT=$(abspath $(BIN)) bash test/opt/prologue_tier.sh
    694 
    695 test-parse: test-parse-ok test-parse-err
    696 
    697 test-parse-ok: lib $(TEST_RT_DEP) $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    698 	bash test/parse/run.sh
    699 
    700 test-parse-err: lib $(PARSE_RUNNER)
    701 	sh test/parse/run_errors.sh
    702 
    703 # test-asm: aggregate alias running every per-arch asm lane (aa64 + x64 + rv64)
    704 # so `make test` covers all three through one target. The harness runs one arch
    705 # per invocation (KIT_TEST_ARCH); each lane scopes its scratch per arch, so
    706 # the prerequisites are safe to run in parallel under `make -j`.
    707 test-asm: test-asm-aa64 test-asm-x64 test-asm-rv64
    708 
    709 # test-asm-aa64: the reference lane. aa64 is the default cross-target, and on
    710 # aa64 hosts the exec paths (D/E/J) run natively, so it uses the full default
    711 # path set (HTLDJE). The Makefile owns the harness binaries so they inherit host
    712 # flags consistently with the rest of the test suite.
    713 test-asm-aa64: lib $(TEST_RT_DEP) $(ASM_RUNNER) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    714 	@KIT_TEST_ARCH=aa64 bash test/asm/run.sh
    715 
    716 # x64/rv64 exercise the encode (H), decode (T) and listing (L) corpora on any
    717 # host: H/T/L need no native execution (they only produce/compare bytes), so the
    718 # exec paths (D/E/J) are deliberately excluded and left to the smoke/qemu
    719 # targets.
    720 test-asm-x64: lib $(ASM_RUNNER)
    721 	@KIT_TEST_ARCH=x64 KIT_TEST_PATHS=HTL bash test/asm/run.sh
    722 test-asm-rv64: lib $(ASM_RUNNER)
    723 	@KIT_TEST_ARCH=rv64 KIT_TEST_PATHS=HT bash test/asm/run.sh
    724 
    725 # Codegen round-trip completeness (doc/ASM_ROUNDTRIP_TESTING.md). These drive
    726 # the `kit` binary itself (cc -S / as / objdump) over a C corpus rather than
    727 # a hand-written asm corpus, so coverage tracks codegen automatically.
    728 #
    729 #   test-disasm-complete    L0: cc -S must decode every in-function word
    730 #                           (no `.inst` markers). Host-independent, no exec.
    731 #   test-asm-roundtrip      L0+L1: also assert cc -c bytes/relocs == cc -S | as.
    732 #   test-asm-roundtrip-exec L0+L1+L2: also run direct vs round-tripped object
    733 #                           and compare exit codes (native arch; opt-in).
    734 #
    735 # Vertical slice: aa64 only for now; L1/L2 run at -O1 (branch-free), L0 at both
    736 # opt levels. Broadening to -O0, other arches, and the default suite is tracked
    737 # in doc/ASM_ROUNDTRIP_TESTING.md once -S symbolization (Phase 2) lands.
    738 test-disasm-complete: bin
    739 	@KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=0 \
    740 		bash test/asm/roundtrip.sh
    741 test-asm-roundtrip: bin
    742 	@KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=01 \
    743 		bash test/asm/roundtrip.sh
    744 test-asm-roundtrip-exec: bin $(JIT_RUNNER)
    745 	@KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=012 \
    746 		bash test/asm/roundtrip.sh
    747 
    748 # test-asm-symmetry: asm<->disasm self-symmetry sweep (aa64). Decode-side sweeps
    749 # every disasm-table form (decode->encode->decode fixed point); encode-side
    750 # asserts every byte the assembler emits over the encode corpus is decodable.
    751 # Catches encode/decode asymmetries the codegen round-trip can't reach (e.g. a
    752 # form one tool handles and the other doesn't). Host-independent, no exec.
    753 test-asm-symmetry: $(ASM_RUNNER) $(AA64_SWEEP_GEN)
    754 	@bash test/asm/symmetry.sh
    755 
    756 # test-diff-llvm: differential cross-check of kit against llvm (aa64), as a
    757 # second oracle. Encode lane: kit as vs llvm-mc bytes over the encode corpus.
    758 # Disasm lane: cc -c bytes vs llvm-mc of cc -S (validates kit's disassembler;
    759 # the benign same-section-call reloc-vs-resolve difference is recognized).
    760 # Opt-in; skips cleanly when llvm-mc is absent.
    761 test-diff-llvm: bin
    762 	@KIT_TEST_OPTS="O0 O1" bash test/asm/diff_llvm.sh
    763 
    764 # test-asm-roundtrip-toy: L2 exec round-trip over the Toy corpus (native arch).
    765 # Reuses the ~150 toy cases (full CG op set, exit-code oracle) for free
    766 # round-trip coverage: kit cc -S | kit as | kit run, exit must match.
    767 # Opt-in; native target. Found a real miscompile (dropped .inst) the hand
    768 # corpus never reached.
    769 test-asm-roundtrip-toy: bin
    770 	@bash test/asm/roundtrip_toy.sh
    771 
    772 # test-hostas-toy: feed one native `cc -S` to BOTH kit's own `as` and clang (a
    773 # third-party host assembler), link + run each, and assert the toy exit-code
    774 # oracle. Only the assembler differs between the two lanes, so the clang lane is
    775 # the real test: a standard assembler can't paper over a private-dialect quirk
    776 # the way kit's own `as` can (cf. test/asm/diff_llvm.sh, but by execution).
    777 # cc -S is now object-format-aware, so the native Mach-O clang lane GATES by
    778 # default (both lanes 312/0); KIT_HOSTAS_ENFORCE_CLANG=0 demotes it to XFAIL.
    779 # Opt-in; skips cleanly if clang is absent.
    780 test-hostas-toy: bin
    781 	@bash test/asm/hostas_toy.sh
    782 
    783 # test-hostas-cross: the same two-assembler-by-execution idea as test-hostas-toy,
    784 # but CROSS — `cc -S -target <triple>` for ELF Linux arches (aarch64/x86_64/
    785 # riscv64), assembled by kit-as AND clang, linked static (+ the start.c crt)
    786 # with kit ld, and run under podman/qemu via test/lib/exec_target.sh. Each
    787 # target self-skips unless the host has a clang cross target, a runner, a
    788 # working `cc -S | kit as` for that arch, and a passing bounded exec smoke —
    789 # so it runs green on whatever the host supports (aarch64-linux today; x86_64
    790 # pends the x64 cc -S symbolizer, riscv64 pends a working rv64 user-mode
    791 # emulator). Opt-in; skips cleanly if clang/podman are absent.
    792 test-hostas-cross: bin
    793 	@bash test/asm/hostas_cross.sh
    794 
    795 test-wasm: test-wasm-front test-wasm-target test-wasm-toy
    796 
    797 test-wasm-front: bin $(WASM_TOOL) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    798 	bash test/wasm/run.sh
    799 
    800 # test-wasm-target: structural checks on `kit cc -target wasm32-none`
    801 # output. test/wasm-target/run.sh is a Type C corpus harness whose lanes each
    802 # compile a tiny C or toy fixture and assert a property of the produced module
    803 # bytes (inline-asm opcodes, memory.copy/fill opcodes, exported "memory",
    804 # (import ...) decls). Opt-in: not in the default `test` target because the
    805 # checks depend on the bulk-memory + (import ...) backend work landing first.
    806 test-wasm-target: bin
    807 	@KIT=$(abspath $(BIN)) bash test/wasm-target/run.sh
    808 
    809 # test-smoke-x64: phase-1 sanity check for the multi-arch bring-up. Builds a
    810 # tiny freestanding x86_64 ELF with clang --target=x86_64-linux-gnu and
    811 # runs it through test/lib/exec_target.sh's podman/qemu pipeline,
    812 # proving the harness end-to-end before any kit-emitted x64 bytes
    813 # exist. Excluded from the default `test` target because it needs
    814 # podman + lld; opt-in via `make test-smoke-x64`.
    815 test-smoke-x64:
    816 	bash test/smoke/x64.sh
    817 
    818 # test-smoke-rv64: phase-2 counterpart of test-smoke-x64. Builds a
    819 # tiny freestanding riscv64 ELF with clang --target=riscv64-linux-gnu
    820 # and runs it through test/lib/exec_target.sh, proving the rv64 lane
    821 # of the harness end-to-end before any kit-emitted rv64 bytes
    822 # exist. Excluded from the default `test` target because it needs
    823 # qemu-riscv64 (or podman with riscv64 emulation) + lld; opt-in via
    824 # `make test-smoke-rv64`.
    825 test-smoke-rv64:
    826 	bash test/smoke/rv64.sh
    827 
    828 # test-parse-rv64-wide: end-to-end coverage of the rv64 128-bit scalar types
    829 # — __int128 (i128_*) and IEEE-754 binary128 long double (ldbl128_*) — built
    830 # with kit and run on riscv64. Exercises the soft-float / i128 lowering to
    831 # the compiler-rt-style runtime (fp_tf, fp_ti, int64), the LP64D register-pair
    832 # ABI for 16-byte scalars, and the conditional-branch range fix that large
    833 # soft-float helpers depend on. Opt-in (needs qemu-riscv64 or podman with
    834 # riscv64 emulation), so excluded from the default `test` target; mirrors
    835 # test-smoke-rv64. Run a single case with
    836 #   KIT_TEST_ARCH=rv64 bash test/parse/run.sh ldbl128_03_arith
    837 test-parse-rv64-wide: lib rt-riscv64-linux $(PARSE_RUNNER) $(ROUNDTRIP_BIN) \
    838                       $(LINK_EXE_RUNNER)
    839 	@KIT_TEST_ARCH=rv64 KIT_TEST_PATHS=RE bash test/parse/run.sh 128
    840 
    841 # test-bounce: format-bounce stress test. Compiles small programs with
    842 # kit, then bounces each object through chains of format conversions
    843 # (ELF<->Mach-O<->COFF), partial links (ld -r), strip, and archive
    844 # round-trips, relinks, and runs the result, asserting the exit code
    845 # matches a host-cc reference. Stresses obj read/write/reloc, ar, and
    846 # partial-link paths rather than the arches. Defaults to the host-native
    847 # Linux arch (no emulation); sweep others with
    848 # KIT_BOUNCE_ARCHES="aarch64 x64 rv64". Excluded from the default `test`
    849 # target because it needs podman/qemu + a host cc; opt-in via
    850 # `make test-bounce`.
    851 test-bounce: $(BIN)
    852 	bash test/bounce/bounce.sh
    853 
    854 # test-libc: aggregate alias that runs test-libc-musl and test-libc-glibc.
    855 # test-libc-musl / test-libc-glibc: end-to-end static + dynamic libc link/run
    856 # on aarch64. Each variant pulls its own pinned sysroot (podman, ~30s on
    857 # first run) and shares the same case files under test/libc/cases/:
    858 #
    859 #   test-libc-musl   — Alpine 3.20 + musl 1.2.5    (test/libc/musl/)
    860 #   test-libc-glibc  — Debian bookworm + glibc 2.36 (test/libc/glibc/)
    861 #
    862 # Both build build/rt/aarch64-linux/libkit_rt.a for soft-float / TF
    863 # builtins, and run `kit ld` against the real libc.a (static) and
    864 # libc.so / libc.so.6 (dynamic). Excluded from the
    865 # default `test` target because they need podman; opt-in via
    866 # `make test-libc-musl` / `make test-libc-glibc` (or `make test-libc` for both).
    867 #
    868 # Each sysroot is treated as a real prerequisite via its PROVENANCE
    869 # marker so subsequent runs skip extraction and re-extract only when
    870 # the file is removed (or extract.sh -f forces a rebuild).
    871 MUSL_SYSROOT_MARKER       = build/musl-sysroot/PROVENANCE
    872 MUSL_SYSROOT_X64_MARKER   = build/musl-sysroot-x64/PROVENANCE
    873 MUSL_SYSROOT_RV64_MARKER  = build/musl-sysroot-rv64/PROVENANCE
    874 GLIBC_SYSROOT_MARKER      = build/glibc-sysroot/PROVENANCE
    875 GLIBC_SYSROOT_X64_MARKER  = build/glibc-sysroot-x64/PROVENANCE
    876 GLIBC_SYSROOT_RV64_MARKER = build/glibc-sysroot-rv64/PROVENANCE
    877 
    878 $(MUSL_SYSROOT_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile
    879 	@bash test/libc/musl/extract.sh
    880 
    881 $(MUSL_SYSROOT_X64_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile.x64
    882 	@bash test/libc/musl/extract.sh -a x64
    883 
    884 $(MUSL_SYSROOT_RV64_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile.rv64
    885 	@bash test/libc/musl/extract.sh -a rv64
    886 
    887 $(GLIBC_SYSROOT_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile
    888 	@bash test/libc/glibc/extract.sh
    889 
    890 $(GLIBC_SYSROOT_X64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.x64
    891 	@bash test/libc/glibc/extract.sh -a x64
    892 
    893 $(GLIBC_SYSROOT_RV64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.rv64
    894 	@bash test/libc/glibc/extract.sh -a rv64
    895 
    896 # test-libc-musl / test-libc-glibc honor KIT_LIBC_ARCHES (default "aa64";
    897 # values: aa64, x64, rv64). Each enabled arch contributes its sysroot
    898 # PROVENANCE marker and its rt archive to the prerequisite list, so
    899 # `KIT_LIBC_ARCHES="aa64 x64" make test-libc-musl` builds both sysroots
    900 # + both rt archives before the runner script picks them up.
    901 KIT_LIBC_ARCHES ?= aa64
    902 
    903 # Map an arch token to its musl/glibc sysroot marker and rt target.
    904 _LIBC_MUSL_SYSROOT_aa64  = $(MUSL_SYSROOT_MARKER)
    905 _LIBC_MUSL_SYSROOT_x64   = $(MUSL_SYSROOT_X64_MARKER)
    906 _LIBC_MUSL_SYSROOT_rv64  = $(MUSL_SYSROOT_RV64_MARKER)
    907 _LIBC_GLIBC_SYSROOT_aa64 = $(GLIBC_SYSROOT_MARKER)
    908 _LIBC_GLIBC_SYSROOT_x64  = $(GLIBC_SYSROOT_X64_MARKER)
    909 _LIBC_GLIBC_SYSROOT_rv64 = $(GLIBC_SYSROOT_RV64_MARKER)
    910 _LIBC_RT_aa64            = rt-aarch64-linux
    911 _LIBC_RT_x64             = rt-x86_64-linux
    912 _LIBC_RT_rv64            = rt-riscv64-linux
    913 
    914 LIBC_MUSL_DEPS  = $(foreach a,$(KIT_LIBC_ARCHES),$(_LIBC_MUSL_SYSROOT_$(a)) $(_LIBC_RT_$(a)))
    915 LIBC_GLIBC_DEPS = $(foreach a,$(KIT_LIBC_ARCHES),$(_LIBC_GLIBC_SYSROOT_$(a)) $(_LIBC_RT_$(a)))
    916 
    917 test-libc: test-libc-musl test-libc-glibc
    918 
    919 test-libc-musl: bin $(LIBC_MUSL_DEPS)
    920 	@KIT_LIBC_ARCHES="$(KIT_LIBC_ARCHES)" bash test/libc/musl/run.sh
    921 
    922 test-libc-glibc: bin $(LIBC_GLIBC_DEPS)
    923 	@KIT_LIBC_ARCHES="$(KIT_LIBC_ARCHES)" bash test/libc/glibc/run.sh
    924 
    925 test-libc-musl-rv64:
    926 	@$(MAKE) test-libc-musl KIT_LIBC_ARCHES=rv64
    927 
    928 test-libc-glibc-rv64:
    929 	@$(MAKE) test-libc-glibc KIT_LIBC_ARCHES=rv64
    930 
    931 # Fail if libkit.a depends on any external symbol not in the allowlist, or
    932 # if a relocatable link exposes non-public global definitions.
    933 # External dependency drift in either direction (new dep, or stale entry) is a
    934 # failure.
    935 #
    936 # Inspect the RELEASE artifacts only: the debug build is ASan/UBSan-instrumented
    937 # and pulls in __asan_*/__ubsan_* externals that are not part of the shipped
    938 # library's dependency surface.
    939 LIB_DEPS_RELEASE_DIR = build/release
    940 LIB_DEPS_AR     = $(LIB_DEPS_RELEASE_DIR)/libkit.a
    941 LIB_DEPS_ACTUAL = $(LIB_DEPS_RELEASE_DIR)/libkit.deps.txt
    942 LIB_RELOC       = $(LIB_DEPS_RELEASE_DIR)/libkit.reloc.o
    943 LIB_RELOC_BAD   = $(LIB_DEPS_RELEASE_DIR)/libkit.reloc.bad-symbols.txt
    944 
    945 test-lib-deps:
    946 	@$(MAKE) lib RELEASE=1
    947 	@mkdir -p $(dir $(LIB_DEPS_ACTUAL))
    948 	@python3 scripts/lib_external_deps.py $(LIB_DEPS_AR) > $(LIB_DEPS_ACTUAL)
    949 	@diff -u scripts/lib_deps.allowlist $(LIB_DEPS_ACTUAL) \
    950 	    || { echo "libkit.a external symbol set drifted from scripts/lib_deps.allowlist"; exit 1; }
    951 	@python3 scripts/lib_reloc_defined_prefixes.py $(LIB_DEPS_AR) \
    952 	    --output $(LIB_RELOC) --ar $(AR) --cc $(CC) > $(LIB_RELOC_BAD)
    953 	@test ! -s $(LIB_RELOC_BAD) \
    954 	    || { echo "libkit relocatable link exposes non-public symbols"; cat $(LIB_RELOC_BAD); exit 1; }