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:
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"