commit 4cff1a6f663d2b9cc7efb6e2cf4305b38ca840db
parent c346c505e43d82f2b3ff1141d9fcae1a5927e903
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 16 Jul 2026 10:11:07 -0700
runtime: build rv32 ABI variants on demand
Diffstat:
3 files changed, 179 insertions(+), 89 deletions(-)
diff --git a/driver/lib/runtime.c b/driver/lib/runtime.c
@@ -370,122 +370,104 @@ static int rt_try_support_root(DriverEnv* env, const char* root,
return ok ? 0 : 1;
}
-/* Directory-separator test for splitting a program path. On Windows the image
- * path (argv[0] / GetModuleFileNameW) uses '\\', so binary-relative rt
- * discovery must accept it as well as '/'; otherwise kit.exe never finds its
- * <bindir>/support/rt or <bindir>/../rt and falls back to the cwd-only ./rt. */
+/* Directory-separator test for splitting the canonical program path. */
static int rt_is_path_sep(char c) {
-#if defined(_WIN32)
return c == '/' || c == '\\';
-#else
- return c == '/';
-#endif
}
-static int rt_try_argv0_support(DriverEnv* env, const char* argv0,
- DriverRuntimeSupport* out) {
+static char* rt_exe_dir(DriverEnv* env, const char* exe, size_t* out_size) {
const char* slash;
size_t dir_len;
char* dir;
- char* support;
size_t dir_size;
- size_t support_size;
- int rc;
-
- if (!argv0) return 1;
- slash = argv0 + driver_strlen(argv0);
- while (slash > argv0 && !rt_is_path_sep(slash[-1])) --slash;
- if (slash == argv0) return 1;
- dir_len = (size_t)(slash - argv0);
- if (dir_len == 0) return 1;
+ if (!exe) return NULL;
+ slash = exe + driver_strlen(exe);
+ while (slash > exe && !rt_is_path_sep(slash[-1])) --slash;
+ if (slash == exe) return NULL;
+ dir_len = (size_t)(slash - exe);
+ if (dir_len == 0) return NULL;
dir_size = dir_len + 1u;
dir = (char*)driver_alloc(env, dir_size);
- if (!dir) return 1;
- driver_memcpy(dir, argv0, dir_len);
+ if (!dir) return NULL;
+ driver_memcpy(dir, exe, dir_len);
dir[dir_len] = '\0';
-
- support = driver_path_join(env, dir, "support", &support_size);
- driver_free(env, dir, dir_size);
- if (!support) return 1;
- rc = rt_try_support_root(env, support, out);
- driver_free(env, support, support_size);
- return rc;
+ if (out_size) *out_size = dir_size;
+ return dir;
}
-static int rt_try_argv0_checkout_root(DriverEnv* env, const char* argv0,
- DriverRuntimeSupport* out) {
- const char* slash;
- size_t dir_len;
- char* dir;
- char* parent;
- size_t dir_size;
- size_t parent_size;
+static int rt_try_canonical_support(DriverEnv* env, const char* candidate,
+ DriverRuntimeSupport* out) {
+ char* canonical = NULL;
+ size_t canonical_size = 0;
int rc;
-
- if (!argv0) return 1;
- slash = argv0 + driver_strlen(argv0);
- while (slash > argv0 && !rt_is_path_sep(slash[-1])) --slash;
- if (slash == argv0) return 1;
- dir_len = (size_t)(slash - argv0);
- if (dir_len == 0) return 1;
-
- dir_size = dir_len + 1u;
- dir = (char*)driver_alloc(env, dir_size);
- if (!dir) return 1;
- driver_memcpy(dir, argv0, dir_len);
- dir[dir_len] = '\0';
-
- parent = driver_path_join(env, dir, "..", &parent_size);
- driver_free(env, dir, dir_size);
- if (!parent) return 1;
- rc = rt_try_support_root(env, parent, out);
- driver_free(env, parent, parent_size);
+ if (driver_path_canonicalize(env, candidate, &canonical, &canonical_size) !=
+ 0)
+ return 1;
+ rc = rt_try_support_root(env, canonical, out);
+ driver_free(env, canonical, canonical_size);
return rc;
}
int driver_runtime_resolve(DriverEnv* env, const char* explicit_support_dir,
const char* argv0, DriverRuntimeSupport* out) {
- int rc;
- DriverRuntimeSupport zero = {0};
- *out = zero;
-
char* self = NULL;
+ char* exe_dir = NULL;
+ char* candidate = NULL;
+ char* parent = NULL;
size_t self_size = 0;
- const char* tool = argv0;
+ size_t exe_dir_size = 0;
+ size_t candidate_size = 0;
+ size_t parent_size = 0;
+ int rc = 1;
+ DriverRuntimeSupport zero = {0};
+ *out = zero;
+ /* An explicit root is authoritative. Normalize and validate it; never hide
+ * a bad explicit value by falling back to discovery. */
if (explicit_support_dir) {
- rc = rt_try_support_root(env, explicit_support_dir, out);
- } else if (rt_try_support_root(env, ".", out) == 0) {
- rc = 0;
- } else if (rt_try_argv0_checkout_root(env, argv0, out) == 0) {
- rc = 0;
- } else if (rt_try_argv0_support(env, argv0, out) == 0) {
- /* rt found as a <bindir>/support/rt sibling: this is a distribution
- * install, not a build tree. Mark it so the on-demand rt archive lands
- * in the user cache dir rather than polluting the install. */
+ rc = rt_try_canonical_support(env, explicit_support_dir, out);
+ if (rc == 0 && driver_self_exe_path(env, &self, &self_size) == 0)
+ out->tool_path = rt_dup(env, self, &out->tool_path_size);
+ else if (rc == 0 && argv0)
+ out->tool_path = rt_dup(env, argv0, &out->tool_path_size);
+ if (self) driver_free(env, self, self_size);
+ return rc;
+ }
+
+ /* Default discovery has one identity and two ordered layouts:
+ * <exe-dir>/support development build
+ * <exe-dir>/../support packaged distribution
+ * No cwd/source-checkout fallback is permitted. */
+ if (driver_self_exe_path(env, &self, &self_size) != 0 || !self) goto out;
+ exe_dir = rt_exe_dir(env, self, &exe_dir_size);
+ if (!exe_dir) goto out;
+
+ candidate = driver_path_join(env, exe_dir, "support", &candidate_size);
+ if (!candidate) goto out;
+ if (rt_try_canonical_support(env, candidate, out) == 0) {
rc = 0;
+ goto found;
+ }
+ driver_free(env, candidate, candidate_size);
+ candidate = NULL;
+ candidate_size = 0;
+
+ parent = driver_path_join(env, exe_dir, "..", &parent_size);
+ if (!parent) goto out;
+ candidate = driver_path_join(env, parent, "support", &candidate_size);
+ if (!candidate) goto out;
+ if (rt_try_canonical_support(env, candidate, out) == 0) {
out->install_layout = 1;
- } else if (driver_self_exe_path(env, &self, &self_size) == 0 && self) {
- /* argv[0] wasn't a usable path for binary-relative discovery — a bare
- * program name when kit is on PATH, a relative invocation, or a tool
- * symlink whose directory holds no rt. Retry against the real image path
- * (GetModuleFileNameW on Windows, /proc/self/exe on Linux, ...). */
- tool = self;
- if (rt_try_argv0_checkout_root(env, self, out) == 0) {
- rc = 0;
- } else if (rt_try_argv0_support(env, self, out) == 0) {
- rc = 0;
- out->install_layout = 1;
- } else {
- rc = 1;
- }
- } else {
- rc = 1;
+ rc = 0;
}
- /* Own the located image path: it outlives `self`'s free below and the
- * caller's argv, and is stat'd later for rt-archive cache invalidation. */
- if (rc == 0) out->tool_path = rt_dup(env, tool, &out->tool_path_size);
+
+found:
+ if (rc == 0) out->tool_path = rt_dup(env, self, &out->tool_path_size);
+out:
+ if (candidate) driver_free(env, candidate, candidate_size);
+ if (parent) driver_free(env, parent, parent_size);
+ if (exe_dir) driver_free(env, exe_dir, exe_dir_size);
if (self) driver_free(env, self, self_size);
return rc;
}
diff --git a/scripts/kit_cross.sh b/scripts/kit_cross.sh
@@ -150,6 +150,9 @@ make bin -j"$JOBS" \
RELEASE=1 HOST_OPTFLAGS="${KIT_CROSS_OPT:--O0}" HOST_MODE_CPPFLAGS= \
HOST_MODE_LDFLAGS="$GC_LDFLAG" \
HOST_SYSROOT_CFLAGS= HOST_SYSROOT_LDFLAGS= \
+ KIT_UPDATE_INDEX_URL="${KIT_UPDATE_INDEX_URL:-https://example.invalid/kit-development.index}" \
+ KIT_RELEASE_PUBKEYS="${KIT_RELEASE_PUBKEYS:-test/dist/keys/nonrelease.pub}" \
+ KIT_RELEASE_ALLOW_TEST_KEY="${KIT_RELEASE_ALLOW_TEST_KEY:-1}" \
CC="$CC_W" AR="$AR_W"
OUT="$BUILD_DIR/kit"
diff --git a/test/smoke/rv32.sh b/test/smoke/rv32.sh
@@ -211,6 +211,111 @@ kitld_lane() { # <name>
kitld_lane "ilp32f"
kitld_lane "ilp32"
+# Relocated, read-only distribution + empty user-cache regression. Two links
+# per ABI race the same cache key: the driver must build entirely with Kit,
+# publish one complete archive atomically, and keep soft/ilp32f variants
+# separate. The linked images and an intermediate -r object retain the merged
+# psABI e_flags and execute under qemu.
+DIST="$BUILD_DIR/Runtime Cache Distribution"
+CACHE="$BUILD_DIR/empty user cache"
+chmod -R u+w "$DIST" 2>/dev/null || true
+rm -rf "$DIST" "$CACHE"
+mkdir -p "$DIST/bin" "$DIST/support"
+cp "$KIT" "$DIST/bin/kit"
+cp -R "$ROOT/rt" "$DIST/support/rt"
+chmod -R a-w "$DIST"
+
+cache_link_lane() { # <name> <suffix>
+ local name="$1" suffix="$2"
+ local starto="$BUILD_DIR/$name.start.o" appo="$BUILD_DIR/$name-O1.o"
+ local elf="$BUILD_DIR/cache-$name-$suffix.elf"
+ env HOME="$BUILD_DIR/cache-home" XDG_CACHE_HOME="$CACHE" \
+ "$DIST/bin/kit" ld -T "$BUILD_DIR/link.ld" -e _start "$starto" "$appo" \
+ -o "$elf" >"$BUILD_DIR/cache-$name-$suffix.out" \
+ 2>"$BUILD_DIR/cache-$name-$suffix.err"
+}
+
+pids= names=
+for lane in ilp32f ilp32; do
+ for suffix in a b; do
+ cache_link_lane "$lane" "$suffix" &
+ pids="$pids $!"
+ names="$names $lane-$suffix"
+ done
+done
+set -- $pids
+set -- "$@" # keep shellcheck from treating the expansion as an array
+i=1
+for name in $names; do
+ eval pid='${'$i'}'
+ if wait "$pid"; then ok "runtime-cache concurrent $name";
+ else not_ok "runtime-cache concurrent $name" "$BUILD_DIR/cache-$name.err"; fi
+ i=$((i + 1))
+done
+
+for variant in riscv32-elf-hardfloat riscv32-elf; do
+ archive="$CACHE/kit/$variant/libkit_rt.a"
+ if [ -s "$archive" ] && "$DIST/bin/kit" ar t "$archive" \
+ >"$BUILD_DIR/cache-$variant.members" 2>"$BUILD_DIR/cache-$variant.ar.err" \
+ && [ -s "$BUILD_DIR/cache-$variant.members" ]; then
+ ok "runtime-cache $variant atomically published archive"
+ else
+ not_ok "runtime-cache $variant archive" "$BUILD_DIR/cache-$variant.ar.err"
+ fi
+done
+if [ ! -e "$DIST/support/build" ]; then
+ ok "runtime-cache read-only distribution stayed unmodified"
+else
+ not_ok "runtime-cache read-only distribution" "support/build was created"
+fi
+
+READELF="$(command -v llvm-readelf 2>/dev/null || command -v readelf 2>/dev/null || true)"
+check_flags() { # <label> <file> <hex>
+ local label="$1" file="$2" want="$3" flags
+ if [ -z "$READELF" ]; then
+ skip_test "$label" "readelf unavailable"; return
+ fi
+ flags="$($READELF -h "$file" 2>"$file.readelf.err" | \
+ sed -n 's/^[[:space:]]*Flags:[[:space:]]*//p')"
+ if printf '%s\n' "$flags" | grep -q "^$want\(,\|$\)"; then
+ ok "$label ($flags)"
+ else
+ not_ok "$label" "expected flags $want, got: $flags"
+ fi
+}
+
+if "$DIST/bin/kit" ld -r "$BUILD_DIR/ilp32f.start.o" \
+ "$BUILD_DIR/ilp32f-O1.o" -o "$BUILD_DIR/merged-ilp32f.o" \
+ >"$BUILD_DIR/merged-ilp32f.out" 2>"$BUILD_DIR/merged-ilp32f.err"; then
+ check_flags "rv32 ilp32f relocatable e_flags" "$BUILD_DIR/merged-ilp32f.o" "0x3"
+else
+ not_ok "rv32 ilp32f relocatable link" "$BUILD_DIR/merged-ilp32f.err"
+fi
+check_flags "rv32 ilp32f final e_flags" "$BUILD_DIR/cache-ilp32f-a.elf" "0x3"
+check_flags "rv32 ilp32 final e_flags" "$BUILD_DIR/cache-ilp32-a.elf" "0x1"
+
+if env HOME="$BUILD_DIR/cache-home" XDG_CACHE_HOME="$CACHE" \
+ "$DIST/bin/kit" ld -T "$BUILD_DIR/link.ld" -e _start \
+ "$BUILD_DIR/ilp32f.start.o" "$BUILD_DIR/ilp32-O1.o" \
+ -o "$BUILD_DIR/mixed-abi.elf" >"$BUILD_DIR/mixed-abi.out" \
+ 2>"$BUILD_DIR/mixed-abi.err"; then
+ not_ok "rv32 mixed float ABI rejection" "link unexpectedly succeeded"
+elif grep -q 'ilp32-O1.o.*float ABI\|float ABI.*ilp32-O1.o' \
+ "$BUILD_DIR/mixed-abi.err"; then
+ ok "rv32 mixed float ABI rejection names input"
+else
+ not_ok "rv32 mixed float ABI diagnostic" "$BUILD_DIR/mixed-abi.err"
+fi
+
+for lane in ilp32f ilp32; do
+ rc=0
+ timeout 20 "$QEMU" -machine virt -bios none \
+ -kernel "$BUILD_DIR/cache-$lane-a.elf" -nographic -no-reboot \
+ >"$BUILD_DIR/cache-$lane.qemu.out" 2>"$BUILD_DIR/cache-$lane.qemu.err" || rc=$?
+ if [ "$rc" -eq 0 ]; then ok "runtime-cache $lane qemu rc=0";
+ else not_ok "runtime-cache $lane qemu" "expected exit 0, got $rc"; fi
+done
+
# 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"
if "$KIT" cc -target riscv32-none-elf -march=rv32imac_zicsr_zifencei -mabi=ilp32 -O1 \