kit

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

commit 01252f062d3256bf8804f706e0226db5be82efcd
parent bf5b73bcb4b8e9cffd12aa173a5a5759cbe71381
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed,  3 Jun 2026 17:25:51 -0700

rv32: auto-build and auto-link the freestanding runtime like any target

riscv32-none-elf was the only freestanding target without a driver
RuntimeVariant, so kit cc/ld could not auto-build or auto-link
libkit_rt.a for it — the smoke test had to `make rt` and pass the
archive explicitly. The blocker was that rv32 has two float ABIs
sharing one arch+pointer-width (ilp32 soft, ilp32f single) and the
runtime must match, but RuntimeVariant could not distinguish them and
target detection did not recover the float ABI.

- driver/lib/runtime.c: add a float_abi axis (+ isa/abi march strings)
  to RuntimeVariant and two rv32 entries (riscv32-elf soft,
  riscv32-elf-hardfloat single). The matcher keys on float_abi with
  DEFAULT as a wildcard, so every existing variant is unchanged; the
  on-demand build passes each variant's -march/-mabi via topts.isa/abi.
- src/api/object_detect.c: recover the float ABI from RISC-V ELF
  e_flags (offset 36/48; soft/single/double bits) so a detected target
  selects the matching runtime.
- driver/cmd/ld.c: reconcile float_abi across all link inputs,
  preferring a hardware ABI, so a foreign (clang-assembled) startup
  stub lacking the flag never mis-selects the soft runtime.
- test/smoke/rv32.sh: two kit-ld auto-rt lanes (ilp32f + ilp32) that
  link with no runtime archive named.

Verified: smoke-rv32 7/7 under qemu; no regressions (elf 41/0,
link 122/0, abi-classify 367/0, cg-api 0 failures, driver-cc 98/0,
smoke-rv64 3/0); riscv64-none-elf auto-link still works.

Diffstat:
Mdoc/plan/RV32.md | 32++++++++++++++++++++++++--------
Mdriver/cmd/ld.c | 28++++++++++++++++++++--------
Mdriver/lib/runtime.c | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mdriver/lib/runtime.h | 8+++++---
Msrc/api/object_detect.c | 32++++++++++++++++++++++++++++++++
Mtest/smoke/rv32.sh | 37+++++++++++++++++++++++--------------
6 files changed, 171 insertions(+), 42 deletions(-)

