commit 9aa8d6e18cf373b4ec23486eeb96c824103b8997
parent a687ea88a10a85efa82c0d5caa8aebcded228003
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 12:01:04 -0700
scripts: add coarse amalgamation cross-test lane
Diffstat:
3 files changed, 335 insertions(+), 22 deletions(-)
diff --git a/doc/plan/PORT.md b/doc/plan/PORT.md
@@ -84,8 +84,11 @@ Resolved centrally by `scripts/hosted.sh expand <selector> [--mode=cross|selfhos
### `DEPTH`
-Uniform across both modes:
+`test-cross` accepts:
+- `coarse` — cross-compile and link the smoke artifacts across the expanded
+ support set, but do not execute them. This is the named form of
+ `make test-cross RUN=0`.
- `smoke` (default) — two cases, building up platform coverage:
1. `exit` — an `#include`-free exit-code program (`test/cross/cases/exit.c`,
returns 42): toolchain + link + crt + the exit-code path; on freestanding
@@ -109,6 +112,9 @@ Uniform across both modes:
(CLAUDE.md). `DEPTH=full` is the deliberate heavy run, usually with a scoped
`TARGET`.
+`test-selfhost` accepts `smoke` and `full`; it has no build-only `coarse` lane
+because its purpose is to run the newly built compiler on the target.
+
### `kit-cross` — cross-build the compiler itself
`test-cross` asks "can kit cross-compile a *program* for a target?";
@@ -157,7 +163,8 @@ The Makefile reads it via `$(shell …)`; nothing else re-encodes the matrix.
- `scripts/cross_test.sh <selector> [DEPTH]` — expand (mode=cross) → **fail-fast
provisioning pre-flight** → compile/link all smoke artifacts → optional
- execution phase (`RUN=0` / `KIT_CROSS_RUN=0` disables it) → aggregate report.
+ execution phase (`DEPTH=coarse`, `RUN=0`, or `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.
@@ -237,14 +244,14 @@ compiled and ran a program there.*
`cross`:
-| Config | smoke | full |
-|---|---|---|
-| linux-{glibc,musl}-{aa64,x64,rv64} | ready | ready (toy X + parse E + libc) |
-| macos-aarch64 | ready (native) | ready (native toy/parse) |
-| freebsd-{aarch64,amd64,riscv64} | ready (VM) | ready (toy via VM) |
-| windows-{aarch64,x64} | ready (VM) | ready (toy via VM) |
-| freestanding-rv32 | ready (bare) | ready (toy X + parse E, bare) |
-| freestanding-{aa64,x64,rv64} | ready (bare smoke) | **deferred** |
+| Config | coarse | smoke | full |
+|---|---|---|---|
+| linux-{glibc,musl}-{aa64,x64,rv64} | ready | ready | ready (toy X + parse E + libc) |
+| macos-aarch64 | ready | ready (native) | ready (native toy/parse) |
+| freebsd-{aarch64,amd64,riscv64} | ready | ready (VM) | ready (toy via VM) |
+| windows-{aarch64,x64} | ready | ready (VM) | ready (toy via VM) |
+| freestanding-rv32 | ready | ready (bare) | ready (toy X + parse E, bare) |
+| freestanding-{aa64,x64,rv64} | ready | ready (bare smoke) | **deferred** |
`selfhost`: macos (native), linux musl/glibc aarch64 (container), linux x64/rv64
(container under emulation), freebsd aarch64/amd64 (VM), windows aa64/x64 (cross
diff --git a/scripts/cross_test.sh b/scripts/cross_test.sh
@@ -8,10 +8,11 @@
# cross_test.sh <selector> [DEPTH]
#
# selector hosted.sh grammar: all | <os> | linux-<libc> | <token> | a,b,c
-# DEPTH smoke (default) — one hello program, build/link + optional run,
+# DEPTH coarse — smoke cases plus compile+run front/back amalgam
+# at -O0 and -O1 (execution follows RUN)
+# smoke (default) — exit/hello smoke cases, 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
+# full — coarse lane + toy/parse/libc corpora
#
# env: KIT (default build/kit), KIT_VM (default 1; 0 drops freebsd/windows),
# KIT_CROSS_RUN (default 1; 0 disables execution), DEPTH (overridden by the
@@ -33,11 +34,17 @@ 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
+case "$DEPTH" in
+ coarse|smoke|full) ;;
+ *) echo "cross_test: bad DEPTH '$DEPTH' (want coarse|smoke|full)" >&2; exit 2 ;;
+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.$$"
+RUN_LIST_SMOKE="$BUILD_DIR/.cross-run-list-smoke.$$"
+RUN_LIST_COARSE="$BUILD_DIR/.cross-run-list-coarse.$$"
+LIST_PATH="$RUN_LIST_SMOKE"
# shellcheck source=../test/lib/kit_sh_report.sh
. "$ROOT/test/lib/kit_sh_report.sh"
@@ -117,8 +124,48 @@ smoke_build_token() {
done
}
+coarse_build_token() {
+ local t="$1" os mode opt sfx cdir tag
+ os="$(tok_os "$t")"
+ tag="$($HOSTED tag "$t")"
+ cdir="$BUILD_DIR/$t"; mkdir -p "$cdir"
+ for opt in O0 O1; do
+ if [ "$os" = freestanding ]; then
+ local fobj="$cdir/c_frontback_amalgam.${opt}.o"
+ local out="$cdir/c_frontback_amalgam.${opt}.out"
+ local err="$cdir/c_frontback_amalgam.${opt}.err"
+ if ! "$HOSTED" cc "$t" -$opt -ffreestanding -c "$CASES/c_frontback_amalgam.c" -o "$fobj" \
+ >"$cdir/c_frontback_amalgam.$opt.cc.out" 2>"$cdir/c_frontback_amalgam.$opt.cc.err"; then
+ kit_fail "frontback_amalgam/$t:$opt:cc" "cc failed"; sed 's/^/ | /' "$cdir/c_frontback_amalgam.$opt.cc.err" | head; return
+ fi
+ kit_pass "frontback_amalgam/$t:$opt:cc"
+ run_list_add target "frontback_amalgam/$t:$opt" "$tag" "$fobj" "$out" "$err" 0 -
+ else
+ for mode in $(link_modes "$t"); do
+ case "$mode" in static|dynamic) sfx=".$mode" ;; *) sfx="" ;; esac
+ local ext=""; [ "$os" = windows ] && ext=.exe
+ local fobj="$cdir/c_frontback_amalgam_${opt}${sfx}.o"
+ local fexe="$cdir/c_frontback_amalgam_${opt}${sfx}$ext"
+ if ! "$HOSTED" cc "$t" -$opt -c "$CASES/c_frontback_amalgam.c" -o "$fobj" \
+ >"$cdir/c_frontback_amalgam_${opt}${sfx}.cc.out" 2>"$cdir/c_frontback_amalgam_${opt}${sfx}.cc.err"; then
+ kit_fail "frontback_amalgam/$t:$opt$sfx:cc" "cc failed"; sed 's/^/ | /' "$cdir/c_frontback_amalgam_${opt}${sfx}.cc.err" | head; return
+ fi
+ kit_pass "frontback_amalgam/$t:$opt$sfx:cc"
+ # shellcheck disable=SC2046
+ if ! "$HOSTED" cc "$t" -$opt $(mode_flag "$mode") "$fobj" -o "$fexe" \
+ >"$cdir/c_frontback_amalgam_${opt}${sfx}.ld.out" 2>"$cdir/c_frontback_amalgam_${opt}${sfx}.ld.err"; then
+ kit_fail "frontback_amalgam/$t:$opt$sfx:ld" "ld failed"; sed 's/^/ | /' "$cdir/c_frontback_amalgam_${opt}${sfx}.ld.err" | head; return
+ fi
+ kit_pass "frontback_amalgam/$t:$opt$sfx:ld"
+ run_list_add target "frontback_amalgam/$t:$opt$sfx" "$tag" "$fexe" \
+ "$cdir/c_frontback_amalgam_${opt}${sfx}.out" "$cdir/c_frontback_amalgam_${opt}${sfx}.err" 0 -
+ done
+ fi
+ done
+}
+
run_list_add() { # kind label tag exe out err expected want-file
- printf '%s|%s|%s|%s|%s|%s|%s|%s\n' "$@" >> "$RUN_LIST"
+ printf '%s|%s|%s|%s|%s|%s|%s|%s\n' "$@" >> "$LIST_PATH"
}
# smoke_build_case TOKEN CASENAME MODE — compile and link one case in one link
@@ -170,9 +217,10 @@ smoke_build_case() {
fi
}
-smoke_run_all() {
+cross_run_all() {
+ local list="$1"
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; }
+ [ -s "$list" ] || { echo "cross: no linked artifacts to execute"; return; }
while IFS='|' read -r kind label tag exe out err exp want_file; do
case "$kind" in
bare)
@@ -190,7 +238,7 @@ smoke_run_all() {
else
kit_pass "$label:run"
fi
- done < "$RUN_LIST"
+ done < "$list"
}
# ---- full lanes ------------------------------------------------------------
@@ -262,23 +310,32 @@ if [ "$missing" -eq 1 ]; then
fi
printf '\ncross: phase 1 — compile/link smoke cases\n'
-: > "$RUN_LIST"
+: > "$RUN_LIST_SMOKE"
+: > "$RUN_LIST_COARSE"
for t in $TOKENS; do
+ LIST_PATH="$RUN_LIST_SMOKE"
smoke_build_token "$t"
+ if [ "$DEPTH" != smoke ]; then
+ LIST_PATH="$RUN_LIST_COARSE"
+ coarse_build_token "$t"
+ fi
done
if [ "$KIT_CROSS_RUN" -eq 1 ]; then
printf '\ncross: phase 2 — execute smoke cases\n'
- smoke_run_all
+ cross_run_all "$RUN_LIST_SMOKE"
+ if [ "$DEPTH" != smoke ]; then
+ printf '\ncross: phase 3 — execute front/back amalgam lane\n'
+ cross_run_all "$RUN_LIST_COARSE"
+ fi
if [ "$DEPTH" = full ]; then
- printf '\ncross: phase 3 — full execution lanes\n'
+ printf '\ncross: phase 4 — 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
diff --git a/test/cross/cases/c_frontback_amalgam.c b/test/cross/cases/c_frontback_amalgam.c
@@ -0,0 +1,249 @@
+#include <stdarg.h>
+#include <stdint.h>
+
+/* --- Front/back smoke amalgamation -------------------------------------- */
+
+#define FB_MUL2(X) ((X) * (X))
+
+_Static_assert(FB_MUL2(3u) == 9u, "macro expansion sanity");
+
+struct fb_small_struct {
+ int32_t a;
+ int16_t b;
+};
+
+struct fb_large_struct {
+ uint64_t a;
+ uint64_t b;
+ uint64_t c;
+ uint64_t d;
+};
+
+union fb_union_bits {
+ uint32_t raw;
+ struct {
+ uint32_t low:12;
+ uint32_t high:20;
+ } bits;
+};
+
+static uint32_t fb_mutable_data = 7;
+static const uint32_t fb_ro_data = 0xA55A3A11u;
+static uint32_t fb_bss[4];
+
+static int fb_fail(const int rc) {
+ return rc;
+}
+
+static int fb_macro_and_static_assert(void) {
+ int x = FB_MUL2(2 + 1);
+ return x != 9 ? fb_fail(1) : 0;
+}
+
+static int fb_integer_promotions(void) {
+ int8_t s8 = -42;
+ uint8_t u8 = 17;
+ int32_t sum = (int32_t)s8 + (int32_t)u8;
+ return sum != -25 ? fb_fail(2) : 0;
+}
+
+static int fb_signed_unsigned(void) {
+ int32_t si = -20;
+ uint32_t ui = 5u;
+ uint32_t mixed = si + ui;
+ return mixed != 4294967281u ? fb_fail(3) : 0;
+}
+
+static int fb_shift_div_rem(void) {
+ uint32_t a = 0x10u;
+ uint32_t b = (a << 4) - 5u;
+ uint32_t c = b / 3u;
+ uint32_t d = b % 3u;
+ return (b != 0xFBu || c != 0x53u || d != 2u) ? fb_fail(4) : 0;
+}
+
+static int fb_pointer_arith(void) {
+ int32_t values[5] = { 1, 2, 3, 4, 5 };
+ int32_t* p = values;
+ int32_t x = p[2] + *(p + 2);
+ return (p + 4) - p != 4 || x != 6 ? fb_fail(5) : 0;
+}
+
+static int fb_arrays_compound(void) {
+ int32_t arr[6] = { [0] = 1, [3] = 4, [5] = 9 };
+ int32_t* tmp = (int32_t[]){ 10, 11, 12, 13 };
+ if (arr[1] != 0 || arr[3] != 4 || arr[5] != 9) return fb_fail(6);
+ return (tmp[0] + tmp[3] == 23 && tmp[2] == 12) ? 0 : fb_fail(6);
+}
+
+static int fb_struct_union_bitfields(void) {
+ struct fb_small_struct s = { .a = 7, .b = -2 };
+ union fb_union_bits u;
+ struct {
+ uint32_t a;
+ uint32_t b;
+ uint32_t c;
+ } t;
+ u.raw = 0x1234u;
+ t.a = s.a;
+ t.b = (uint32_t)u.bits.low;
+ t.c = (uint32_t)u.bits.high;
+ if (t.a != 7u || t.b != 0x234u || t.c != 0x1u) return fb_fail(7);
+ return 0;
+}
+
+static int fb_switches(void) {
+ int32_t dense = 0;
+ switch (2) {
+ case 0: dense = 1; break;
+ case 1: dense = 2; break;
+ case 2: dense = 3; break;
+ default: dense = 9; break;
+ }
+ if (dense != 3) return fb_fail(8);
+
+ int32_t sparse = 0;
+ switch (100) {
+ case 1: sparse = 1; break;
+ case 100: sparse = 2; break;
+ case 200: sparse = 3; break;
+ default: sparse = 4; break;
+ }
+ return sparse == 2 ? 0 : fb_fail(8);
+}
+
+static int fb_loops_goto(void) {
+ int32_t sum = 0;
+ int32_t i = 0;
+ while (i < 4) {
+ sum += i;
+ i += 1;
+ if (i == 3) goto fb_gate;
+ }
+fb_gate:
+ return (sum == 3) ? 0 : fb_fail(9);
+}
+
+static int fb_recursion(int32_t n) {
+ return n <= 1 ? n : fb_recursion(n - 1) + fb_recursion(n - 2);
+}
+
+static int fb_many_args(int32_t a0, int32_t a1, int32_t a2, int32_t a3, int32_t a4,
+ int32_t a5, int32_t a6, int32_t a7, int32_t a8, int32_t a9,
+ int32_t a10) {
+ return a0 + a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10;
+}
+
+static struct fb_small_struct fb_struct_by_value_small(struct fb_small_struct s) {
+ struct fb_small_struct out;
+ out.a = s.a + 1;
+ out.b = (int16_t)(s.b + 2);
+ return out;
+}
+
+static struct fb_large_struct fb_struct_by_value_large(uint32_t seed) {
+ struct fb_large_struct r;
+ r.a = (uint64_t)seed + 1u;
+ r.b = (uint64_t)seed + 2u;
+ r.c = (uint64_t)seed + 3u;
+ r.d = (uint64_t)seed + 4u;
+ return r;
+}
+
+static int fb_mixed_calls(int32_t i, float f, double d) {
+ int32_t mixed = (int32_t)d + (int32_t)f + i;
+ return mixed == 10 ? 0 : fb_fail(10);
+}
+
+static int fb_varargs(const int count, ...) {
+ va_list ap;
+ int32_t sum;
+ int i;
+ va_start(ap, count);
+ sum = 0;
+ for (i = 0; i < count; ++i) {
+ sum += va_arg(ap, int32_t);
+ }
+ va_end(ap);
+ return sum == 10 ? 0 : fb_fail(11);
+}
+
+static int fb_function_pointer(void) {
+ int32_t (*fp)(int32_t) = fb_recursion;
+ return (fp == 0) ? fb_fail(12) : (fp(2) != 1 ? fb_fail(12) : 0);
+}
+
+static int fb_storage_and_memory(void) {
+ static uint32_t local_static;
+ struct fb_small_struct s = { 1, 2 };
+ struct fb_small_struct t = fb_struct_by_value_small(s);
+ local_static += 3u;
+ fb_mutable_data += 1u;
+ if (t.a != 2 || local_static != 3u || fb_mutable_data != 8u) return fb_fail(13);
+
+ if (fb_bss[0] != 0u || fb_bss[1] != 0u || fb_bss[2] != 0u || fb_bss[3] != 0u) {
+ return fb_fail(14);
+ }
+ fb_bss[2] = 0x1234u;
+ if (fb_bss[2] != 0x1234u || fb_ro_data != 0xA55A3A11u) return fb_fail(14);
+ return 0;
+}
+
+static int fb_float_double(void) {
+ float f = 3.0f;
+ double d = 4.5;
+ float f2 = 0.5f;
+ double a = (double)f * f2;
+ double b = d - 0.5;
+ if (!(a == 1.5 && b == 4.0)) return fb_fail(15);
+ return 0;
+}
+
+int main(void) {
+ int32_t rc;
+
+ rc = fb_macro_and_static_assert();
+ if (rc) return rc;
+ rc = fb_integer_promotions();
+ if (rc) return rc;
+ rc = fb_signed_unsigned();
+ if (rc) return rc;
+ rc = fb_shift_div_rem();
+ if (rc) return rc;
+ rc = fb_pointer_arith();
+ if (rc) return rc;
+ rc = fb_arrays_compound();
+ if (rc) return rc;
+ rc = fb_struct_union_bitfields();
+ if (rc) return rc;
+ rc = fb_switches();
+ if (rc) return rc;
+ rc = fb_loops_goto();
+ if (rc) return rc;
+ rc = fb_recursion(6) == 8 ? 0 : fb_fail(16);
+ if (rc) return rc;
+ rc = (fb_many_args(1,2,3,4,5,6,7,8,9,10,10) == 65) ? 0 : fb_fail(17);
+ if (rc) return rc;
+ {
+ struct fb_small_struct s = { 10, 20 };
+ struct fb_small_struct p = fb_struct_by_value_small(s);
+ if (p.a != 11 || p.b != 22) return fb_fail(18);
+ }
+ {
+ struct fb_large_struct r = fb_struct_by_value_large(5);
+ uint64_t sum = r.a + r.b + r.c + r.d;
+ if (sum != 30u) return fb_fail(19);
+ }
+ rc = fb_mixed_calls(2, 3.0f, 5.0);
+ if (rc) return rc;
+ rc = fb_varargs(4, 1, 2, 3, 4);
+ if (rc) return rc;
+ rc = fb_function_pointer();
+ if (rc) return rc;
+ rc = fb_storage_and_memory();
+ if (rc) return rc;
+ rc = fb_float_double();
+ if (rc) return rc;
+
+ return 0;
+}