kit

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

commit c7e3f5fb08e677e1bab9fb3734f4c2b42a396eac
parent af4c29e112fc606ecc1cff13311ce1f141863928
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 10 Jun 2026 14:07:37 -0700

feat(port): rv64 hybrid self-host (clang seed + emulated stage2/3)

rv64-linux has no native host on a macOS/arm64 dev box, and an in-container
clang stage1 would itself run under qemu emulation (slow). Build the stage1
seed on the host with clang at native speed (kit_cross.sh linux-musl-rv64
--cc=clang) and feed it to the 3-stage bootstrap; only stages 2/3 run emulated.

- mk/bootstrap.mk: BOOTSTRAP_SEED — a pre-built stage1. When set, stage1 is
  copied from the seed and the host $(BIN) / outer rt are not prerequisites, so
  no native compiler is needed in the container. Stages 2/3 stay byte-identical
  (seed and stage2 are the same kit source = functionally identical compilers).
- scripts/linux_bootstrap.sh: KIT_LINUX_BOOT_SEED drives the seeded flow (no
  clang/lld/binutils in the container — only make + libc dev + perl). Emulated
  builds write objects to container-local storage instead of the :Z mount
  (virtiofs flakes a new-file create under sustained emulated write load) and
  copy the stage kits back to the host build dir.
- scripts/selfhost.sh: rv64 linux targets auto-build the clang seed then
  bootstrap stages 2/3 emulated.

Validated: linux-musl-rv64 debug bootstrap is byte-identical (stage2 == stage3,
sha 837ade11…fef20b9).

Doc: doc/plan/PORT.md self-host shapes notes the rv64 hybrid.

Diffstat:
Mdoc/plan/PORT.md | 8++++++++
Mmk/bootstrap.mk | 20++++++++++++++++++--
Mscripts/linux_bootstrap.sh | 72+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mscripts/selfhost.sh | 12++++++++++--
4 files changed, 97 insertions(+), 15 deletions(-)