diff --git a/doc/plan/RV32.md b/doc/plan/RV32.md @@ -7,7 +7,12 @@ working cross target. WS6 — the flagged "hardest part", 64-bit-value legalizat — is **done and behaviorally verified under `qemu-system-riscv32`** at -O0 and -O1 for both ABIs. The full kit toolchain (`kit cc → kit ld → qemu-system`) builds and runs a correct bare-metal rv32 image with **no special flags** (freestanding -defaults to no-PIE and links no auto-runtime). **RV64 / x64 / aa64 fully +defaults to no-PIE). As of 2026-06-03 the rv32 runtime is **no longer +special-cased**: `kit cc`/`kit ld` auto-build and auto-link `libkit_rt.a` for +`riscv32-none-elf` exactly like every other target — the driver carries two +rv32 runtime variants (`riscv32-elf` soft ilp32, `riscv32-elf-hardfloat` +ilp32f), selected by the float ABI recovered from the objects' ELF e_flags, so +no explicit archive or `-nostdlib` is needed. **RV64 / x64 / aa64 fully non-regressed**: asm goldens byte-identical, isa (rv64 21 + rv32 31)/0, abi-classify 367/0, elf 41/0, link 122/0 + x64 79/0, cg-api 544/0, smoke-rv64 3/0, dwarf/driver/interp green. @@ -48,9 +53,17 @@ real remaining rv32 gaps, enumerated in the checklist below. bug). `kit ld` derives the PIC default from the *target* via `driver_default_pic` (hosted → PIE, freestanding → no-PIE) and scans all inputs for a freestanding object — the host's default never leaks onto a cross target. So `kit ld` for rv32 needs **no `-no-pie`**. - - `kit ld` auto-links a runtime only for targets that have one (`driver_runtime_has_variant`); - a freestanding target supplies its own, so **no `-nostdlib`** is needed. New `-Ttext ADDR` - and `-nostdlib`/`--no-default-libs` flags remain available. + - `kit ld`/`kit cc` auto-link a runtime for any target that has a variant + (`driver_runtime_has_variant`) — **now including `riscv32-none-elf`**. The driver + (`driver/lib/runtime.c`) carries two rv32 runtime variants distinguished by a new + `float_abi` axis on `RuntimeVariant` (`riscv32-elf` soft `ilp32`/`rv32imac`, + `riscv32-elf-hardfloat` `ilp32f`/`rv32imafc`); each is built on demand with its own + `-march`/`-mabi` via `topts.isa`/`topts.abi`. The float ABI is recovered from the RISC-V + ELF `e_flags` in `src/api/object_detect.c` and reconciled across all link inputs in + `driver/cmd/ld.c` (a foreign startup stub that lacks the flag never mis-selects the soft + runtime). So a freestanding rv32 link needs **no explicit `libkit_rt.a` and no + `-nostdlib`**. New `-Ttext ADDR` and `-nostdlib`/`--no-default-libs` flags remain + available for images that supply their own runtime. - `.eh_frame` suppressed for `KIT_OS_FREESTANDING` (`src/arch/mc.c`); hosted byte-identical. - `layout_dyn` emits a clean diagnostic for an ELF32 dynamic/PIE link (was an ELF64 SEGV). - jump-table / label-address slots are width-aware (`R_ABS32` on rv32, `R_ABS64` on 64-bit) @@ -58,8 +71,9 @@ real remaining rv32 gaps, enumerated in the checklist below. - [x] **WS9 tests + CI wiring:** `test/arch/rv32_decode_test.c` (→ `test-isa`, 31 checks), `test/link/rv32_jit_test.c` (→ `test-rv32-jit`, exit-77 host gate), `test/elf/unit/rv32_class32.c` (ELFCLASS32 round-trip, → `test-elf`), - `test/smoke/rv32.sh` (→ `test-smoke-rv32`): 6 lanes — ilp32f + ilp32 × {-O0,-O1} covering i64 - + soft-double + soft-single, a `kit ld` end-to-end lane, a negative control. Wired in + `test/smoke/rv32.sh` (→ `test-smoke-rv32`): 7 lanes — ilp32f + ilp32 × {-O0,-O1} covering i64 + + soft-double + soft-single, two `kit ld` end-to-end lanes that **auto-link the runtime** + (no explicit `libkit_rt.a`), a negative control. Wired in `mk/test.mk`/`mk/test_unit.mk` (`test-rv32-jit`, `test-smoke-rv32`). - [x] **Toy + C cross lanes (rv32 as an arch).** Shared bare-metal runner `test/lib/exec_rv32_bare.sh` (clang startup → `kit cc`/parse-runner → `kit ld` → qemu-system, @@ -117,8 +131,10 @@ clean-panics on an ELF32 dynamic/PIE link and that is the intended behavior. - ABI: `src/abi/abi_rv64.c` + `src/abi/registry.c`. - ELF / kit ld / freestanding policy: `src/obj/elf/{elf.h,emit.c,read.c,link.c,link_dyn.c}` + `reloc_riscv32.c`; `driver/cmd/ld.c` (`-Ttext`/`-nostdlib`/PIC-from-target), `driver/lib/target.c` - (`driver_default_pic`), `driver/lib/runtime.{c,h}` (`driver_runtime_has_variant`), - `src/api/object_detect.c` (EI_OSABI → os), `src/link/{link.c,link_layout.c}`, `src/api/link.c`. + (`driver_default_pic`), `driver/lib/runtime.{c,h}` (`driver_runtime_has_variant`, the two rv32 + `RuntimeVariant` entries + `float_abi`/`isa`/`abi` axis, `rt_build_archive`), + `src/api/object_detect.c` (EI_OSABI → os; RISC-V `e_flags` → `float_abi`), + `src/link/{link.c,link_layout.c}`, `src/api/link.c`. - Runtime/intrinsics: `mk/rt.mk` (ARCH_FLAGS), `src/cg/type.c` (rv32 ≡ rv64 for intrinsics). - Tests: `test/smoke/rv32.sh`, `test/lib/{check_rv32_env.sh,exec_rv32_bare.sh,kit_test_target.h}`, `test/toy/run.sh` (`cross_one_rv32`), `test/parse/run.sh` (`kit_lane_E` rv32 branch), diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c @@ -1211,9 +1211,20 @@ static int ld_run_link(LdOptions* o) { for (oi = 1; oi < o->nobject_files; ++oi) { KitTargetSpec t; if (kit_detect_target(obj_lf[oi].data.data, obj_lf[oi].data.size, &t) == - KIT_OK && - t.os == KIT_OS_FREESTANDING) - detected.os = KIT_OS_FREESTANDING; + KIT_OK) { + if (t.os == KIT_OS_FREESTANDING) detected.os = KIT_OS_FREESTANDING; + /* Reconcile the float ABI across inputs. A foreign startup stub (e.g. + * a clang-assembled .S) may not carry the float-ABI e_flags that the + * kit-compiled objects do, so prefer any object's hardware ABI + * (single/double) over a soft/unset reading. This picks the runtime + * variant (rv32 ilp32 vs ilp32f) the real code was compiled for even + * when obj[0] is the stub. */ + if (detected.float_abi != KIT_FLOAT_ABI_SINGLE && + detected.float_abi != KIT_FLOAT_ABI_DOUBLE && + (t.float_abi == KIT_FLOAT_ABI_SINGLE || + t.float_abi == KIT_FLOAT_ABI_DOUBLE)) + detected.float_abi = t.float_abi; + } } o->target = detected; if (o->pic_explicit) @@ -1223,11 +1234,12 @@ static int ld_run_link(LdOptions* o) { } } - /* Auto-link kit's compiler runtime only for targets that have one. A - * freestanding/static-only target (e.g. riscv32-none-elf) has no auto-runtime - * variant — it is implicitly -nostdlib and supplies its own libkit_rt.a on the - * command line — so skip resolution rather than erroring. -nostdlib forces the - * skip for any target. */ + /* Auto-link kit's compiler runtime for any target that has a variant — + * including the freestanding riscv32-none-elf / riscv64-none-elf targets, + * whose runtime (and, for rv32, the float-ABI it was detected with) is + * resolved like every other target's. A target with no variant is implicitly + * -nostdlib and supplies its own libkit_rt.a on the command line, so skip + * resolution rather than erroring. -nostdlib forces the skip for any target. */ if (!o->relocatable && !o->no_default_libs && driver_runtime_has_variant(o->target)) { if (driver_runtime_resolve(o->env, o->support_dir, o->driver_path, diff --git a/driver/lib/runtime.c b/driver/lib/runtime.c @@ -19,6 +19,18 @@ typedef struct RuntimeVariant { uint8_t ldbl128; const char* const* sources; uint32_t nsources; + /* Float-ABI axis (orthogonal to XLEN). A single arch+ptr_size can have + * several float ABIs (rv32 ilp32 soft vs ilp32f single), each needing its + * own runtime build, so the ABI is part of the variant identity. DEFAULT (0) + * means "wildcard" — it matches any target.float_abi (every non-RISC-V + * target and rv64, whose runtime is float-ABI-agnostic, leave it 0). */ + uint8_t float_abi; /* KitFloatAbi */ + /* -march / -mabi the runtime is compiled with. NULL falls back to the arch + * defaults (correct for every existing variant). rv32 sets them explicitly + * because the default profile (rv32imafc/ilp32f) is wrong for the soft + * ilp32 build. */ + const char* isa; + const char* abi; } RuntimeVariant; static const char* const kRtSrcX64[] = { @@ -95,34 +107,67 @@ static const char* const kRtSrcRv64Elf[] = { "coro/riscv64.c", "coro/coro.c", }; +/* rv32 freestanding runtime: ilp32 integer layout (int32/int32.c for the + * 64-bit-on-32-bit helpers — __muldi3/__ashldi3/...; int/int.c for the i64 + * div/mod) and soft double (fp/fp.c). Shared by the ilp32 (soft) and ilp32f + * (hardware single-float) variants; only the -march/-mabi the sources are + * compiled with differ, captured per-variant in the table below. */ +static const char* const kRtSrcRv32Elf[] = { + "int/int.c", "fp/fp.c", + "mem/mem.c", "atomic/atomic_freestanding.c", + "kit/ifunc_init.c", "int32/int32.c", + "coro/riscv32.c", "coro/coro.c", +}; + static const RuntimeVariant kRtVariants[] = { {"x86_64-linux", KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcX64, - (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0]))}, + (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"x86_64-apple-darwin", KIT_ARCH_X86_64, KIT_OS_MACOS, KIT_OBJ_MACHO, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcX64, - (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0]))}, + (uint32_t)(sizeof(kRtSrcX64) / sizeof(kRtSrcX64[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"x86_64-pc-windows", KIT_ARCH_X86_64, KIT_OS_WINDOWS, KIT_OBJ_COFF, 8, 8, "lib/include/llp64_le", 1, 0, kRtSrcX64Windows, - (uint32_t)(sizeof(kRtSrcX64Windows) / sizeof(kRtSrcX64Windows[0]))}, + (uint32_t)(sizeof(kRtSrcX64Windows) / sizeof(kRtSrcX64Windows[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"aarch64-linux", KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF, 8, 8, "lib/include/lp64_le", 1, 1, kRtSrcAarch64Linux, - (uint32_t)(sizeof(kRtSrcAarch64Linux) / sizeof(kRtSrcAarch64Linux[0]))}, + (uint32_t)(sizeof(kRtSrcAarch64Linux) / sizeof(kRtSrcAarch64Linux[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"aarch64-apple-darwin", KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcAarch64Darwin, - (uint32_t)(sizeof(kRtSrcAarch64Darwin) / sizeof(kRtSrcAarch64Darwin[0]))}, + (uint32_t)(sizeof(kRtSrcAarch64Darwin) / sizeof(kRtSrcAarch64Darwin[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"aarch64-windows", KIT_ARCH_ARM_64, KIT_OS_WINDOWS, KIT_OBJ_COFF, 8, 8, "lib/include/llp64_le", 1, 0, kRtSrcAarch64Windows, (uint32_t)(sizeof(kRtSrcAarch64Windows) / - sizeof(kRtSrcAarch64Windows[0]))}, + sizeof(kRtSrcAarch64Windows[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, /* rv64 long double = double per the locked decision (matches RV64 * musl/glibc default and avoids the binary128 soft-float tail). */ {"riscv64-linux", KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcRv64Linux, - (uint32_t)(sizeof(kRtSrcRv64Linux) / sizeof(kRtSrcRv64Linux[0]))}, + (uint32_t)(sizeof(kRtSrcRv64Linux) / sizeof(kRtSrcRv64Linux[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, {"riscv64-elf", KIT_ARCH_RV64, KIT_OS_FREESTANDING, KIT_OBJ_ELF, 8, 8, "lib/include/lp64_le", 1, 0, kRtSrcRv64Elf, - (uint32_t)(sizeof(kRtSrcRv64Elf) / sizeof(kRtSrcRv64Elf[0]))}, + (uint32_t)(sizeof(kRtSrcRv64Elf) / sizeof(kRtSrcRv64Elf[0])), + KIT_FLOAT_ABI_DEFAULT, NULL, NULL}, + /* rv32 freestanding. ilp32 (pure soft float) and ilp32f (hardware single + * float, soft double) share one arch+ptr_size, so float_abi is part of the + * variant identity and each is built with its own -march/-mabi. Keys match + * the `make rt` variant dirs (riscv32-elf / riscv32-elf-hardfloat) so a + * checkout shares build/rt with the Makefile. */ + {"riscv32-elf", KIT_ARCH_RV32, KIT_OS_FREESTANDING, KIT_OBJ_ELF, 4, 4, + "lib/include/ilp32_le", 0, 0, kRtSrcRv32Elf, + (uint32_t)(sizeof(kRtSrcRv32Elf) / sizeof(kRtSrcRv32Elf[0])), + KIT_FLOAT_ABI_SOFT, "rv32imac", "ilp32"}, + {"riscv32-elf-hardfloat", KIT_ARCH_RV32, KIT_OS_FREESTANDING, KIT_OBJ_ELF, 4, + 4, "lib/include/ilp32_le", 0, 0, kRtSrcRv32Elf, + (uint32_t)(sizeof(kRtSrcRv32Elf) / sizeof(kRtSrcRv32Elf[0])), + KIT_FLOAT_ABI_SINGLE, "rv32imafc", "ilp32f"}, }; static char* rt_dup(DriverEnv* env, const char* s, size_t* out_size) { @@ -323,8 +368,14 @@ static const RuntimeVariant* rt_variant_for_target(KitTargetSpec target) { for (i = 0; i < (uint32_t)(sizeof(kRtVariants) / sizeof(kRtVariants[0])); ++i) { const RuntimeVariant* v = &kRtVariants[i]; + /* A variant with float_abi DEFAULT is float-ABI-agnostic and matches any + * target (every non-RISC-V variant and rv64). A variant that pins a float + * ABI (the two rv32 entries) matches only that ABI, so ilp32 vs ilp32f + * targets select distinct runtimes. */ if (target.arch == v->arch && target.os == v->os && target.obj == v->obj && - target.ptr_size == v->ptr_size && target.ptr_align == v->ptr_align) + target.ptr_size == v->ptr_size && target.ptr_align == v->ptr_align && + (v->float_abi == KIT_FLOAT_ABI_DEFAULT || + v->float_abi == target.float_abi)) return v; } return NULL; @@ -619,11 +670,18 @@ static int rt_build_archive(DriverEnv* env, const DriverRuntimeSupport* support, target.big_endian = 0; target.pic = KIT_PIC_NONE; target.code_model = KIT_CM_DEFAULT; + target.float_abi = KIT_FLOAT_ABI_DEFAULT; /* resolved from abi below */ { KitTargetOptions topts; memset(&topts, 0, sizeof topts); topts.spec = target; + /* Build the runtime with the variant's -march/-mabi. NULL leaves the arch + * defaults (every non-rv32 variant); rv32 pins them so the soft ilp32 and + * hard ilp32f archives are each compiled correctly, and kit_target_new + * resolves spec.float_abi from the ABI string to match the call sites. */ + if (variant->isa) topts.isa = kit_slice_cstr(variant->isa); + if (variant->abi) topts.abi = kit_slice_cstr(variant->abi); if (kit_target_new(&ctx, &topts, &resolved_target) != KIT_OK || driver_compiler_new(resolved_target, &ctx, &compiler) != KIT_OK) { driver_errf(tool, "failed to initialize compiler for runtime"); diff --git a/driver/lib/runtime.h b/driver/lib/runtime.h @@ -49,9 +49,11 @@ int driver_runtime_prepare_archive(DriverEnv* env, const char* tool, DriverRuntimeArchive* out); void driver_runtime_archive_fini(DriverEnv* env, DriverRuntimeArchive* a); -/* True if kit knows how to build/provide a compiler runtime for `target`. A - * freestanding/static-only target (e.g. riscv32-none-elf) has none, so a linker - * should not try to auto-link one — the freestanding image supplies its own. */ +/* True if kit knows how to build/provide a compiler runtime for `target`, + * including the freestanding riscv32/riscv64-none-elf targets. For RISC-V the + * match also keys on target.float_abi, so rv32 ilp32 (soft) and ilp32f (single) + * select distinct runtimes. A target with no variant should not be auto-linked + * — that image supplies its own runtime. */ int driver_runtime_has_variant(KitTargetSpec target); #endif diff --git a/src/api/object_detect.c b/src/api/object_detect.c @@ -84,6 +84,7 @@ static void detect_target_defaults(KitTargetSpec* t) { t->big_endian = 0; t->pic = KIT_PIC_NONE; t->code_model = KIT_CM_DEFAULT; + t->float_abi = KIT_FLOAT_ABI_DEFAULT; } static void detect_set_ptr(KitTargetSpec* t, KitArchKind arch) { @@ -157,6 +158,37 @@ static KitStatus detect_elf(const u8* d, size_t len, KitTargetSpec* out) { out->os = KIT_OS_LINUX; else out->os = KIT_OS_FREESTANDING; + + /* Recover the RISC-V float ABI from e_flags so a detected target selects the + * matching runtime variant (rv32 ilp32 soft vs ilp32f single share an arch + + * pointer width and differ only here). e_flags is a 4-byte LE field after the + * three native-width addr fields: offset 36 on ELFCLASS32, 48 on ELFCLASS64. + * The two FP-ABI bits map SOFT(0)/SINGLE(2)/DOUBLE(4); leave DEFAULT (the + * wildcard) when the header is truncated. */ + if (e_machine == 0xF3) { + size_t flags_off = (ei_class == 1) ? 36u : 48u; + if (len >= flags_off + 4u) { + u32 e_flags; + if (ei_data == 1) + e_flags = (u32)d[flags_off] | ((u32)d[flags_off + 1] << 8) | + ((u32)d[flags_off + 2] << 16) | ((u32)d[flags_off + 3] << 24); + else + e_flags = (u32)d[flags_off + 3] | ((u32)d[flags_off + 2] << 8) | + ((u32)d[flags_off + 1] << 16) | ((u32)d[flags_off] << 24); + switch (e_flags & 0x6u) { + case 0x2u: + out->float_abi = KIT_FLOAT_ABI_SINGLE; + break; + case 0x4u: + case 0x6u: /* QUAD — no kit ABI; treat as double-float for selection */ + out->float_abi = KIT_FLOAT_ABI_DOUBLE; + break; + default: + out->float_abi = KIT_FLOAT_ABI_SOFT; + break; + } + } + } return KIT_OK; } diff --git a/test/smoke/rv32.sh b/test/smoke/rv32.sh @@ -184,24 +184,33 @@ run_lane "ilp32f" "rv32imafc_zicsr_zifencei" "ilp32f" "$BUILD_DIR/start_hf.S" # ilp32: pure soft float (no FPU) + i64. run_lane "ilp32" "rv32imac_zicsr_zifencei" "ilp32" "$BUILD_DIR/start_soft.S" "$RT_SF" "" -# Full kit toolchain: re-link the ilp32f objects with `kit ld` (not ld.lld) to -# prove kit produces a correct bootable rv32 static image end-to-end. A -# freestanding rv32 target defaults to non-PIE and auto-links no runtime, so no +# Full kit toolchain, runtime auto-linked: re-link the per-ABI objects with +# `kit ld` (not ld.lld) and WITHOUT naming a runtime archive. rv32 is no longer +# special-cased — kit ld resolves the float ABI from the objects' ELF e_flags +# (ilp32f -> single, ilp32 -> soft), builds the matching libkit_rt.a on demand +# (build/rt/riscv32-elf-hardfloat / riscv32-elf), and links it in, exactly like +# any other target. A freestanding rv32 target defaults to non-PIE, so no # -no-pie / -nostdlib is needed; -T places .text.start at the qemu `virt` RAM -# base (0x80000000) and the runtime archive is supplied explicitly. -if [ -f "$BUILD_DIR/ilp32f-O1.o" ] && [ -f "$RT_HF" ]; then - if "$KIT" ld -T "$BUILD_DIR/link.ld" -e _start \ - "$BUILD_DIR/ilp32f.start.o" "$BUILD_DIR/ilp32f-O1.o" "$RT_HF" \ - -o "$BUILD_DIR/kitld.elf" 2>"$BUILD_DIR/kitld.ld.err"; then - rc=0 - timeout 20 "$QEMU" -machine virt -bios none -kernel "$BUILD_DIR/kitld.elf" \ +# base (0x80000000). The i64-mul/div/shift and soft-double helpers come from the +# auto-linked runtime, so a clean qemu exit also proves runtime resolution. +kitld_lane() { # <name> + local name="$1" + local starto="$BUILD_DIR/$name.start.o" appo="$BUILD_DIR/$name-O1.o" + local elf="$BUILD_DIR/kitld-$name.elf" + [ -f "$starto" ] && [ -f "$appo" ] || return + if "$KIT" ld -T "$BUILD_DIR/link.ld" -e _start "$starto" "$appo" \ + -o "$elf" 2>"$BUILD_DIR/kitld-$name.ld.err"; then + local rc=0 + timeout 20 "$QEMU" -machine virt -bios none -kernel "$elf" \ -nographic -no-reboot >/dev/null 2>&1 || rc=$? - if [ "$rc" -eq 0 ]; then ok "kit-ld ilp32f -O1 (qemu rc=0)"; - else not_ok "kit-ld ilp32f -O1" "expected exit 0, got $rc"; fi + if [ "$rc" -eq 0 ]; then ok "kit-ld $name -O1 auto-rt (qemu rc=0)"; + else not_ok "kit-ld $name -O1 auto-rt" "expected exit 0, got $rc"; fi else - not_ok "kit-ld ilp32f -O1 (link)" "$BUILD_DIR/kitld.ld.err" + not_ok "kit-ld $name -O1 auto-rt (link)" "$BUILD_DIR/kitld-$name.ld.err" fi -fi +} +kitld_lane "ilp32f" +kitld_lane "ilp32" # Negative control: a deliberately wrong result must produce a nonzero exit. sed 's/if (acc != 45) return 1;/if (acc != 45) return 1; return 99;/' "$BUILD_DIR/app.c" > "$BUILD_DIR/bad.c"