kit

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

commit a687ea88a10a85efa82c0d5caa8aebcded228003
parent ec872f18f7cfdbe0a2e9b5ff84bc5f1db93f4475
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 11:47:18 -0700

Split test-cross build and run phases

Diffstat:
Mdoc/plan/PORT.md | 9+++++----
Mmk/port.mk | 5++++-
Mscripts/cross_test.sh | 125++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mtest/lib/exec_bare.sh | 30++++++++++++++++++++++--------
4 files changed, 123 insertions(+), 46 deletions(-)

diff --git a/doc/plan/PORT.md b/doc/plan/PORT.md @@ -54,14 +54,14 @@ accepted as input aliases by `scripts/hosted.sh`. ``` make test-port # test-cross + test-selfhost -make test-cross [TARGET=…] [DEPTH=…] [KIT_VM=…] +make test-cross [TARGET=…] [DEPTH=…] [KIT_VM=…] [RUN=0|1] make test-selfhost [TARGET=…] [DEPTH=…] [KIT_VM=…] make kit-cross [TARGET=…] [CROSS_CC=kit|clang] [VERIFY=0|1] make provision [TARGET=…] [KIT_VM=…] ``` -Defaults: `TARGET ?= all`, `DEPTH ?= smoke`, `KIT_VM ?= 1`, `CROSS_CC ?= kit`, -`VERIFY ?= 0`. +Defaults: `TARGET ?= all`, `DEPTH ?= smoke`, `KIT_VM ?= 1`, `RUN ?= 1`, +`CROSS_CC ?= kit`, `VERIFY ?= 0`. `test-port` is **not** part of the default `make test` (it is heavy and needs provisioning). The native 3-stage `bootstrap` + `test-bootstrap-toy` stay in the @@ -156,7 +156,8 @@ The Makefile reads it via `$(shell …)`; nothing else re-encodes the matrix. ### Two orchestrators - `scripts/cross_test.sh <selector> [DEPTH]` — expand (mode=cross) → **fail-fast - provisioning pre-flight** → per-token build/run per DEPTH → aggregate report. + provisioning pre-flight** → compile/link all smoke artifacts → optional + execution phase (`RUN=0` / `KIT_CROSS_RUN=0` disables it) → aggregate report. - `scripts/selfhost.sh <selector> [DEPTH]` — expand (mode=selfhost) → pre-flight → per-token self-host (dispatch by OS) → on-target corpus → report. diff --git a/mk/port.mk b/mk/port.mk @@ -12,11 +12,13 @@ # <token> | a,b,c (see scripts/hosted.sh expand) # DEPTH smoke (default) | full # KIT_VM 1 (default; include freebsd/windows) | 0 (drop them) +# RUN 1 (default; execute linked smoke/full artifacts) | 0 (build/link only) # CROSS_CC kit (default) | clang — kit-cross toolchain backend # VERIFY 0 (default) | 1 — kit-cross: run the built kit on-target # # Examples: # make test-cross # smoke across the whole support set +# make test-cross RUN=0 # compile/link across the set, no execution # make test-cross TARGET=linux-glibc-x64 DEPTH=full # make test-cross TARGET=freestanding # all 4 bare-metal arches # make test-selfhost TARGET=freebsd-aa64 @@ -27,6 +29,7 @@ TARGET ?= all DEPTH ?= smoke KIT_VM ?= 1 +RUN ?= 1 CROSS_CC ?= kit VERIFY ?= 0 @@ -35,7 +38,7 @@ VERIFY ?= 0 test-port: test-cross test-selfhost test-cross: bin - @KIT='$(abspath $(BIN))' KIT_VM='$(KIT_VM)' \ + @KIT='$(abspath $(BIN))' KIT_VM='$(KIT_VM)' KIT_CROSS_RUN='$(RUN)' \ bash scripts/cross_test.sh '$(TARGET)' '$(DEPTH)' test-selfhost: bin diff --git a/scripts/cross_test.sh b/scripts/cross_test.sh @@ -1,18 +1,21 @@ #!/usr/bin/env bash # scripts/cross_test.sh — cross-compilation correctness orchestrator: the engine # behind `make test-cross`. For each target in the (expanded) support set it -# cross-compiles with the host kit and runs the artifact on that target through -# the shared exec seam, checking the result. +# cross-compiles and cross-links with the host kit, then optionally runs the +# linked artifact on that target through the shared exec seam, checking the +# result. # # cross_test.sh <selector> [DEPTH] # # selector hosted.sh grammar: all | <os> | linux-<libc> | <token> | a,b,c -# DEPTH smoke (default) — one hello program, build + run, exit code (+ stdout) +# DEPTH smoke (default) — one hello program, build/link + optional run, +# exit code (+ stdout) when execution is enabled # full — also the toy + parse corpora (+ libc on linux), # cross-compiled and run on the target # # env: KIT (default build/kit), KIT_VM (default 1; 0 drops freebsd/windows), -# DEPTH (overridden by the positional arg). +# KIT_CROSS_RUN (default 1; 0 disables execution), DEPTH (overridden by the +# positional arg). # # Provision-or-error: a requested target whose sysroot/image/VM is missing is a # hard error (all missing reported up front), never a silent skip. Provision with @@ -28,9 +31,13 @@ BUILD_DIR="$ROOT/build/test/cross" SELECTOR="${1:-${TARGET:-all}}" DEPTH="${2:-${DEPTH:-smoke}}" export KIT_VM="${KIT_VM:-1}" +KIT_CROSS_RUN="${KIT_CROSS_RUN:-${CROSS_RUN:-${RUN:-1}}}" +case "$KIT_CROSS_RUN" in 0|false|FALSE|no|NO|off|OFF) KIT_CROSS_RUN=0 ;; *) KIT_CROSS_RUN=1 ;; esac +export KIT_CROSS_RUN [ -x "$KIT" ] || { echo "cross_test: kit not found at $KIT (run 'make bin')" >&2; exit 2; } mkdir -p "$BUILD_DIR" +RUN_LIST="$BUILD_DIR/.cross-run-list.$$" # shellcheck source=../test/lib/kit_sh_report.sh . "$ROOT/test/lib/kit_sh_report.sh" @@ -81,7 +88,7 @@ preflight_token() { echo "sysroot missing — run 'make provision TARGET=$t'"; return fi ;; esac - if ! exec_target_supported "$tag"; then + if [ "$KIT_CROSS_RUN" -eq 1 ] && ! exec_target_supported "$tag"; then case "$os" in freestanding) echo "qemu-system missing for $tag — install qemu-system-$(printf '%s' "$($HOSTED triple "$t")" | sed 's/-none-elf//')" ;; freebsd|windows) echo "VM not provisioned/reachable — run 'make provision TARGET=$t KIT_VM=1'" ;; @@ -92,61 +99,98 @@ preflight_token() { fi } -# ---- smoke lane ------------------------------------------------------------ +# ---- smoke build/link + run lanes ----------------------------------------- # Per target: the `exit` case (toolchain + link + crt + exit path; the only case # freestanding can run) then `hello` (sysroot headers + libc + stdout, hosted -# only) — each run once per link mode (link_modes): musl gets static + dynamic. -smoke_token() { +# only) — each built once per link mode (link_modes): musl gets static + dynamic. +smoke_build_token() { local t="$1" os name m os="$(tok_os "$t")" if [ "$os" = freestanding ]; then - smoke_case "$t" exit "" # bare-metal: no link mode + smoke_build_case "$t" exit "" # bare-metal: no link mode return fi for name in exit hello; do for m in $(link_modes "$t"); do - smoke_case "$t" "$name" "$m" + smoke_build_case "$t" "$name" "$m" done done } -# smoke_case TOKEN CASENAME MODE — build + run one case in one link mode, check -# exit code (+ stdout if the case ships a <name>.stdout sidecar). MODE is +run_list_add() { # kind label tag exe out err expected want-file + printf '%s|%s|%s|%s|%s|%s|%s|%s\n' "$@" >> "$RUN_LIST" +} + +# smoke_build_case TOKEN CASENAME MODE — compile and link one case in one link +# mode, then queue the linked artifact for phase-2 execution. MODE is # static/dynamic (suffixes the lane) or default/"" (unsuffixed). -smoke_case() { - local t="$1" name="$2" mode="$3" os tag cdir label sfx src exp want got rc +smoke_build_case() { + local t="$1" name="$2" mode="$3" os tag cdir label sfx src exp want_file os="$(tok_os "$t")"; tag="$("$HOSTED" tag "$t")" cdir="$BUILD_DIR/$t"; mkdir -p "$cdir" src="$CASES/$name.c" case "$mode" in static|dynamic) sfx=".$mode" ;; *) sfx="" ;; esac label="$name/$t${sfx}" exp=0; [ -f "$CASES/$name.expected" ] && exp="$(cat "$CASES/$name.expected")" - want=""; [ -f "$CASES/$name.stdout" ] && want="$(cat "$CASES/$name.stdout")" + want_file="-"; [ -f "$CASES/$name.stdout" ] && want_file="$CASES/$name.stdout" if [ "$os" = freestanding ]; then - # Bare-metal: compile to an object; exec_bare links it with the reset stub - # and the exit value is read back through the per-arch oracle (no stdout). - local obj="$cdir/$name.o" + # Bare-metal: compile to an object, link with the reset stub now, and leave + # only the qemu boot for the execution phase. + local obj="$cdir/$name.o" elf="$cdir/$name.elf" barch reason + barch="$(tok_arch "$t")" if ! "$HOSTED" cc "$t" -O1 -ffreestanding -c "$src" -o "$obj" \ >"$cdir/$name.cc.out" 2>"$cdir/$name.cc.err"; then - kit_fail "$label" "cc failed"; sed 's/^/ | /' "$cdir/$name.cc.err" | head; return + kit_fail "$label:cc" "cc failed"; sed 's/^/ | /' "$cdir/$name.cc.err" | head; return fi - exec_target_run "$tag" "$obj" "$cdir/$name.out" "$cdir/$name.err" + kit_pass "$label:cc" + if ! exec_bare_setup_build "$barch" "$EXEC_BARE_WORK" >"$cdir/$name.bare.out" 2>"$cdir/$name.bare.err"; then + kit_fail "$label:ld" "bare setup failed"; sed 's/^/ | /' "$cdir/$name.bare.err" | head; return + fi + reason="$(exec_bare_link "$barch" "$obj" "$EXEC_BARE_WORK" "$elf" 2>&1)" || { + kit_fail "$label:ld" "${reason:-kit ld failed}"; return + } + kit_pass "$label:ld" + run_list_add bare "$label" "$tag" "$elf" "$cdir/$name.out" "$cdir/$name.err" "$exp" "$want_file" else local ext=""; [ "$os" = windows ] && ext=.exe - local exe="$cdir/$name$sfx$ext" - # shellcheck disable=SC2046 - if ! "$HOSTED" cc "$t" $(mode_flag "$mode") "$src" -o "$exe" \ + local obj="$cdir/$name$sfx.o" exe="$cdir/$name$sfx$ext" + if ! "$HOSTED" cc "$t" -O1 -c "$src" -o "$obj" \ >"$cdir/$name$sfx.cc.out" 2>"$cdir/$name$sfx.cc.err"; then - kit_fail "$label" "cc failed"; sed 's/^/ | /' "$cdir/$name$sfx.cc.err" | head; return + kit_fail "$label:cc" "cc failed"; sed 's/^/ | /' "$cdir/$name$sfx.cc.err" | head; return fi - exec_target_run "$tag" "$exe" "$cdir/$name$sfx.out" "$cdir/$name$sfx.err" + kit_pass "$label:cc" + # shellcheck disable=SC2046 + if ! "$HOSTED" cc "$t" $(mode_flag "$mode") "$obj" -o "$exe" \ + >"$cdir/$name$sfx.ld.out" 2>"$cdir/$name$sfx.ld.err"; then + kit_fail "$label:ld" "ld failed"; sed 's/^/ | /' "$cdir/$name$sfx.ld.err" | head; return + fi + kit_pass "$label:ld" + run_list_add target "$label" "$tag" "$exe" "$cdir/$name$sfx.out" "$cdir/$name$sfx.err" "$exp" "$want_file" fi +} - rc="${RUN_RC:-127}"; got="$(cat "$cdir/$name$sfx.out" 2>/dev/null)" - if [ "$((rc & 255))" -ne "$((exp & 255))" ]; then kit_fail "$label" "expected rc $exp, got $rc" - elif [ -n "$want" ] && [ "$got" != "$want" ]; then kit_fail "$label" "stdout mismatch (want '$want' got '$got')" - else kit_pass "$label"; fi +smoke_run_all() { + local kind label tag exe out err exp want_file rc got want barch + [ -s "$RUN_LIST" ] || { echo "cross: no linked smoke artifacts to execute"; return; } + while IFS='|' read -r kind label tag exe out err exp want_file; do + case "$kind" in + bare) + barch="$(_exec_target_arch "$tag")" + exec_bare_run_image "$barch" "$exe" "$out" "$err" ;; + *) + exec_target_run "$tag" "$exe" "$out" "$err" ;; + esac + rc="${RUN_RC:-127}"; got="$(cat "$out" 2>/dev/null)" + want=""; [ "$want_file" != "-" ] && want="$(cat "$want_file")" + if [ "$((rc & 255))" -ne "$((exp & 255))" ]; then + kit_fail "$label:run" "expected rc $exp, got $rc" + elif [ "$want_file" != "-" ] && [ "$got" != "$want" ]; then + kit_fail "$label:run" "stdout mismatch (want '$want' got '$got')" + else + kit_pass "$label:run" + fi + done < "$RUN_LIST" } # ---- full lanes ------------------------------------------------------------ @@ -202,8 +246,8 @@ full_token() { TOKENS="$("$HOSTED" expand "$SELECTOR" --mode=cross)" || exit 1 [ -n "$TOKENS" ] || { echo "cross_test: selector '$SELECTOR' expanded to nothing" >&2; exit 2; } -printf 'cross: DEPTH=%s KIT_VM=%s\ncross: targets=%s\n' \ - "$DEPTH" "$KIT_VM" "$(echo $TOKENS | tr '\n' ' ')" +printf 'cross: DEPTH=%s KIT_VM=%s KIT_CROSS_RUN=%s\ncross: targets=%s\n' \ + "$DEPTH" "$KIT_VM" "$KIT_CROSS_RUN" "$(echo $TOKENS | tr '\n' ' ')" missing=0 for t in $TOKENS; do @@ -217,11 +261,26 @@ if [ "$missing" -eq 1 ]; then exit 1 fi +printf '\ncross: phase 1 — compile/link smoke cases\n' +: > "$RUN_LIST" for t in $TOKENS; do - smoke_token "$t" - [ "$DEPTH" = full ] && full_token "$t" + smoke_build_token "$t" done +if [ "$KIT_CROSS_RUN" -eq 1 ]; then + printf '\ncross: phase 2 — execute smoke cases\n' + smoke_run_all + if [ "$DEPTH" = full ]; then + printf '\ncross: phase 3 — full execution lanes\n' + for t in $TOKENS; do + full_token "$t" + done + fi +else + printf '\ncross: phase 2 — execution disabled (KIT_CROSS_RUN=0)\n' + [ "$DEPTH" = full ] && printf 'cross: NOTE DEPTH=full lanes are execution lanes and were skipped\n' +fi + KIT_SKIP_IS_FAILURE=0 kit_summary test-cross kit_exit diff --git a/test/lib/exec_bare.sh b/test/lib/exec_bare.sh @@ -118,13 +118,12 @@ exec_bare_run_image() { # arch elf out err -> sets RUN_RC RUN_RC="$(_bare_decode "$arch" "$RUN_RC_RAW")" } -# ---- corpus runner: setup (build per-arch stub) ---------------------------- -exec_bare_setup() { # arch work [entry] +# ---- corpus runner: setup/link/run ----------------------------------------- +exec_bare_setup_build() { # arch work [entry] -> build per-arch stub local arch work entry dir arch="$(_bare_canon "$1")" || return 1 work="$2"; entry="${3:-${EXEC_BARE_ENTRY:-main}}" [ -x "$EXEC_BARE_KIT" ] || return 1 - exec_bare_supported "$arch" || return 1 dir="$work/_bare/$arch"; mkdir -p "$dir" rm -f "$dir/.ok" case "$arch" in @@ -140,14 +139,20 @@ exec_bare_setup() { # arch work [entry] eval "EXEC_BARE_DIR_$arch=\$dir" } -# ---- corpus runner: run (link object + boot) ------------------------------- -exec_bare_run() { # arch obj work rcfile -> echoes reason; 0 ran / 2 build-fail - local arch obj work rcf dir elf lderr rt +exec_bare_setup() { # arch work [entry] + local arch + arch="$(_bare_canon "$1")" || return 1 + exec_bare_supported "$arch" || return 1 + exec_bare_setup_build "$arch" "$2" "${3:-${EXEC_BARE_ENTRY:-main}}" +} + +exec_bare_link() { # arch obj work elf -> echoes reason; 0 linked / 2 failed + local arch obj work elf dir lderr rt arch="$(_bare_canon "$1")" || { echo "unknown bare arch '$1'"; return 2; } - obj="$2"; work="$3"; rcf="$4" + obj="$2"; work="$3"; elf="$4" eval "dir=\"\${EXEC_BARE_DIR_$arch:-$work/_bare/$arch}\"" [ -f "$dir/.ok" ] || { echo "bare runner not set up for $arch"; return 2; } - elf="$work/$(basename "$obj").$arch.elf"; lderr="$elf.ld.err" + lderr="$elf.ld.err" if [ "$arch" = rv32 ]; then rt="$EXEC_BARE_ROOT/build/rt/riscv32-elf-hardfloat/libkit_rt.a" if ! "$EXEC_BARE_KIT" ld -T "$dir/link.ld" -e _start \ @@ -160,6 +165,15 @@ exec_bare_run() { # arch obj work rcfile -> echoes reason; 0 ran / 2 build-fai echo "kit ld ($arch) failed: $(head -n1 "$lderr" 2>/dev/null)"; return 2 fi fi + return 0 +} + +exec_bare_run() { # arch obj work rcfile -> echoes reason; 0 ran / 2 build-fail + local arch obj work rcf elf + arch="$(_bare_canon "$1")" || { echo "unknown bare arch '$1'"; return 2; } + obj="$2"; work="$3"; rcf="$4" + elf="$work/$(basename "$obj").$arch.elf" + exec_bare_link "$arch" "$obj" "$work" "$elf" || return 2 _bare_qemu_run "$arch" "$elf" "$elf.out" "$elf.err" _bare_decode "$arch" "$RUN_RC_RAW" > "$rcf" return 0