commit 7cdfa2286fcf7310e20656c9873ad62f59661971
parent 6ff61d3c14d3eacb93b08ccc06f0dc8261fd892f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 3 Jun 2026 11:11:26 -0700
mk: move test mk files to mk/
Diffstat:
8 files changed, 1046 insertions(+), 1042 deletions(-)
diff --git a/Makefile b/Makefile
@@ -196,4 +196,4 @@ include mk/maint.mk
-include $(LIB_DEPS)
-include $(DRIVER_DEPS)
-include test/test.mk
+include mk/test.mk
diff --git a/doc/BUILD.md b/doc/BUILD.md
@@ -211,7 +211,7 @@ honest. The check is part of the default `make test` set.
## Tests
-Tests are a large family of `make test-*` targets defined in `test/test.mk`,
+Tests are a large family of `make test-*` targets defined in `mk/test.mk`,
grouped roughly as:
```
diff --git a/doc/TESTING.md b/doc/TESTING.md
@@ -14,7 +14,7 @@ exercise see [ASM.md](ASM.md) (assembler/disassembler/`cc -S`),
The test tree lives under `test/`, with per-area subdirectories (`test/asm/`,
`test/toy/`, `test/smoke/`, `test/libc/`, plus unit-test areas like
`test/arch/`, `test/elf/`, `test/opt/`). Build/run wiring lives in
-`test/test.mk`. Every harness conforms to **one of four canonical test types**,
+`mk/test.mk`. Every harness conforms to **one of four canonical test types**,
each backed by a shared library under `test/lib/`, so test infrastructure is
written once and reused rather than re-invented per area.
@@ -22,7 +22,7 @@ written once and reused rather than re-invented per area.
| Type | Library | What it is |
|------|---------|------------|
-| **U** unit | `kit_unit.h` (+ `unit.mk` build manifest) | a C translation unit linked against libkit, self-checking in-process |
+| **U** unit | `kit_unit.h` (+ `test_unit.mk` build manifest) | a C translation unit linked against libkit, self-checking in-process |
| **C** corpus | `cf_corpus.sh` | a directory of case files run through one or more *lanes*, each with its own oracle |
| **K** scripted | `kit_sh_kit.sh` | a hand-written shell test driving the `kit` binary, judged by golden transcript (mode G) or procedural asserts (mode P) |
| **D** differential | `cf_differential.sh` | correctness defined as *agreement* — vs a checked-in baseline, or vs a reference tool |
@@ -327,7 +327,7 @@ harnesses on `kit_sh_kit.sh`, in two oracle modes:
## Unit tests (Type U)
Lower-level invariants are covered by C unit-test binaries built from
-`test/lib/unit.mk` and linking `test/lib/kit_unit.h`. There are two link
+`mk/test_unit.mk` and linking `test/lib/kit_unit.h`. There are two link
flavors that differ only in what they can reach: `UNIT_TESTS_AR` link the public
archive (internal symbols hidden — exercises the public surface), `UNIT_TESTS_OBJS`
link the raw objects (internal hidden symbols reachable). These cover ISA
@@ -398,14 +398,14 @@ and to triage O1 codegen without `-g`, which perturbs object layout.
## Aggregation and conventions
-`test/test.mk` defines the targets. A default `test` aggregate runs the
+`mk/test.mk` defines the targets. A default `test` aggregate runs the
host-independent lanes (frontend corpora, unit tests, L0/L1 round-trip, the
`cf_corpus` engine selftest, the libc-dep guard); the exec-dependent and
second-oracle lanes (L2 exec, symmetry, diff-llvm, hostas-toy/cross, smoke, libc
conformance) are opt-in so the default run stays host-independent and fast.
Bootstrap is *not* part of this test system: it is a separate top-level target
(`make bootstrap`, `make bootstrap-debug/release`) driven from the top-level
-`Makefile`, not from `test/test.mk` — see the bootstrap section above.
+`Makefile`, not from `mk/test.mk` — see the bootstrap section above.
Conventions shared across all four test types:
- **One report layer.** Every shell harness records through `cf_pass`/`cf_fail`/
diff --git a/doc/plan/JIT.md b/doc/plan/JIT.md
@@ -233,7 +233,7 @@ trampoline.
- [ ] Regression harness: a scripted `test/run/` suite diffing exit codes
and stdout across `.c`, stdin, `.o`, `.a`, multi-file, and `-e`
entry cases, plus a `--no-jit` interpreter-vs-JIT cross-check. No
- coverage today; wire a `test-run` target into `test/test.mk`.
+ coverage today; wire a `test-run` target into `mk/test.mk`.
### 3.2 Inspector / debugger surface
diff --git a/mk/test.mk b/mk/test.mk
@@ -0,0 +1,956 @@
+# Data-driven tests. Included from the top-level Makefile.
+#
+# - test-driver: aggregate alias that runs all test-driver-* targets.
+# - test-driver-cc: narrow CLI behavior checks that do not belong to a specific
+# frontend/linker corpus. Depends on the kit driver binary.
+# - test-pp: aggregate alias that runs test-pp-ok and test-pp-err.
+# - test-pp-ok: C preprocessor success cases; depends on the kit driver binary.
+# - test-elf: ELF roundtrip harness in test/elf/; depends only on
+# libkit.a and compiles its own test binaries against it. Skipped
+# layers are reported (set KIT_TEST_ALLOW_SKIP=1 to allow skips).
+# - test-ar: in-process ar reader/writer tests; depends only on
+# libkit.a. Set KIT_AR_TEST_HOST=1 to also dump produced bytes
+# to /tmp and run the host's `ar t` / `nm --print-armap` as a
+# cross-check.
+# - test-driver-ar: scenario-driven CLI harness for `kit ar`. Each
+# case under test/ar/cases/ runs a small script and diffs stdout.
+# Depends on the kit driver binary.
+# - test-link: linker + JIT behavioral harness in test/link/; three paths
+# per case (roundtrip R, ELF exec E, JIT J). Depends only on libkit.a.
+# Set KIT_TEST_ALLOW_SKIP=1 to allow skipped layers.
+# - test-macho: Mach-O variant of test-link; defaults to roundtrip+JIT
+# paths because hosted Mach-O executable execution is target/SDK-specific.
+# - test-parse: aggregate alias that runs test-parse-ok and test-parse-err.
+# - test-parse-ok: file-driven C parser success harness in test/parse/; each
+# case is a .c source file. Built against the public kit.h surface;
+# reuses kit-roundtrip, link-exe-runner, and jit-runner.
+# - test-asm: aggregate alias over the per-arch asm lanes test-asm-aa64,
+# test-asm-x64 and test-asm-rv64. The file-driven assembler/disassembler
+# harness in test/asm/ runs one arch per invocation (KIT_TEST_ARCH) over
+# three sub-corpora (encode/, decode/, listing/), one mode per sub-dir.
+# aa64 runs the full path set (incl. native exec on aa64 hosts); x64/rv64
+# run the host-independent encode/decode/listing lanes only. See doc/ASM.md.
+
+TEST_TARGETS = \
+ test-cf-corpus-selftest \
+ test-aa64-inline \
+ test-abi-classify \
+ test-ar \
+ test-asm \
+ test-asm-aa64 \
+ test-asm-x64 \
+ test-asm-rv64 \
+ test-disasm-complete \
+ test-asm-roundtrip \
+ test-asm-roundtrip-exec \
+ test-asm-symmetry \
+ test-asm-roundtrip-toy \
+ test-hostas-toy \
+ test-hostas-cross \
+ test-diff-llvm \
+ test-bootstrap-toy \
+ test-bootstrap-toy-debug \
+ test-bootstrap-toy-release \
+ test-bounce \
+ test-cbackend \
+ test-cg-api \
+ test-coff \
+ test-coff-mingw-import \
+ test-coff-windows-ucrt \
+ test-debug \
+ test-dbg \
+ test-driver \
+ test-driver-ar \
+ test-driver-cas \
+ test-driver-cc \
+ test-driver-compile \
+ test-driver-objcopy \
+ test-driver-objdump \
+ test-driver-pkg \
+ test-driver-strings \
+ test-driver-tools \
+ test-hash \
+ test-driver-strip \
+ test-dwarf \
+ test-elf \
+ test-emu \
+ test-emu-unit \
+ test-interp \
+ test-interp-emu \
+ test-interp-toy \
+ test-ir-recorder \
+ test-isa \
+ test-lib-deps \
+ test-libc \
+ test-libc-glibc \
+ test-libc-glibc-rv64 \
+ test-libc-musl \
+ test-libc-musl-rv64 \
+ test-link \
+ test-link-reloc-uleb128 \
+ test-macho \
+ test-native-direct-target \
+ test-opt \
+ test-parse \
+ test-parse-err \
+ test-parse-ok \
+ test-parse-rv64-wide \
+ test-pp \
+ test-pp-err \
+ test-pp-ok \
+ test-rt-headers \
+ test-rt-runtime \
+ test-link-x64 \
+ test-rv64-inline \
+ test-rv64-jit \
+ test-rv64-tls-link \
+ test-smoke-rv64 \
+ test-smoke-x64 \
+ test-toy \
+ test-wasm \
+ test-wasm-c \
+ test-wasm-front \
+ test-wasm-target \
+ test-wasm-toy \
+ test-x64-dbg \
+ test-x64-inline
+
+DEFAULT_TEST_TARGETS = \
+ test-cf-corpus-selftest \
+ test-driver \
+ test-pp \
+ test-elf \
+ test-coff \
+ test-ar \
+ test-link \
+ test-toy \
+ test-dwarf \
+ test-debug \
+ test-parse \
+ test-asm \
+ test-asm-roundtrip \
+ test-isa \
+ test-aa64-inline \
+ test-rv64-inline \
+ test-rv64-jit \
+ test-rv64-tls-link \
+ test-emu \
+ test-emu-unit \
+ test-interp \
+ test-interp-emu \
+ test-x64-inline \
+ test-x64-dbg \
+ test-rt-headers \
+ test-lib-deps \
+ test-cg-api \
+ test-abi-classify \
+ test-ir-recorder \
+ test-native-direct-target \
+ test-opt \
+ test-asm-symmetry \
+ test-link-reloc-uleb128 \
+ test-dbg \
+ test-disasm-complete \
+ test-macho \
+ test-interp-toy \
+ test-wasm \
+ test-libc \
+ test-link-x64 \
+ test-rt-runtime \
+ test-bounce \
+ bootstrap \
+ test-bootstrap-toy
+
+.PHONY: test $(TEST_TARGETS)
+
+test: $(DEFAULT_TEST_TARGETS)
+
+# Unit-test binary build rules: two regimes (public vs internal interface).
+include mk/test_unit.mk
+
+# Provision the pinned per-arch container rootfs images that exec_target.sh runs
+# kit-emitted binaries inside. This is the ONLY test step that touches the
+# network: the cross-arch exec harnesses (toy/parse/link ... path X/E/L) run with
+# `podman run --pull=never`, so without these images those paths SKIP. Run once,
+# and again only after the pin in test/lib/test_images.sh changes. FORCE=1
+# re-pulls. The images are pinned per-arch by content digest, so they coexist in
+# local storage and can never clobber one another.
+.PHONY: test-images
+test-images:
+ @bash test/lib/pull_test_images.sh
+
+# Hermetic self-test of the shared corpus harness engine (test/lib/kit_corpus.sh):
+# asserts serial==parallel determinism, SKIP-NA, and the parallel-safety
+# invariant (exec_target queued only on the parent, never in a worker). No
+# kit binary / podman / qemu needed.
+test-cf-corpus-selftest:
+ @bash test/lib/kit_corpus_selftest.sh
+
+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
+
+test-driver-cc: bin
+ @KIT=$(abspath $(BIN)) sh test/driver/run.sh
+
+test-driver-compile: bin
+ @KIT=$(abspath $(BIN)) sh test/compile/run.sh
+
+# test-cbackend: --emit=c C-source backend, driven through three
+# frontends — parse-runner (C), toy-runner (toy), wasm-runner (wat/wasm).
+# Each invokes its existing runner with paths=C so a single corpus per
+# frontend exercises both the existing backends and the C backend.
+# Together they prove the CGTarget seam is frontend-agnostic.
+# Unimplemented CGTarget methods report as SKIP; see doc/CBACKEND.md.
+KIT_CBACKEND_TEST_JOBS ?= $(if $(KIT_TEST_JOBS),$(KIT_TEST_JOBS),1)
+test-cbackend: bin
+ @KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT_TEST_ALLOW_SKIP=1 sh test/parse/run.sh
+ @KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT=$(abspath $(BIN)) sh test/toy/run.sh
+ @KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT=$(abspath $(BIN)) bash test/wasm/run.sh
+
+# test-wasm-toy: opt-in Toy -> Wasm -> JIT roundtrip. Runs the toy corpus
+# under the W path (compile -target wasm32-none, then `kit run` the .wasm,
+# which routes back through the lang/wasm frontend to native CG). Most cases
+# will fail or skip today; the target exists so progress on the Wasm CGTarget
+# is visible without putting noise into the default `test` summary.
+# Drop `KIT_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
+test-wasm-toy: bin
+ @KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) sh test/toy/run.sh
+
+# test-wasm-c: opt-in C -> Wasm -> JIT roundtrip, the C-frontend analogue of
+# test-wasm-toy. Runs the test/parse corpus under the W path (compile
+# -target wasm32-none, then `kit run -e test_main` the .wasm, which routes
+# back through the lang/wasm frontend to native CG). The C corpus exercises
+# far more of the language than the toy corpus, so expect many SKIPs for
+# not-yet-implemented Wasm lowerings; the target makes that progress visible
+# without adding noise to the default `test` summary. Drop
+# `KIT_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
+test-wasm-c: bin $(PARSE_RUNNER)
+ @KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) bash test/parse/run.sh
+
+test-pp: test-pp-ok test-pp-err
+
+test-pp-ok: bin
+ @KIT=$(abspath $(BIN)) test/pp/run.sh
+
+test-pp-err: bin
+ @KIT=$(abspath $(BIN)) test/pp/run_errors.sh
+
+# Best-effort kit binary build: Layer D needs build/kit, but the
+# binary may not link until enough libkit symbols exist. The harness
+# detects a missing binary and skips that layer; don't break test-elf
+# when bin fails.
+.PHONY: bin-soft
+bin-soft:
+ -@$(MAKE) bin 2>/dev/null || true
+
+AR_TEST_BIN = build/test/ar_test
+
+test-ar: $(AR_TEST_BIN)
+ $(AR_TEST_BIN)
+
+
+test-driver-ar: bin
+ @KIT=$(abspath $(BIN)) test/ar/run.sh
+
+test-driver-cas: bin
+ @KIT=$(abspath $(BIN)) sh test/cas/run.sh
+
+test-driver-strip: bin
+ @KIT=$(abspath $(BIN)) test/strip/run.sh
+
+test-driver-objcopy: bin
+ @KIT=$(abspath $(BIN)) test/objcopy/run.sh
+
+test-driver-objdump: bin
+ @KIT=$(abspath $(BIN)) sh test/objdump/run.sh
+
+test-driver-pkg: bin
+ @KIT=$(abspath $(BIN)) sh test/pkg/run.sh
+
+test-driver-strings: bin
+ @KIT=$(abspath $(BIN)) sh test/strings/run.sh
+
+test-driver-tools: bin
+ @KIT=$(abspath $(BIN)) sh test/tools/run.sh
+
+# DWARF consumer unit test: builds a hand-crafted DWARF-bearing ELF in
+# memory and exercises every kit_dwarf_* entry. It reaches into the
+# internal object builder to synthesize the fixture, so link individual
+# lib objects rather than libkit.a (which hides internal symbols).
+DWARF_TEST_BIN = build/test/dwarf_test
+
+test-dwarf: $(DWARF_TEST_BIN)
+ $(DWARF_TEST_BIN)
+
+
+# DWARF producer self-roundtrip unit test. Drives Debug directly, calls
+# debug_emit, asserts the produced sections have valid DWARF 5 structure
+# (length fields, version, address sizes, expected relocations against
+# function symbol). Deliberately bypasses the consumer (kit_dwarf_open)
+# so encoder bugs aren't masked by matching decoder bugs.
+DEBUG_TEST_BIN = build/test/debug_roundtrip_unit
+CFI_TEST_BIN = build/test/debug_cfi_unit
+
+test-debug: $(DEBUG_TEST_BIN) $(CFI_TEST_BIN)
+ $(DEBUG_TEST_BIN)
+ $(CFI_TEST_BIN)
+
+
+# CFI/.eh_frame producer roundtrip for aa64/rv64/x64 (validates the per-arch
+# CIE template: code/data-align, return-address reg, CFA-init reg).
+
+test-dbg: bin
+ @KIT=$(abspath $(BIN)) sh test/dbg/run.sh
+
+.PHONY: test-dbg-red
+test-dbg-red: bin
+ @KIT=$(abspath $(BIN)) DBG_STRICT_XFAIL=1 sh test/dbg/run.sh
+
+# aa64 ISA descriptor-table unit test (doc/ASM.md phase 2). Covers
+# every AA64Format the table maps and the alias-precedence invariant
+# (first-match disasm picks the alias spelling over the canonical
+# form). Internal arch/ surface — needs -Isrc.
+AA64_ISA_TEST_BIN = build/test/aa64_isa_test
+RV64_DECODE_TEST_BIN = build/test/rv64_decode_test
+
+test-isa: $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN)
+ $(AA64_ISA_TEST_BIN)
+ $(RV64_DECODE_TEST_BIN)
+
+
+
+# aa64_sweep_gen: emits one representative encoding per disasm-table row for the
+# asm<->disasm self-symmetry sweep (test/asm/symmetry.sh). Needs the internal
+# arch/aa64/isa.h surface, so -Isrc + LIB_OBJS like the ISA unit test.
+AA64_SWEEP_GEN = build/test/aa64_sweep_gen
+
+# test-emu: emulator end-to-end integration test. Builds tiny in-memory rv64
+# ELFs and runs them to their exit syscall, asserting the exit code — entirely
+# through the PUBLIC kit_emu_* API, so it links the public archive ($(LIB_AR),
+# whose `ld -r` step localizes internal symbols). -Isrc is only for the
+# header-only rv64 encoders / ELF constants the in-memory ELF builders use.
+EMU_RV64_TEST_BIN = build/test/emu_rv64_test
+
+test-emu: $(EMU_RV64_TEST_BIN)
+ $(EMU_RV64_TEST_BIN)
+
+$(EMU_RV64_TEST_BIN): test/emu/rv64_smoke_test.c $(UNIT_HDR_DEPS) $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HOST_CFLAGS) -Iinclude -Isrc -Itest test/emu/rv64_smoke_test.c $(LIB_AR) -o $@
+
+# RISC-V ULEB128 diff-reloc application unit test. link_reloc_apply is an
+# internal (hidden) symbol, so link the raw lib objects like the other
+# internal-surface unit tests rather than libkit.a.
+RELOC_ULEB128_TEST_BIN = build/test/reloc_uleb128_unit
+
+test-link-reloc-uleb128: $(RELOC_ULEB128_TEST_BIN)
+ $(RELOC_ULEB128_TEST_BIN)
+
+# test-emu-unit: white-box unit tests for the emulator's INTERNAL units (rv64
+# decoder, EmuAddrSpace, Linux syscall handler) that have no public API. Reaches
+# internal symbols -> links $(LIB_OBJS) (mirrors test-interp), not the archive.
+EMU_RV64_UNIT_TEST_BIN = build/test/emu_rv64_unit_test
+
+test-emu-unit: $(EMU_RV64_UNIT_TEST_BIN)
+ $(EMU_RV64_UNIT_TEST_BIN)
+
+
+# test-interp: threaded-bytecode interpreter unit smoke test. Builds tiny CG IR
+# by hand, runs opt_run_o1_interp + interp_lower + the engine, asserts the
+# returned value. Reaches internal opt/interp symbols -> links $(LIB_OBJS) and
+# needs -Isrc (mirrors test-opt).
+INTERP_SMOKE_TEST_BIN = build/test/interp_smoke_test
+
+test-interp: $(INTERP_SMOKE_TEST_BIN)
+ $(INTERP_SMOKE_TEST_BIN)
+
+
+# test-interp-emu: differential test of the emulator's INTERP execution mode
+# (doc/INTERPRETER.md Phase 4). Builds a tiny rv64 ELF with SD/LD/ecall and runs
+# it through kit_emu_run in JIT and INTERP modes (both -O1), asserting the exit
+# codes match. Public-API only, but links $(LIB_OBJS) like test-interp to dodge
+# the visibility-hidden archive's localized internals.
+INTERP_EMU_TEST_BIN = build/test/rv64_interp_smoke_test
+
+test-interp-emu: $(INTERP_EMU_TEST_BIN)
+ $(INTERP_EMU_TEST_BIN)
+
+
+# test-interp-toy: run the toy suite's interpreter (--no-jit) path only,
+# asserting it matches the golden exit codes (and SKIPping unimplemented ops).
+test-interp-toy: bin
+ @KIT=$(abspath $(BIN)) KIT_TEST_PATHS=I bash test/toy/run.sh
+
+CG_API_TEST_BIN = build/test/cg_api_test
+CG_SWITCH_TEST_BIN = build/test/cg_switch_test
+CG_FP_CMP_TEST_BIN = build/test/cg_fp_cmp_test
+STRENGTH_REDUCE_TEST_BIN = build/test/strength_reduce_test
+TARGET_TEST_BIN = build/test/target_test
+HASH_TEST_BIN = build/test/hash_test
+ABI_CLASSIFY_TEST_BIN = build/test/abi_classify_test
+IR_RECORDER_TEST_BIN = build/test/ir_recorder_test
+NATIVE_DIRECT_TARGET_TEST_BIN = build/test/native_direct_target_test
+
+test-cg-api: $(TARGET_TEST_BIN) $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) \
+ $(CG_FP_CMP_TEST_BIN) $(STRENGTH_REDUCE_TEST_BIN)
+ $(TARGET_TEST_BIN)
+ $(CG_API_TEST_BIN)
+ $(CG_SWITCH_TEST_BIN)
+ $(CG_FP_CMP_TEST_BIN)
+ $(STRENGTH_REDUCE_TEST_BIN)
+
+test-hash: $(HASH_TEST_BIN)
+ $(HASH_TEST_BIN)
+
+test-abi-classify: $(ABI_CLASSIFY_TEST_BIN)
+ $(ABI_CLASSIFY_TEST_BIN)
+
+
+
+
+test-ir-recorder: $(IR_RECORDER_TEST_BIN)
+ $(IR_RECORDER_TEST_BIN)
+
+
+test-native-direct-target: $(NATIVE_DIRECT_TARGET_TEST_BIN)
+ $(NATIVE_DIRECT_TARGET_TEST_BIN)
+
+
+test-toy: bin
+ @KIT=$(abspath $(BIN)) test/toy/run.sh
+
+# test-bootstrap-toy: run the Toy corpus through the bootstrapped (self-built)
+# stage3 kit instead of the host-built binary, so the self-hosted compiler is
+# exercised on real codegen (not just self-reproduction like `make bootstrap`).
+# Split per build mode: -debug runs the debug stage3, -release the release
+# (-O1) stage3; the aggregate target runs both.
+test-bootstrap-toy: test-bootstrap-toy-debug test-bootstrap-toy-release
+
+test-bootstrap-toy-debug: bootstrap-debug
+ @KIT='$(abspath $(BUILD_DIR)/debug/bootstrap/stage3/kit)' test/toy/run.sh
+
+test-bootstrap-toy-release: bootstrap-release
+ @KIT='$(abspath $(BUILD_DIR)/release/bootstrap/stage3/kit)' test/toy/run.sh
+
+
+# Public-API inline-asm backend tests. These emit a tiny function through CG,
+# reopen the object through the public object reader, and assert the expected
+# instruction bytes are present in .text.
+AA64_INLINE_TEST_BIN = build/test/aa64_inline_test
+
+test-aa64-inline: $(AA64_INLINE_TEST_BIN)
+ $(AA64_INLINE_TEST_BIN)
+
+
+RV64_INLINE_TEST_BIN = build/test/rv64_inline_test
+
+test-rv64-inline: $(RV64_INLINE_TEST_BIN)
+ $(RV64_INLINE_TEST_BIN)
+
+
+# rv64 JIT smoke test. Builds a tiny rv64 ELF .o in memory, runs it
+# through kit_link_session in JIT-output mode, and skips native execution
+# on non-riscv64 hosts after exercising the JIT mapping/reloc path.
+RV64_JIT_TEST_BIN = build/test/rv64_jit_test
+
+test-rv64-jit: $(RV64_JIT_TEST_BIN)
+ @$(RV64_JIT_TEST_BIN); rc=$$?; \
+ if [ $$rc -eq 77 ]; then \
+ echo " (rv64_jit_test SKIPPED on non-rv64 host)"; \
+ exit 0; \
+ else \
+ exit $$rc; \
+ fi
+
+
+# Link-only regression for rv64 TLS Local-Exec lowering (runs on any host).
+test-rv64-tls-link: bin
+ @KIT='$(abspath $(BIN))' bash test/smoke/rv64_tls_link.sh
+
+X64_INLINE_TEST_BIN = build/test/x64_inline_test
+
+test-x64-inline: $(X64_INLINE_TEST_BIN)
+ $(X64_INLINE_TEST_BIN)
+
+
+X64_DBG_TEST_BIN = build/test/x64_dbg_test
+
+test-x64-dbg: $(X64_DBG_TEST_BIN)
+ $(X64_DBG_TEST_BIN)
+
+# Reaches the internal arch/arch.h surface (arch_lookup, ArchDbgOps) -> links
+# $(LIB_OBJS), not the archive, whose relocatable merge localizes non-public
+# symbols (mirrors the aa64/rv64 arch unit tests above).
+
+RT_HEADER_TEST_TARGETS = \
+ aarch64-linux-gnu \
+ x86_64-linux-gnu \
+ riscv64-linux-gnu \
+ aarch64-apple-darwin \
+ x86_64-apple-darwin
+
+test-rt-headers: bin
+ @set -e; \
+ for target in $(RT_HEADER_TEST_TARGETS); do \
+ out="build/test/rt-headers/$$target/smoke.o"; \
+ mkdir -p "$$(dirname "$$out")"; \
+ $(BIN) cc -target "$$target" -Werror -c test/rt/smoke.c -o "$$out"; \
+ done
+
+# Arch token -> linux rt variant target. The cross-target test harnesses link
+# the matching LINUX runtime archive (compiler-rt-style helpers like
+# __kit_sext64ti, __kit_assert_fail) rather than the native `rt` target,
+# which builds only the host's own variant. Shared by the single-arch
+# parse/asm/macho/link tests (via KIT_TEST_ARCH/TEST_RT_DEP) and by
+# test-rt-runtime, which sweeps several arches at once.
+KIT_TEST_ARCH ?= aa64
+_TEST_RT_aa64 = rt-aarch64-linux
+_TEST_RT_aarch64 = rt-aarch64-linux
+_TEST_RT_arm64 = rt-aarch64-linux
+_TEST_RT_x64 = rt-x86_64-linux
+_TEST_RT_x86_64 = rt-x86_64-linux
+_TEST_RT_amd64 = rt-x86_64-linux
+_TEST_RT_rv64 = rt-riscv64-linux
+_TEST_RT_riscv64 = rt-riscv64-linux
+TEST_RT_DEP = $(_TEST_RT_$(KIT_TEST_ARCH))
+
+# test-rt-runtime compiles each case for the configured arches and links it
+# against that arch's LINUX runtime archive, so it must depend on each variant
+# (not the host `rt`). A stale per-arch archive otherwise silently breaks the
+# link — e.g. a dropped __kit_assert_fail weak def surfaces as an undefined
+# reference. KIT_RT_RUNTIME_ARCHES mirrors the default in test/rt/run.sh.
+KIT_RT_RUNTIME_ARCHES ?= aa64 x64 rv64
+RT_RUNTIME_DEPS := $(foreach a,$(KIT_RT_RUNTIME_ARCHES),$(_TEST_RT_$(a)))
+
+test-rt-runtime: bin $(RT_RUNTIME_DEPS) $(LINK_EXE_RUNNER)
+ @bash test/rt/run.sh
+
+# Test harness binaries shared by test-elf and test-link.
+# Declared as Make targets (not built by the run.sh scripts) so they pick
+# up libkit.a changes deterministically.
+#
+# HARNESS_CFLAGS drops -Wpedantic from the normal host flags; the runners cast
+# kit_jit_lookup's void* to a function pointer, which pedantic rejects under
+# C11.
+HARNESS_CFLAGS = $(filter-out -Wpedantic,$(HOST_CFLAGS)) -Iinclude -Itest
+
+ROUNDTRIP_BIN = build/test/kit-roundtrip
+ROUNDTRIP_BIN_MACHO = build/test/kit-roundtrip-macho
+ROUNDTRIP_BIN_COFF = build/test/kit-roundtrip-coff
+COFF_IMPORT_SMOKE_BIN = build/test/pe-import-smoke
+COFF_IMPORT_MINGW_BIN = build/test/pe-import-mingw
+COFF_DSO_FORWARDER_BIN = build/test/pe-dso-forwarder
+COFF_MIXED_ARCHIVE_BIN = build/test/pe-mixed-archive
+LINK_EXE_RUNNER = build/test/link-exe-runner
+JIT_RUNNER = build/test/jit-runner
+PARSE_RUNNER = build/test/parse-runner
+ASM_RUNNER = build/test/asm-runner
+WASM_TOOL = build/test/wasm-tool
+
+$(ROUNDTRIP_BIN): test/elf/kit-roundtrip.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) test/elf/kit-roundtrip.c $(LIB_AR) -o $@
+
+# Mach-O peer of kit-roundtrip — read_macho + emit_macho. Used by
+# test-link's path R when KIT_TEST_OBJ=macho.
+$(ROUNDTRIP_BIN_MACHO): test/macho/kit-roundtrip-macho.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/macho/kit-roundtrip-macho.c $(LIB_AR) -o $@
+
+# PE/COFF round-trip harness (test/coff/). All-in-one binary: builds
+# hand-crafted ObjBuilders and asserts emit_coff/read_coff round-trip
+# stability for both x86_64-windows and aarch64-windows.
+$(ROUNDTRIP_BIN_COFF): test/coff/kit-roundtrip-coff.c $(LIB_OBJS)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/kit-roundtrip-coff.c $(LIB_OBJS) -o $@
+
+# PE import-directory smoke test (test/coff/pe-import-smoke.c).
+# Exercises the full chain: short-import shim bytes -> link_add_obj_bytes
+# (reclassified as DSO) -> link_resolve -> link_emit_coff. Verifies the
+# produced PE32+ via x86_64-w64-mingw32-objdump; skips cleanly if absent.
+$(COFF_IMPORT_SMOKE_BIN): test/coff/pe-import-smoke.c $(LIB_OBJS)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-smoke.c $(LIB_OBJS) -o $@
+
+# PE import test against a real mingw archive (test/coff/pe-import-mingw.c).
+# Exercises the long-form import-archive absorption path
+# (link_add_archive_bytes -> classify_coff_archive_member). Skips cleanly
+# when the mingw toolchain isn't installed.
+$(COFF_IMPORT_MINGW_BIN): test/coff/pe-import-mingw.c $(LIB_OBJS)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-mingw.c $(LIB_OBJS) -o $@
+
+# read_coff_dso forwarder-export contract (test/coff/pe-dso-forwarder.c).
+# Synthesizes a tiny PE32+ DLL with one direct and one forwarder export
+# and asserts both surface as OBJ_SEC_NONE globals on the ObjBuilder.
+$(COFF_DSO_FORWARDER_BIN): test/coff/pe-dso-forwarder.c $(LIB_OBJS)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-dso-forwarder.c $(LIB_OBJS) -o $@
+
+# Mixed-member archive (test/coff/pe-mixed-archive.c). Verifies that
+# one archive containing both a short-import member and a long-form
+# COFF object with a defined data symbol satisfies references through
+# both shapes — the same composition libucrt.a uses (API-set imports
+# alongside lib64_libucrt_extra_a-*.o helpers).
+$(COFF_MIXED_ARCHIVE_BIN): test/coff/pe-mixed-archive.c $(LIB_OBJS)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-mixed-archive.c $(LIB_OBJS) -o $@
+
+$(LINK_EXE_RUNNER): test/link/harness/link_exe_runner.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) test/link/harness/link_exe_runner.c $(LIB_AR) -o $@
+
+$(JIT_RUNNER): test/link/harness/jit_runner.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) test/link/harness/jit_runner.c $(LIB_AR) -o $@
+
+$(PARSE_RUNNER): test/parse/harness/parse_runner.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) test/parse/harness/parse_runner.c $(LIB_AR) -o $@
+
+$(ASM_RUNNER): test/asm/harness/asm_runner.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) test/asm/harness/asm_runner.c $(LIB_AR) -o $@
+
+$(WASM_TOOL): test/wasm/harness/wasm_tool.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) -I. test/wasm/harness/wasm_tool.c $(LIB_AR) -o $@
+
+test-elf: lib bin-soft $(ROUNDTRIP_BIN)
+ KIT_ELF_UNIT_CFLAGS='$(HOST_MODE_CFLAGS)' \
+ KIT_ELF_UNIT_LDFLAGS='$(HOST_MODE_LDFLAGS)' \
+ bash test/elf/run.sh
+
+# PE/COFF round-trip harness plus optional hosted Windows smoke. The
+# UCRT smoke self-skips when llvm-mingw is not installed.
+test-coff: lib bin rt-aarch64-windows $(ROUNDTRIP_BIN_COFF) $(COFF_IMPORT_SMOKE_BIN) $(COFF_DSO_FORWARDER_BIN) $(COFF_MIXED_ARCHIVE_BIN)
+ $(ROUNDTRIP_BIN_COFF)
+ $(COFF_IMPORT_SMOKE_BIN)
+ $(COFF_DSO_FORWARDER_BIN)
+ $(COFF_MIXED_ARCHIVE_BIN)
+ bash test/coff/windows-ucrt-hosted-smoke.sh
+ bash test/coff/windows-system-dlls-smoke.sh
+
+# Separate target so it can be skipped gracefully if mingw isn't
+# installed. The test itself self-skips on missing tooling, but the
+# build target only fires when explicitly requested.
+test-coff-mingw-import: lib $(COFF_IMPORT_MINGW_BIN)
+ $(COFF_IMPORT_MINGW_BIN)
+
+test-coff-windows-ucrt: bin rt-aarch64-windows
+ bash test/coff/windows-ucrt-hosted-smoke.sh
+
+# The parse/asm/macho harnesses select a cross-target via KIT_TEST_ARCH
+# (default aa64); the link rt dependency is resolved through the shared
+# _TEST_RT_<arch> map defined above (near test-rt-runtime).
+test-link: lib $(ROUNDTRIP_BIN) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
+ bash test/link/run.sh
+
+# x64 ELF link/reloc-application coverage. test-link defaults to aa64, so
+# kit's x64 static-link reloc fixups (R_X64_PLT32/GOTPCREL/TPOFF/...) were
+# only ever run via a manual KIT_TEST_ARCH=x64 override. Opt-in (not in the
+# default set) because the E path links + runs under podman/qemu-x86_64; the R
+# path (roundtrip + reloc layout) runs on any host.
+test-link-x64: lib rt-x86_64-linux $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
+ @KIT_TEST_ARCH=x64 KIT_TEST_PATHS=RE bash test/link/run.sh
+
+test-macho: lib $(TEST_RT_DEP) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
+ KIT_TEST_OBJ=macho \
+ KIT_TEST_ARCH=$${KIT_TEST_ARCH:-aa64} \
+ KIT_TEST_PATHS=$${KIT_TEST_PATHS:-RJ} \
+ KIT_TEST_ALLOW_SKIP=$${KIT_TEST_ALLOW_SKIP:-1} \
+ bash test/link/run.sh
+
+OPT_TEST_BIN = build/test/cg_ir_lower_test
+TINY_INLINE_TEST_BIN = build/test/tiny_inline_test
+
+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
+ $(OPT_TEST_BIN)
+
+
+test-opt-tiny-inline: bin $(TINY_INLINE_TEST_BIN)
+ $(TINY_INLINE_TEST_BIN)
+
+
+# Behavioral disasm check: tiny callee `bl` disappears from its caller at -O1.
+test-opt-inline: bin
+ @KIT=$(abspath $(BIN)) bash test/opt/run.sh
+
+# Behavioral disasm check: a pointer-typed null call arg is not routed through
+# a scratch temp at -O1 (PERCALL.md item 3, "zero through a temp").
+test-opt-zero-arg: bin
+ @KIT=$(abspath $(BIN)) bash test/opt/zero_arg.sh
+
+.PHONY: test-opt-static-prune-aa64
+test-opt-static-prune-aa64: bin
+ @KIT=$(abspath $(BIN)) bash test/opt/static_prune_aa64.sh
+
+.PHONY: test-opt-aa64-tail
+test-opt-aa64-tail: bin
+ @KIT=$(abspath $(BIN)) bash test/opt/aa64_tail_call.sh
+
+# Structural disasm check: the -O1 known-frame prologue cost-model tiers
+# (aa64 reference + ported x64 slim/red-zone and rv64 leaf shapes).
+.PHONY: test-opt-prologue-tier
+test-opt-prologue-tier: bin
+ @KIT=$(abspath $(BIN)) bash test/opt/prologue_tier.sh
+
+test-parse: test-parse-ok test-parse-err
+
+test-parse-ok: lib $(TEST_RT_DEP) $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
+ bash test/parse/run.sh
+
+test-parse-err: lib $(PARSE_RUNNER)
+ sh test/parse/run_errors.sh
+
+# test-asm: aggregate alias running every per-arch asm lane (aa64 + x64 + rv64)
+# so `make test` covers all three through one target. The harness runs one arch
+# per invocation (KIT_TEST_ARCH); each lane scopes its scratch per arch, so
+# the prerequisites are safe to run in parallel under `make -j`.
+test-asm: test-asm-aa64 test-asm-x64 test-asm-rv64
+
+# test-asm-aa64: the reference lane. aa64 is the default cross-target, and on
+# aa64 hosts the exec paths (D/E/J) run natively, so it uses the full default
+# path set (HTLDJE). The Makefile owns the harness binaries so they inherit host
+# flags consistently with the rest of the test suite.
+test-asm-aa64: lib $(TEST_RT_DEP) $(ASM_RUNNER) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
+ @KIT_TEST_ARCH=aa64 bash test/asm/run.sh
+
+# x64/rv64 exercise the encode (H), decode (T) and listing (L) corpora on any
+# host: H/T/L need no native execution (they only produce/compare bytes), so the
+# exec paths (D/E/J) are deliberately excluded and left to the smoke/qemu
+# targets.
+test-asm-x64: lib $(ASM_RUNNER)
+ @KIT_TEST_ARCH=x64 KIT_TEST_PATHS=HTL bash test/asm/run.sh
+test-asm-rv64: lib $(ASM_RUNNER)
+ @KIT_TEST_ARCH=rv64 KIT_TEST_PATHS=HT bash test/asm/run.sh
+
+# Codegen round-trip completeness (doc/ASM_ROUNDTRIP_TESTING.md). These drive
+# the `kit` binary itself (cc -S / as / objdump) over a C corpus rather than
+# a hand-written asm corpus, so coverage tracks codegen automatically.
+#
+# test-disasm-complete L0: cc -S must decode every in-function word
+# (no `.inst` markers). Host-independent, no exec.
+# test-asm-roundtrip L0+L1: also assert cc -c bytes/relocs == cc -S | as.
+# test-asm-roundtrip-exec L0+L1+L2: also run direct vs round-tripped object
+# and compare exit codes (native arch; opt-in).
+#
+# Vertical slice: aa64 only for now; L1/L2 run at -O1 (branch-free), L0 at both
+# opt levels. Broadening to -O0, other arches, and the default suite is tracked
+# in doc/ASM_ROUNDTRIP_TESTING.md once -S symbolization (Phase 2) lands.
+test-disasm-complete: bin
+ @KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=0 \
+ bash test/asm/roundtrip.sh
+test-asm-roundtrip: bin
+ @KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=01 \
+ bash test/asm/roundtrip.sh
+test-asm-roundtrip-exec: bin $(JIT_RUNNER)
+ @KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=012 \
+ bash test/asm/roundtrip.sh
+
+# test-asm-symmetry: asm<->disasm self-symmetry sweep (aa64). Decode-side sweeps
+# every disasm-table form (decode->encode->decode fixed point); encode-side
+# asserts every byte the assembler emits over the encode corpus is decodable.
+# Catches encode/decode asymmetries the codegen round-trip can't reach (e.g. a
+# form one tool handles and the other doesn't). Host-independent, no exec.
+test-asm-symmetry: $(ASM_RUNNER) $(AA64_SWEEP_GEN)
+ @bash test/asm/symmetry.sh
+
+# test-diff-llvm: differential cross-check of kit against llvm (aa64), as a
+# second oracle. Encode lane: kit as vs llvm-mc bytes over the encode corpus.
+# Disasm lane: cc -c bytes vs llvm-mc of cc -S (validates kit's disassembler;
+# the benign same-section-call reloc-vs-resolve difference is recognized).
+# Opt-in; skips cleanly when llvm-mc is absent.
+test-diff-llvm: bin
+ @KIT_TEST_OPTS="O0 O1" bash test/asm/diff_llvm.sh
+
+# test-asm-roundtrip-toy: L2 exec round-trip over the Toy corpus (native arch).
+# Reuses the ~150 toy cases (full CG op set, exit-code oracle) for free
+# round-trip coverage: kit cc -S | kit as | kit run, exit must match.
+# Opt-in; native target. Found a real miscompile (dropped .inst) the hand
+# corpus never reached.
+test-asm-roundtrip-toy: bin
+ @bash test/asm/roundtrip_toy.sh
+
+# test-hostas-toy: feed one native `cc -S` to BOTH kit's own `as` and clang (a
+# third-party host assembler), link + run each, and assert the toy exit-code
+# oracle. Only the assembler differs between the two lanes, so the clang lane is
+# the real test: a standard assembler can't paper over a private-dialect quirk
+# the way kit's own `as` can (cf. test/asm/diff_llvm.sh, but by execution).
+# cc -S is now object-format-aware, so the native Mach-O clang lane GATES by
+# default (both lanes 312/0); KIT_HOSTAS_ENFORCE_CLANG=0 demotes it to XFAIL.
+# Opt-in; skips cleanly if clang is absent.
+test-hostas-toy: bin
+ @bash test/asm/hostas_toy.sh
+
+# test-hostas-cross: the same two-assembler-by-execution idea as test-hostas-toy,
+# but CROSS — `cc -S -target <triple>` for ELF Linux arches (aarch64/x86_64/
+# riscv64), assembled by kit-as AND clang, linked static (+ the start.c crt)
+# with kit ld, and run under podman/qemu via test/lib/exec_target.sh. Each
+# target self-skips unless the host has a clang cross target, a runner, a
+# working `cc -S | kit as` for that arch, and a passing bounded exec smoke —
+# so it runs green on whatever the host supports (aarch64-linux today; x86_64
+# pends the x64 cc -S symbolizer, riscv64 pends a working rv64 user-mode
+# emulator). Opt-in; skips cleanly if clang/podman are absent.
+test-hostas-cross: bin
+ @bash test/asm/hostas_cross.sh
+
+test-wasm: test-wasm-front test-wasm-target test-wasm-toy
+
+test-wasm-front: bin $(WASM_TOOL) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
+ bash test/wasm/run.sh
+
+# test-wasm-target: structural checks on `kit cc -target wasm32-none`
+# output. test/wasm-target/run.sh is a Type C corpus harness whose lanes each
+# compile a tiny C or toy fixture and assert a property of the produced module
+# bytes (inline-asm opcodes, memory.copy/fill opcodes, exported "memory",
+# (import ...) decls). Opt-in: not in the default `test` target because the
+# checks depend on the bulk-memory + (import ...) backend work landing first.
+test-wasm-target: bin
+ @KIT=$(abspath $(BIN)) bash test/wasm-target/run.sh
+
+# test-smoke-x64: phase-1 sanity check for the multi-arch bring-up. Builds a
+# tiny freestanding x86_64 ELF with clang --target=x86_64-linux-gnu and
+# runs it through test/lib/exec_target.sh's podman/qemu pipeline,
+# proving the harness end-to-end before any kit-emitted x64 bytes
+# exist. Excluded from the default `test` target because it needs
+# podman + lld; opt-in via `make test-smoke-x64`.
+test-smoke-x64:
+ bash test/smoke/x64.sh
+
+# test-smoke-rv64: phase-2 counterpart of test-smoke-x64. Builds a
+# tiny freestanding riscv64 ELF with clang --target=riscv64-linux-gnu
+# and runs it through test/lib/exec_target.sh, proving the rv64 lane
+# of the harness end-to-end before any kit-emitted rv64 bytes
+# exist. Excluded from the default `test` target because it needs
+# qemu-riscv64 (or podman with riscv64 emulation) + lld; opt-in via
+# `make test-smoke-rv64`.
+test-smoke-rv64:
+ bash test/smoke/rv64.sh
+
+# test-parse-rv64-wide: end-to-end coverage of the rv64 128-bit scalar types
+# — __int128 (i128_*) and IEEE-754 binary128 long double (ldbl128_*) — built
+# with kit and run on riscv64. Exercises the soft-float / i128 lowering to
+# the compiler-rt-style runtime (fp_tf, fp_ti, int64), the LP64D register-pair
+# ABI for 16-byte scalars, and the conditional-branch range fix that large
+# soft-float helpers depend on. Opt-in (needs qemu-riscv64 or podman with
+# riscv64 emulation), so excluded from the default `test` target; mirrors
+# test-smoke-rv64. Run a single case with
+# KIT_TEST_ARCH=rv64 bash test/parse/run.sh ldbl128_03_arith
+test-parse-rv64-wide: lib rt-riscv64-linux $(PARSE_RUNNER) $(ROUNDTRIP_BIN) \
+ $(LINK_EXE_RUNNER)
+ @KIT_TEST_ARCH=rv64 KIT_TEST_PATHS=RE bash test/parse/run.sh 128
+
+# test-bounce: format-bounce stress test. Compiles small programs with
+# kit, then bounces each object through chains of format conversions
+# (ELF<->Mach-O<->COFF), partial links (ld -r), strip, and archive
+# round-trips, relinks, and runs the result, asserting the exit code
+# matches a host-cc reference. Stresses obj read/write/reloc, ar, and
+# partial-link paths rather than the arches. Defaults to the host-native
+# Linux arch (no emulation); sweep others with
+# KIT_BOUNCE_ARCHES="aarch64 x64 rv64". Excluded from the default `test`
+# target because it needs podman/qemu + a host cc; opt-in via
+# `make test-bounce`.
+test-bounce: $(BIN)
+ bash test/bounce/bounce.sh
+
+# test-libc: aggregate alias that runs test-libc-musl and test-libc-glibc.
+# test-libc-musl / test-libc-glibc: end-to-end static + dynamic libc link/run
+# on aarch64. Each variant pulls its own pinned sysroot (podman, ~30s on
+# first run) and shares the same case files under test/libc/cases/:
+#
+# test-libc-musl — Alpine 3.20 + musl 1.2.5 (test/libc/musl/)
+# test-libc-glibc — Debian bookworm + glibc 2.36 (test/libc/glibc/)
+#
+# Both build build/rt/aarch64-linux/libkit_rt.a for soft-float / TF
+# builtins, and run `kit ld` against the real libc.a (static) and
+# libc.so / libc.so.6 (dynamic). Excluded from the
+# default `test` target because they need podman; opt-in via
+# `make test-libc-musl` / `make test-libc-glibc` (or `make test-libc` for both).
+#
+# Each sysroot is treated as a real prerequisite via its PROVENANCE
+# marker so subsequent runs skip extraction and re-extract only when
+# the file is removed (or extract.sh -f forces a rebuild).
+MUSL_SYSROOT_MARKER = build/musl-sysroot/PROVENANCE
+MUSL_SYSROOT_X64_MARKER = build/musl-sysroot-x64/PROVENANCE
+MUSL_SYSROOT_RV64_MARKER = build/musl-sysroot-rv64/PROVENANCE
+GLIBC_SYSROOT_MARKER = build/glibc-sysroot/PROVENANCE
+GLIBC_SYSROOT_X64_MARKER = build/glibc-sysroot-x64/PROVENANCE
+GLIBC_SYSROOT_RV64_MARKER = build/glibc-sysroot-rv64/PROVENANCE
+
+$(MUSL_SYSROOT_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile
+ @bash test/libc/musl/extract.sh
+
+$(MUSL_SYSROOT_X64_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile.x64
+ @bash test/libc/musl/extract.sh -a x64
+
+$(MUSL_SYSROOT_RV64_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile.rv64
+ @bash test/libc/musl/extract.sh -a rv64
+
+$(GLIBC_SYSROOT_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile
+ @bash test/libc/glibc/extract.sh
+
+$(GLIBC_SYSROOT_X64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.x64
+ @bash test/libc/glibc/extract.sh -a x64
+
+$(GLIBC_SYSROOT_RV64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.rv64
+ @bash test/libc/glibc/extract.sh -a rv64
+
+# test-libc-musl / test-libc-glibc honor KIT_LIBC_ARCHES (default "aa64";
+# values: aa64, x64, rv64). Each enabled arch contributes its sysroot
+# PROVENANCE marker and its rt archive to the prerequisite list, so
+# `KIT_LIBC_ARCHES="aa64 x64" make test-libc-musl` builds both sysroots
+# + both rt archives before the runner script picks them up.
+KIT_LIBC_ARCHES ?= aa64
+
+# Map an arch token to its musl/glibc sysroot marker and rt target.
+_LIBC_MUSL_SYSROOT_aa64 = $(MUSL_SYSROOT_MARKER)
+_LIBC_MUSL_SYSROOT_x64 = $(MUSL_SYSROOT_X64_MARKER)
+_LIBC_MUSL_SYSROOT_rv64 = $(MUSL_SYSROOT_RV64_MARKER)
+_LIBC_GLIBC_SYSROOT_aa64 = $(GLIBC_SYSROOT_MARKER)
+_LIBC_GLIBC_SYSROOT_x64 = $(GLIBC_SYSROOT_X64_MARKER)
+_LIBC_GLIBC_SYSROOT_rv64 = $(GLIBC_SYSROOT_RV64_MARKER)
+_LIBC_RT_aa64 = rt-aarch64-linux
+_LIBC_RT_x64 = rt-x86_64-linux
+_LIBC_RT_rv64 = rt-riscv64-linux
+
+LIBC_MUSL_DEPS = $(foreach a,$(KIT_LIBC_ARCHES),$(_LIBC_MUSL_SYSROOT_$(a)) $(_LIBC_RT_$(a)))
+LIBC_GLIBC_DEPS = $(foreach a,$(KIT_LIBC_ARCHES),$(_LIBC_GLIBC_SYSROOT_$(a)) $(_LIBC_RT_$(a)))
+
+test-libc: test-libc-musl test-libc-glibc
+
+test-libc-musl: bin $(LIBC_MUSL_DEPS)
+ @KIT_LIBC_ARCHES="$(KIT_LIBC_ARCHES)" bash test/libc/musl/run.sh
+
+test-libc-glibc: bin $(LIBC_GLIBC_DEPS)
+ @KIT_LIBC_ARCHES="$(KIT_LIBC_ARCHES)" bash test/libc/glibc/run.sh
+
+test-libc-musl-rv64:
+ @$(MAKE) test-libc-musl KIT_LIBC_ARCHES=rv64
+
+test-libc-glibc-rv64:
+ @$(MAKE) test-libc-glibc KIT_LIBC_ARCHES=rv64
+
+# Fail if libkit.a depends on any external symbol not in the allowlist, or
+# if a relocatable link exposes non-public global definitions.
+# External dependency drift in either direction (new dep, or stale entry) is a
+# failure.
+#
+# Inspect the RELEASE artifacts only: the debug build is ASan/UBSan-instrumented
+# and pulls in __asan_*/__ubsan_* externals that are not part of the shipped
+# library's dependency surface.
+LIB_DEPS_RELEASE_DIR = build/release
+LIB_DEPS_AR = $(LIB_DEPS_RELEASE_DIR)/libkit.a
+LIB_DEPS_ACTUAL = $(LIB_DEPS_RELEASE_DIR)/libkit.deps.txt
+LIB_RELOC = $(LIB_DEPS_RELEASE_DIR)/libkit.reloc.o
+LIB_RELOC_BAD = $(LIB_DEPS_RELEASE_DIR)/libkit.reloc.bad-symbols.txt
+
+test-lib-deps:
+ @$(MAKE) lib RELEASE=1
+ @mkdir -p $(dir $(LIB_DEPS_ACTUAL))
+ @python3 scripts/lib_external_deps.py $(LIB_DEPS_AR) > $(LIB_DEPS_ACTUAL)
+ @diff -u scripts/lib_deps.allowlist $(LIB_DEPS_ACTUAL) \
+ || { echo "libkit.a external symbol set drifted from scripts/lib_deps.allowlist"; exit 1; }
+ @python3 scripts/lib_reloc_defined_prefixes.py $(LIB_DEPS_AR) \
+ --output $(LIB_RELOC) --ar $(AR) --cc $(CC) > $(LIB_RELOC_BAD)
+ @test ! -s $(LIB_RELOC_BAD) \
+ || { echo "libkit relocatable link exposes non-public symbols"; cat $(LIB_RELOC_BAD); exit 1; }
diff --git a/mk/test_unit.mk b/mk/test_unit.mk
@@ -0,0 +1,82 @@
+# mk/test_unit.mk — build rules for the C unit-test binaries.
+#
+# Included by mk/test.mk. Replaces the hand-written per-binary rules (each a
+# $(CC) ... SRC $(LIB_AR|LIB_OBJS) -o $@ triple) with two static-pattern rules,
+# one per regime. Registering a unit test is two lines: add its stem to a list
+# and set <stem>_SRC. Run-targets (test-isa, test-cg-api, ...) stay in mk/test.mk
+# and invoke build/test/<stem> directly, so per-binary sequencing and skip
+# semantics (e.g. rv64-jit's exit-77 wrapper) are unchanged.
+#
+# Two regimes, by the interface under test. We prefer tests that exercise the
+# PUBLIC interface — keep a test public unless it genuinely needs internals.
+#
+# PUBLIC public headers only (-Iinclude), linked against the public
+# archive libkit.a. Internal (visibility-hidden) symbols are
+# unreachable, so these prove the public surface is self-sufficient.
+# INTERNAL additionally sees src/ internal headers (-Isrc) and links the raw
+# objects (LIB_OBJS), exposing internal symbols. For units that have
+# no public API (ISA tables, ABI classifier, IR/opt internals, ...).
+#
+# Both add -Itest so `#include "lib/kit_unit.h"` resolves; neither adds -Ilang
+# (no unit test needs the frontend-private headers). emu_rv64_test is a
+# deliberate exception kept in mk/test.mk — see the note there.
+
+UNIT_HDR_DEPS := test/lib/kit_unit.h test/arch/inline_public_test.h
+
+# Deferred (=) so they pick up HOST_CFLAGS regardless of include order.
+UNIT_CFLAGS_PUBLIC = $(HOST_CFLAGS) -Iinclude -Itest
+UNIT_CFLAGS_INTERNAL = $(HOST_CFLAGS) -Iinclude -Isrc -Itest
+
+# ---- registrations: stem lists + per-stem source ---------------------------
+
+UNIT_TESTS_PUBLIC := \
+ ar_test target_test cg_api_test cg_switch_test cg_fp_cmp_test hash_test \
+ rv64_jit_test aa64_inline_test rv64_inline_test x64_inline_test \
+ strength_reduce_test
+ar_test_SRC := test/ar/ar_test.c
+target_test_SRC := test/api/target_test.c
+hash_test_SRC := test/api/hash_test.c
+cg_api_test_SRC := test/api/cg_type_test.c
+cg_switch_test_SRC := test/api/cg_switch_test.c
+cg_fp_cmp_test_SRC := test/api/cg_fp_cmp_test.c
+strength_reduce_test_SRC := test/cg/strength_reduce_test.c
+rv64_jit_test_SRC := test/link/rv64_jit_test.c
+aa64_inline_test_SRC := test/arch/aa64_inline_test.c
+rv64_inline_test_SRC := test/arch/rv64_inline_test.c
+x64_inline_test_SRC := test/arch/x64_inline_test.c
+
+UNIT_TESTS_INTERNAL := \
+ dwarf_test debug_roundtrip_unit debug_cfi_unit \
+ aa64_isa_test rv64_decode_test aa64_sweep_gen \
+ reloc_uleb128_unit emu_rv64_unit_test interp_smoke_test \
+ rv64_interp_smoke_test abi_classify_test ir_recorder_test \
+ native_direct_target_test x64_dbg_test cg_ir_lower_test tiny_inline_test
+dwarf_test_SRC := test/dwarf/dwarf_test.c
+debug_roundtrip_unit_SRC := test/debug/roundtrip_unit.c
+debug_cfi_unit_SRC := test/debug/cfi_unit.c
+aa64_isa_test_SRC := test/arch/aa64_isa_test.c
+rv64_decode_test_SRC := test/arch/rv64_decode_test.c
+aa64_sweep_gen_SRC := test/arch/aa64_sweep_gen.c
+reloc_uleb128_unit_SRC := test/link/reloc_uleb128_unit.c
+emu_rv64_unit_test_SRC := test/emu/rv64_vm_unit_test.c
+interp_smoke_test_SRC := test/interp/interp_smoke_test.c
+rv64_interp_smoke_test_SRC := test/emu/rv64_interp_smoke_test.c
+abi_classify_test_SRC := test/api/abi_classify_test.c
+ir_recorder_test_SRC := test/cg/ir_recorder_test.c
+native_direct_target_test_SRC := test/cg/native_direct_target_test.c
+x64_dbg_test_SRC := test/arch/x64_dbg_test.c
+cg_ir_lower_test_SRC := test/opt/cg_ir_lower_test.c
+tiny_inline_test_SRC := test/opt/tiny_inline_test.c
+
+# ---- build rules ------------------------------------------------------------
+# Secondary expansion lets a static-pattern prerequisite reference the
+# per-stem source via $$($$*_SRC).
+.SECONDEXPANSION:
+
+$(UNIT_TESTS_PUBLIC:%=build/test/%): build/test/%: $$($$*_SRC) $(UNIT_HDR_DEPS) $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(UNIT_CFLAGS_PUBLIC) $($*_SRC) $(LIB_AR) -o $@
+
+$(UNIT_TESTS_INTERNAL:%=build/test/%): build/test/%: $$($$*_SRC) $(UNIT_HDR_DEPS) $(LIB_OBJS)
+ @mkdir -p $(dir $@)
+ $(CC) $(UNIT_CFLAGS_INTERNAL) $($*_SRC) $(LIB_OBJS) -o $@
diff --git a/test/lib/unit.mk b/test/lib/unit.mk
@@ -1,80 +0,0 @@
-# test/lib/unit.mk — build rules for the C unit-test binaries.
-#
-# Included by test/test.mk. Replaces the hand-written per-binary rules (each a
-# $(CC) ... SRC $(LIB_AR|LIB_OBJS) -o $@ triple) with two static-pattern rules,
-# one per regime. Registering a unit test is two lines: add its stem to a list
-# and set <stem>_SRC. Run-targets (test-isa, test-cg-api, ...) stay in test.mk
-# and invoke build/test/<stem> directly, so per-binary sequencing and skip
-# semantics (e.g. rv64-jit's exit-77 wrapper) are unchanged.
-#
-# Two regimes, by the interface under test. We prefer tests that exercise the
-# PUBLIC interface — keep a test public unless it genuinely needs internals.
-#
-# PUBLIC public headers only (-Iinclude), linked against the public
-# archive libkit.a. Internal (visibility-hidden) symbols are
-# unreachable, so these prove the public surface is self-sufficient.
-# INTERNAL additionally sees src/ internal headers (-Isrc) and links the raw
-# objects (LIB_OBJS), exposing internal symbols. For units that have
-# no public API (ISA tables, ABI classifier, IR/opt internals, ...).
-#
-# Both add -Itest so `#include "lib/kit_unit.h"` resolves; neither adds -Ilang
-# (no unit test needs the frontend-private headers). emu_rv64_test is a
-# deliberate exception kept in test.mk — see the note there.
-
-UNIT_HDR_DEPS := test/lib/kit_unit.h test/arch/inline_public_test.h
-
-# Deferred (=) so they pick up HOST_CFLAGS regardless of include order.
-UNIT_CFLAGS_PUBLIC = $(HOST_CFLAGS) -Iinclude -Itest
-UNIT_CFLAGS_INTERNAL = $(HOST_CFLAGS) -Iinclude -Isrc -Itest
-
-# ---- registrations: stem lists + per-stem source ---------------------------
-
-UNIT_TESTS_PUBLIC := \
- ar_test cg_api_test cg_switch_test cg_fp_cmp_test hash_test rv64_jit_test \
- aa64_inline_test rv64_inline_test x64_inline_test strength_reduce_test
-ar_test_SRC := test/ar/ar_test.c
-hash_test_SRC := test/api/hash_test.c
-cg_api_test_SRC := test/api/cg_type_test.c
-cg_switch_test_SRC := test/api/cg_switch_test.c
-cg_fp_cmp_test_SRC := test/api/cg_fp_cmp_test.c
-strength_reduce_test_SRC := test/cg/strength_reduce_test.c
-rv64_jit_test_SRC := test/link/rv64_jit_test.c
-aa64_inline_test_SRC := test/arch/aa64_inline_test.c
-rv64_inline_test_SRC := test/arch/rv64_inline_test.c
-x64_inline_test_SRC := test/arch/x64_inline_test.c
-
-UNIT_TESTS_INTERNAL := \
- dwarf_test debug_roundtrip_unit debug_cfi_unit \
- aa64_isa_test rv64_decode_test aa64_sweep_gen \
- reloc_uleb128_unit emu_rv64_unit_test interp_smoke_test \
- rv64_interp_smoke_test abi_classify_test ir_recorder_test \
- native_direct_target_test x64_dbg_test cg_ir_lower_test tiny_inline_test
-dwarf_test_SRC := test/dwarf/dwarf_test.c
-debug_roundtrip_unit_SRC := test/debug/roundtrip_unit.c
-debug_cfi_unit_SRC := test/debug/cfi_unit.c
-aa64_isa_test_SRC := test/arch/aa64_isa_test.c
-rv64_decode_test_SRC := test/arch/rv64_decode_test.c
-aa64_sweep_gen_SRC := test/arch/aa64_sweep_gen.c
-reloc_uleb128_unit_SRC := test/link/reloc_uleb128_unit.c
-emu_rv64_unit_test_SRC := test/emu/rv64_vm_unit_test.c
-interp_smoke_test_SRC := test/interp/interp_smoke_test.c
-rv64_interp_smoke_test_SRC := test/emu/rv64_interp_smoke_test.c
-abi_classify_test_SRC := test/api/abi_classify_test.c
-ir_recorder_test_SRC := test/cg/ir_recorder_test.c
-native_direct_target_test_SRC := test/cg/native_direct_target_test.c
-x64_dbg_test_SRC := test/arch/x64_dbg_test.c
-cg_ir_lower_test_SRC := test/opt/cg_ir_lower_test.c
-tiny_inline_test_SRC := test/opt/tiny_inline_test.c
-
-# ---- build rules ------------------------------------------------------------
-# Secondary expansion lets a static-pattern prerequisite reference the
-# per-stem source via $$($$*_SRC).
-.SECONDEXPANSION:
-
-$(UNIT_TESTS_PUBLIC:%=build/test/%): build/test/%: $$($$*_SRC) $(UNIT_HDR_DEPS) $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(UNIT_CFLAGS_PUBLIC) $($*_SRC) $(LIB_AR) -o $@
-
-$(UNIT_TESTS_INTERNAL:%=build/test/%): build/test/%: $$($$*_SRC) $(UNIT_HDR_DEPS) $(LIB_OBJS)
- @mkdir -p $(dir $@)
- $(CC) $(UNIT_CFLAGS_INTERNAL) $($*_SRC) $(LIB_OBJS) -o $@
diff --git a/test/test.mk b/test/test.mk
@@ -1,954 +0,0 @@
-# Data-driven tests. Included from the top-level Makefile.
-#
-# - test-driver: aggregate alias that runs all test-driver-* targets.
-# - test-driver-cc: narrow CLI behavior checks that do not belong to a specific
-# frontend/linker corpus. Depends on the kit driver binary.
-# - test-pp: aggregate alias that runs test-pp-ok and test-pp-err.
-# - test-pp-ok: C preprocessor success cases; depends on the kit driver binary.
-# - test-elf: ELF roundtrip harness in test/elf/; depends only on
-# libkit.a and compiles its own test binaries against it. Skipped
-# layers are reported (set KIT_TEST_ALLOW_SKIP=1 to allow skips).
-# - test-ar: in-process ar reader/writer tests; depends only on
-# libkit.a. Set KIT_AR_TEST_HOST=1 to also dump produced bytes
-# to /tmp and run the host's `ar t` / `nm --print-armap` as a
-# cross-check.
-# - test-driver-ar: scenario-driven CLI harness for `kit ar`. Each
-# case under test/ar/cases/ runs a small script and diffs stdout.
-# Depends on the kit driver binary.
-# - test-link: linker + JIT behavioral harness in test/link/; three paths
-# per case (roundtrip R, ELF exec E, JIT J). Depends only on libkit.a.
-# Set KIT_TEST_ALLOW_SKIP=1 to allow skipped layers.
-# - test-macho: Mach-O variant of test-link; defaults to roundtrip+JIT
-# paths because hosted Mach-O executable execution is target/SDK-specific.
-# - test-parse: aggregate alias that runs test-parse-ok and test-parse-err.
-# - test-parse-ok: file-driven C parser success harness in test/parse/; each
-# case is a .c source file. Built against the public kit.h surface;
-# reuses kit-roundtrip, link-exe-runner, and jit-runner.
-# - test-asm: aggregate alias over the per-arch asm lanes test-asm-aa64,
-# test-asm-x64 and test-asm-rv64. The file-driven assembler/disassembler
-# harness in test/asm/ runs one arch per invocation (KIT_TEST_ARCH) over
-# three sub-corpora (encode/, decode/, listing/), one mode per sub-dir.
-# aa64 runs the full path set (incl. native exec on aa64 hosts); x64/rv64
-# run the host-independent encode/decode/listing lanes only. See doc/ASM.md.
-
-TEST_TARGETS = \
- test-cf-corpus-selftest \
- test-aa64-inline \
- test-abi-classify \
- test-ar \
- test-asm \
- test-asm-aa64 \
- test-asm-x64 \
- test-asm-rv64 \
- test-disasm-complete \
- test-asm-roundtrip \
- test-asm-roundtrip-exec \
- test-asm-symmetry \
- test-asm-roundtrip-toy \
- test-hostas-toy \
- test-hostas-cross \
- test-diff-llvm \
- test-bootstrap-toy \
- test-bootstrap-toy-debug \
- test-bootstrap-toy-release \
- test-bounce \
- test-cbackend \
- test-cg-api \
- test-coff \
- test-coff-mingw-import \
- test-coff-windows-ucrt \
- test-debug \
- test-dbg \
- test-driver \
- test-driver-ar \
- test-driver-cas \
- test-driver-cc \
- test-driver-compile \
- test-driver-objcopy \
- test-driver-objdump \
- test-driver-pkg \
- test-driver-strings \
- test-driver-tools \
- test-hash \
- test-driver-strip \
- test-dwarf \
- test-elf \
- test-emu \
- test-emu-unit \
- test-interp \
- test-interp-emu \
- test-interp-toy \
- test-ir-recorder \
- test-isa \
- test-lib-deps \
- test-libc \
- test-libc-glibc \
- test-libc-glibc-rv64 \
- test-libc-musl \
- test-libc-musl-rv64 \
- test-link \
- test-link-reloc-uleb128 \
- test-macho \
- test-native-direct-target \
- test-opt \
- test-parse \
- test-parse-err \
- test-parse-ok \
- test-parse-rv64-wide \
- test-pp \
- test-pp-err \
- test-pp-ok \
- test-rt-headers \
- test-rt-runtime \
- test-link-x64 \
- test-rv64-inline \
- test-rv64-jit \
- test-rv64-tls-link \
- test-smoke-rv64 \
- test-smoke-x64 \
- test-toy \
- test-wasm \
- test-wasm-c \
- test-wasm-front \
- test-wasm-target \
- test-wasm-toy \
- test-x64-dbg \
- test-x64-inline
-
-DEFAULT_TEST_TARGETS = \
- test-cf-corpus-selftest \
- test-driver \
- test-pp \
- test-elf \
- test-coff \
- test-ar \
- test-link \
- test-toy \
- test-dwarf \
- test-debug \
- test-parse \
- test-asm \
- test-asm-roundtrip \
- test-isa \
- test-aa64-inline \
- test-rv64-inline \
- test-rv64-jit \
- test-rv64-tls-link \
- test-emu \
- test-emu-unit \
- test-interp \
- test-interp-emu \
- test-x64-inline \
- test-x64-dbg \
- test-rt-headers \
- test-lib-deps \
- test-cg-api \
- test-abi-classify \
- test-ir-recorder \
- test-native-direct-target \
- test-opt \
- test-asm-symmetry \
- test-link-reloc-uleb128 \
- test-dbg \
- test-disasm-complete \
- test-macho \
- test-interp-toy \
- test-wasm \
- test-libc \
- test-link-x64 \
- test-rt-runtime \
- test-bounce \
- bootstrap \
- test-bootstrap-toy
-
-.PHONY: test $(TEST_TARGETS)
-
-test: $(DEFAULT_TEST_TARGETS)
-
-# Unit-test binary build rules: two regimes (public vs internal interface).
-include test/lib/unit.mk
-
-# Provision the pinned per-arch container rootfs images that exec_target.sh runs
-# kit-emitted binaries inside. This is the ONLY test step that touches the
-# network: the cross-arch exec harnesses (toy/parse/link ... path X/E/L) run with
-# `podman run --pull=never`, so without these images those paths SKIP. Run once,
-# and again only after the pin in test/lib/test_images.sh changes. FORCE=1
-# re-pulls. The images are pinned per-arch by content digest, so they coexist in
-# local storage and can never clobber one another.
-.PHONY: test-images
-test-images:
- @bash test/lib/pull_test_images.sh
-
-# Hermetic self-test of the shared corpus harness engine (test/lib/kit_corpus.sh):
-# asserts serial==parallel determinism, SKIP-NA, and the parallel-safety
-# invariant (exec_target queued only on the parent, never in a worker). No
-# kit binary / podman / qemu needed.
-test-cf-corpus-selftest:
- @bash test/lib/kit_corpus_selftest.sh
-
-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
-
-test-driver-cc: bin
- @KIT=$(abspath $(BIN)) sh test/driver/run.sh
-
-test-driver-compile: bin
- @KIT=$(abspath $(BIN)) sh test/compile/run.sh
-
-# test-cbackend: --emit=c C-source backend, driven through three
-# frontends — parse-runner (C), toy-runner (toy), wasm-runner (wat/wasm).
-# Each invokes its existing runner with paths=C so a single corpus per
-# frontend exercises both the existing backends and the C backend.
-# Together they prove the CGTarget seam is frontend-agnostic.
-# Unimplemented CGTarget methods report as SKIP; see doc/CBACKEND.md.
-KIT_CBACKEND_TEST_JOBS ?= $(if $(KIT_TEST_JOBS),$(KIT_TEST_JOBS),1)
-test-cbackend: bin
- @KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT_TEST_ALLOW_SKIP=1 sh test/parse/run.sh
- @KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT=$(abspath $(BIN)) sh test/toy/run.sh
- @KIT_TEST_JOBS=$(KIT_CBACKEND_TEST_JOBS) KIT_TEST_PATHS=C KIT=$(abspath $(BIN)) bash test/wasm/run.sh
-
-# test-wasm-toy: opt-in Toy -> Wasm -> JIT roundtrip. Runs the toy corpus
-# under the W path (compile -target wasm32-none, then `kit run` the .wasm,
-# which routes back through the lang/wasm frontend to native CG). Most cases
-# will fail or skip today; the target exists so progress on the Wasm CGTarget
-# is visible without putting noise into the default `test` summary.
-# Drop `KIT_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
-test-wasm-toy: bin
- @KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) sh test/toy/run.sh
-
-# test-wasm-c: opt-in C -> Wasm -> JIT roundtrip, the C-frontend analogue of
-# test-wasm-toy. Runs the test/parse corpus under the W path (compile
-# -target wasm32-none, then `kit run -e test_main` the .wasm, which routes
-# back through the lang/wasm frontend to native CG). The C corpus exercises
-# far more of the language than the toy corpus, so expect many SKIPs for
-# not-yet-implemented Wasm lowerings; the target makes that progress visible
-# without adding noise to the default `test` summary. Drop
-# `KIT_TEST_ALLOW_SKIP=1` once the corpus is mostly green.
-test-wasm-c: bin $(PARSE_RUNNER)
- @KIT_TEST_PATHS=W KIT_TEST_ALLOW_SKIP=1 KIT=$(abspath $(BIN)) bash test/parse/run.sh
-
-test-pp: test-pp-ok test-pp-err
-
-test-pp-ok: bin
- @KIT=$(abspath $(BIN)) test/pp/run.sh
-
-test-pp-err: bin
- @KIT=$(abspath $(BIN)) test/pp/run_errors.sh
-
-# Best-effort kit binary build: Layer D needs build/kit, but the
-# binary may not link until enough libkit symbols exist. The harness
-# detects a missing binary and skips that layer; don't break test-elf
-# when bin fails.
-.PHONY: bin-soft
-bin-soft:
- -@$(MAKE) bin 2>/dev/null || true
-
-AR_TEST_BIN = build/test/ar_test
-
-test-ar: $(AR_TEST_BIN)
- $(AR_TEST_BIN)
-
-
-test-driver-ar: bin
- @KIT=$(abspath $(BIN)) test/ar/run.sh
-
-test-driver-cas: bin
- @KIT=$(abspath $(BIN)) sh test/cas/run.sh
-
-test-driver-strip: bin
- @KIT=$(abspath $(BIN)) test/strip/run.sh
-
-test-driver-objcopy: bin
- @KIT=$(abspath $(BIN)) test/objcopy/run.sh
-
-test-driver-objdump: bin
- @KIT=$(abspath $(BIN)) sh test/objdump/run.sh
-
-test-driver-pkg: bin
- @KIT=$(abspath $(BIN)) sh test/pkg/run.sh
-
-test-driver-strings: bin
- @KIT=$(abspath $(BIN)) sh test/strings/run.sh
-
-test-driver-tools: bin
- @KIT=$(abspath $(BIN)) sh test/tools/run.sh
-
-# DWARF consumer unit test: builds a hand-crafted DWARF-bearing ELF in
-# memory and exercises every kit_dwarf_* entry. It reaches into the
-# internal object builder to synthesize the fixture, so link individual
-# lib objects rather than libkit.a (which hides internal symbols).
-DWARF_TEST_BIN = build/test/dwarf_test
-
-test-dwarf: $(DWARF_TEST_BIN)
- $(DWARF_TEST_BIN)
-
-
-# DWARF producer self-roundtrip unit test. Drives Debug directly, calls
-# debug_emit, asserts the produced sections have valid DWARF 5 structure
-# (length fields, version, address sizes, expected relocations against
-# function symbol). Deliberately bypasses the consumer (kit_dwarf_open)
-# so encoder bugs aren't masked by matching decoder bugs.
-DEBUG_TEST_BIN = build/test/debug_roundtrip_unit
-CFI_TEST_BIN = build/test/debug_cfi_unit
-
-test-debug: $(DEBUG_TEST_BIN) $(CFI_TEST_BIN)
- $(DEBUG_TEST_BIN)
- $(CFI_TEST_BIN)
-
-
-# CFI/.eh_frame producer roundtrip for aa64/rv64/x64 (validates the per-arch
-# CIE template: code/data-align, return-address reg, CFA-init reg).
-
-test-dbg: bin
- @KIT=$(abspath $(BIN)) sh test/dbg/run.sh
-
-.PHONY: test-dbg-red
-test-dbg-red: bin
- @KIT=$(abspath $(BIN)) DBG_STRICT_XFAIL=1 sh test/dbg/run.sh
-
-# aa64 ISA descriptor-table unit test (doc/ASM.md phase 2). Covers
-# every AA64Format the table maps and the alias-precedence invariant
-# (first-match disasm picks the alias spelling over the canonical
-# form). Internal arch/ surface — needs -Isrc.
-AA64_ISA_TEST_BIN = build/test/aa64_isa_test
-RV64_DECODE_TEST_BIN = build/test/rv64_decode_test
-
-test-isa: $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN)
- $(AA64_ISA_TEST_BIN)
- $(RV64_DECODE_TEST_BIN)
-
-
-
-# aa64_sweep_gen: emits one representative encoding per disasm-table row for the
-# asm<->disasm self-symmetry sweep (test/asm/symmetry.sh). Needs the internal
-# arch/aa64/isa.h surface, so -Isrc + LIB_OBJS like the ISA unit test.
-AA64_SWEEP_GEN = build/test/aa64_sweep_gen
-
-# test-emu: emulator end-to-end integration test. Builds tiny in-memory rv64
-# ELFs and runs them to their exit syscall, asserting the exit code — entirely
-# through the PUBLIC kit_emu_* API, so it links the public archive ($(LIB_AR),
-# whose `ld -r` step localizes internal symbols). -Isrc is only for the
-# header-only rv64 encoders / ELF constants the in-memory ELF builders use.
-EMU_RV64_TEST_BIN = build/test/emu_rv64_test
-
-test-emu: $(EMU_RV64_TEST_BIN)
- $(EMU_RV64_TEST_BIN)
-
-$(EMU_RV64_TEST_BIN): test/emu/rv64_smoke_test.c $(UNIT_HDR_DEPS) $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(HOST_CFLAGS) -Iinclude -Isrc -Itest test/emu/rv64_smoke_test.c $(LIB_AR) -o $@
-
-# RISC-V ULEB128 diff-reloc application unit test. link_reloc_apply is an
-# internal (hidden) symbol, so link the raw lib objects like the other
-# internal-surface unit tests rather than libkit.a.
-RELOC_ULEB128_TEST_BIN = build/test/reloc_uleb128_unit
-
-test-link-reloc-uleb128: $(RELOC_ULEB128_TEST_BIN)
- $(RELOC_ULEB128_TEST_BIN)
-
-# test-emu-unit: white-box unit tests for the emulator's INTERNAL units (rv64
-# decoder, EmuAddrSpace, Linux syscall handler) that have no public API. Reaches
-# internal symbols -> links $(LIB_OBJS) (mirrors test-interp), not the archive.
-EMU_RV64_UNIT_TEST_BIN = build/test/emu_rv64_unit_test
-
-test-emu-unit: $(EMU_RV64_UNIT_TEST_BIN)
- $(EMU_RV64_UNIT_TEST_BIN)
-
-
-# test-interp: threaded-bytecode interpreter unit smoke test. Builds tiny CG IR
-# by hand, runs opt_run_o1_interp + interp_lower + the engine, asserts the
-# returned value. Reaches internal opt/interp symbols -> links $(LIB_OBJS) and
-# needs -Isrc (mirrors test-opt).
-INTERP_SMOKE_TEST_BIN = build/test/interp_smoke_test
-
-test-interp: $(INTERP_SMOKE_TEST_BIN)
- $(INTERP_SMOKE_TEST_BIN)
-
-
-# test-interp-emu: differential test of the emulator's INTERP execution mode
-# (doc/INTERPRETER.md Phase 4). Builds a tiny rv64 ELF with SD/LD/ecall and runs
-# it through kit_emu_run in JIT and INTERP modes (both -O1), asserting the exit
-# codes match. Public-API only, but links $(LIB_OBJS) like test-interp to dodge
-# the visibility-hidden archive's localized internals.
-INTERP_EMU_TEST_BIN = build/test/rv64_interp_smoke_test
-
-test-interp-emu: $(INTERP_EMU_TEST_BIN)
- $(INTERP_EMU_TEST_BIN)
-
-
-# test-interp-toy: run the toy suite's interpreter (--no-jit) path only,
-# asserting it matches the golden exit codes (and SKIPping unimplemented ops).
-test-interp-toy: bin
- @KIT=$(abspath $(BIN)) KIT_TEST_PATHS=I bash test/toy/run.sh
-
-CG_API_TEST_BIN = build/test/cg_api_test
-CG_SWITCH_TEST_BIN = build/test/cg_switch_test
-CG_FP_CMP_TEST_BIN = build/test/cg_fp_cmp_test
-STRENGTH_REDUCE_TEST_BIN = build/test/strength_reduce_test
-HASH_TEST_BIN = build/test/hash_test
-ABI_CLASSIFY_TEST_BIN = build/test/abi_classify_test
-IR_RECORDER_TEST_BIN = build/test/ir_recorder_test
-NATIVE_DIRECT_TARGET_TEST_BIN = build/test/native_direct_target_test
-
-test-cg-api: $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) $(CG_FP_CMP_TEST_BIN) \
- $(STRENGTH_REDUCE_TEST_BIN)
- $(CG_API_TEST_BIN)
- $(CG_SWITCH_TEST_BIN)
- $(CG_FP_CMP_TEST_BIN)
- $(STRENGTH_REDUCE_TEST_BIN)
-
-test-hash: $(HASH_TEST_BIN)
- $(HASH_TEST_BIN)
-
-test-abi-classify: $(ABI_CLASSIFY_TEST_BIN)
- $(ABI_CLASSIFY_TEST_BIN)
-
-
-
-
-test-ir-recorder: $(IR_RECORDER_TEST_BIN)
- $(IR_RECORDER_TEST_BIN)
-
-
-test-native-direct-target: $(NATIVE_DIRECT_TARGET_TEST_BIN)
- $(NATIVE_DIRECT_TARGET_TEST_BIN)
-
-
-test-toy: bin
- @KIT=$(abspath $(BIN)) test/toy/run.sh
-
-# test-bootstrap-toy: run the Toy corpus through the bootstrapped (self-built)
-# stage3 kit instead of the host-built binary, so the self-hosted compiler is
-# exercised on real codegen (not just self-reproduction like `make bootstrap`).
-# Split per build mode: -debug runs the debug stage3, -release the release
-# (-O1) stage3; the aggregate target runs both.
-test-bootstrap-toy: test-bootstrap-toy-debug test-bootstrap-toy-release
-
-test-bootstrap-toy-debug: bootstrap-debug
- @KIT='$(abspath $(BUILD_DIR)/debug/bootstrap/stage3/kit)' test/toy/run.sh
-
-test-bootstrap-toy-release: bootstrap-release
- @KIT='$(abspath $(BUILD_DIR)/release/bootstrap/stage3/kit)' test/toy/run.sh
-
-
-# Public-API inline-asm backend tests. These emit a tiny function through CG,
-# reopen the object through the public object reader, and assert the expected
-# instruction bytes are present in .text.
-AA64_INLINE_TEST_BIN = build/test/aa64_inline_test
-
-test-aa64-inline: $(AA64_INLINE_TEST_BIN)
- $(AA64_INLINE_TEST_BIN)
-
-
-RV64_INLINE_TEST_BIN = build/test/rv64_inline_test
-
-test-rv64-inline: $(RV64_INLINE_TEST_BIN)
- $(RV64_INLINE_TEST_BIN)
-
-
-# rv64 JIT smoke test. Builds a tiny rv64 ELF .o in memory, runs it
-# through kit_link_session in JIT-output mode, and skips native execution
-# on non-riscv64 hosts after exercising the JIT mapping/reloc path.
-RV64_JIT_TEST_BIN = build/test/rv64_jit_test
-
-test-rv64-jit: $(RV64_JIT_TEST_BIN)
- @$(RV64_JIT_TEST_BIN); rc=$$?; \
- if [ $$rc -eq 77 ]; then \
- echo " (rv64_jit_test SKIPPED on non-rv64 host)"; \
- exit 0; \
- else \
- exit $$rc; \
- fi
-
-
-# Link-only regression for rv64 TLS Local-Exec lowering (runs on any host).
-test-rv64-tls-link: bin
- @KIT='$(abspath $(BIN))' bash test/smoke/rv64_tls_link.sh
-
-X64_INLINE_TEST_BIN = build/test/x64_inline_test
-
-test-x64-inline: $(X64_INLINE_TEST_BIN)
- $(X64_INLINE_TEST_BIN)
-
-
-X64_DBG_TEST_BIN = build/test/x64_dbg_test
-
-test-x64-dbg: $(X64_DBG_TEST_BIN)
- $(X64_DBG_TEST_BIN)
-
-# Reaches the internal arch/arch.h surface (arch_lookup, ArchDbgOps) -> links
-# $(LIB_OBJS), not the archive, whose relocatable merge localizes non-public
-# symbols (mirrors the aa64/rv64 arch unit tests above).
-
-RT_HEADER_TEST_TARGETS = \
- aarch64-linux-gnu \
- x86_64-linux-gnu \
- riscv64-linux-gnu \
- aarch64-apple-darwin \
- x86_64-apple-darwin
-
-test-rt-headers: bin
- @set -e; \
- for target in $(RT_HEADER_TEST_TARGETS); do \
- out="build/test/rt-headers/$$target/smoke.o"; \
- mkdir -p "$$(dirname "$$out")"; \
- $(BIN) cc -target "$$target" -Werror -c test/rt/smoke.c -o "$$out"; \
- done
-
-# Arch token -> linux rt variant target. The cross-target test harnesses link
-# the matching LINUX runtime archive (compiler-rt-style helpers like
-# __kit_sext64ti, __kit_assert_fail) rather than the native `rt` target,
-# which builds only the host's own variant. Shared by the single-arch
-# parse/asm/macho/link tests (via KIT_TEST_ARCH/TEST_RT_DEP) and by
-# test-rt-runtime, which sweeps several arches at once.
-KIT_TEST_ARCH ?= aa64
-_TEST_RT_aa64 = rt-aarch64-linux
-_TEST_RT_aarch64 = rt-aarch64-linux
-_TEST_RT_arm64 = rt-aarch64-linux
-_TEST_RT_x64 = rt-x86_64-linux
-_TEST_RT_x86_64 = rt-x86_64-linux
-_TEST_RT_amd64 = rt-x86_64-linux
-_TEST_RT_rv64 = rt-riscv64-linux
-_TEST_RT_riscv64 = rt-riscv64-linux
-TEST_RT_DEP = $(_TEST_RT_$(KIT_TEST_ARCH))
-
-# test-rt-runtime compiles each case for the configured arches and links it
-# against that arch's LINUX runtime archive, so it must depend on each variant
-# (not the host `rt`). A stale per-arch archive otherwise silently breaks the
-# link — e.g. a dropped __kit_assert_fail weak def surfaces as an undefined
-# reference. KIT_RT_RUNTIME_ARCHES mirrors the default in test/rt/run.sh.
-KIT_RT_RUNTIME_ARCHES ?= aa64 x64 rv64
-RT_RUNTIME_DEPS := $(foreach a,$(KIT_RT_RUNTIME_ARCHES),$(_TEST_RT_$(a)))
-
-test-rt-runtime: bin $(RT_RUNTIME_DEPS) $(LINK_EXE_RUNNER)
- @bash test/rt/run.sh
-
-# Test harness binaries shared by test-elf and test-link.
-# Declared as Make targets (not built by the run.sh scripts) so they pick
-# up libkit.a changes deterministically.
-#
-# HARNESS_CFLAGS drops -Wpedantic from the normal host flags; the runners cast
-# kit_jit_lookup's void* to a function pointer, which pedantic rejects under
-# C11.
-HARNESS_CFLAGS = $(filter-out -Wpedantic,$(HOST_CFLAGS)) -Iinclude -Itest
-
-ROUNDTRIP_BIN = build/test/kit-roundtrip
-ROUNDTRIP_BIN_MACHO = build/test/kit-roundtrip-macho
-ROUNDTRIP_BIN_COFF = build/test/kit-roundtrip-coff
-COFF_IMPORT_SMOKE_BIN = build/test/pe-import-smoke
-COFF_IMPORT_MINGW_BIN = build/test/pe-import-mingw
-COFF_DSO_FORWARDER_BIN = build/test/pe-dso-forwarder
-COFF_MIXED_ARCHIVE_BIN = build/test/pe-mixed-archive
-LINK_EXE_RUNNER = build/test/link-exe-runner
-JIT_RUNNER = build/test/jit-runner
-PARSE_RUNNER = build/test/parse-runner
-ASM_RUNNER = build/test/asm-runner
-WASM_TOOL = build/test/wasm-tool
-
-$(ROUNDTRIP_BIN): test/elf/kit-roundtrip.c $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) test/elf/kit-roundtrip.c $(LIB_AR) -o $@
-
-# Mach-O peer of kit-roundtrip — read_macho + emit_macho. Used by
-# test-link's path R when KIT_TEST_OBJ=macho.
-$(ROUNDTRIP_BIN_MACHO): test/macho/kit-roundtrip-macho.c $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/macho/kit-roundtrip-macho.c $(LIB_AR) -o $@
-
-# PE/COFF round-trip harness (test/coff/). All-in-one binary: builds
-# hand-crafted ObjBuilders and asserts emit_coff/read_coff round-trip
-# stability for both x86_64-windows and aarch64-windows.
-$(ROUNDTRIP_BIN_COFF): test/coff/kit-roundtrip-coff.c $(LIB_OBJS)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/kit-roundtrip-coff.c $(LIB_OBJS) -o $@
-
-# PE import-directory smoke test (test/coff/pe-import-smoke.c).
-# Exercises the full chain: short-import shim bytes -> link_add_obj_bytes
-# (reclassified as DSO) -> link_resolve -> link_emit_coff. Verifies the
-# produced PE32+ via x86_64-w64-mingw32-objdump; skips cleanly if absent.
-$(COFF_IMPORT_SMOKE_BIN): test/coff/pe-import-smoke.c $(LIB_OBJS)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-smoke.c $(LIB_OBJS) -o $@
-
-# PE import test against a real mingw archive (test/coff/pe-import-mingw.c).
-# Exercises the long-form import-archive absorption path
-# (link_add_archive_bytes -> classify_coff_archive_member). Skips cleanly
-# when the mingw toolchain isn't installed.
-$(COFF_IMPORT_MINGW_BIN): test/coff/pe-import-mingw.c $(LIB_OBJS)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-mingw.c $(LIB_OBJS) -o $@
-
-# read_coff_dso forwarder-export contract (test/coff/pe-dso-forwarder.c).
-# Synthesizes a tiny PE32+ DLL with one direct and one forwarder export
-# and asserts both surface as OBJ_SEC_NONE globals on the ObjBuilder.
-$(COFF_DSO_FORWARDER_BIN): test/coff/pe-dso-forwarder.c $(LIB_OBJS)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-dso-forwarder.c $(LIB_OBJS) -o $@
-
-# Mixed-member archive (test/coff/pe-mixed-archive.c). Verifies that
-# one archive containing both a short-import member and a long-form
-# COFF object with a defined data symbol satisfies references through
-# both shapes — the same composition libucrt.a uses (API-set imports
-# alongside lib64_libucrt_extra_a-*.o helpers).
-$(COFF_MIXED_ARCHIVE_BIN): test/coff/pe-mixed-archive.c $(LIB_OBJS)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-mixed-archive.c $(LIB_OBJS) -o $@
-
-$(LINK_EXE_RUNNER): test/link/harness/link_exe_runner.c $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) test/link/harness/link_exe_runner.c $(LIB_AR) -o $@
-
-$(JIT_RUNNER): test/link/harness/jit_runner.c $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) test/link/harness/jit_runner.c $(LIB_AR) -o $@
-
-$(PARSE_RUNNER): test/parse/harness/parse_runner.c $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) test/parse/harness/parse_runner.c $(LIB_AR) -o $@
-
-$(ASM_RUNNER): test/asm/harness/asm_runner.c $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) test/asm/harness/asm_runner.c $(LIB_AR) -o $@
-
-$(WASM_TOOL): test/wasm/harness/wasm_tool.c $(LIB_AR)
- @mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -I. test/wasm/harness/wasm_tool.c $(LIB_AR) -o $@
-
-test-elf: lib bin-soft $(ROUNDTRIP_BIN)
- KIT_ELF_UNIT_CFLAGS='$(HOST_MODE_CFLAGS)' \
- KIT_ELF_UNIT_LDFLAGS='$(HOST_MODE_LDFLAGS)' \
- bash test/elf/run.sh
-
-# PE/COFF round-trip harness plus optional hosted Windows smoke. The
-# UCRT smoke self-skips when llvm-mingw is not installed.
-test-coff: lib bin rt-aarch64-windows $(ROUNDTRIP_BIN_COFF) $(COFF_IMPORT_SMOKE_BIN) $(COFF_DSO_FORWARDER_BIN) $(COFF_MIXED_ARCHIVE_BIN)
- $(ROUNDTRIP_BIN_COFF)
- $(COFF_IMPORT_SMOKE_BIN)
- $(COFF_DSO_FORWARDER_BIN)
- $(COFF_MIXED_ARCHIVE_BIN)
- bash test/coff/windows-ucrt-hosted-smoke.sh
- bash test/coff/windows-system-dlls-smoke.sh
-
-# Separate target so it can be skipped gracefully if mingw isn't
-# installed. The test itself self-skips on missing tooling, but the
-# build target only fires when explicitly requested.
-test-coff-mingw-import: lib $(COFF_IMPORT_MINGW_BIN)
- $(COFF_IMPORT_MINGW_BIN)
-
-test-coff-windows-ucrt: bin rt-aarch64-windows
- bash test/coff/windows-ucrt-hosted-smoke.sh
-
-# The parse/asm/macho harnesses select a cross-target via KIT_TEST_ARCH
-# (default aa64); the link rt dependency is resolved through the shared
-# _TEST_RT_<arch> map defined above (near test-rt-runtime).
-test-link: lib $(ROUNDTRIP_BIN) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
- bash test/link/run.sh
-
-# x64 ELF link/reloc-application coverage. test-link defaults to aa64, so
-# kit's x64 static-link reloc fixups (R_X64_PLT32/GOTPCREL/TPOFF/...) were
-# only ever run via a manual KIT_TEST_ARCH=x64 override. Opt-in (not in the
-# default set) because the E path links + runs under podman/qemu-x86_64; the R
-# path (roundtrip + reloc layout) runs on any host.
-test-link-x64: lib rt-x86_64-linux $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
- @KIT_TEST_ARCH=x64 KIT_TEST_PATHS=RE bash test/link/run.sh
-
-test-macho: lib $(TEST_RT_DEP) $(ROUNDTRIP_BIN_MACHO) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
- KIT_TEST_OBJ=macho \
- KIT_TEST_ARCH=$${KIT_TEST_ARCH:-aa64} \
- KIT_TEST_PATHS=$${KIT_TEST_PATHS:-RJ} \
- KIT_TEST_ALLOW_SKIP=$${KIT_TEST_ALLOW_SKIP:-1} \
- bash test/link/run.sh
-
-OPT_TEST_BIN = build/test/cg_ir_lower_test
-TINY_INLINE_TEST_BIN = build/test/tiny_inline_test
-
-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
- $(OPT_TEST_BIN)
-
-
-test-opt-tiny-inline: bin $(TINY_INLINE_TEST_BIN)
- $(TINY_INLINE_TEST_BIN)
-
-
-# Behavioral disasm check: tiny callee `bl` disappears from its caller at -O1.
-test-opt-inline: bin
- @KIT=$(abspath $(BIN)) bash test/opt/run.sh
-
-# Behavioral disasm check: a pointer-typed null call arg is not routed through
-# a scratch temp at -O1 (PERCALL.md item 3, "zero through a temp").
-test-opt-zero-arg: bin
- @KIT=$(abspath $(BIN)) bash test/opt/zero_arg.sh
-
-.PHONY: test-opt-static-prune-aa64
-test-opt-static-prune-aa64: bin
- @KIT=$(abspath $(BIN)) bash test/opt/static_prune_aa64.sh
-
-.PHONY: test-opt-aa64-tail
-test-opt-aa64-tail: bin
- @KIT=$(abspath $(BIN)) bash test/opt/aa64_tail_call.sh
-
-# Structural disasm check: the -O1 known-frame prologue cost-model tiers
-# (aa64 reference + ported x64 slim/red-zone and rv64 leaf shapes).
-.PHONY: test-opt-prologue-tier
-test-opt-prologue-tier: bin
- @KIT=$(abspath $(BIN)) bash test/opt/prologue_tier.sh
-
-test-parse: test-parse-ok test-parse-err
-
-test-parse-ok: lib $(TEST_RT_DEP) $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
- bash test/parse/run.sh
-
-test-parse-err: lib $(PARSE_RUNNER)
- sh test/parse/run_errors.sh
-
-# test-asm: aggregate alias running every per-arch asm lane (aa64 + x64 + rv64)
-# so `make test` covers all three through one target. The harness runs one arch
-# per invocation (KIT_TEST_ARCH); each lane scopes its scratch per arch, so
-# the prerequisites are safe to run in parallel under `make -j`.
-test-asm: test-asm-aa64 test-asm-x64 test-asm-rv64
-
-# test-asm-aa64: the reference lane. aa64 is the default cross-target, and on
-# aa64 hosts the exec paths (D/E/J) run natively, so it uses the full default
-# path set (HTLDJE). The Makefile owns the harness binaries so they inherit host
-# flags consistently with the rest of the test suite.
-test-asm-aa64: lib $(TEST_RT_DEP) $(ASM_RUNNER) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
- @KIT_TEST_ARCH=aa64 bash test/asm/run.sh
-
-# x64/rv64 exercise the encode (H), decode (T) and listing (L) corpora on any
-# host: H/T/L need no native execution (they only produce/compare bytes), so the
-# exec paths (D/E/J) are deliberately excluded and left to the smoke/qemu
-# targets.
-test-asm-x64: lib $(ASM_RUNNER)
- @KIT_TEST_ARCH=x64 KIT_TEST_PATHS=HTL bash test/asm/run.sh
-test-asm-rv64: lib $(ASM_RUNNER)
- @KIT_TEST_ARCH=rv64 KIT_TEST_PATHS=HT bash test/asm/run.sh
-
-# Codegen round-trip completeness (doc/ASM_ROUNDTRIP_TESTING.md). These drive
-# the `kit` binary itself (cc -S / as / objdump) over a C corpus rather than
-# a hand-written asm corpus, so coverage tracks codegen automatically.
-#
-# test-disasm-complete L0: cc -S must decode every in-function word
-# (no `.inst` markers). Host-independent, no exec.
-# test-asm-roundtrip L0+L1: also assert cc -c bytes/relocs == cc -S | as.
-# test-asm-roundtrip-exec L0+L1+L2: also run direct vs round-tripped object
-# and compare exit codes (native arch; opt-in).
-#
-# Vertical slice: aa64 only for now; L1/L2 run at -O1 (branch-free), L0 at both
-# opt levels. Broadening to -O0, other arches, and the default suite is tracked
-# in doc/ASM_ROUNDTRIP_TESTING.md once -S symbolization (Phase 2) lands.
-test-disasm-complete: bin
- @KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=0 \
- bash test/asm/roundtrip.sh
-test-asm-roundtrip: bin
- @KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=01 \
- bash test/asm/roundtrip.sh
-test-asm-roundtrip-exec: bin $(JIT_RUNNER)
- @KIT_TEST_ARCH=aa64 KIT_TEST_OPTS="O0 O1" KIT_TEST_PATHS=012 \
- bash test/asm/roundtrip.sh
-
-# test-asm-symmetry: asm<->disasm self-symmetry sweep (aa64). Decode-side sweeps
-# every disasm-table form (decode->encode->decode fixed point); encode-side
-# asserts every byte the assembler emits over the encode corpus is decodable.
-# Catches encode/decode asymmetries the codegen round-trip can't reach (e.g. a
-# form one tool handles and the other doesn't). Host-independent, no exec.
-test-asm-symmetry: $(ASM_RUNNER) $(AA64_SWEEP_GEN)
- @bash test/asm/symmetry.sh
-
-# test-diff-llvm: differential cross-check of kit against llvm (aa64), as a
-# second oracle. Encode lane: kit as vs llvm-mc bytes over the encode corpus.
-# Disasm lane: cc -c bytes vs llvm-mc of cc -S (validates kit's disassembler;
-# the benign same-section-call reloc-vs-resolve difference is recognized).
-# Opt-in; skips cleanly when llvm-mc is absent.
-test-diff-llvm: bin
- @KIT_TEST_OPTS="O0 O1" bash test/asm/diff_llvm.sh
-
-# test-asm-roundtrip-toy: L2 exec round-trip over the Toy corpus (native arch).
-# Reuses the ~150 toy cases (full CG op set, exit-code oracle) for free
-# round-trip coverage: kit cc -S | kit as | kit run, exit must match.
-# Opt-in; native target. Found a real miscompile (dropped .inst) the hand
-# corpus never reached.
-test-asm-roundtrip-toy: bin
- @bash test/asm/roundtrip_toy.sh
-
-# test-hostas-toy: feed one native `cc -S` to BOTH kit's own `as` and clang (a
-# third-party host assembler), link + run each, and assert the toy exit-code
-# oracle. Only the assembler differs between the two lanes, so the clang lane is
-# the real test: a standard assembler can't paper over a private-dialect quirk
-# the way kit's own `as` can (cf. test/asm/diff_llvm.sh, but by execution).
-# cc -S is now object-format-aware, so the native Mach-O clang lane GATES by
-# default (both lanes 312/0); KIT_HOSTAS_ENFORCE_CLANG=0 demotes it to XFAIL.
-# Opt-in; skips cleanly if clang is absent.
-test-hostas-toy: bin
- @bash test/asm/hostas_toy.sh
-
-# test-hostas-cross: the same two-assembler-by-execution idea as test-hostas-toy,
-# but CROSS — `cc -S -target <triple>` for ELF Linux arches (aarch64/x86_64/
-# riscv64), assembled by kit-as AND clang, linked static (+ the start.c crt)
-# with kit ld, and run under podman/qemu via test/lib/exec_target.sh. Each
-# target self-skips unless the host has a clang cross target, a runner, a
-# working `cc -S | kit as` for that arch, and a passing bounded exec smoke —
-# so it runs green on whatever the host supports (aarch64-linux today; x86_64
-# pends the x64 cc -S symbolizer, riscv64 pends a working rv64 user-mode
-# emulator). Opt-in; skips cleanly if clang/podman are absent.
-test-hostas-cross: bin
- @bash test/asm/hostas_cross.sh
-
-test-wasm: test-wasm-front test-wasm-target test-wasm-toy
-
-test-wasm-front: bin $(WASM_TOOL) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
- bash test/wasm/run.sh
-
-# test-wasm-target: structural checks on `kit cc -target wasm32-none`
-# output. test/wasm-target/run.sh is a Type C corpus harness whose lanes each
-# compile a tiny C or toy fixture and assert a property of the produced module
-# bytes (inline-asm opcodes, memory.copy/fill opcodes, exported "memory",
-# (import ...) decls). Opt-in: not in the default `test` target because the
-# checks depend on the bulk-memory + (import ...) backend work landing first.
-test-wasm-target: bin
- @KIT=$(abspath $(BIN)) bash test/wasm-target/run.sh
-
-# test-smoke-x64: phase-1 sanity check for the multi-arch bring-up. Builds a
-# tiny freestanding x86_64 ELF with clang --target=x86_64-linux-gnu and
-# runs it through test/lib/exec_target.sh's podman/qemu pipeline,
-# proving the harness end-to-end before any kit-emitted x64 bytes
-# exist. Excluded from the default `test` target because it needs
-# podman + lld; opt-in via `make test-smoke-x64`.
-test-smoke-x64:
- bash test/smoke/x64.sh
-
-# test-smoke-rv64: phase-2 counterpart of test-smoke-x64. Builds a
-# tiny freestanding riscv64 ELF with clang --target=riscv64-linux-gnu
-# and runs it through test/lib/exec_target.sh, proving the rv64 lane
-# of the harness end-to-end before any kit-emitted rv64 bytes
-# exist. Excluded from the default `test` target because it needs
-# qemu-riscv64 (or podman with riscv64 emulation) + lld; opt-in via
-# `make test-smoke-rv64`.
-test-smoke-rv64:
- bash test/smoke/rv64.sh
-
-# test-parse-rv64-wide: end-to-end coverage of the rv64 128-bit scalar types
-# — __int128 (i128_*) and IEEE-754 binary128 long double (ldbl128_*) — built
-# with kit and run on riscv64. Exercises the soft-float / i128 lowering to
-# the compiler-rt-style runtime (fp_tf, fp_ti, int64), the LP64D register-pair
-# ABI for 16-byte scalars, and the conditional-branch range fix that large
-# soft-float helpers depend on. Opt-in (needs qemu-riscv64 or podman with
-# riscv64 emulation), so excluded from the default `test` target; mirrors
-# test-smoke-rv64. Run a single case with
-# KIT_TEST_ARCH=rv64 bash test/parse/run.sh ldbl128_03_arith
-test-parse-rv64-wide: lib rt-riscv64-linux $(PARSE_RUNNER) $(ROUNDTRIP_BIN) \
- $(LINK_EXE_RUNNER)
- @KIT_TEST_ARCH=rv64 KIT_TEST_PATHS=RE bash test/parse/run.sh 128
-
-# test-bounce: format-bounce stress test. Compiles small programs with
-# kit, then bounces each object through chains of format conversions
-# (ELF<->Mach-O<->COFF), partial links (ld -r), strip, and archive
-# round-trips, relinks, and runs the result, asserting the exit code
-# matches a host-cc reference. Stresses obj read/write/reloc, ar, and
-# partial-link paths rather than the arches. Defaults to the host-native
-# Linux arch (no emulation); sweep others with
-# KIT_BOUNCE_ARCHES="aarch64 x64 rv64". Excluded from the default `test`
-# target because it needs podman/qemu + a host cc; opt-in via
-# `make test-bounce`.
-test-bounce: $(BIN)
- bash test/bounce/bounce.sh
-
-# test-libc: aggregate alias that runs test-libc-musl and test-libc-glibc.
-# test-libc-musl / test-libc-glibc: end-to-end static + dynamic libc link/run
-# on aarch64. Each variant pulls its own pinned sysroot (podman, ~30s on
-# first run) and shares the same case files under test/libc/cases/:
-#
-# test-libc-musl — Alpine 3.20 + musl 1.2.5 (test/libc/musl/)
-# test-libc-glibc — Debian bookworm + glibc 2.36 (test/libc/glibc/)
-#
-# Both build build/rt/aarch64-linux/libkit_rt.a for soft-float / TF
-# builtins, and run `kit ld` against the real libc.a (static) and
-# libc.so / libc.so.6 (dynamic). Excluded from the
-# default `test` target because they need podman; opt-in via
-# `make test-libc-musl` / `make test-libc-glibc` (or `make test-libc` for both).
-#
-# Each sysroot is treated as a real prerequisite via its PROVENANCE
-# marker so subsequent runs skip extraction and re-extract only when
-# the file is removed (or extract.sh -f forces a rebuild).
-MUSL_SYSROOT_MARKER = build/musl-sysroot/PROVENANCE
-MUSL_SYSROOT_X64_MARKER = build/musl-sysroot-x64/PROVENANCE
-MUSL_SYSROOT_RV64_MARKER = build/musl-sysroot-rv64/PROVENANCE
-GLIBC_SYSROOT_MARKER = build/glibc-sysroot/PROVENANCE
-GLIBC_SYSROOT_X64_MARKER = build/glibc-sysroot-x64/PROVENANCE
-GLIBC_SYSROOT_RV64_MARKER = build/glibc-sysroot-rv64/PROVENANCE
-
-$(MUSL_SYSROOT_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile
- @bash test/libc/musl/extract.sh
-
-$(MUSL_SYSROOT_X64_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile.x64
- @bash test/libc/musl/extract.sh -a x64
-
-$(MUSL_SYSROOT_RV64_MARKER): test/libc/musl/extract.sh test/libc/musl/Containerfile.rv64
- @bash test/libc/musl/extract.sh -a rv64
-
-$(GLIBC_SYSROOT_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile
- @bash test/libc/glibc/extract.sh
-
-$(GLIBC_SYSROOT_X64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.x64
- @bash test/libc/glibc/extract.sh -a x64
-
-$(GLIBC_SYSROOT_RV64_MARKER): test/libc/glibc/extract.sh test/libc/glibc/Containerfile.rv64
- @bash test/libc/glibc/extract.sh -a rv64
-
-# test-libc-musl / test-libc-glibc honor KIT_LIBC_ARCHES (default "aa64";
-# values: aa64, x64, rv64). Each enabled arch contributes its sysroot
-# PROVENANCE marker and its rt archive to the prerequisite list, so
-# `KIT_LIBC_ARCHES="aa64 x64" make test-libc-musl` builds both sysroots
-# + both rt archives before the runner script picks them up.
-KIT_LIBC_ARCHES ?= aa64
-
-# Map an arch token to its musl/glibc sysroot marker and rt target.
-_LIBC_MUSL_SYSROOT_aa64 = $(MUSL_SYSROOT_MARKER)
-_LIBC_MUSL_SYSROOT_x64 = $(MUSL_SYSROOT_X64_MARKER)
-_LIBC_MUSL_SYSROOT_rv64 = $(MUSL_SYSROOT_RV64_MARKER)
-_LIBC_GLIBC_SYSROOT_aa64 = $(GLIBC_SYSROOT_MARKER)
-_LIBC_GLIBC_SYSROOT_x64 = $(GLIBC_SYSROOT_X64_MARKER)
-_LIBC_GLIBC_SYSROOT_rv64 = $(GLIBC_SYSROOT_RV64_MARKER)
-_LIBC_RT_aa64 = rt-aarch64-linux
-_LIBC_RT_x64 = rt-x86_64-linux
-_LIBC_RT_rv64 = rt-riscv64-linux
-
-LIBC_MUSL_DEPS = $(foreach a,$(KIT_LIBC_ARCHES),$(_LIBC_MUSL_SYSROOT_$(a)) $(_LIBC_RT_$(a)))
-LIBC_GLIBC_DEPS = $(foreach a,$(KIT_LIBC_ARCHES),$(_LIBC_GLIBC_SYSROOT_$(a)) $(_LIBC_RT_$(a)))
-
-test-libc: test-libc-musl test-libc-glibc
-
-test-libc-musl: bin $(LIBC_MUSL_DEPS)
- @KIT_LIBC_ARCHES="$(KIT_LIBC_ARCHES)" bash test/libc/musl/run.sh
-
-test-libc-glibc: bin $(LIBC_GLIBC_DEPS)
- @KIT_LIBC_ARCHES="$(KIT_LIBC_ARCHES)" bash test/libc/glibc/run.sh
-
-test-libc-musl-rv64:
- @$(MAKE) test-libc-musl KIT_LIBC_ARCHES=rv64
-
-test-libc-glibc-rv64:
- @$(MAKE) test-libc-glibc KIT_LIBC_ARCHES=rv64
-
-# Fail if libkit.a depends on any external symbol not in the allowlist, or
-# if a relocatable link exposes non-public global definitions.
-# External dependency drift in either direction (new dep, or stale entry) is a
-# failure.
-#
-# Inspect the RELEASE artifacts only: the debug build is ASan/UBSan-instrumented
-# and pulls in __asan_*/__ubsan_* externals that are not part of the shipped
-# library's dependency surface.
-LIB_DEPS_RELEASE_DIR = build/release
-LIB_DEPS_AR = $(LIB_DEPS_RELEASE_DIR)/libkit.a
-LIB_DEPS_ACTUAL = $(LIB_DEPS_RELEASE_DIR)/libkit.deps.txt
-LIB_RELOC = $(LIB_DEPS_RELEASE_DIR)/libkit.reloc.o
-LIB_RELOC_BAD = $(LIB_DEPS_RELEASE_DIR)/libkit.reloc.bad-symbols.txt
-
-test-lib-deps:
- @$(MAKE) lib RELEASE=1
- @mkdir -p $(dir $(LIB_DEPS_ACTUAL))
- @python3 scripts/lib_external_deps.py $(LIB_DEPS_AR) > $(LIB_DEPS_ACTUAL)
- @diff -u scripts/lib_deps.allowlist $(LIB_DEPS_ACTUAL) \
- || { echo "libkit.a external symbol set drifted from scripts/lib_deps.allowlist"; exit 1; }
- @python3 scripts/lib_reloc_defined_prefixes.py $(LIB_DEPS_AR) \
- --output $(LIB_RELOC) --ar $(AR) --cc $(CC) > $(LIB_RELOC_BAD)
- @test ! -s $(LIB_RELOC_BAD) \
- || { echo "libkit relocatable link exposes non-public symbols"; cat $(LIB_RELOC_BAD); exit 1; }