diff --git a/doc/plan/PORT.md b/doc/plan/PORT.md @@ -215,6 +215,14 @@ Exit-code oracle per arch (so callers compare `rc == expected` uniformly): - **macos** → native 3-stage `bootstrap` here, then the corpus through stage3. - **linux** → native 3-stage in a podman container (emulated for non-host arch), then the corpus. Generalizes `scripts/linux_bootstrap.sh` to (arch, libc). + **rv64 uses a hybrid seed**: an in-container clang stage1 would itself run + emulated (slow), so the stage1 seed is cross-built on the host with clang + (`kit_cross.sh linux-musl-rv64 --cc=clang`, native speed) and fed to + `mk/bootstrap.mk` via `BOOTSTRAP_SEED`; only stages 2/3 run emulated. They are + still byte-identical because the seed and stage2 are the same kit source, hence + functionally identical compilers. Emulated builds write objects to + container-local storage (the `:Z` virtiofs mount flakes new-file creates under + sustained emulated write load) and copy the stage kits back. - **freebsd** → native 3-stage in the VM, then the corpus (`scripts/freebsd_bootstrap.sh`). - **windows** → **cross**-build `kit.exe` on the host (the VM has no seed diff --git a/mk/bootstrap.mk b/mk/bootstrap.mk @@ -43,6 +43,22 @@ BOOTSTRAP_RT_LIBS = $(addprefix $(RT_BUILD_DIR)/,$(addsuffix /libkit_rt.a,$(RT_D BOOTSTRAP_MAKEFILES = Makefile mk/env.mk mk/config.mk mk/flags.mk \ mk/lib_srcs.mk mk/driver_srcs.mk mk/rt.mk +# BOOTSTRAP_SEED: a pre-built stage1 kit. Empty (default) = the normal native +# flow where stage1 is the host $(BIN). When set (a path), stage1 is copied from +# the seed and the host $(BIN) / outer rt are NOT prerequisites — this is how a +# foreign-arch kit cross-compiled on the host (e.g. riscv64 via clang) bootstraps +# itself: stages 2/3 (and their rt) run the seed under emulation in a container, +# but the expensive stage1 was built natively on the host. Stages 2/3 are still +# byte-identical because the seed and stage2 are the same kit source, hence +# functionally identical compilers. Driven by scripts/linux_bootstrap.sh. +BOOTSTRAP_SEED ?= +BOOTSTRAP_STAGE1_SRC = $(BIN) +BOOTSTRAP_DEPS = $(BIN) $(BOOTSTRAP_RT_LIBS) $(BOOTSTRAP_MAKEFILES) +ifneq ($(BOOTSTRAP_SEED),) +BOOTSTRAP_STAGE1_SRC = $(BOOTSTRAP_SEED) +BOOTSTRAP_DEPS = $(BOOTSTRAP_SEED) $(BOOTSTRAP_MAKEFILES) +endif + ifeq ($(RELEASE),1) $(BOOTSTRAP_STAMP): HOST_OPTFLAGS = -O1 $(BOOTSTRAP_STAMP): BOOTSTRAP_HOST_MODE_CFLAGS = $(HOST_MODE_CFLAGS) @@ -65,10 +81,10 @@ _do-bootstrap: $(BOOTSTRAP_STAMP) # and scripts/windows_cross.sh per target. See doc/plan/PORT.md. `make bootstrap` # here remains the native (host-arch) 3-stage byte-identical self-reproduction. -$(BOOTSTRAP_STAMP): $(BIN) $(BOOTSTRAP_RT_LIBS) $(BOOTSTRAP_MAKEFILES) +$(BOOTSTRAP_STAMP): $(BOOTSTRAP_DEPS) rm -rf $(BOOTSTRAP_DIR) @mkdir -p $(BOOTSTRAP_STAGE1_DIR) - cp $(BIN) $(BOOTSTRAP_STAGE1_BIN) + cp $(BOOTSTRAP_STAGE1_SRC) $(BOOTSTRAP_STAGE1_BIN) @for tool in $(BOOTSTRAP_TOOLS); do ln -sf kit "$(BOOTSTRAP_STAGE1_DIR)/$$tool"; done $(MAKE) lib bin \ BUILD_DIR='$(abspath $(BOOTSTRAP_STAGE2_DIR))' \ diff --git a/scripts/linux_bootstrap.sh b/scripts/linux_bootstrap.sh @@ -22,6 +22,13 @@ # KIT_LINUX_BOOT_IMAGE container image (defaults per libc+arch, below) # KIT_LINUX_BOOT_PLATFORM podman --platform (default derived from arch) # KIT_LINUX_BOOT_TOY=1 also run the Toy corpus through the stage3 compiler +# KIT_LINUX_BOOT_SEED repo-relative path to a pre-built stage1 kit (the +# "hybrid" flow). When set, stage1 is this seed instead +# of an in-container clang build — so the seed can be +# cross-compiled on the host at native speed and only +# stages 2/3 run emulated. Intended for rv64, where an +# in-container clang stage1 would itself run emulated. +# Build one with: scripts/kit_cross.sh <target> --cc=clang # # The stage tree lands under build/linux-boot/<libc>/<arch>/ on the host # (gitignored), so artifacts survive the run for inspection / per-object diffing. @@ -65,8 +72,13 @@ case "$CHAIN" in esac TOY="${KIT_LINUX_BOOT_TOY:-0}" +SEED="${KIT_LINUX_BOOT_SEED:-}" BUILD_DIR="build/linux-boot/$LIBC/$ARCH" +if [ -n "$SEED" ]; then + [ -x "$ROOT/$SEED" ] || { echo "linux_bootstrap: seed not found/executable: $ROOT/$SEED (build with scripts/kit_cross.sh)" >&2; exit 2; } +fi + # In-container provisioning + build. The package sets give: a seed clang/lld, # make, the libc dev headers, binutils (ar/ranlib used by the host stage1 # link), the ASan/UBSan runtime + unwinder for the -O0 stage1, and perl (the @@ -74,30 +86,68 @@ BUILD_DIR="build/linux-boot/$LIBC/$ARCH" # arena-allocated and never frees -- LeakSanitizer (absent on the macOS # reference host, which is why this only bites on Linux) would otherwise abort # every stage1 cc invocation. -case "$LIBC" in - musl) - PROVISION='apk add --no-cache clang lld make musl-dev binutils compiler-rt libgcc perl-utils bash >/dev/null' - ;; - glibc) - PROVISION='export DEBIAN_FRONTEND=noninteractive; apt-get update -qq >/dev/null && apt-get install -y -qq clang lld make libc6-dev binutils perl >/dev/null' - ;; -esac +# When seeded, stage1 is the host-built kit and stages 2/3 are compiled by kit +# itself (its own cc/ar/ld), so the container needs no clang/lld/binutils — only +# make, the libc dev headers/crt, and perl (shasum). Otherwise install the full +# seed-compiler set for the in-container clang stage1. +if [ -n "$SEED" ]; then + case "$LIBC" in + musl) PROVISION='apk add --no-cache make musl-dev perl-utils bash >/dev/null' ;; + glibc) PROVISION='export DEBIAN_FRONTEND=noninteractive; apt-get update -qq >/dev/null && apt-get install -y -qq make libc6-dev perl >/dev/null' ;; + esac +else + case "$LIBC" in + musl) PROVISION='apk add --no-cache clang lld make musl-dev binutils compiler-rt libgcc perl-utils bash >/dev/null' ;; + glibc) PROVISION='export DEBIAN_FRONTEND=noninteractive; apt-get update -qq >/dev/null && apt-get install -y -qq clang lld make libc6-dev binutils perl >/dev/null' ;; + esac +fi + +# Hybrid (seeded) vs native stage1: a seed bypasses clang entirely and feeds +# mk/bootstrap.mk a pre-built stage1. The seeded flow is emulated (a foreign +# arch under binfmt), so it builds into container-local storage rather than the +# :Z mount: virtiofs/9p occasionally fails a new-file create under sustained +# emulated write load (hundreds of objects rapid-fire), which would derail an +# otherwise-good build. Source reads from /work stay reliable; only the stage +# kit binaries are copied back to the host BUILD_DIR for inspection. +if [ -n "$SEED" ]; then + EFF_BUILD_DIR="/root/boot" + BUILD_CMD="make $TARGET BUILD_DIR='$EFF_BUILD_DIR' BOOTSTRAP_SEED='/work/$SEED'" + SEED_BANNER="echo \"stage1 seed: /work/$SEED\"; /work/$SEED --help >/dev/null 2>&1 && echo 'seed runs' || echo 'seed FAILED to run'" + SEEDED=1 +else + EFF_BUILD_DIR="$BUILD_DIR" + BUILD_CMD="make $TARGET BUILD_DIR='$EFF_BUILD_DIR' CC=clang AR=ar" + SEED_BANNER='clang --version | head -1' + SEEDED=0 +fi read -r -d '' REMOTE <<EOF || true set -eu $PROVISION cd /work +EFF_BUILD_DIR='$EFF_BUILD_DIR' echo "=== linux_bootstrap: \$(uname -m) / $LIBC / $TARGET ===" -clang --version | head -1 -make $TARGET BUILD_DIR='$BUILD_DIR' CC=clang AR=ar +$SEED_BANNER +$BUILD_CMD if [ "$TOY" = "1" ]; then for m in debug release; do - s3="$BUILD_DIR/\$m/bootstrap/stage3/kit" + s3="\$EFF_BUILD_DIR/\$m/bootstrap/stage3/kit" [ -x "\$s3" ] || continue echo "=== Toy corpus through \$m stage3 ===" KIT="\$(pwd)/\$s3" test/toy/run.sh || true done fi +if [ "$SEEDED" = 1 ]; then + for m in debug release; do + for st in stage2 stage3; do + k="\$EFF_BUILD_DIR/\$m/bootstrap/\$st/kit" + [ -x "\$k" ] || continue + mkdir -p "/work/$BUILD_DIR/\$m/bootstrap/\$st" + cp "\$k" "/work/$BUILD_DIR/\$m/bootstrap/\$st/kit" + done + done + echo "=== linux_bootstrap: copied stage kits to $BUILD_DIR ===" +fi echo "=== linux_bootstrap: DONE $TARGET ===" EOF diff --git a/scripts/selfhost.sh b/scripts/selfhost.sh @@ -103,9 +103,10 @@ macos_stage3_hello() { # ---- dispatch -------------------------------------------------------------- selfhost_token() { - local t="$1" os arch libc toy + local t="$1" os arch libc toy seed_env os="$(tok_os "$t")"; arch="$(tok_arch "$t")"; libc="$(tok_libc "$t")" toy=0; [ "$DEPTH" = full ] && toy=1 + seed_env="" case "$os" in macos) if [ "$DEPTH" = full ]; then @@ -115,7 +116,14 @@ selfhost_token() { run_step "$t:run" macos_stage3_hello fi ;; linux) - run_step "$t:bootstrap" env KIT_LINUX_BOOT_ARCH="$arch" KIT_LINUX_BOOT_TOY="$toy" \ + # rv64 has no native host here, and an in-container clang stage1 would + # itself run emulated. Build the stage1 seed via clang cross on the host + # (native speed), then bootstrap stages 2/3 in the emulated container. + if [ "$arch" = rv64 ]; then + run_step "$t:seed" bash "$ROOT/scripts/kit_cross.sh" "linux-$libc-rv64" --cc=clang || return 1 + seed_env="KIT_LINUX_BOOT_SEED=build/kit-cross/clang/linux-$libc-rv64/kit" + fi + run_step "$t:bootstrap" env KIT_LINUX_BOOT_ARCH="$arch" KIT_LINUX_BOOT_TOY="$toy" $seed_env \ bash "$ROOT/scripts/linux_bootstrap.sh" "$libc" both ;; freebsd) run_step "$t:bootstrap" env KIT_FREEBSD_BOOT_TOY="$toy" \