commit a9e0c865122ac82d5b27160ac10ad0ec53fa4dd2
parent c6e10c46a421a8e9127fdb5b284aad2537aaf592
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 19:00:40 -0700
arm32 Phase 2: DEPTH=full corpus lanes (toy X + parse E, bare-metal)
cross_one_arm32 in test/toy/run.sh (opt-in via KIT_TOY_CROSS_ARCHS=arm32, not
the default set — no test-toy disruption) + the parse E-lane arm32 branch
(test/parse/run.sh + kit_test_target.h) + the arm32 case in cross_test.sh's
freestanding full lane. Both run bare-metal under qemu-system-arm via exec_bare,
mirroring rv32; real gaps (i64 atomics, TLS, __int128) are left RED on purpose.
Diffstat:
4 files changed, 79 insertions(+), 1 deletion(-)
diff --git a/scripts/cross_test.sh b/scripts/cross_test.sh
@@ -291,6 +291,9 @@ full_token() {
rv32)
_full_lane "$t:toy" env KIT="$KIT" KIT_TOY_CROSS_ARCHS=rv32 KIT_TEST_PATHS=X bash "$ROOT/test/toy/run.sh"
_full_lane "$t:parse" env KIT="$KIT" KIT_TEST_ARCH=rv32 KIT_TEST_PATHS=E bash "$ROOT/test/parse/run.sh" ;;
+ arm32)
+ _full_lane "$t:toy" env KIT="$KIT" KIT_TOY_CROSS_ARCHS=arm32 KIT_TEST_PATHS=X bash "$ROOT/test/toy/run.sh"
+ _full_lane "$t:parse" env KIT="$KIT" KIT_TEST_ARCH=arm32 KIT_TEST_PATHS=E bash "$ROOT/test/parse/run.sh" ;;
*)
printf 'cross: NOTE %s — full-corpus bare-metal not yet wired for %s (smoke only); see doc/plan/PORT.md\n' "$t" "$arch" ;;
esac
diff --git a/test/lib/kit_test_target.h b/test/lib/kit_test_target.h
@@ -85,9 +85,23 @@ static inline int kit_test_target_init(KitTargetSpec* t) {
t->float_abi = KIT_FLOAT_ABI_SINGLE;
return 0;
}
+ if (!strcmp(a, "arm32") || !strcmp(a, "arm")) {
+ if (t->os == KIT_OS_MACOS) {
+ fprintf(stderr, "kit_test_target: arm32 has no macOS target\n");
+ return -1;
+ }
+ /* arm-none-eabi: freestanding Cortex-M, 4-byte pointers, soft-float (float
+ * + double via the AEABI/compiler-rt helpers). */
+ t->arch = KIT_ARCH_ARM_32;
+ t->os = KIT_OS_FREESTANDING;
+ t->ptr_size = 4;
+ t->ptr_align = 4;
+ t->float_abi = KIT_FLOAT_ABI_SOFT;
+ return 0;
+ }
fprintf(stderr,
"kit_test_target: unrecognized KIT_TEST_ARCH=\"%s\" "
- "(expected aa64/x64/rv64/rv32)\n",
+ "(expected aa64/x64/rv64/rv32/arm32)\n",
a);
return -1;
}
diff --git a/test/parse/run.sh b/test/parse/run.sh
@@ -92,6 +92,9 @@ case "$KIT_TEST_ARCH" in
# via exec_bare (rv32), not exec_target's qemu-user path. CLANG_TRIPLE is
# only for clang probes; the kit target comes from KIT_TEST_ARCH.
rv32|riscv32) TEST_ARCH=rv32; CLANG_TRIPLE=riscv32-unknown-elf; EXEC_ARCH=rv32 ;;
+ # arm32 is freestanding too: the E lane runs bare-metal under qemu-system-arm
+ # via exec_bare (arm32), mirroring rv32.
+ arm32|arm) TEST_ARCH=arm32; CLANG_TRIPLE=arm-none-eabi; EXEC_ARCH=arm32 ;;
*) printf 'unknown KIT_TEST_ARCH=%s\n' "$KIT_TEST_ARCH" >&2; exit 2 ;;
esac
export KIT_TEST_ARCH
@@ -101,6 +104,7 @@ case "$TEST_ARCH" in
x64) RT_AR="$ROOT/build/rt/x86_64-linux/libkit_rt.a" ;;
rv64) RT_AR="$ROOT/build/rt/riscv64-linux/libkit_rt.a" ;;
rv32) RT_AR="$ROOT/build/rt/riscv32-elf-hardfloat/libkit_rt.a" ;; # used by exec_bare (rv32)
+ arm32) RT_AR="$ROOT/build/rt/arm-eabi-thumb2/libkit_rt.a" ;; # used by exec_bare (arm32)
esac
RT_LINK_ARGS=()
if [ -f "$RT_AR" ]; then
@@ -192,6 +196,9 @@ if [ "$TEST_ARCH" = "rv32" ]; then
# The parse corpus's entry is test_main() (path C bridges main->test_main).
if exec_bare_setup rv32 "$BUILD_DIR/rv32" test_main; then RV32_BARE_OK=1; else RV32_BARE_OK=0; fi
fi
+if [ "$TEST_ARCH" = "arm32" ]; then
+ if exec_bare_setup arm32 "$BUILD_DIR/arm32" test_main; then ARM32_BARE_OK=1; else ARM32_BARE_OK=0; fi
+fi
# ---- harness binaries ------------------------------------------------------
@@ -379,6 +386,27 @@ kit_lane_E() {
else kit_fail "$KIT_NAME/E" "expected $exp_byte got $rc (qemu-system-riscv32), ${dt}ms"; fi
return
fi
+ if [ "$TEST_ARCH" = "arm32" ]; then
+ # arm32: freestanding bare-metal, same shape as rv32 but under
+ # qemu-system-arm (ARM semihosting exit). Gaps stay RED.
+ local exp_byte rc reason t0 dt run_rc
+ if [ "${ARM32_BARE_OK:-0}" -ne 1 ]; then
+ kit_skip "$KIT_NAME/E" "no arm32 runner (qemu-system-arm)"
+ return
+ fi
+ _parse_emit_obj || return
+ exp_byte=$(( KIT_EXPECTED & 0xff ))
+ t0=$(kit_now_ms)
+ reason="$(exec_bare_run arm32 "$PARSE_OBJ" "$KIT_WORK" "$KIT_WORK/exec.rc")"
+ run_rc=$?
+ dt=$(( $(kit_now_ms) - t0 ))
+ kit_time E "$dt"
+ if [ "$run_rc" -eq 2 ]; then kit_fail "$KIT_NAME/E" "$reason, ${dt}ms"; return; fi
+ rc="$(cat "$KIT_WORK/exec.rc" 2>/dev/null || echo 99)"
+ if [ "$rc" -eq "$exp_byte" ]; then kit_pass "$KIT_NAME/E (${dt}ms)"
+ else kit_fail "$KIT_NAME/E" "expected $exp_byte got $rc (qemu-system-arm), ${dt}ms"; fi
+ return
+ fi
if [ $have_exe_runner -ne 1 ] || [ $have_clang_cross -ne 1 ] || [ $have_start_obj -ne 1 ]; then
kit_skip "$KIT_NAME/E" "no link-exe-runner, $TEST_ARCH clang, or start.o"
return
diff --git a/test/toy/run.sh b/test/toy/run.sh
@@ -408,10 +408,39 @@ cross_one_rv32() {
else kit_fail "$label" "expected rc $exp, got $rc (qemu-system-riscv32)"; fi
}
+# arm32 cross arch: freestanding (arm-none-eabi, Cortex-M), bare-metal under
+# qemu-system-arm via exec_bare — same model as rv32. Real arm32 gaps (i64
+# atomics, TLS needing a thread pointer, __int128) are left RED on purpose, not
+# skipped, so they are not silently hidden.
+cross_one_arm32() {
+ local label="$KIT_BASE/X-O$KIT_OPT:arm32" rc reason exp obj cc_err
+ local skip="${KIT_SRC%.toy}.arm32.skip"
+ if [ -e "$skip" ]; then kit_skip "$label" "$(head -n1 "$skip")"; return; fi
+ if [ "${ARM32_BARE_OK:-0}" -ne 1 ]; then
+ kit_skip "$label" "no arm32 runner (qemu-system-arm)"; return; fi
+ if grep -q 'asmnop' "$KIT_SRC" 2>/dev/null; then
+ kit_skip "$label" "asmnop is target-specific before toy asm selectors"; return; fi
+ obj="$KIT_WORK/$KIT_BASE.O$KIT_OPT.arm32.o"; cc_err="$KIT_WORK/arm32.cc.err"
+ if ! "$KIT" cc "-O$KIT_OPT" -target arm-none-eabi -mcpu=cortex-m3 \
+ -mfloat-abi=soft -ffreestanding \
+ -c "$KIT_SRC" -o "$obj" > "$KIT_WORK/arm32.cc.out" 2> "$cc_err"; then
+ kit_fail "$label" "kit cc -target arm-none-eabi failed"
+ sed 's/^/ | /' "$cc_err"; return
+ fi
+ if [ -s "$cc_err" ]; then
+ kit_fail "$label" "kit cc arm32 wrote stderr"; sed 's/^/ | /' "$cc_err"; return; fi
+ reason="$(exec_bare_run arm32 "$obj" "$KIT_WORK" "$KIT_WORK/arm32.rc")"
+ if [ $? -eq 2 ]; then kit_skip "$label" "$reason"; return; fi
+ rc="$(cat "$KIT_WORK/arm32.rc" 2>/dev/null || echo 99)"; exp=$(( KIT_EXPECTED & 255 ))
+ if [ "$rc" -eq "$exp" ]; then kit_pass "$label"
+ else kit_fail "$label" "expected rc $exp, got $rc (qemu-system-arm)"; fi
+}
+
cross_one() {
local arch="$1"
local triple tag obj exe start_obj cc_err ld_err out err label
if [ "$arch" = "rv32" ]; then cross_one_rv32; return; fi
+ if [ "$arch" = "arm32" ]; then cross_one_arm32; return; fi
triple="$(cross_triple_for "$arch")" || {
kit_skip "$KIT_BASE/X-O$KIT_OPT:$arch" "unknown cross arch"
return
@@ -670,6 +699,10 @@ if [ "$RUN_X" -eq 1 ]; then
*" rv32 "*)
if exec_bare_setup rv32 "$BUILD_DIR/rv32"; then RV32_BARE_OK=1; else RV32_BARE_OK=0; fi ;;
esac
+ case " $TOY_CROSS_ARCHS " in
+ *" arm32 "*)
+ if exec_bare_setup arm32 "$BUILD_DIR/arm32"; then ARM32_BARE_OK=1; else ARM32_BARE_OK=0; fi ;;
+ esac
fi
# ---- drive the corpora -----------------------------------------------------