kit

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

test.mk (52696B)


      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-asm-rv32 \
     44     test-toy-rv32 \
     45     test-parse-rv32 \
     46     test-disasm-complete \
     47     test-asm-roundtrip \
     48     test-asm-roundtrip-exec \
     49     test-asm-symmetry \
     50     test-asm-roundtrip-toy \
     51     test-hostas-toy \
     52     test-hostas-cross \
     53     test-diff-llvm \
     54     test-bootstrap-toy \
     55     test-bootstrap-toy-debug \
     56     test-bootstrap-toy-release \
     57     test-bounce \
     58     test-cbackend \
     59     test-cg-api \
     60     test-coff \
     61     test-coff-mingw-import \
     62     test-coff-windows-o1-abi \
     63     test-coff-windows-ucrt \
     64     test-debug \
     65     test-dbg \
     66     test-driver \
     67     test-driver-ar \
     68     test-driver-cas \
     69     test-driver-cc \
     70     test-driver-build \
     71     test-driver-lsan \
     72     test-driver-objcopy \
     73     test-driver-objdump \
     74     test-driver-pkg \
     75     test-driver-strings \
     76     test-driver-tools \
     77     test-driver-wasm \
     78     test-hash \
     79     test-driver-strip \
     80     test-dwarf \
     81     test-elf \
     82     test-emu \
     83     test-emu-unit \
     84     test-interp \
     85     test-interp-emu \
     86     test-interp-toy \
     87     test-ir-recorder \
     88     test-isa \
     89     test-lib-deps \
     90     test-libc \
     91     test-libc-glibc \
     92     test-libc-glibc-rv64 \
     93     test-libc-musl \
     94     test-libc-musl-rv64 \
     95     test-link \
     96     test-link-reloc-uleb128 \
     97     test-link-reloc-desc \
     98     test-link-reloc-apply \
     99     test-link-jit-tls-relax \
    100     test-link-coff-weak-alias \
    101     test-macho \
    102     test-native-direct-target \
    103     test-opt \
    104     test-parse \
    105     test-parse-err \
    106     test-parse-ok \
    107     test-parse-rv64-wide \
    108     test-pp \
    109     test-pp-err \
    110     test-pp-ok \
    111     test-pp-file-escape \
    112     test-rt-headers \
    113     test-rt-runtime \
    114     test-rt-backtrace \
    115     test-link-x64 \
    116     test-rv64-inline \
    117     test-rv64-jit \
    118     test-rv32-jit \
    119     test-rv64-tls-link \
    120     test-smoke-rv64 \
    121     test-smoke-rv32 \
    122     test-smoke-x64 \
    123     test-toy \
    124     test-wasm \
    125     test-wasm-c \
    126     test-wasm-front \
    127     test-wasm-target \
    128     test-wasm-toy \
    129     test-x64-dbg \
    130     test-x64-inline
    131 
    132 DEFAULT_TEST_TARGETS = \
    133     test-cf-corpus-selftest \
    134     test-driver \
    135     test-pp \
    136     test-elf \
    137     test-coff \
    138     test-ar \
    139     test-link \
    140     test-toy \
    141     test-dwarf \
    142     test-debug \
    143     test-parse \
    144     test-asm \
    145     test-asm-roundtrip \
    146     test-isa \
    147     test-aa64-inline \
    148     test-rv64-inline \
    149     test-rv64-jit \
    150     test-rv32-jit \
    151     test-rv64-tls-link \
    152     test-emu \
    153     test-emu-unit \
    154     test-interp \
    155     test-interp-emu \
    156     test-x64-inline \
    157     test-x64-dbg \
    158     test-rt-headers \
    159     test-lib-deps \
    160     test-cg-api \
    161     test-abi-classify \
    162     test-ir-recorder \
    163     test-native-direct-target \
    164     test-opt \
    165     test-asm-symmetry \
    166     test-link-reloc-uleb128 \
    167     test-link-reloc-desc \
    168     test-link-reloc-apply \
    169     test-link-jit-tls-relax \
    170     test-link-coff-weak-alias \
    171     test-dbg \
    172     test-disasm-complete \
    173     test-macho \
    174     test-interp-toy \
    175     test-wasm \
    176 		test-libc \
    177 		test-link-x64 \
    178 		test-rt-runtime \
    179 		test-rt-backtrace \
    180 		test-bounce \
    181 		bootstrap \
    182 		test-bootstrap-toy
    183 
    184 .PHONY: test $(TEST_TARGETS) windows-ucrt-sysroots \
    185     test-toy-vm test-toy-freebsd-vm test-toy-windows-vm \
    186     test-hosted test-hosted-vm hosted-glibc-images
    187 
    188 test: $(DEFAULT_TEST_TARGETS)
    189 
    190 # Unit-test binary build rules: two regimes (public vs internal interface).
    191 include mk/test_unit.mk
    192 
    193 # Provision the pinned per-arch container rootfs images that exec_target.sh runs
    194 # kit-emitted binaries inside. This is the ONLY test step that touches the
    195 # network: the cross-arch exec harnesses (toy/parse/link ... path X/E/L) run with
    196 # `podman run --pull=never`, so without these images those paths SKIP. Run once,
    197 # and again only after the pin in test/lib/test_images.sh changes. FORCE=1
    198 # re-pulls. The images are pinned per-arch by content digest, so they coexist in
    199 # local storage and can never clobber one another.
    200 .PHONY: test-images
    201 test-images:
    202 	@bash test/lib/pull_test_images.sh
    203 
    204 # Hermetic self-test of the shared corpus harness engine (test/lib/kit_corpus.sh):
    205 # asserts serial==parallel determinism, SKIP-NA, and the parallel-safety
    206 # invariant (exec_target queued only on the parent, never in a worker). No
    207 # kit binary / podman / qemu needed.
    208 test-cf-corpus-selftest:
    209 	@bash test/lib/kit_corpus_selftest.sh
    210 
    211 test-driver: test-driver-cc test-driver-build test-driver-ar test-driver-cas test-driver-strip test-driver-objcopy test-driver-objdump test-driver-pkg test-driver-strings test-driver-tools test-driver-wasm
    212 
    213 test-driver-cc: bin
    214 	@KIT=$(abspath $(BIN)) sh test/driver/run.sh
    215 
    216 test-driver-build: bin
    217 	@KIT=$(abspath $(BIN)) sh test/buildcmds/run.sh
    218 
    219 ifeq ($(HOST_OS),linux)
    220 test-driver-lsan:
    221 	@$(MAKE) test-driver RELEASE=0 BUILD_DIR=build/lsan \
    222 	    ASAN_OPTIONS=detect_leaks=1:halt_on_error=1:abort_on_error=1
    223 else
    224 test-driver-lsan:
    225 	@printf '%s\n' 'SKIP test-driver-lsan: LeakSanitizer requires HOST_OS=linux'
    226 endif
    227 
    228 # test-cbackend: --emit=c C-source backend, driven through three
    229 # frontends — parse-runner (C), toy-runner (toy), wasm-runner (wat/wasm).
    230 # Each invokes its existing runner with paths=C so a single corpus per
    231 # frontend exercises both the existing backends and the C backend.
    232 # Together they prove the CGTarget seam is frontend-agnostic.
    233 # Unimplemented CGTarget methods report as SKIP; see doc/CBACKEND.md.
    234 KIT_CBACKEND_TEST_JOBS ?= $(if $(KIT_TEST_JOBS),$(KIT_TEST_JOBS),1)
    235 test-cbackend: bin
    236 	@KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT_TEST_ALLOW_SKIP=1 sh test/parse/run.sh
    237 	@KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT=$(abspath $(BIN)) sh test/toy/run.sh
    238 	@KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT=$(abspath $(BIN)) bash test/wasm/run.sh
    239 
    240 # test-wasm-toy: opt-in Toy -> Wasm -> JIT roundtrip. Runs the toy corpus
    241 # under the W path (compile -target wasm32-none, then `kit run` the .wasm,
    242 # which routes back through the lang/wasm frontend to native CG). Most cases
    243 # will fail or skip today; the target exists so progress on the Wasm CGTarget
    244 # is visible without putting noise into the default `test` summary.
    245 # Drop `KIT_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
    246 test-wasm-toy: bin
    247 	@KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) sh test/toy/run.sh
    248 
    249 # test-wasm-c: opt-in C -> Wasm -> JIT roundtrip, the C-frontend analogue of
    250 # test-wasm-toy. Runs the test/parse corpus under the W path (compile
    251 # -target wasm32-none, then `kit run -e test_main` the .wasm, which routes
    252 # back through the lang/wasm frontend to native CG). The C corpus exercises
    253 # far more of the language than the toy corpus, so expect many SKIPs for
    254 # not-yet-implemented Wasm lowerings; the target makes that progress visible
    255 # without adding noise to the default `test` summary. Drop
    256 # `KIT_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
    257 test-wasm-c: bin $(PARSE_RUNNER)
    258 	@KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) bash test/parse/run.sh
    259 
    260 test-pp: test-pp-ok test-pp-err test-pp-file-escape
    261 
    262 test-pp-ok: bin
    263 	@KIT=$(abspath $(BIN)) test/pp/run.sh
    264 
    265 test-pp-err: bin
    266 	@KIT=$(abspath $(BIN)) test/pp/run_errors.sh
    267 
    268 test-pp-file-escape: bin
    269 	@KIT=$(abspath $(BIN)) bash test/pp/run_file_escape.sh
    270 
    271 # Best-effort kit binary build: Layer D needs build/kit, but the
    272 # binary may not link until enough libkit symbols exist. The harness
    273 # detects a missing binary and skips that layer; don't break test-elf
    274 # when bin fails.
    275 .PHONY: bin-soft
    276 bin-soft:
    277 	-@$(MAKE) bin 2>/dev/null || true
    278 
    279 AR_TEST_BIN = build/test/ar_test
    280 
    281 test-ar: $(AR_TEST_BIN)
    282 	$(AR_TEST_BIN)
    283 
    284 
    285 test-driver-ar: bin
    286 	@KIT=$(abspath $(BIN)) test/ar/run.sh
    287 
    288 test-driver-cas: bin
    289 	@KIT=$(abspath $(BIN)) sh test/cas/run.sh
    290 
    291 test-driver-strip: bin
    292 	@KIT=$(abspath $(BIN)) test/strip/run.sh
    293 
    294 test-driver-objcopy: bin
    295 	@KIT=$(abspath $(BIN)) test/objcopy/run.sh
    296 
    297 test-driver-objdump: bin
    298 	@KIT=$(abspath $(BIN)) sh test/objdump/run.sh
    299 
    300 test-driver-pkg: bin
    301 	@KIT=$(abspath $(BIN)) sh test/pkg/run.sh
    302 
    303 test-driver-strings: bin
    304 	@KIT=$(abspath $(BIN)) sh test/strings/run.sh
    305 
    306 test-driver-tools: bin
    307 	@KIT=$(abspath $(BIN)) sh test/tools/run.sh
    308 
    309 test-driver-wasm: bin
    310 	@KIT=$(abspath $(BIN)) sh test/driver-wasm/run.sh
    311 
    312 # DWARF consumer unit test: builds a hand-crafted DWARF-bearing ELF in
    313 # memory and exercises every kit_dwarf_* entry. It reaches into the
    314 # internal object builder to synthesize the fixture, so link individual
    315 # lib objects rather than libkit.a (which hides internal symbols).
    316 DWARF_TEST_BIN = build/test/dwarf_test
    317 
    318 test-dwarf: $(DWARF_TEST_BIN)
    319 	$(DWARF_TEST_BIN)
    320 
    321 
    322 # DWARF producer self-roundtrip unit test. Drives Debug directly, calls
    323 # debug_emit, asserts the produced sections have valid DWARF 5 structure
    324 # (length fields, version, address sizes, expected relocations against
    325 # function symbol). Deliberately bypasses the consumer (kit_dwarf_open)
    326 # so encoder bugs aren't masked by matching decoder bugs.
    327 DEBUG_TEST_BIN = build/test/debug_roundtrip_unit
    328 CFI_TEST_BIN = build/test/debug_cfi_unit
    329 
    330 test-debug: $(DEBUG_TEST_BIN) $(CFI_TEST_BIN)
    331 	$(DEBUG_TEST_BIN)
    332 	$(CFI_TEST_BIN)
    333 
    334 
    335 # CFI/.eh_frame producer roundtrip for aa64/rv64/x64 (validates the per-arch
    336 # CIE template: code/data-align, return-address reg, CFA-init reg).
    337 
    338 test-dbg: bin
    339 	@KIT=$(abspath $(BIN)) sh test/dbg/run.sh
    340 
    341 .PHONY: test-dbg-red
    342 test-dbg-red: bin
    343 	@KIT=$(abspath $(BIN)) DBG_STRICT_XFAIL=1 sh test/dbg/run.sh
    344 
    345 # aa64 ISA descriptor-table unit test (doc/ASM.md phase 2). Covers
    346 # every AA64Format the table maps and the alias-precedence invariant
    347 # (first-match disasm picks the alias spelling over the canonical
    348 # form). Internal arch/ surface — needs -Isrc.
    349 AA64_ISA_TEST_BIN = build/test/aa64_isa_test
    350 RV64_DECODE_TEST_BIN = build/test/rv64_decode_test
    351 RV32_DECODE_TEST_BIN = build/test/rv32_decode_test
    352 
    353 test-isa: $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN) $(RV32_DECODE_TEST_BIN)
    354 	$(AA64_ISA_TEST_BIN)
    355 	$(RV64_DECODE_TEST_BIN)
    356 	$(RV32_DECODE_TEST_BIN)
    357 
    358 
    359 
    360 # aa64_sweep_gen: emits one representative encoding per disasm-table row for the
    361 # asm<->disasm self-symmetry sweep (test/asm/symmetry.sh). Needs the internal
    362 # arch/aa64/isa.h surface, so -Isrc + LIB_OBJS like the ISA unit test.
    363 AA64_SWEEP_GEN = build/test/aa64_sweep_gen
    364 
    365 # test-emu: emulator end-to-end integration test. Builds tiny in-memory rv64
    366 # ELFs and runs them to their exit syscall, asserting the exit code — entirely
    367 # through the PUBLIC kit_emu_* API, so it links the public archive ($(LIB_AR),
    368 # whose `ld -r` step localizes internal symbols). -Isrc is only for the
    369 # header-only rv64 encoders / ELF constants the in-memory ELF builders use.
    370 EMU_RV64_TEST_BIN = build/test/emu_rv64_test
    371 
    372 test-emu: $(EMU_RV64_TEST_BIN)
    373 	$(EMU_RV64_TEST_BIN)
    374 
    375 $(EMU_RV64_TEST_BIN): test/emu/rv64_smoke_test.c $(UNIT_HDR_DEPS) $(LIB_AR)
    376 	@mkdir -p $(dir $@)
    377 	$(CC) $(HOST_CFLAGS) -Iinclude -Isrc -Itest test/emu/rv64_smoke_test.c $(LIB_AR) -o $@
    378 
    379 # RISC-V ULEB128 diff-reloc application unit test. link_reloc_apply is an
    380 # internal (hidden) symbol, so link the raw lib objects like the other
    381 # internal-surface unit tests rather than libkit.a.
    382 RELOC_ULEB128_TEST_BIN = build/test/reloc_uleb128_unit
    383 
    384 test-link-reloc-uleb128: $(RELOC_ULEB128_TEST_BIN)
    385 	$(RELOC_ULEB128_TEST_BIN)
    386 
    387 # Relocation-descriptor migration guard (doc/plan/RELOC.md, WS-B): pins
    388 # reloc_desc()/reloc_kind_* to the frozen pre-refactor width + classification
    389 # behaviour across every backend arch. Internal-surface, so links the raw lib
    390 # objects like the other internal unit tests.
    391 RELOC_DESC_TEST_BIN = build/test/reloc_desc_test
    392 
    393 test-link-reloc-desc: $(RELOC_DESC_TEST_BIN)
    394 	$(RELOC_DESC_TEST_BIN)
    395 
    396 # Relocation byte-encoder migration guard (doc/plan/RELOC.md, WS-C): pins
    397 # link_reloc_apply to the frozen pre-refactor patched bytes for every
    398 # instruction-immediate kind, so partitioning the encoders per-arch stays
    399 # byte-identical. Internal-surface, so links the raw lib objects.
    400 RELOC_APPLY_TEST_BIN = build/test/reloc_apply_test
    401 
    402 test-link-reloc-apply: $(RELOC_APPLY_TEST_BIN)
    403 	$(RELOC_APPLY_TEST_BIN)
    404 
    405 # In-process JIT TLS Local-Exec relaxation for the Windows (COFF) TEB idiom:
    406 # pins the per-arch byte rewrite (jit_tls_le_relax) for x86-64 and aarch64. The
    407 # aarch64 path is also covered end-to-end on the Windows VM; this is the only
    408 # automated guard for the x86-64 idiom. Internal-surface (raw lib objects).
    409 JIT_TLS_RELAX_TEST_BIN = build/test/jit_tls_relax_test
    410 
    411 test-link-jit-tls-relax: $(JIT_TLS_RELAX_TEST_BIN)
    412 	$(JIT_TLS_RELAX_TEST_BIN)
    413 
    414 # COFF WEAK_EXTERNAL alias capture in read_coff: pins the aux-TagIndex decode
    415 # that lets the linker resolve mingw x86_64's `_setjmp` -> `__intrinsic_setjmp`
    416 # alias (the x64-windows cross-link blocker). Internal-surface (raw lib objects).
    417 COFF_WEAK_ALIAS_TEST_BIN = build/test/coff_weak_alias_test
    418 
    419 test-link-coff-weak-alias: $(COFF_WEAK_ALIAS_TEST_BIN)
    420 	$(COFF_WEAK_ALIAS_TEST_BIN)
    421 
    422 # test-emu-unit: white-box unit tests for the emulator's INTERNAL units (rv64
    423 # decoder, EmuAddrSpace, Linux syscall handler) that have no public API. Reaches
    424 # internal symbols -> links $(LIB_OBJS) (mirrors test-interp), not the archive.
    425 EMU_RV64_UNIT_TEST_BIN = build/test/emu_rv64_unit_test
    426 
    427 test-emu-unit: $(EMU_RV64_UNIT_TEST_BIN)
    428 	$(EMU_RV64_UNIT_TEST_BIN)
    429 
    430 
    431 # test-interp: threaded-bytecode interpreter unit smoke test. Builds tiny CG IR
    432 # by hand, runs opt_run_o1_interp + interp_lower + the engine, asserts the
    433 # returned value. Reaches internal opt/interp symbols -> links $(LIB_OBJS) and
    434 # needs -Isrc (mirrors test-opt).
    435 INTERP_SMOKE_TEST_BIN = build/test/interp_smoke_test
    436 
    437 test-interp: $(INTERP_SMOKE_TEST_BIN)
    438 	$(INTERP_SMOKE_TEST_BIN)
    439 
    440 
    441 # test-interp-emu: differential test of the emulator's INTERP execution mode
    442 # (doc/INTERPRETER.md Phase 4). Builds a tiny rv64 ELF with SD/LD/ecall and runs
    443 # it through kit_emu_run in JIT and INTERP modes (both -O1), asserting the exit
    444 # codes match. Public-API only, but links $(LIB_OBJS) like test-interp to dodge
    445 # the visibility-hidden archive's localized internals.
    446 INTERP_EMU_TEST_BIN = build/test/rv64_interp_smoke_test
    447 
    448 test-interp-emu: $(INTERP_EMU_TEST_BIN)
    449 	$(INTERP_EMU_TEST_BIN)
    450 
    451 
    452 # test-interp-toy: run the toy suite's interpreter (--no-jit) path only,
    453 # asserting it matches the golden exit codes (and SKIPping unimplemented ops).
    454 test-interp-toy: bin
    455 	@KIT=$(abspath $(BIN)) KIT_TEST_PATHS=I bash test/toy/run.sh
    456 
    457 CG_API_TEST_BIN = build/test/cg_api_test
    458 CG_SWITCH_TEST_BIN = build/test/cg_switch_test
    459 CG_FP_CMP_TEST_BIN = build/test/cg_fp_cmp_test
    460 CG_CONTROL_TEST_BIN = build/test/cg_control_test
    461 STRENGTH_REDUCE_TEST_BIN = build/test/strength_reduce_test
    462 TARGET_TEST_BIN = build/test/target_test
    463 HASH_TEST_BIN = build/test/hash_test
    464 PANIC_RECOVERY_TEST_BIN = build/test/panic_recovery_test
    465 ABI_CLASSIFY_TEST_BIN = build/test/abi_classify_test
    466 IR_RECORDER_TEST_BIN = build/test/ir_recorder_test
    467 NATIVE_DIRECT_TARGET_TEST_BIN = build/test/native_direct_target_test
    468 
    469 test-cg-api: $(TARGET_TEST_BIN) $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) \
    470              $(CG_FP_CMP_TEST_BIN) $(CG_CONTROL_TEST_BIN) \
    471              $(STRENGTH_REDUCE_TEST_BIN) \
    472              $(PANIC_RECOVERY_TEST_BIN)
    473 	$(TARGET_TEST_BIN)
    474 	$(CG_API_TEST_BIN)
    475 	$(CG_SWITCH_TEST_BIN)
    476 	$(CG_FP_CMP_TEST_BIN)
    477 	$(CG_CONTROL_TEST_BIN)
    478 	$(STRENGTH_REDUCE_TEST_BIN)
    479 	$(PANIC_RECOVERY_TEST_BIN)
    480 
    481 test-hash: $(HASH_TEST_BIN)
    482 	$(HASH_TEST_BIN)
    483 
    484 test-abi-classify: $(ABI_CLASSIFY_TEST_BIN)
    485 	$(ABI_CLASSIFY_TEST_BIN)
    486 
    487 
    488 
    489 
    490 test-ir-recorder: $(IR_RECORDER_TEST_BIN)
    491 	$(IR_RECORDER_TEST_BIN)
    492 
    493 
    494 test-native-direct-target: $(NATIVE_DIRECT_TARGET_TEST_BIN)
    495 	$(NATIVE_DIRECT_TARGET_TEST_BIN)
    496 
    497 
    498 test-toy: bin
    499 	@KIT=$(abspath $(BIN)) test/toy/run.sh
    500 
    501 # Opt-in: run the Toy corpus as real hosted programs inside the FreeBSD and
    502 # Windows VMs (test/toy/vm.sh), asserting each case's .expected exit code on the
    503 # genuine OS — the hosted counterpart to run.sh's freestanding-Linux X lane.
    504 # Not in the default set (needs provisioned VMs + cross sysroots; amd64/riscv64
    505 # FreeBSD run under slow TCG). The FreeBSD script lazily builds its base.txz
    506 # sysroots and self-skips arches whose VM/sysroot is absent; the Windows target
    507 # carries the same rt + UCRT-sysroot deps as test-coff-windows-vm.
    508 test-toy-freebsd-vm: bin
    509 	@KIT=$(abspath $(BIN)) bash test/toy/vm.sh freebsd
    510 
    511 test-toy-windows-vm: bin rt-x86_64-pc-windows rt-aarch64-windows windows-ucrt-sysroots
    512 	@KIT=$(abspath $(BIN)) bash test/toy/vm.sh windows
    513 
    514 test-toy-vm: test-toy-freebsd-vm test-toy-windows-vm
    515 
    516 # test-bootstrap-toy: run the Toy corpus through the bootstrapped (self-built)
    517 # stage3 kit instead of the host-built binary, so the self-hosted compiler is
    518 # exercised on real codegen (not just self-reproduction like `make bootstrap`).
    519 # Split per build mode: -debug runs the debug stage3, -release the release
    520 # (-O1) stage3; the aggregate target runs both.
    521 test-bootstrap-toy: test-bootstrap-toy-debug test-bootstrap-toy-release
    522 
    523 test-bootstrap-toy-debug: bootstrap-debug
    524 	@KIT='$(abspath $(BUILD_DIR)/debug/bootstrap/stage3/kit)' test/toy/run.sh
    525 
    526 test-bootstrap-toy-release: bootstrap-release
    527 	@KIT='$(abspath $(BUILD_DIR)/release/bootstrap/stage3/kit)' test/toy/run.sh
    528 
    529 
    530 # Public-API inline-asm backend tests. These emit a tiny function through CG,
    531 # reopen the object through the public object reader, and assert the expected
    532 # instruction bytes are present in .text.
    533 AA64_INLINE_TEST_BIN = build/test/aa64_inline_test
    534 
    535 test-aa64-inline: $(AA64_INLINE_TEST_BIN)
    536 	$(AA64_INLINE_TEST_BIN)
    537 
    538 
    539 RV64_INLINE_TEST_BIN = build/test/rv64_inline_test
    540 
    541 test-rv64-inline: $(RV64_INLINE_TEST_BIN)
    542 	$(RV64_INLINE_TEST_BIN)
    543 
    544 
    545 # rv64 JIT smoke test. Builds a tiny rv64 ELF .o in memory, runs it
    546 # through kit_link_session in JIT-output mode, and skips native execution
    547 # on non-riscv64 hosts after exercising the JIT mapping/reloc path.
    548 RV64_JIT_TEST_BIN = build/test/rv64_jit_test
    549 
    550 test-rv64-jit: $(RV64_JIT_TEST_BIN)
    551 	@$(RV64_JIT_TEST_BIN); rc=$$?; \
    552 	  if [ $$rc -eq 77 ]; then \
    553 	    echo "  (rv64_jit_test SKIPPED on non-rv64 host)"; \
    554 	    exit 0; \
    555 	  else \
    556 	    exit $$rc; \
    557 	  fi
    558 
    559 # rv32 JIT smoke test. Mirror of test-rv64-jit: builds a tiny rv32 ELF .o in
    560 # memory, runs it through kit_link_session in JIT-output mode, and skips native
    561 # execution on non-riscv32 hosts after exercising the JIT mapping/reloc path.
    562 RV32_JIT_TEST_BIN = build/test/rv32_jit_test
    563 
    564 test-rv32-jit: $(RV32_JIT_TEST_BIN)
    565 	@$(RV32_JIT_TEST_BIN); rc=$$?; \
    566 	  if [ $$rc -eq 77 ]; then \
    567 	    echo "  (rv32_jit_test SKIPPED on non-rv32 host)"; \
    568 	    exit 0; \
    569 	  else \
    570 	    exit $$rc; \
    571 	  fi
    572 
    573 
    574 # Link-only regression for rv64 TLS Local-Exec lowering (runs on any host).
    575 test-rv64-tls-link: bin
    576 	@KIT='$(abspath $(BIN))' bash test/smoke/rv64_tls_link.sh
    577 
    578 X64_INLINE_TEST_BIN = build/test/x64_inline_test
    579 
    580 test-x64-inline: $(X64_INLINE_TEST_BIN)
    581 	$(X64_INLINE_TEST_BIN)
    582 
    583 
    584 X64_DBG_TEST_BIN = build/test/x64_dbg_test
    585 
    586 test-x64-dbg: $(X64_DBG_TEST_BIN)
    587 	$(X64_DBG_TEST_BIN)
    588 
    589 # Reaches the internal arch/arch.h surface (arch_lookup, ArchDbgOps) -> links
    590 # $(LIB_OBJS), not the archive, whose relocatable merge localizes non-public
    591 # symbols (mirrors the aa64/rv64 arch unit tests above).
    592 
    593 RT_HEADER_TEST_TARGETS = \
    594     aarch64-linux-gnu        \
    595     x86_64-linux-gnu         \
    596     riscv64-linux-gnu        \
    597     aarch64-apple-darwin     \
    598     x86_64-apple-darwin
    599 
    600 test-rt-headers: bin
    601 	@set -e; \
    602 	for target in $(RT_HEADER_TEST_TARGETS); do \
    603 	  out="build/test/rt-headers/$$target/smoke.o"; \
    604 	  mkdir -p "$$(dirname "$$out")"; \
    605 	  $(BIN) cc -target "$$target" -Werror -c test/rt/smoke.c -o "$$out"; \
    606 	done
    607 
    608 # Arch token -> linux rt variant target. The cross-target test harnesses link
    609 # the matching LINUX runtime archive (compiler-rt-style helpers like
    610 # __kit_sext64ti, __kit_assert_fail) rather than the native `rt` target,
    611 # which builds only the host's own variant. Shared by the single-arch
    612 # parse/asm/macho/link tests (via KIT_TEST_ARCH/TEST_RT_DEP) and by
    613 # test-rt-runtime, which sweeps several arches at once.
    614 KIT_TEST_ARCH ?= aa64
    615 _TEST_RT_aa64    = rt-aarch64-linux
    616 _TEST_RT_aarch64 = rt-aarch64-linux
    617 _TEST_RT_arm64   = rt-aarch64-linux
    618 _TEST_RT_x64     = rt-x86_64-linux
    619 _TEST_RT_x86_64  = rt-x86_64-linux
    620 _TEST_RT_amd64   = rt-x86_64-linux
    621 _TEST_RT_rv64    = rt-riscv64-linux
    622 _TEST_RT_riscv64 = rt-riscv64-linux
    623 TEST_RT_DEP      = $(_TEST_RT_$(KIT_TEST_ARCH))
    624 
    625 # test-rt-runtime compiles each case for the configured arches and links it
    626 # against that arch's LINUX runtime archive, so it must depend on each variant
    627 # (not the host `rt`). A stale per-arch archive otherwise silently breaks the
    628 # link — e.g. a dropped __kit_assert_fail weak def surfaces as an undefined
    629 # reference. KIT_RT_RUNTIME_ARCHES mirrors the default in test/rt/run.sh.
    630 KIT_RT_RUNTIME_ARCHES ?= aa64 x64 rv64
    631 RT_RUNTIME_DEPS := $(foreach a,$(KIT_RT_RUNTIME_ARCHES),$(_TEST_RT_$(a)))
    632 LINK_EXE_RUNNER = build/test/link-exe-runner
    633 
    634 test-rt-runtime: bin $(RT_RUNTIME_DEPS) $(LINK_EXE_RUNNER)
    635 	@bash test/rt/run.sh
    636 
    637 # L3a backtrace round-trip: run a kit-compiled program that prints its own
    638 # backtrace, then symbolize the captured addresses two ways — `kit addr2line`
    639 # (bare addresses) and `kit symbolize` (the raw "#N 0xADDR" stream, annotated
    640 # in place). Same per-arch deps as test-rt-runtime (each arch's linux rt
    641 # archive + the Path-E link runner). See test/rt/addr2line.sh and
    642 # doc/plan/BACKTRACE.md (L3a / WS5).
    643 test-rt-backtrace: bin $(RT_RUNTIME_DEPS) $(LINK_EXE_RUNNER)
    644 	@bash test/rt/addr2line.sh
    645 
    646 # Test harness binaries shared by test-elf and test-link.
    647 # Declared as Make targets (not built by the run.sh scripts) so they pick
    648 # up libkit.a changes deterministically.
    649 #
    650 # HARNESS_CFLAGS drops -Wpedantic from the normal host flags; the runners cast
    651 # kit_jit_lookup's void* to a function pointer, which pedantic rejects under
    652 # C11.
    653 HARNESS_CFLAGS = $(filter-out -Wpedantic,$(HOST_CFLAGS)) -Iinclude -Itest
    654 
    655 ROUNDTRIP_BIN         = build/test/kit-roundtrip
    656 ROUNDTRIP_BIN_MACHO   = build/test/kit-roundtrip-macho
    657 ROUNDTRIP_BIN_COFF    = build/test/kit-roundtrip-coff
    658 COFF_IMPORT_SMOKE_BIN = build/test/pe-import-smoke
    659 COFF_IMPORT_MINGW_BIN = build/test/pe-import-mingw
    660 COFF_DSO_FORWARDER_BIN = build/test/pe-dso-forwarder
    661 COFF_MIXED_ARCHIVE_BIN = build/test/pe-mixed-archive
    662 COFF_IMAGE_READ_BIN    = build/test/pe-image-read
    663 LLVM_MINGW_SYSROOT_X64_MARKER = build/llvm-mingw/20260602/ucrt/x86_64-w64-mingw32/PROVENANCE
    664 LLVM_MINGW_SYSROOT_AARCH64_MARKER = build/llvm-mingw/20260602/ucrt/aarch64-w64-mingw32/PROVENANCE
    665 JIT_RUNNER          = build/test/jit-runner
    666 PARSE_RUNNER        = build/test/parse-runner
    667 ASM_RUNNER          = build/test/asm-runner
    668 WASM_TOOL           = build/test/wasm-tool
    669 
    670 $(ROUNDTRIP_BIN): test/elf/kit-roundtrip.c $(LIB_AR)
    671 	@mkdir -p $(dir $@)
    672 	$(CC) $(HARNESS_CFLAGS) test/elf/kit-roundtrip.c $(LIB_AR) -o $@
    673 
    674 # Mach-O peer of kit-roundtrip — read_macho + emit_macho. Used by
    675 # test-link's path R when KIT_TEST_OBJ=macho.
    676 $(ROUNDTRIP_BIN_MACHO): test/macho/kit-roundtrip-macho.c $(LIB_AR)
    677 	@mkdir -p $(dir $@)
    678 	$(CC) $(HARNESS_CFLAGS) -Isrc test/macho/kit-roundtrip-macho.c $(LIB_AR) -o $@
    679 
    680 # PE/COFF round-trip harness (test/coff/). All-in-one binary: builds
    681 # hand-crafted ObjBuilders and asserts emit_coff/read_coff round-trip
    682 # stability for both x86_64-windows and aarch64-windows.
    683 $(ROUNDTRIP_BIN_COFF): test/coff/kit-roundtrip-coff.c $(LIB_OBJS)
    684 	@mkdir -p $(dir $@)
    685 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/kit-roundtrip-coff.c $(LIB_OBJS) -o $@
    686 
    687 # PE import-directory smoke test (test/coff/pe-import-smoke.c).
    688 # Exercises the full chain: short-import shim bytes -> link_add_obj_bytes
    689 # (reclassified as DSO) -> link_resolve -> link_emit_coff. Verifies the
    690 # produced PE32+ via x86_64-w64-mingw32-objdump; skips cleanly if absent.
    691 $(COFF_IMPORT_SMOKE_BIN): test/coff/pe-import-smoke.c $(LIB_OBJS)
    692 	@mkdir -p $(dir $@)
    693 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-smoke.c $(LIB_OBJS) -o $@
    694 
    695 # PE import test against a real mingw archive (test/coff/pe-import-mingw.c).
    696 # Exercises the long-form import-archive absorption path
    697 # (link_add_archive_bytes -> classify_coff_archive_member). Skips cleanly
    698 # when the mingw toolchain isn't installed.
    699 $(COFF_IMPORT_MINGW_BIN): test/coff/pe-import-mingw.c $(LIB_OBJS)
    700 	@mkdir -p $(dir $@)
    701 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-mingw.c $(LIB_OBJS) -o $@
    702 
    703 # read_coff_dso forwarder-export contract (test/coff/pe-dso-forwarder.c).
    704 # Synthesizes a tiny PE32+ DLL with one direct and one forwarder export
    705 # and asserts both surface as OBJ_SEC_NONE globals on the ObjBuilder.
    706 $(COFF_DSO_FORWARDER_BIN): test/coff/pe-dso-forwarder.c $(LIB_OBJS)
    707 	@mkdir -p $(dir $@)
    708 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-dso-forwarder.c $(LIB_OBJS) -o $@
    709 
    710 # Mixed-member archive (test/coff/pe-mixed-archive.c).  Verifies that
    711 # one archive containing both a short-import member and a long-form
    712 # COFF object with a defined data symbol satisfies references through
    713 # both shapes — the same composition libucrt.a uses (API-set imports
    714 # alongside lib64_libucrt_extra_a-*.o helpers).
    715 $(COFF_MIXED_ARCHIVE_BIN): test/coff/pe-mixed-archive.c $(LIB_OBJS)
    716 	@mkdir -p $(dir $@)
    717 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-mixed-archive.c $(LIB_OBJS) -o $@
    718 
    719 # PE32+ linked-image reader round-trip (test/coff/pe-image-read.c). Links a
    720 # tiny PIE .exe in memory (import + base reloc), re-opens it via kit_obj_open,
    721 # and asserts the neutral image view + raw escape hatch. Needs no external
    722 # toolchain — runs on every host.
    723 $(COFF_IMAGE_READ_BIN): test/coff/pe-image-read.c $(LIB_OBJS)
    724 	@mkdir -p $(dir $@)
    725 	$(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-image-read.c $(LIB_OBJS) -o $@
    726 
    727 $(LLVM_MINGW_SYSROOT_X64_MARKER): scripts/llvm_mingw_sysroot.sh
    728 	@bash scripts/llvm_mingw_sysroot.sh prepare x64
    729 
    730 $(LLVM_MINGW_SYSROOT_AARCH64_MARKER): scripts/llvm_mingw_sysroot.sh
    731 	@bash scripts/llvm_mingw_sysroot.sh prepare aarch64
    732 
    733 windows-ucrt-sysroots: $(LLVM_MINGW_SYSROOT_X64_MARKER) $(LLVM_MINGW_SYSROOT_AARCH64_MARKER)
    734 
    735 $(LINK_EXE_RUNNER): test/link/harness/link_exe_runner.c $(LIB_AR)
    736 	@mkdir -p $(dir $@)
    737 	$(CC) $(HARNESS_CFLAGS) test/link/harness/link_exe_runner.c $(LIB_AR) -o $@
    738 
    739 $(JIT_RUNNER): test/link/harness/jit_runner.c $(LIB_AR)
    740 	@mkdir -p $(dir $@)
    741 	$(CC) $(HARNESS_CFLAGS) test/link/harness/jit_runner.c $(LIB_AR) -o $@
    742 
    743 $(PARSE_RUNNER): test/parse/harness/parse_runner.c $(LIB_AR)
    744 	@mkdir -p $(dir $@)
    745 	$(CC) $(HARNESS_CFLAGS) test/parse/harness/parse_runner.c $(LIB_AR) -o $@
    746 
    747 $(ASM_RUNNER): test/asm/harness/asm_runner.c $(LIB_AR)
    748 	@mkdir -p $(dir $@)
    749 	$(CC) $(HARNESS_CFLAGS) test/asm/harness/asm_runner.c $(LIB_AR) -o $@
    750 
    751 $(WASM_TOOL): test/wasm/harness/wasm_tool.c $(LIB_AR)
    752 	@mkdir -p $(dir $@)
    753 	$(CC) $(HARNESS_CFLAGS) -I. test/wasm/harness/wasm_tool.c $(LIB_AR) -o $@
    754 
    755 test-elf: lib bin-soft $(ROUNDTRIP_BIN)
    756 	KIT_ELF_UNIT_CFLAGS='$(HOST_MODE_CFLAGS)' \
    757 	KIT_ELF_UNIT_LDFLAGS='$(HOST_MODE_LDFLAGS)' \
    758 	bash test/elf/run.sh
    759 
    760 # PE/COFF round-trip harness plus optional hosted Windows smoke. The
    761 # UCRT smoke self-skips when llvm-mingw is not installed.
    762 test-coff: lib bin rt-aarch64-windows $(ROUNDTRIP_BIN_COFF) $(COFF_IMPORT_SMOKE_BIN) $(COFF_DSO_FORWARDER_BIN) $(COFF_MIXED_ARCHIVE_BIN) $(COFF_IMAGE_READ_BIN)
    763 	$(ROUNDTRIP_BIN_COFF)
    764 	$(COFF_IMPORT_SMOKE_BIN)
    765 	$(COFF_DSO_FORWARDER_BIN)
    766 	$(COFF_MIXED_ARCHIVE_BIN)
    767 	$(COFF_IMAGE_READ_BIN)
    768 	bash test/coff/windows-ucrt-hosted-smoke.sh
    769 	bash test/coff/windows-system-dlls-smoke.sh
    770 
    771 # Separate target so it can be skipped gracefully if mingw isn't
    772 # installed. The test itself self-skips on missing tooling, but the
    773 # build target only fires when explicitly requested.
    774 test-coff-mingw-import: lib $(COFF_IMPORT_MINGW_BIN)
    775 	$(COFF_IMPORT_MINGW_BIN)
    776 
    777 test-coff-windows-ucrt: bin rt-x86_64-pc-windows rt-aarch64-windows windows-ucrt-sysroots
    778 	KIT_SYSROOT=$(abspath build/llvm-mingw/20260602/ucrt) bash test/coff/windows-ucrt-hosted-smoke.sh
    779 	KIT_SYSROOT=$(abspath build/llvm-mingw/20260602/ucrt) bash test/coff/windows-system-dlls-smoke.sh
    780 	KIT_SYSROOT=$(abspath build/llvm-mingw/20260602/ucrt) bash test/coff/windows-o1-abi-smoke.sh
    781 
    782 test-coff-windows-o1-abi: bin rt-x86_64-pc-windows rt-aarch64-windows windows-ucrt-sysroots
    783 	KIT_SYSROOT=$(abspath build/llvm-mingw/20260602/ucrt) bash test/coff/windows-o1-abi-smoke.sh
    784 
    785 # Opt-in: run the COFF/PE hosted smokes against a real Windows 11 ARM64 VM, so
    786 # their per-program run lanes execute for real instead of self-skipping. On
    787 # Apple Silicon a single hvf-accelerated VM serves both targets: aarch64-windows
    788 # runs natively and x86_64-windows runs via the in-box x64 emulator, so both run
    789 # lanes point at the same endpoint. Requires a provisioned VM and a user-supplied
    790 # Windows ISO (see doc/WINDOWS.md), then boots it, waits for SSH, and runs the
    791 # smokes against it.
    792 KIT_WINDOWS_VM_USER ?= kit
    793 KIT_WINDOWS_VM_PORT ?= 2227
    794 # SSH key is left unset so windows_vm.sh uses its default in the durable cache
    795 # (~/.cache/kit/windows-vm/ssh/id_ed25519); the working disk auto-restores from
    796 # the cached golden, so a clean build/ still finds a ready VM.
    797 _WIN_VM_ENV = KIT_SYSROOT=$(abspath build/llvm-mingw/20260602/ucrt) \
    798 	KIT_WINDOWS_VM_AARCH64=$(KIT_WINDOWS_VM_USER)@127.0.0.1 KIT_WINDOWS_VM_AARCH64_PORT=$(KIT_WINDOWS_VM_PORT) \
    799 	KIT_WINDOWS_VM_X64=$(KIT_WINDOWS_VM_USER)@127.0.0.1 KIT_WINDOWS_VM_X64_PORT=$(KIT_WINDOWS_VM_PORT)
    800 
    801 test-coff-windows-vm: bin rt-x86_64-pc-windows rt-aarch64-windows windows-ucrt-sysroots
    802 	bash scripts/windows_vm.sh boot
    803 	bash scripts/windows_vm.sh wait-ssh 600
    804 	$(_WIN_VM_ENV) bash test/coff/windows-ucrt-hosted-smoke.sh
    805 	$(_WIN_VM_ENV) bash test/coff/windows-system-dlls-smoke.sh
    806 	$(_WIN_VM_ENV) bash test/coff/windows-o1-abi-smoke.sh
    807 
    808 # The parse/asm/macho harnesses select a cross-target via KIT_TEST_ARCH
    809 # (default aa64); the link rt dependency is resolved through the shared
    810 # _TEST_RT_<arch> map defined above (near test-rt-runtime).
    811 test-link: lib $(ROUNDTRIP_BIN) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    812 	bash test/link/run.sh
    813 
    814 # x64 ELF link/reloc-application coverage. test-link defaults to aa64, so
    815 # kit's x64 static-link reloc fixups (R_X64_PLT32/GOTPCREL/TPOFF/...) were
    816 # only ever run via a manual KIT_TEST_ARCH=x64 override. Opt-in (not in the
    817 # default set) because the E path links + runs under podman/qemu-x86_64; the R
    818 # path (roundtrip + reloc layout) runs on any host.
    819 test-link-x64: lib rt-x86_64-linux $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    820 	@KIT_TEST_ARCH=x64 KIT_TEST_PATHS=RE bash test/link/run.sh
    821 
    822 test-macho: lib $(TEST_RT_DEP) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    823 	KIT_TEST_OBJ=macho \
    824 	KIT_TEST_ARCH=$${KIT_TEST_ARCH:-aa64} \
    825 	KIT_TEST_PATHS=$${KIT_TEST_PATHS:-RJ} \
    826 	KIT_TEST_ALLOW_SKIP=$${KIT_TEST_ALLOW_SKIP:-1} \
    827 	bash test/link/run.sh
    828 
    829 OPT_TEST_BIN = build/test/cg_ir_lower_test
    830 TINY_INLINE_TEST_BIN = build/test/tiny_inline_test
    831 
    832 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-x64-win-tail-sret test-opt-prologue-tier test-opt-whole-program-inline test-opt-lto-phase1
    833 	$(OPT_TEST_BIN)
    834 
    835 
    836 test-opt-tiny-inline: bin $(TINY_INLINE_TEST_BIN)
    837 	$(TINY_INLINE_TEST_BIN)
    838 
    839 
    840 # Behavioral disasm check: tiny callee `bl` disappears from its caller at -O1.
    841 test-opt-inline: bin
    842 	@KIT=$(abspath $(BIN)) bash test/opt/run.sh
    843 
    844 # Behavioral disasm check: a pointer-typed null call arg is not routed through
    845 # a scratch temp at -O1 (PERCALL.md item 3, "zero through a temp").
    846 test-opt-zero-arg: bin
    847 	@KIT=$(abspath $(BIN)) bash test/opt/zero_arg.sh
    848 
    849 .PHONY: test-opt-static-prune-aa64
    850 test-opt-static-prune-aa64: bin
    851 	@KIT=$(abspath $(BIN)) bash test/opt/static_prune_aa64.sh
    852 
    853 .PHONY: test-opt-aa64-tail
    854 test-opt-aa64-tail: bin
    855 	@KIT=$(abspath $(BIN)) bash test/opt/aa64_tail_call.sh
    856 
    857 .PHONY: test-opt-x64-win-tail-sret
    858 test-opt-x64-win-tail-sret: bin
    859 	@KIT=$(abspath $(BIN)) bash test/opt/x64_win_tail_sret.sh
    860 
    861 # Structural disasm check: the -O1 known-frame prologue cost-model tiers
    862 # (aa64 reference + ported x64 slim/red-zone and rv64 leaf shapes).
    863 .PHONY: test-opt-prologue-tier
    864 test-opt-prologue-tier: bin
    865 	@KIT=$(abspath $(BIN)) bash test/opt/prologue_tier.sh
    866 
    867 # Whole-program (LTO Phase 0) cross-function inlining: a small static callee
    868 # fuses into its caller at -O1 on every arch, and opt_inline actually fires.
    869 .PHONY: test-opt-whole-program-inline
    870 test-opt-whole-program-inline: bin
    871 	@KIT=$(abspath $(BIN)) bash test/opt/whole_program_inline.sh
    872 
    873 .PHONY: test-opt-lto-phase1
    874 test-opt-lto-phase1: bin
    875 	@KIT=$(abspath $(BIN)) bash test/opt/lto_phase1.sh
    876 
    877 test-parse: test-parse-ok test-parse-err
    878 
    879 test-parse-ok: lib $(TEST_RT_DEP) $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    880 	bash test/parse/run.sh
    881 
    882 test-parse-err: lib $(PARSE_RUNNER)
    883 	sh test/parse/run_errors.sh
    884 
    885 # test-asm: aggregate alias running every per-arch asm lane (aa64 + x64 + rv64)
    886 # so `make test` covers all three through one target. The harness runs one arch
    887 # per invocation (KIT_TEST_ARCH); each lane scopes its scratch per arch, so
    888 # the prerequisites are safe to run in parallel under `make -j`.
    889 test-asm: test-asm-aa64 test-asm-x64 test-asm-rv64 test-asm-rv32
    890 
    891 # test-asm-aa64: the reference lane. aa64 is the default cross-target, and on
    892 # aa64 hosts the exec paths (D/E/J) run natively, so it uses the full default
    893 # path set (HTLDJE). The Makefile owns the harness binaries so they inherit host
    894 # flags consistently with the rest of the test suite.
    895 test-asm-aa64: lib $(TEST_RT_DEP) $(ASM_RUNNER) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
    896 	@KIT_TEST_ARCH=aa64 bash test/asm/run.sh
    897 
    898 # x64/rv64 exercise the encode (H), decode (T) and listing (L) corpora on any
    899 # host: H/T/L need no native execution (they only produce/compare bytes), so the
    900 # exec paths (D/E/J) are deliberately excluded and left to the smoke/qemu
    901 # targets.
    902 test-asm-x64: lib $(ASM_RUNNER)
    903 	@KIT_TEST_ARCH=x64 KIT_TEST_PATHS=HTL bash test/asm/run.sh
    904 test-asm-rv64: lib $(ASM_RUNNER)
    905 	@KIT_TEST_ARCH=rv64 KIT_TEST_PATHS=HT bash test/asm/run.sh
    906 # riscv32-none-elf is freestanding (no qemu-user exec lane), so like rv64 it
    907 # runs the host-independent encode (H) + decode (T) lanes only. The rv32_*
    908 # goldens use a no-compressed ISA so encodings stay a stable 4 bytes.
    909 test-asm-rv32: lib $(ASM_RUNNER)
    910 	@KIT_TEST_ARCH=rv32 KIT_TEST_PATHS=HT bash test/asm/run.sh
    911 
    912 # test-toy-rv32: the rv32 cross lane (path X) of the Toy corpus, scoped to the
    913 # rv32 arch only. test/toy/run.sh's cross_one_rv32 compiles each case with
    914 # `kit cc -target riscv32-none-elf` and runs the freestanding ELF bare-metal
    915 # under qemu-system-riscv32 via test/lib/exec_rv32_bare.sh (the qemu exit code
    916 # is the exit-code oracle). Self-skips per case when the rv32 toolchain
    917 # (clang riscv32 + qemu-system-riscv32) is absent. The corpus is green; this
    918 # lane is opt-in (not in DEFAULT_TEST_TARGETS) because it needs that toolchain,
    919 # matching the rv64 cross lanes. The only non-passing cases are intentionally
    920 # unsupported on rv32 (__int128, binary128 long double, LP64-data-model
    921 # assumptions, aa64-only intrinsics) and carry committed .rv32.skip sidecars.
    922 test-toy-rv32: bin rt-riscv32-elf-hardfloat
    923 	@KIT=$(abspath $(BIN)) KIT_TOY_CROSS_ARCHS=rv32 KIT_TEST_PATHS=X \
    924 		test/toy/run.sh
    925 
    926 # test-parse-rv32: the C-parser corpus run for riscv32-none-elf, exec lane (E)
    927 # only. parse-runner --emit -> kit ld + start crt -> qemu-system-riscv32
    928 # (test/parse/run.sh's rv32 freestanding E path via exec_rv32_bare.sh). Models
    929 # test-parse-rv64-wide; opt-in (needs the rv32 toolchain/qemu), so excluded
    930 # from DEFAULT_TEST_TARGETS, matching test-parse-rv64-wide. The corpus is green;
    931 # the only skips are intentionally-unsupported cases (__int128, binary128 long
    932 # double, LP64-data-model assumptions) with committed .rv32.skip sidecars.
    933 test-parse-rv32: lib rt-riscv32-elf-hardfloat $(PARSE_RUNNER) $(ROUNDTRIP_BIN) \
    934                  $(LINK_EXE_RUNNER)
    935 	@KIT_TEST_ARCH=rv32 KIT_TEST_PATHS=E bash test/parse/run.sh
    936 
    937 # Codegen round-trip completeness (doc/ASM_ROUNDTRIP_TESTING.md). These drive
    938 # the `kit` binary itself (cc -S / as / objdump) over a C corpus rather than
    939 # a hand-written asm corpus, so coverage tracks codegen automatically.
    940 #
    941 #   test-disasm-complete    L0: cc -S must decode every in-function word
    942 #                           (no `.inst` markers). Host-independent, no exec.
    943 #   test-asm-roundtrip      L0+L1: also assert cc -c bytes/relocs == cc -S | as.
    944 #   test-asm-roundtrip-exec L0+L1+L2: also run direct vs round-tripped object
    945 #                           and compare exit codes (native arch; opt-in).
    946 #
    947 # Vertical slice: aa64 only for now; L1/L2 run at -O1 (branch-free), L0 at both
    948 # opt levels. Broadening to -O0, other arches, and the default suite is tracked
    949 # in doc/ASM_ROUNDTRIP_TESTING.md once -S symbolization (Phase 2) lands.
    950 test-disasm-complete: bin
    951 	@KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=0 \
    952 		bash test/asm/roundtrip.sh
    953 test-asm-roundtrip: bin
    954 	@KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=01 \
    955 		bash test/asm/roundtrip.sh
    956 test-asm-roundtrip-exec: bin $(JIT_RUNNER)
    957 	@KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=012 \
    958 		bash test/asm/roundtrip.sh
    959 
    960 # test-asm-symmetry: asm<->disasm self-symmetry sweep (aa64). Decode-side sweeps
    961 # every disasm-table form (decode->encode->decode fixed point); encode-side
    962 # asserts every byte the assembler emits over the encode corpus is decodable.
    963 # Catches encode/decode asymmetries the codegen round-trip can't reach (e.g. a
    964 # form one tool handles and the other doesn't). Host-independent, no exec.
    965 test-asm-symmetry: $(ASM_RUNNER) $(AA64_SWEEP_GEN)
    966 	@bash test/asm/symmetry.sh
    967 
    968 # test-diff-llvm: differential cross-check of kit against llvm (aa64), as a
    969 # second oracle. Encode lane: kit as vs llvm-mc bytes over the encode corpus.
    970 # Disasm lane: cc -c bytes vs llvm-mc of cc -S (validates kit's disassembler;
    971 # the benign same-section-call reloc-vs-resolve difference is recognized).
    972 # Opt-in; skips cleanly when llvm-mc is absent.
    973 test-diff-llvm: bin
    974 	@KIT_TEST_OPTS="O0 O1" bash test/asm/diff_llvm.sh
    975 
    976 # test-asm-roundtrip-toy: L2 exec round-trip over the Toy corpus (native arch).
    977 # Reuses the ~150 toy cases (full CG op set, exit-code oracle) for free
    978 # round-trip coverage: kit cc -S | kit as | kit run, exit must match.
    979 # Opt-in; native target. Found a real miscompile (dropped .inst) the hand
    980 # corpus never reached.
    981 test-asm-roundtrip-toy: bin
    982 	@bash test/asm/roundtrip_toy.sh
    983 
    984 # test-hostas-toy: feed one native `cc -S` to BOTH kit's own `as` and clang (a
    985 # third-party host assembler), link + run each, and assert the toy exit-code
    986 # oracle. Only the assembler differs between the two lanes, so the clang lane is
    987 # the real test: a standard assembler can't paper over a private-dialect quirk
    988 # the way kit's own `as` can (cf. test/asm/diff_llvm.sh, but by execution).
    989 # cc -S is now object-format-aware, so the native Mach-O clang lane GATES by
    990 # default (both lanes 312/0); KIT_HOSTAS_ENFORCE_CLANG=0 demotes it to XFAIL.
    991 # Opt-in; skips cleanly if clang is absent.
    992 test-hostas-toy: bin
    993 	@bash test/asm/hostas_toy.sh
    994 
    995 # test-hostas-cross: the same two-assembler-by-execution idea as test-hostas-toy,
    996 # but CROSS — `cc -S -target <triple>` for ELF Linux arches (aarch64/x86_64/
    997 # riscv64), assembled by kit-as AND clang, linked static (+ the start.c crt)
    998 # with kit ld, and run under podman/qemu via test/lib/exec_target.sh. Each
    999 # target self-skips unless the host has a clang cross target, a runner, a
   1000 # working `cc -S | kit as` for that arch, and a passing bounded exec smoke —
   1001 # so it runs green on whatever the host supports (aarch64-linux today; x86_64
   1002 # pends the x64 cc -S symbolizer, riscv64 pends a working rv64 user-mode
   1003 # emulator). Opt-in; skips cleanly if clang/podman are absent.
   1004 test-hostas-cross: bin
   1005 	@bash test/asm/hostas_cross.sh
   1006 
   1007 test-wasm: test-wasm-front test-wasm-target test-wasm-toy
   1008 
   1009 test-wasm-front: bin $(WASM_TOOL) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
   1010 	bash test/wasm/run.sh
   1011 
   1012 # test-wasm-target: structural checks on `kit cc -target wasm32-none`
   1013 # output. test/wasm-target/run.sh is a Type C corpus harness whose lanes each
   1014 # compile a tiny C or toy fixture and assert a property of the produced module
   1015 # bytes (inline-asm opcodes, memory.copy/fill opcodes, exported "memory",
   1016 # (import ...) decls). Opt-in: not in the default `test` target because the
   1017 # checks depend on the bulk-memory + (import ...) backend work landing first.
   1018 test-wasm-target: bin
   1019 	@KIT=$(abspath $(BIN)) bash test/wasm-target/run.sh
   1020 
   1021 # test-smoke-x64: phase-1 sanity check for the multi-arch bring-up. Builds a
   1022 # tiny freestanding x86_64 ELF with clang --target=x86_64-linux-gnu and
   1023 # runs it through test/lib/exec_target.sh's podman/qemu pipeline,
   1024 # proving the harness end-to-end before any kit-emitted x64 bytes
   1025 # exist. Excluded from the default `test` target because it needs
   1026 # podman + lld; opt-in via `make test-smoke-x64`.
   1027 test-smoke-x64:
   1028 	bash test/smoke/x64.sh
   1029 
   1030 # test-smoke-rv64: phase-2 counterpart of test-smoke-x64. Builds a
   1031 # tiny freestanding riscv64 ELF with clang --target=riscv64-linux-gnu
   1032 # and runs it through test/lib/exec_target.sh, proving the rv64 lane
   1033 # of the harness end-to-end before any kit-emitted rv64 bytes
   1034 # exist. Excluded from the default `test` target because it needs
   1035 # qemu-riscv64 (or podman with riscv64 emulation) + lld; opt-in via
   1036 # `make test-smoke-rv64`.
   1037 test-smoke-rv64:
   1038 	bash test/smoke/rv64.sh
   1039 
   1040 # test-smoke-rv32: behavioral oracle for riscv32-none-elf codegen under
   1041 # qemu-system-riscv32 (ilp32f + ilp32 lanes, i64 + soft-float). Skips if the
   1042 # rv32 toolchain/qemu prerequisites are absent (see test/lib/check_rv32_env.sh).
   1043 test-smoke-rv32:
   1044 	bash test/smoke/rv32.sh
   1045 
   1046 # test-smoke-freestanding-system: whole-toolchain bare-metal smoke for
   1047 # aarch64/x86_64/riscv64/riscv32 under qemu-system. Each lane compiles with kit,
   1048 # assembles its reset stub with kit, links with kit ld -T, then boots the image
   1049 # under the matching qemu-system binary. rv32 may need compiler-rt-style helpers
   1050 # for 64-bit operations, so build that freestanding runtime variant with kit too.
   1051 test-smoke-freestanding-system: bin rt-riscv32-elf-hardfloat
   1052 	bash test/smoke/freestanding_system.sh
   1053 
   1054 # test-parse-rv64-wide: end-to-end coverage of the rv64 128-bit scalar types
   1055 # — __int128 (i128_*) and IEEE-754 binary128 long double (ldbl128_*) — built
   1056 # with kit and run on riscv64. Exercises the soft-float / i128 lowering to
   1057 # the compiler-rt-style runtime (fp_tf, fp_ti, int64), the LP64D register-pair
   1058 # ABI for 16-byte scalars, and the conditional-branch range fix that large
   1059 # soft-float helpers depend on. Opt-in (needs qemu-riscv64 or podman with
   1060 # riscv64 emulation), so excluded from the default `test` target; mirrors
   1061 # test-smoke-rv64. Run a single case with
   1062 #   KIT_TEST_ARCH=rv64 bash test/parse/run.sh ldbl128_03_arith
   1063 test-parse-rv64-wide: lib rt-riscv64-linux $(PARSE_RUNNER) $(ROUNDTRIP_BIN) \
   1064                       $(LINK_EXE_RUNNER)
   1065 	@KIT_TEST_ARCH=rv64 KIT_TEST_PATHS=RE bash test/parse/run.sh 128
   1066 
   1067 # test-bounce: format-bounce stress test. Compiles small programs with
   1068 # kit, then bounces each object through chains of format conversions
   1069 # (ELF<->Mach-O<->COFF), partial links (ld -r), strip, and archive
   1070 # round-trips, relinks, and runs the result, asserting the exit code
   1071 # matches a host-cc reference. Stresses obj read/write/reloc, ar, and
   1072 # partial-link paths rather than the arches. Defaults to the host-native
   1073 # Linux arch (no emulation); sweep others with
   1074 # KIT_BOUNCE_ARCHES="aarch64 x64 rv64". Excluded from the default `test`
   1075 # target because it needs podman/qemu + a host cc; opt-in via
   1076 # `make test-bounce`.
   1077 test-bounce: $(BIN)
   1078 	bash test/bounce/bounce.sh
   1079 
   1080 # test-libc: aggregate alias that runs test-libc-musl and test-libc-glibc.
   1081 # test-libc-musl / test-libc-glibc: end-to-end static + dynamic libc link/run
   1082 # on aarch64. Each variant pulls its own pinned sysroot (podman, ~30s on
   1083 # first run) and shares the same case files under test/libc/cases/:
   1084 #
   1085 #   test-libc-musl   — Alpine 3.20 + musl 1.2.5    (test/libc/musl/)
   1086 #   test-libc-glibc  — Debian bookworm + glibc 2.36 (test/libc/glibc/)
   1087 #
   1088 # Both build build/rt/aarch64-linux/libkit_rt.a for soft-float / TF
   1089 # builtins, and run `kit ld` against the real libc.a (static) and
   1090 # libc.so / libc.so.6 (dynamic). Excluded from the
   1091 # default `test` target because they need podman; opt-in via
   1092 # `make test-libc-musl` / `make test-libc-glibc` (or `make test-libc` for both).
   1093 #
   1094 # Each sysroot is treated as a real prerequisite via its PROVENANCE
   1095 # marker so subsequent runs skip extraction and re-extract only when
   1096 # the file is removed (or extract.sh -f forces a rebuild).
   1097 MUSL_SYSROOT_MARKER       = build/musl-sysroot/PROVENANCE
   1098 MUSL_SYSROOT_X64_MARKER   = build/musl-sysroot-x64/PROVENANCE
   1099 MUSL_SYSROOT_RV64_MARKER  = build/musl-sysroot-rv64/PROVENANCE
   1100 GLIBC_SYSROOT_MARKER      = build/glibc-sysroot/PROVENANCE
   1101 GLIBC_SYSROOT_X64_MARKER  = build/glibc-sysroot-x64/PROVENANCE
   1102 GLIBC_SYSROOT_RV64_MARKER = build/glibc-sysroot-rv64/PROVENANCE
   1103 
   1104 $(MUSL_SYSROOT_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile
   1105 	@bash test/libc/musl/extract.sh
   1106 
   1107 $(MUSL_SYSROOT_X64_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile.x64
   1108 	@bash test/libc/musl/extract.sh -a x64
   1109 
   1110 $(MUSL_SYSROOT_RV64_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile.rv64
   1111 	@bash test/libc/musl/extract.sh -a rv64
   1112 
   1113 $(GLIBC_SYSROOT_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile
   1114 	@bash test/libc/glibc/extract.sh
   1115 
   1116 $(GLIBC_SYSROOT_X64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.x64
   1117 	@bash test/libc/glibc/extract.sh -a x64
   1118 
   1119 $(GLIBC_SYSROOT_RV64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.rv64
   1120 	@bash test/libc/glibc/extract.sh -a rv64
   1121 
   1122 # test-libc-musl / test-libc-glibc honor KIT_LIBC_ARCHES (default "aa64";
   1123 # values: aa64, x64, rv64). Each enabled arch contributes its sysroot
   1124 # PROVENANCE marker and its rt archive to the prerequisite list, so
   1125 # `KIT_LIBC_ARCHES="aa64 x64" make test-libc-musl` builds both sysroots
   1126 # + both rt archives before the runner script picks them up.
   1127 KIT_LIBC_ARCHES ?= aa64
   1128 
   1129 # Map an arch token to its musl/glibc sysroot marker and rt target.
   1130 _LIBC_MUSL_SYSROOT_aa64  = $(MUSL_SYSROOT_MARKER)
   1131 _LIBC_MUSL_SYSROOT_x64   = $(MUSL_SYSROOT_X64_MARKER)
   1132 _LIBC_MUSL_SYSROOT_rv64  = $(MUSL_SYSROOT_RV64_MARKER)
   1133 _LIBC_GLIBC_SYSROOT_aa64 = $(GLIBC_SYSROOT_MARKER)
   1134 _LIBC_GLIBC_SYSROOT_x64  = $(GLIBC_SYSROOT_X64_MARKER)
   1135 _LIBC_GLIBC_SYSROOT_rv64 = $(GLIBC_SYSROOT_RV64_MARKER)
   1136 _LIBC_RT_aa64            = rt-aarch64-linux
   1137 _LIBC_RT_x64             = rt-x86_64-linux
   1138 _LIBC_RT_rv64            = rt-riscv64-linux
   1139 
   1140 LIBC_MUSL_DEPS  = $(foreach a,$(KIT_LIBC_ARCHES),$(_LIBC_MUSL_SYSROOT_$(a)) $(_LIBC_RT_$(a)))
   1141 LIBC_GLIBC_DEPS = $(foreach a,$(KIT_LIBC_ARCHES),$(_LIBC_GLIBC_SYSROOT_$(a)) $(_LIBC_RT_$(a)))
   1142 
   1143 test-libc: test-libc-musl test-libc-glibc
   1144 
   1145 test-libc-musl: bin $(LIBC_MUSL_DEPS)
   1146 	@KIT_LIBC_ARCHES="$(KIT_LIBC_ARCHES)" bash test/libc/musl/run.sh
   1147 
   1148 test-libc-glibc: bin $(LIBC_GLIBC_DEPS)
   1149 	@KIT_LIBC_ARCHES="$(KIT_LIBC_ARCHES)" bash test/libc/glibc/run.sh
   1150 
   1151 # FreeBSD hosted executable smoke (static + dynamic). Sysroots come from
   1152 # scripts/freebsd_sysroot.sh (cached base.txz extracts under ~/.cache/kit). By
   1153 # default these validate compile/link + ELF metadata; set KIT_FREEBSD_RUN_VM=1
   1154 # to boot the cached FreeBSD VMs and execute each binary under real FreeBSD.
   1155 test-freebsd: bin
   1156 	@KIT_FREEBSD_LINK=both bash test/libc/freebsd/run.sh
   1157 
   1158 test-freebsd-static: bin
   1159 	@KIT_FREEBSD_LINK=static bash test/libc/freebsd/run.sh
   1160 
   1161 test-freebsd-dynamic: bin
   1162 	@KIT_FREEBSD_LINK=dynamic bash test/libc/freebsd/run.sh
   1163 
   1164 test-libc-musl-rv64:
   1165 	@$(MAKE) test-libc-musl KIT_LIBC_ARCHES=rv64
   1166 
   1167 test-libc-glibc-rv64:
   1168 	@$(MAKE) test-libc-glibc KIT_LIBC_ARCHES=rv64
   1169 
   1170 # Hosted test suite (test/hosted/run.sh): build each C case for every
   1171 # (target, link-mode) config in the support set via scripts/hosted.sh and run it
   1172 # through the shared exec seam, checking exit code + stdout. Opt-in (not in the
   1173 # default set). The default config set is Linux (musl static+dynamic + glibc,
   1174 # all 3 arches) + macOS; it provisions the linux sysroots + rt + the per-arch
   1175 # run images (alpine for musl, debian for glibc) here. test-hosted-vm adds the
   1176 # FreeBSD + Windows VM configs.
   1177 HOSTED_LINUX_DEPS = \
   1178     $(MUSL_SYSROOT_MARKER) $(MUSL_SYSROOT_X64_MARKER) $(MUSL_SYSROOT_RV64_MARKER) \
   1179     $(GLIBC_SYSROOT_MARKER) $(GLIBC_SYSROOT_X64_MARKER) $(GLIBC_SYSROOT_RV64_MARKER) \
   1180     rt-aarch64-linux rt-x86_64-linux rt-riscv64-linux
   1181 
   1182 # Per-arch glibc (Debian) run images for the linux-glibc configs. musl uses the
   1183 # pinned alpine images from `make test-images`. Best-effort pull (idempotent).
   1184 HOSTED_GLIBC_IMAGES = \
   1185     docker.io/arm64v8/debian:bookworm-slim \
   1186     docker.io/amd64/debian:bookworm-slim \
   1187     docker.io/riscv64/debian:trixie-slim
   1188 
   1189 hosted-glibc-images:
   1190 	@for img in $(HOSTED_GLIBC_IMAGES); do \
   1191 	  podman image exists "$$img" 2>/dev/null || podman pull "$$img" || \
   1192 	    echo "warn: could not pull $$img (linux-glibc run lanes will SKIP)"; \
   1193 	done
   1194 
   1195 test-hosted: bin $(HOSTED_LINUX_DEPS) test-images hosted-glibc-images
   1196 	@KIT=$(abspath $(BIN)) bash test/hosted/run.sh
   1197 
   1198 test-hosted-vm: bin $(HOSTED_LINUX_DEPS) test-images hosted-glibc-images rt-x86_64-pc-windows rt-aarch64-windows windows-ucrt-sysroots
   1199 	@KIT=$(abspath $(BIN)) KIT_HOSTED_VM=1 bash test/hosted/run.sh
   1200 
   1201 # Fail if libkit.a depends on any external symbol not in the allowlist, or
   1202 # if a relocatable link exposes non-public global definitions.
   1203 # External dependency drift in either direction (new dep, or stale entry) is a
   1204 # failure.
   1205 #
   1206 # Inspect the RELEASE artifacts only: the debug build is ASan/UBSan-instrumented
   1207 # and pulls in __asan_*/__ubsan_* externals that are not part of the shipped
   1208 # library's dependency surface.
   1209 LIB_DEPS_RELEASE_DIR = build/release
   1210 LIB_DEPS_AR     = $(LIB_DEPS_RELEASE_DIR)/libkit.a
   1211 LIB_DEPS_ACTUAL = $(LIB_DEPS_RELEASE_DIR)/libkit.deps.txt
   1212 LIB_RELOC       = $(LIB_DEPS_RELEASE_DIR)/libkit.reloc.o
   1213 LIB_RELOC_BAD   = $(LIB_DEPS_RELEASE_DIR)/libkit.reloc.bad-symbols.txt
   1214 
   1215 test-lib-deps:
   1216 	@$(MAKE) lib RELEASE=1
   1217 	@mkdir -p $(dir $(LIB_DEPS_ACTUAL))
   1218 	@python3 scripts/lib_external_deps.py $(LIB_DEPS_AR) > $(LIB_DEPS_ACTUAL)
   1219 	@diff -u scripts/lib_deps.allowlist $(LIB_DEPS_ACTUAL) \
   1220 	    || { echo "libkit.a external symbol set drifted from scripts/lib_deps.allowlist"; exit 1; }
   1221 	@python3 scripts/lib_reloc_defined_prefixes.py $(LIB_DEPS_AR) \
   1222 	    --output $(LIB_RELOC) --ar $(AR) --cc $(CC) > $(LIB_RELOC_BAD)
   1223 	@test ! -s $(LIB_RELOC_BAD) \
   1224 	    || { echo "libkit relocatable link exposes non-public symbols"; cat $(LIB_RELOC_BAD); exit 1; }