commit c26c833c509d82ff3c9c7023792eab3cb1745e23
parent 3a02008437033476d122af2f3ec92dcd6c61235e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 6 May 2026 15:20:20 -0700
reorg PR2: generate run.scm at prep, finish scripts/ → bootprep/ move
run.scm files are now host-generated once at prep time and read at
runtime, instead of regenerated inside each bootN.sh. Removes the
runtime/prep boundary blur that motivated the reorg.
- scripts/{boot4,boot5,boot6}-gen-runscm.sh -> bootprep/
- scripts/{boot3-run.scm,boot-hello.c} -> bootprep/assets/
- scripts/ removed entirely.
- New bootprep/boot5-enumerate.sh extracts the musl source-enumeration
block from boot5.sh (base/arch/replaced/keep_base, crt-mode). Runs
at prep against the post-prep-musl tree; writes
build/<arch>/src/run/boot5/{build-srcs.txt,crt-mode}.
- bootprep/{boot4,boot5,boot6}-gen-runscm.sh take a single <arch> arg
and write build/<arch>/src/run/bootN.scm. boot5-gen reads the
enumerator's outputs from the same run/ tree.
- prep-src.sh now copies boot3-run.scm + invokes boot4/6-gen-runscm.
prep-musl.sh invokes boot5-enumerate + boot5-gen-runscm after
applying the skip filter.
- boot/boot{3,4,5,6}.sh: drop runscm_gen (and boot5's enumeration
block); each just calls runscm_runscm "$SRC/run/bootN.scm". boot5
keeps the per-source obj-dir mkdir (writes into the per-driver
build dir, not src/) but reads the source list from the prep-time
build-srcs.txt.
- boot/lib-runscm.sh: runscm_gen deleted; the special-case
self-copy in runscm_run goes with it.
Verified: ./boot/boot.sh aarch64 runs clean to boot6/Image (51s).
Diffstat:
18 files changed, 559 insertions(+), 516 deletions(-)
diff --git a/Makefile b/Makefile
@@ -97,6 +97,8 @@ clean:
PREP_SRC_COMMON_SRCS := \
bootprep/prep-src.sh boot/lib-arch.sh \
bootprep/stage1-flatten.sh bootprep/libc-flatten.sh \
+ bootprep/boot4-gen-runscm.sh bootprep/boot6-gen-runscm.sh \
+ bootprep/assets/boot3-run.scm bootprep/assets/boot-hello.c \
M1pp/M1pp.P1 hex2pp/hex2pp.P1 \
P1/P1.M1pp P1/P1pp.P1pp \
P1/entry-libc.P1pp P1/entry-plain.P1pp P1/elf-end.P1pp \
@@ -104,7 +106,6 @@ PREP_SRC_COMMON_SRCS := \
scheme1/scheme1.P1pp scheme1/prelude.scm \
cc/cc.scm cc/main.scm \
tcc/cc/mem.c \
- scripts/boot-hello.c \
seed-kernel/kernel.c \
vendor/musl/1.2.5.tar.gz \
vendor/musl/deletes.txt
@@ -144,6 +145,7 @@ build/$1/src/.stamp: $$(PREP_SRC_COMMON_SRCS) $$(call prep_src_arch_srcs,$1)
# its own boot4 build outside this make graph — keep make's deps simple.
build/$1/src/musl/.stamp: build/$1/src/.stamp \
bootprep/prep-musl.sh boot/lib-arch.sh \
+ bootprep/boot5-enumerate.sh bootprep/boot5-gen-runscm.sh \
$$(wildcard vendor/musl/skip-$1.txt)
bootprep/prep-musl.sh $1
@mkdir -p $$(@D) && touch $$@
@@ -196,7 +198,6 @@ build/$1/$2/boot2/catm build/$1/$2/boot2/scheme1: build/$1/$2/boot2/.stamp ;
build/$1/$2/boot3/.stamp: \
build/$1/$2/boot2/.stamp build/$1/$2/boot1/.stamp \
boot/boot3.sh boot/lib-arch.sh boot/lib-runscm.sh \
- scripts/boot3-run.scm \
$$(call seed_kernel_dep,$1,$2)
DRIVER=$2 boot/boot3.sh $1
@touch $$@
@@ -207,8 +208,7 @@ build/$1/$2/boot3/tcc.flat.P1pp: build/$1/$2/boot3/.stamp ;
# boot4: tcc0 -> tcc1 -> tcc2 -> tcc3 self-host chain (+ libc.a, libtcc1.a, hello)
build/$1/$2/boot4/.stamp: \
build/$1/$2/boot3/.stamp build/$1/$2/boot2/.stamp \
- boot/boot4.sh scripts/boot4-gen-runscm.sh \
- boot/lib-arch.sh boot/lib-runscm.sh \
+ boot/boot4.sh boot/lib-arch.sh boot/lib-runscm.sh \
$$(call seed_kernel_dep,$1,$2)
DRIVER=$2 boot/boot4.sh $1
@touch $$@
@@ -222,8 +222,7 @@ build/$1/$2/boot4/libtcc1.a: build/$1/$2/boot4/.stamp ;
build/$1/$2/boot5/.stamp: \
build/$1/$2/boot4/.stamp build/$1/$2/boot2/.stamp \
build/$1/src/musl/.stamp \
- boot/boot5.sh scripts/boot5-gen-runscm.sh \
- boot/lib-arch.sh boot/lib-runscm.sh \
+ boot/boot5.sh boot/lib-arch.sh boot/lib-runscm.sh \
$$(call seed_kernel_dep,$1,$2)
DRIVER=$2 boot/boot5.sh $1
@touch $$@
@@ -235,8 +234,7 @@ build/$1/$2/boot5/hello: build/$1/$2/boot5/.stamp ;
# boot6: seed-kernel ELF/Image, built with boot4's tcc3
build/$1/$2/boot6/.stamp: \
build/$1/$2/boot4/.stamp build/$1/$2/boot2/.stamp \
- boot/boot6.sh scripts/boot6-gen-runscm.sh \
- boot/lib-arch.sh boot/lib-runscm.sh \
+ boot/boot6.sh boot/lib-arch.sh boot/lib-runscm.sh \
$$(call seed_kernel_dep,$1,$2)
DRIVER=$2 boot/boot6.sh $1
@touch $$@
diff --git a/boot/boot3.sh b/boot/boot3.sh
@@ -25,8 +25,9 @@
## build/$ARCH/$DRIVER/boot2/{catm, scheme1}
##
## ─── Tools ────────────────────────────────────────────────────────────
-## scheme1 evaluates scripts/boot3-run.scm against the flat staging
-## root. Same run.scm drives both DRIVER=podman (cwd=/work) and
+## scheme1 evaluates build/$ARCH/src/run/boot3.scm against the flat
+## staging root (copied from bootprep/assets/boot3-run.scm by
+## prep-src). Same run.scm drives both DRIVER=podman (cwd=/work) and
## DRIVER=seed (cwd=/). Stage A is pure scheme1 + M1pp + hex2pp; no
## asm step.
##
@@ -59,7 +60,7 @@ require_prev "$BOOT2" catm scheme1
runscm_init "$STAGE" "$OUT"
runscm_scheme1 "$BOOT2/scheme1"
runscm_prelude "$SRC/src/scheme1/prelude.scm"
-runscm_runscm scripts/boot3-run.scm
+runscm_runscm "$SRC/run/boot3.scm"
runscm_input catm "$BOOT2/catm"
runscm_input M1pp "$BOOT1/M1pp"
diff --git a/boot/boot4.sh b/boot/boot4.sh
@@ -26,8 +26,9 @@
## build/$ARCH/$DRIVER/boot2/{catm, scheme1}
##
## ─── Tools ────────────────────────────────────────────────────────────
-## scheme1 evaluates a host-generated run.scm (from boot4-gen-runscm.sh)
-## against the flat staging root. Every arch has CONFIG_TCC_ASM and
+## scheme1 evaluates the prep-time run.scm at
+## build/$ARCH/src/run/boot4.scm (generated by
+## bootprep/boot4-gen-runscm.sh) against the flat staging root. Every arch has CONFIG_TCC_ASM and
## assembles .S inputs (start.S, sys_stubs.S) directly inside the
## container; no host asm step. The aarch64 assembler is the phase-1
## arm64-asm.c that flatten patches into tcc-0.9.26 (see
@@ -87,7 +88,7 @@ done
# ── stage inputs and run scheme1 + boot4 run.scm under $DRIVER ────────
. boot/lib-runscm.sh
runscm_init "$STAGE" "$OUT"
-runscm_gen scripts/boot4-gen-runscm.sh "$ARCH"
+runscm_runscm "$SRC/run/boot4.scm"
runscm_scheme1 "$BOOT2/scheme1"
runscm_prelude "$SRC/src/scheme1/prelude.scm"
diff --git a/boot/boot5.sh b/boot/boot5.sh
@@ -19,8 +19,10 @@
## build/$ARCH/src/src/test-fixtures/boot-hello.c
##
## ─── Tools ────────────────────────────────────────────────────────────
-## scheme1 evaluates a host-generated run.scm (from boot5-gen-runscm.sh)
-## against the flat staging root.
+## scheme1 evaluates the prep-time run.scm at
+## build/$ARCH/src/run/boot5.scm (generated by
+## bootprep/boot5-gen-runscm.sh after prep-musl) against the flat
+## staging root.
##
## ─── Outputs ─────────────────────────────────────────────────────────
## build/$ARCH/$DRIVER/boot5/libc.a
@@ -51,99 +53,27 @@ require_file "$MUSL_DIR/skip.txt" "run bootprep/prep-musl.sh $ARCH"
require_file "$SRC/src/tcc/stdarg-bridge.h" "run bootprep/prep-src.sh $ARCH"
# ── prepare staging dirs ──────────────────────────────────────────────
-# $STAGE/in/ — read-only inputs (becomes /work/in or in/ in tmpfs)
-# $STAGE/out/ — writable outputs (becomes /work/out or out/ in tmpfs)
-# $STAGE/_host/ — host-side scratch (enumeration outputs); never
-# visible to the container/kernel.
+# $STAGE/in/ — read-only inputs (becomes /work/in or in/ in tmpfs)
+# $STAGE/out/ — writable outputs (becomes /work/out or out/ in tmpfs)
. boot/lib-runscm.sh
runscm_init "$STAGE" "$OUT"
-mkdir -p "$STAGE/_host"
+runscm_runscm "$SRC/run/boot5.scm"
-# ── enumerate musl sources from the canonical tree ────────────────────
-# Mirrors musl's Makefile rule: a per-arch override (under
-# $d/$MUSL_ARCH/) replaces the same-stem base file (under $d/). The
-# canonical tree already had the per-arch skip filter applied by
-# prep-musl.sh, so no skip subtraction is needed here.
-SRC_TOP="src/aio src/conf src/crypt src/ctype src/dirent
- src/env src/errno src/exit src/fcntl src/fenv src/internal
- src/ipc src/legacy src/linux src/locale src/malloc
- src/malloc/mallocng src/math src/misc src/mman src/mq
- src/multibyte src/network src/passwd src/prng src/process
- src/regex src/sched src/search src/select src/setjmp src/signal
- src/stat src/stdio src/stdlib src/string src/temp src/termios
- src/thread src/time src/unistd"
-
-(
- cd "$MUSL_DIR"
- for d in $SRC_TOP; do
- [ -d "$d" ] || continue
- for f in $d/*.c; do [ -f "$f" ] && echo "$f"; done
- done
-) > "$STAGE/_host/base.txt"
-
-(
- cd "$MUSL_DIR"
- for d in $SRC_TOP; do
- [ -d "$d/$MUSL_ARCH" ] || continue
- for f in $d/$MUSL_ARCH/*.c $d/$MUSL_ARCH/*.s $d/$MUSL_ARCH/*.S; do
- [ -f "$f" ] && echo "$f"
- done
- done
-) > "$STAGE/_host/arch.txt"
-
-# REPLACED: bases that have arch-specific overrides (drop them from
-# BASE). KEEP = (BASE - REPLACED) ∪ ARCH.
-awk -v ARCH="$MUSL_ARCH" '
- {
- sub(/\.[^.]*$/, "") # strip extension
- slot = "/" ARCH "/"
- i = index($0, slot)
- head = substr($0, 1, i - 1)
- tail = substr($0, i + length(slot))
- print head "/" tail
- }
-' "$STAGE/_host/arch.txt" | sort -u > "$STAGE/_host/replaced.txt"
-
-# Filter base by removing stems that appear in replaced.
-awk -v REPF="$STAGE/_host/replaced.txt" '
- BEGIN { while ((getline l < REPF) > 0) rep[l] = 1 }
- {
- stem = $0
- sub(/\.c$/, "", stem)
- if (!(stem in rep)) print
- }
-' "$STAGE/_host/base.txt" > "$STAGE/_host/keep_base.txt"
-
-cat "$STAGE/_host/keep_base.txt" "$STAGE/_host/arch.txt" | sort -u > "$STAGE/_host/build-srcs.txt"
-
-n_src=$(wc -l < "$STAGE/_host/build-srcs.txt")
-n_skip=$(grep -cv '^[[:space:]]*\(#\|$\)' "$MUSL_DIR/skip.txt" || true)
-echo "[$BOOT_TAG] keep=$n_src skip=$n_skip (calibrated)"
-
-# Record CRT mode (asm vs c) so the gen-runscm step picks the right
-# crti/crtn source set without re-checking $MUSL_DIR.
-if [ -f "$MUSL_DIR/crt/$MUSL_ARCH/crti.s" ]; then
- echo asm > "$STAGE/_host/crt-mode"
-else
- echo c > "$STAGE/_host/crt-mode"
-fi
-
-# Pre-create per-source obj/ directories under $STAGE/out/obj/musl/ so
+# Pre-create per-source obj/ dirs under $STAGE/out/obj/musl/ so
# scheme1's (run "in/tcc" -c …) doesn't need to mkdir at runtime (tcc
# errors out if the parent dir is missing, and scheme1 has no mkdir
-# primitive).
+# primitive). Per-source list is the build-srcs.txt produced by
+# bootprep/boot5-enumerate.sh at prep time.
+SRCS=$SRC/run/boot5/build-srcs.txt
+require_file "$SRCS" "run bootprep/prep-musl.sh $ARCH"
+COBJ=$STAGE/out/obj/musl
+mkdir -p "$COBJ/crt"
awk '
{
sub(/\.[^.]*$/, "")
if (match($0, /\/[^\/]*$/)) print substr($0, 1, RSTART - 1)
}
-' "$STAGE/_host/build-srcs.txt" | sort -u > "$STAGE/_host/build-objdirs.txt"
-COBJ=$STAGE/out/obj/musl
-mkdir -p "$COBJ/crt"
-while read -r d; do mkdir -p "$COBJ/$d"; done < "$STAGE/_host/build-objdirs.txt"
-
-# ── generate run.scm and stage chain binaries ─────────────────────────
-runscm_gen scripts/boot5-gen-runscm.sh "$MUSL_ARCH" "$STAGE/_host"
+' "$SRCS" | sort -u | while read -r d; do mkdir -p "$COBJ/$d"; done
runscm_scheme1 "$BOOT2/scheme1"
runscm_prelude "$SRC/src/scheme1/prelude.scm"
diff --git a/boot/boot6.sh b/boot/boot6.sh
@@ -15,8 +15,9 @@
## build/$ARCH/$DRIVER/boot2/scheme1
##
## ─── Tools ────────────────────────────────────────────────────────────
-## scheme1 evaluates a host-generated run.scm (from boot6-gen-runscm.sh)
-## against the flat staging root.
+## scheme1 evaluates the prep-time run.scm at
+## build/$ARCH/src/run/boot6.scm (generated by
+## bootprep/boot6-gen-runscm.sh) against the flat staging root.
##
## ─── Outputs ─────────────────────────────────────────────────────────
## build/$ARCH/$DRIVER/boot6/$KERNEL_NAME
@@ -51,7 +52,7 @@ done
# ── stage inputs and run scheme1 + run.scm under $DRIVER ──────────────
. boot/lib-runscm.sh
runscm_init "$STAGE" "$OUT"
-runscm_gen scripts/boot6-gen-runscm.sh "$ARCH"
+runscm_runscm "$SRC/run/boot6.scm"
runscm_scheme1 "$BOOT2/scheme1"
runscm_prelude "$SRC/src/scheme1/prelude.scm"
diff --git a/boot/lib-runscm.sh b/boot/lib-runscm.sh
@@ -20,9 +20,7 @@
# runscm_init <staging-dir> <out-dir>
# runscm_scheme1 <path> # init=scheme1 (boot2)
# runscm_prelude <path> # scheme1/prelude.scm
-# runscm_runscm <path> # static driver script
-# runscm_gen <gen-script> <args...> # OR generate run.scm,
-# # log size, register it.
+# runscm_runscm <path> # driver script (run.scm)
# runscm_input <name> <host-path> # repeatable; staged at in/<name>
# runscm_input_tree <prefix> <src-root> # repeatable; tree under in/<prefix>
# runscm_export <name>... # one or more output names
@@ -52,18 +50,6 @@ runscm_scheme1() { S_SCHEME1=$1; }
runscm_prelude() { S_PRELUDE=$1; }
runscm_runscm() { S_RUNSCM=$1; }
-# runscm_gen <gen-script> <args...>
-# Run a host-side generator that emits run.scm to $S_STAGE_DIR/run.scm,
-# log its size, and register it as the driver script. Used by
-# boot4/5/6 which build their run.scm dynamically.
-runscm_gen() {
- _gen=$1; shift
- _runscm=$S_STAGE_DIR/run.scm
- "$_gen" "$@" "$_runscm"
- echo "[$BOOT_TAG] generated run.scm: $(wc -l <"$_runscm") lines, $(wc -c <"$_runscm") bytes"
- S_RUNSCM=$_runscm
-}
-
runscm_input() {
name=$1; src=$2
case "$name" in
@@ -116,11 +102,7 @@ runscm_run() {
chmod +x "$S_STAGE_DIR/in/scheme1"
cat "$S_PRELUDE" "$S_RUNSCM" > "$S_STAGE_DIR/in/combined.scm"
# Top-level reference copy of run.scm for human inspection.
- # boot4/5 gen scripts already write here; skip the self-copy.
- case "$S_RUNSCM" in
- "$S_STAGE_DIR/run.scm") : ;;
- *) cp "$S_RUNSCM" "$S_STAGE_DIR/run.scm" ;;
- esac
+ cp "$S_RUNSCM" "$S_STAGE_DIR/run.scm"
case "${DRIVER:-podman}" in
podman) _runscm_run_podman "$timeout" ;;
diff --git a/scripts/boot-hello.c b/bootprep/assets/boot-hello.c
diff --git a/scripts/boot3-run.scm b/bootprep/assets/boot3-run.scm
diff --git a/bootprep/boot4-gen-runscm.sh b/bootprep/boot4-gen-runscm.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+## boot4-gen-runscm.sh — emit run.scm driving boot4's tcc0→tcc1→tcc2→tcc3
+## chain inside the seed kernel. Mirrors boot/boot4.sh's per-stage shell
+## emission; per-arch values resolved on the host so the .scm body is
+## straight-line (run …) calls.
+##
+## Reads use in/<name>; writes (intermediates and exports) use out/<name>.
+##
+## Usage: bootprep/boot4-gen-runscm.sh <arch>
+## Writes build/<arch>/src/run/boot4.scm.
+
+set -eu
+[ "$#" -eq 1 ] || { echo "usage: $0 <arch>" >&2; exit 2; }
+ARCH=$1
+OUT=build/$ARCH/src/run/boot4.scm
+mkdir -p "$(dirname "$OUT")"
+
+case "$ARCH" in
+ aarch64) LIB_HELPER_SRC=lib-arm64.c; LIB_HELPER_OBJ=lib-arm64.o
+ LIB_HELPER_DEFS='"-D" "HAVE_CONFIG_H=1" "-D" "TCC_TARGET_ARM64=1" "-D" "TCC_TARGET_ARM=1"'
+ LIBTCC1_C_SRCS="lib-arm64.c"
+ LIBTCC1_C_DEFS='"-D" "HAVE_CONFIG_H=1" "-D" "TCC_TARGET_ARM64=1" "-D" "TCC_TARGET_ARM=1"'
+ LIBTCC1_ASM_SRCS="" ;;
+ amd64) LIB_HELPER_SRC=va_list.c; LIB_HELPER_OBJ=va_list.o
+ LIB_HELPER_DEFS='"-D" "TCC_TARGET_X86_64=1"'
+ LIBTCC1_C_SRCS="libtcc1.c va_list.c"
+ LIBTCC1_C_DEFS='"-D" "TCC_TARGET_X86_64=1"'
+ LIBTCC1_ASM_SRCS="alloca86_64.S alloca86_64-bt.S" ;;
+ riscv64) LIB_HELPER_SRC=lib-arm64.c; LIB_HELPER_OBJ=lib-arm64.o
+ LIB_HELPER_DEFS='"-D" "HAVE_CONFIG_H=1" "-D" "TCC_TARGET_RISCV64=1"'
+ LIBTCC1_C_SRCS="lib-arm64.c"
+ LIBTCC1_C_DEFS='"-D" "HAVE_CONFIG_H=1" "-D" "TCC_TARGET_RISCV64=1"'
+ LIBTCC1_ASM_SRCS="" ;;
+ *) echo "boot4-gen: unknown arch $ARCH" >&2; exit 2 ;;
+esac
+
+# Per-arch link base for user binaries. tcc 0.9.26's riscv64-link.c
+# defaults to ELF_START_ADDR=0x10000, which lives below the seed
+# kernel's USER_VA_LO (0x200000). amd64 (0x400000) and aarch64
+# (0x400000) defaults already sit inside the user window, so we leave
+# them alone. Everywhere else in the chain (M0/hex2pp -B, boot6
+# -Wl,-Ttext) we link riscv64 user binaries at 0x600000; do the same
+# here so tcc-built outputs are loadable inside the seed kernel.
+case "$ARCH" in
+ riscv64) LINK_TTEXT='"-Wl,-Ttext=0x600000"' ;;
+ *) LINK_TTEXT= ;;
+esac
+
+# emit_helpers — cc reads .S/.c sources from in/, writes .o to out/.
+# cc_path is the cwd-relative path to the spawned compiler binary (in/tcc0
+# for round B; out/tcc1, out/tcc2 in later rounds).
+emit_helpers() {
+ cc_path=$1; tag=$2
+ cat <<EOF
+(must (run "$cc_path" "-nostdlib" "-c" "-o" "out/start.o" "in/start.S") "$tag start.o")
+(must (run "$cc_path" "-nostdlib" "-c" "-o" "out/sys_stubs.o" "in/sys_stubs.S") "$tag sys_stubs.o")
+(must (run "$cc_path" "-nostdlib" "-c" "-o" "out/mem.o" "in/mem.c") "$tag mem.o")
+(must (run "$cc_path" "-nostdlib" "-c" "-o" "out/libc.o" "in/libc.flat.c") "$tag libc.o")
+(must (run "$cc_path" "-nostdlib" $LIB_HELPER_DEFS "-c" "-o" "out/$LIB_HELPER_OBJ" "in/$LIB_HELPER_SRC") "$tag $LIB_HELPER_OBJ")
+EOF
+}
+
+# emit_archive — uses prefix to namespace output object names per stage.
+# pfx="s2-"/"s3-" for stage2/3. The .o objects archived into libtcc1.a
+# keep their bare basenames (lib-arm64.o, …) — tcc -ar stores basenames
+# only, so this matches podman's archive members exactly. They overwrite
+# the stage's helper-named .o files; nothing post-archive in the same
+# stage reads them as standalone .o.
+emit_archive() {
+ cc_path=$1; tag=$2; pfx=$3
+ echo "(must (run \"in/catm\" \"out/${pfx}crt1.o\" \"out/start.o\") \"copy crt1.o $pfx\")"
+ echo "(must (run \"$cc_path\" \"-ar\" \"rcs\" \"out/${pfx}libc.a\" \"out/sys_stubs.o\" \"out/mem.o\" \"out/libc.o\") \"$tag ${pfx}libc.a\")"
+ libtcc1_objs=""
+ for src in $LIBTCC1_C_SRCS; do
+ obj=${src%.c}.o
+ echo "(must (run \"$cc_path\" \"-nostdlib\" $LIBTCC1_C_DEFS \"-c\" \"-o\" \"out/${obj}\" \"in/$src\") \"$tag lt ${obj}\")"
+ libtcc1_objs="$libtcc1_objs \"out/${obj}\""
+ done
+ for src in $LIBTCC1_ASM_SRCS; do
+ obj=${src%.S}.o
+ echo "(must (run \"$cc_path\" \"-nostdlib\" \"-c\" \"-o\" \"out/${obj}\" \"in/$src\") \"$tag lt ${obj}\")"
+ libtcc1_objs="$libtcc1_objs \"out/${obj}\""
+ done
+ echo "(must (run \"$cc_path\" \"-ar\" \"rcs\" \"out/${pfx}libtcc1.a\"$libtcc1_objs) \"$tag ${pfx}libtcc1.a\")"
+}
+
+emit_link_tcc() {
+ cc_path=$1; tag=$2; pfx=$3; out=$4
+ echo "(must (run \"$cc_path\" \"-nostdlib\" $LINK_TTEXT \"out/${pfx}crt1.o\" \"in/tcc.flat.c\" \"out/${pfx}libc.a\" \"out/${pfx}libtcc1.a\" \"out/${pfx}libc.a\" \"-o\" \"out/$out\") \"$tag -> $out\")"
+}
+
+{
+cat <<'PROLOGUE'
+;; boot4 run.scm — drive tcc0 -> tcc1 -> tcc2 -> tcc3 inside seed kernel.
+;; Generated by bootprep/boot4-gen-runscm.sh; mirrors boot/boot4.sh's
+;; podman path stage-for-stage. Reads use in/; writes (intermediates and
+;; exports) use out/. tcc0 is staged as in/tcc0; tcc1/tcc2/tcc3 are
+;; produced and exported under out/.
+
+(define (must r tag)
+ (if (and (car r) (= 0 (cdr r)))
+ r
+ (begin
+ (write-string stderr "boot4: step failed: ")
+ (write-string stderr tag)
+ (write-string stderr "\n")
+ (exit 1))))
+
+(write-string stdout "boot4: stage B (tcc0 helpers)\n")
+PROLOGUE
+
+# Stage B: tcc0 builds helper objects (no archive).
+emit_helpers in/tcc0 tcc0
+
+cat <<EOF
+
+(write-string stdout "boot4: stage C (tcc0 -> tcc1)\n")
+(must (run "in/tcc0" "-nostdlib" $LINK_TTEXT "out/start.o" "out/sys_stubs.o" "out/mem.o" "out/libc.o" "out/$LIB_HELPER_OBJ" "in/tcc.flat.c" "-o" "out/tcc1") "tcc0 -> tcc1")
+
+(write-string stdout "boot4: stage D (tcc1 -> tcc2)\n")
+EOF
+
+# Stage D: tcc1 rebuilds helpers + archive, links tcc2.
+emit_helpers out/tcc1 tcc1
+emit_archive out/tcc1 tcc1 "s2-"
+emit_link_tcc out/tcc1 tcc1 "s2-" tcc2
+
+cat <<EOF
+
+(write-string stdout "boot4: stage E (tcc2 -> tcc3)\n")
+EOF
+
+# Stage E: tcc2 rebuilds helpers + archive, links tcc3.
+emit_helpers out/tcc2 tcc2
+emit_archive out/tcc2 tcc2 "s3-"
+emit_link_tcc out/tcc2 tcc2 "s3-" tcc3
+
+cat <<EOF
+
+(write-string stdout "boot4: linking hello\n")
+(must (run "out/tcc2" "-nostdlib" $LINK_TTEXT "out/s3-crt1.o" "in/hello.c" "out/s3-libc.a" "out/s3-libtcc1.a" "out/s3-libc.a" "-o" "out/hello") "tcc2 -> hello")
+(write-string stdout "boot4: ALL-OK\n")
+(exit 0)
+EOF
+} > "$OUT"
diff --git a/bootprep/boot5-enumerate.sh b/bootprep/boot5-enumerate.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+## boot5-enumerate.sh — enumerate musl sources from the canonical
+## post-prep-musl tree and emit the build-srcs / crt-mode files that
+## boot5-gen-runscm.sh consumes.
+##
+## Mirrors musl's Makefile rule: a per-arch override (under
+## $d/$MUSL_ARCH/) replaces the same-stem base file (under $d/). The
+## canonical tree already had the per-arch skip filter applied by
+## prep-musl.sh, so no skip subtraction is needed here.
+##
+## Inputs: build/<arch>/src/src/musl/ (post-prep-musl tree)
+## Outputs: build/<arch>/src/run/boot5/build-srcs.txt
+## build/<arch>/src/run/boot5/crt-mode ("asm" or "c")
+##
+## Usage: bootprep/boot5-enumerate.sh <arch>
+
+set -eu
+[ "$#" -eq 1 ] || { echo "usage: $0 <arch>" >&2; exit 2; }
+ARCH=$1
+
+case "$ARCH" in
+ aarch64) MUSL_ARCH=aarch64 ;;
+ amd64) MUSL_ARCH=x86_64 ;;
+ riscv64) MUSL_ARCH=riscv64 ;;
+ *) echo "boot5-enumerate: unsupported arch '$ARCH'" >&2; exit 2 ;;
+esac
+
+MUSL_DIR=build/$ARCH/src/src/musl
+OUT_DIR=build/$ARCH/src/run/boot5
+[ -d "$MUSL_DIR" ] || { echo "missing $MUSL_DIR (run bootprep/prep-musl.sh $ARCH)" >&2; exit 1; }
+mkdir -p "$OUT_DIR"
+
+SRC_TOP="src/aio src/conf src/crypt src/ctype src/dirent
+ src/env src/errno src/exit src/fcntl src/fenv src/internal
+ src/ipc src/legacy src/linux src/locale src/malloc
+ src/malloc/mallocng src/math src/misc src/mman src/mq
+ src/multibyte src/network src/passwd src/prng src/process
+ src/regex src/sched src/search src/select src/setjmp src/signal
+ src/stat src/stdio src/stdlib src/string src/temp src/termios
+ src/thread src/time src/unistd"
+
+(
+ cd "$MUSL_DIR"
+ for d in $SRC_TOP; do
+ [ -d "$d" ] || continue
+ for f in $d/*.c; do [ -f "$f" ] && echo "$f"; done
+ done
+) > "$OUT_DIR/base.txt"
+
+(
+ cd "$MUSL_DIR"
+ for d in $SRC_TOP; do
+ [ -d "$d/$MUSL_ARCH" ] || continue
+ for f in $d/$MUSL_ARCH/*.c $d/$MUSL_ARCH/*.s $d/$MUSL_ARCH/*.S; do
+ [ -f "$f" ] && echo "$f"
+ done
+ done
+) > "$OUT_DIR/arch.txt"
+
+# REPLACED: bases that have arch-specific overrides (drop them from
+# BASE). KEEP = (BASE - REPLACED) ∪ ARCH.
+awk -v ARCH="$MUSL_ARCH" '
+ {
+ sub(/\.[^.]*$/, "") # strip extension
+ slot = "/" ARCH "/"
+ i = index($0, slot)
+ head = substr($0, 1, i - 1)
+ tail = substr($0, i + length(slot))
+ print head "/" tail
+ }
+' "$OUT_DIR/arch.txt" | sort -u > "$OUT_DIR/replaced.txt"
+
+# Filter base by removing stems that appear in replaced.
+awk -v REPF="$OUT_DIR/replaced.txt" '
+ BEGIN { while ((getline l < REPF) > 0) rep[l] = 1 }
+ {
+ stem = $0
+ sub(/\.c$/, "", stem)
+ if (!(stem in rep)) print
+ }
+' "$OUT_DIR/base.txt" > "$OUT_DIR/keep_base.txt"
+
+cat "$OUT_DIR/keep_base.txt" "$OUT_DIR/arch.txt" | sort -u > "$OUT_DIR/build-srcs.txt"
+
+n_src=$(wc -l < "$OUT_DIR/build-srcs.txt")
+n_skip=$(grep -cv '^[[:space:]]*\(#\|$\)' "$MUSL_DIR/skip.txt" || true)
+echo "[boot5-enumerate/$ARCH] keep=$n_src skip=$n_skip (calibrated)"
+
+# Record CRT mode (asm vs c) so boot5-gen-runscm picks the right
+# crti/crtn source set without re-checking $MUSL_DIR.
+if [ -f "$MUSL_DIR/crt/$MUSL_ARCH/crti.s" ]; then
+ echo asm > "$OUT_DIR/crt-mode"
+else
+ echo c > "$OUT_DIR/crt-mode"
+fi
diff --git a/bootprep/boot5-gen-runscm.sh b/bootprep/boot5-gen-runscm.sh
@@ -0,0 +1,156 @@
+#!/bin/sh
+## boot5-gen-runscm.sh — emit run.scm driving boot5's musl + hello build
+## inside the seed kernel. Mirrors boot/boot5.sh's podman-path script
+## generation step-for-step: per-source `tcc -c`, per-arch CRT, archive,
+## link hello. Source enumeration done by bootprep/boot5-enumerate.sh
+## at prep time; this script consumes the resulting build-srcs.txt and
+## emits one `(run "in/tcc" …)` form per TU.
+##
+## Usage: bootprep/boot5-gen-runscm.sh <arch>
+## Reads build/<arch>/src/run/boot5/{build-srcs.txt, crt-mode}
+## Writes build/<arch>/src/run/boot5.scm
+##
+## Conventions (cwd-relative; resolves to / under seed init, /work under
+## podman bind-mount):
+## musl tree in/musl/<rel-path> (read-only; canonical
+## tree from prep-src/
+## prep-musl)
+## pre-gen hdrs in/musl/obj/include/bits/{alltypes,syscall}.h,
+## in/musl/obj/src/internal/version.h
+## .o outputs out/obj/musl/<src-with-.o> (rw; pre-mkdir'd by host)
+## tcc binary in/tcc (input)
+## libtcc1.a in/libtcc1.a (input)
+## stdarg bridge in/tcc-stdarg-bridge.h
+## hello.c in/hello.c
+## exports out/{libc.a,crt1.o,crti.o,crtn.o,hello}
+## (flat at out/ root so runscm_export pulls by basename)
+
+set -eu
+[ "$#" -eq 1 ] || { echo "usage: $0 <arch>" >&2; exit 2; }
+ARCH=$1
+case "$ARCH" in
+ aarch64) MUSL_ARCH=aarch64 ;;
+ amd64) MUSL_ARCH=x86_64 ;;
+ riscv64) MUSL_ARCH=riscv64 ;;
+ *) echo "boot5-gen: unsupported arch '$ARCH'" >&2; exit 2 ;;
+esac
+
+RUN_DIR=build/$ARCH/src/run
+SRCS=$RUN_DIR/boot5/build-srcs.txt
+CRT_MODE=$(cat "$RUN_DIR/boot5/crt-mode")
+OUT=$RUN_DIR/boot5.scm
+[ -e "$SRCS" ] || { echo "missing $SRCS (run bootprep/boot5-enumerate.sh $ARCH)" >&2; exit 1; }
+mkdir -p "$(dirname "$OUT")"
+
+CIN=in/musl
+COUT=out/obj/musl
+
+# Mirrors boot5.sh's CFLAGS_BASE exactly; the only difference is that
+# every per-arg token is quoted as its own scheme bytevector. The leading
+# "in/tcc" is the spawned binary; everything after is its argv.
+CFLAGS_BASE_QUOTED='"-std=c99" "-nostdinc" "-ffreestanding" "-fno-strict-aliasing" "-D_XOPEN_SOURCE=700"'
+CFLAGS_BASE_QUOTED="$CFLAGS_BASE_QUOTED \"-I$CIN/arch/$MUSL_ARCH\" \"-I$CIN/arch/generic\" \"-I$CIN/obj/src/internal\" \"-I$CIN/src/include\" \"-I$CIN/src/internal\" \"-I$CIN/obj/include\" \"-I$CIN/include\""
+CFLAGS_BASE_QUOTED="$CFLAGS_BASE_QUOTED \"-O2\" \"-fomit-frame-pointer\" \"-Werror=implicit-function-declaration\" \"-Werror=implicit-int\" \"-Werror=pointer-sign\" \"-Werror=pointer-arith\""
+CFLAGS_C_QUOTED="$CFLAGS_BASE_QUOTED \"-include\" \"in/tcc-stdarg-bridge.h\""
+CFLAGS_ASM_QUOTED="$CFLAGS_BASE_QUOTED"
+CRTFLAGS_C_QUOTED="$CFLAGS_C_QUOTED \"-fno-stack-protector\" \"-DCRT\""
+CRTFLAGS_ASM_QUOTED="$CFLAGS_ASM_QUOTED \"-fno-stack-protector\" \"-DCRT\""
+
+# tcc 0.9.26's riscv64-link.c default ELF_START_ADDR=0x10000 sits below
+# the seed kernel's USER_VA_LO (0x200000); land riscv64 user binaries
+# in the same 0x600000 window the rest of the chain uses. amd64
+# (0x400000) and aarch64 (0x400000) defaults already fit the window.
+case "$MUSL_ARCH" in
+ riscv64) LINK_TTEXT='"-Wl,-Ttext=0x600000"' ;;
+ *) LINK_TTEXT= ;;
+esac
+
+{
+cat <<'PROLOGUE'
+;; boot5 run.scm — drive musl-1.2.5 (~500 TUs) + hello.
+;; Generated by bootprep/boot5-gen-runscm.sh; consumed by both DRIVER=podman
+;; (cwd=/work bind mount) and DRIVER=seed (cwd=/, cpio rootfs). The musl
+;; source tree is staged read-only at in/tmp/musl-1.2.5/...; per-source .o
+;; outputs go to out/obj/musl-1.2.5/...; final artefacts (libc.a, crt1.o,
+;; crti.o, crtn.o, hello) land at flat out/ paths so runscm_export can
+;; pull them by basename.
+
+(define (must r tag)
+ (if (and (car r) (= 0 (cdr r)))
+ r
+ (begin
+ (write-string stderr "boot5: step failed: ")
+ (write-string stderr tag)
+ (write-string stderr "\n")
+ (exit 1))))
+
+(write-string stdout "boot5: stage A (compile sources)\n")
+PROLOGUE
+
+# Stage A: per-source compile. Each line of build-srcs.txt is a path
+# relative to musl-1.2.5/; choose flags by extension.
+awk -v CFLAGS_C="$CFLAGS_C_QUOTED" -v CFLAGS_ASM="$CFLAGS_ASM_QUOTED" -v CIN="$CIN" -v COUT="$COUT" '
+{
+ src = $0
+ obj = src
+ sub(/\.[^.]*$/, ".o", obj)
+ if (src ~ /\.c$/) flags = CFLAGS_C
+ else if (src ~ /\.[sS]$/) flags = CFLAGS_ASM
+ else flags = CFLAGS_C
+ printf "(must (run \"in/tcc\" %s \"-c\" \"%s/%s\" \"-o\" \"%s/%s\") \"%s\")\n", \
+ flags, CIN, src, COUT, obj, src
+}' "$SRCS"
+
+cat <<EOF
+
+(write-string stdout "boot5: stage B (CRT)\n")
+;; Position-independent + non-PIC CRT helpers. -fPIC objects are needed
+;; for shared-binding tools, even though our hello is fully static.
+(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-fPIC" "-c" "$CIN/crt/Scrt1.c" "-o" "$COUT/crt/Scrt1.o") "Scrt1.o")
+(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-c" "$CIN/crt/crt1.c" "-o" "$COUT/crt/crt1.o") "crt1.o")
+(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-fPIC" "-c" "$CIN/crt/rcrt1.c" "-o" "$COUT/crt/rcrt1.o") "rcrt1.o")
+EOF
+
+if [ "$CRT_MODE" = asm ]; then
+ cat <<EOF
+(must (run "in/tcc" $CRTFLAGS_ASM_QUOTED "-c" "$CIN/crt/$MUSL_ARCH/crti.s" "-o" "$COUT/crt/crti.o") "crti.o")
+(must (run "in/tcc" $CRTFLAGS_ASM_QUOTED "-c" "$CIN/crt/$MUSL_ARCH/crtn.s" "-o" "$COUT/crt/crtn.o") "crtn.o")
+EOF
+else
+ cat <<EOF
+(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-c" "$CIN/crt/crti.c" "-o" "$COUT/crt/crti.o") "crti.o")
+(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-c" "$CIN/crt/crtn.c" "-o" "$COUT/crt/crtn.o") "crtn.o")
+EOF
+fi
+
+# Stage C: archive libc.a. tcc -ar accepts many obj args; assemble the
+# full list inline. The list is enormous (~1500 paths × ~40 chars =
+# ~60 KB on a single line) but the prelude reader handles it fine.
+{
+ printf '\n(write-string stdout "boot5: stage C (libc.a)\\n")\n'
+ printf '(must (run "in/tcc" "-ar" "rcs" "out/libc.a"'
+ awk -v COUT="$COUT" '{
+ obj = $0
+ sub(/\.[^.]*$/, ".o", obj)
+ printf " \"%s/%s\"", COUT, obj
+ }' "$SRCS"
+ printf ') "libc.a")\n'
+}
+
+cat <<EOF
+
+;; Publish CRT objects at flat out/ paths so runscm_export can pull them.
+(must (run "in/catm" "out/crt1.o" "$COUT/crt/crt1.o") "crt1.o publish")
+(must (run "in/catm" "out/crti.o" "$COUT/crt/crti.o") "crti.o publish")
+(must (run "in/catm" "out/crtn.o" "$COUT/crt/crtn.o") "crtn.o publish")
+
+(write-string stdout "boot5: stage D (link hello)\n")
+;; -Lout pulls libc.a (just built); -Lin pulls libtcc1.a (input).
+(must (run "in/tcc" "-static" "-nostdinc" "-nostdlib" "-include" "in/tcc-stdarg-bridge.h" $LINK_TTEXT
+ "-I$CIN/include" "-I$CIN/arch/$MUSL_ARCH" "-I$CIN/arch/generic" "-I$CIN/obj/include"
+ "out/crt1.o" "in/hello.c" "-Lout" "-lc" "-Lin" "-ltcc1" "-Lout" "-lc" "-o" "out/hello") "link hello")
+
+(write-string stdout "boot5: ALL-OK\n")
+(exit 0)
+EOF
+} > "$OUT"
diff --git a/bootprep/boot6-gen-runscm.sh b/bootprep/boot6-gen-runscm.sh
@@ -0,0 +1,103 @@
+#!/bin/sh
+## boot6-gen-runscm.sh — emit run.scm driving tcc3 to build seed-kernel.
+##
+## tcc3 -c each translation unit (kernel.S, kernel.c, mem.c) → .o, then
+## tcc3 links + emits a flat arm64 boot Image directly (no `ld -T
+## kernel.lds`, no objcopy). The link line is three tcc flags:
+##
+## -nostdlib -static freestanding link, no startfiles
+## -Wl,-Ttext=0x40080000 base address (arm64 RAM_BASE +
+## text_offset; QEMU's `-kernel` loads
+## us here)
+## -Wl,--oformat=binary flat-bytes output (objcopy -O binary
+## equivalent). Required because QEMU's
+## `-kernel <ELF>` path doesn't run the
+## arm64 boot wrapper that puts DTB in
+## x0 — only the flat-Image path does.
+## Detection is by `ARM\x64` magic at
+## file offset 0x38, which is in
+## kernel.S's Image header at the top of
+## `.text`.
+##
+## Everything else the kernel needs from the linker is supplied by
+## conventions tcc3 already honors:
+##
+## _start hard-coded entry symbol; kernel.S's arm64 Image
+## header is named `_start` (not `_head`) so this works
+## out of the box.
+## __bss_start auto-defined at the start of merged .bss via the
+## `bss-start-symbol` simple-patch.
+## _end auto-defined by stock tcc at the end of merged .bss
+## (tcc_add_linker_symbols). kernel.S uses it to
+## bracket the bss-zero loop and to compute the
+## Image-header `image_size` field; kernel.c uses it
+## to place the kernel heap above the loaded image.
+##
+## Usage: bootprep/boot6-gen-runscm.sh <arch>
+## Writes build/<arch>/src/run/boot6.scm.
+
+set -eu
+[ "$#" -eq 1 ] || { echo "usage: $0 <arch>" >&2; exit 2; }
+ARCH=$1
+OUT=build/$ARCH/src/run/boot6.scm
+mkdir -p "$(dirname "$OUT")"
+
+# Per-arch link parameters. aarch64 alone needs --oformat=binary because
+# QEMU's `-kernel <ELF>` path skips the arm64 boot wrapper that puts DTB
+# in x0 — only the flat-Image path honors it (detected via the `ARM\x64`
+# magic at file offset 0x38 in kernel.S's Image header). amd64/riscv64
+# stay as ELF: QEMU's `-kernel` path on those arches consumes ELF
+# directly (PVH note for amd64, OpenSBI for riscv64).
+case "$ARCH" in
+ aarch64) TTEXT=0x40080000; OUT_FILE=Image; LINK_OFORMAT='"-Wl,--oformat=binary"' ;;
+ amd64) TTEXT=0x40000000; OUT_FILE=kernel.elf; LINK_OFORMAT= ;;
+ riscv64) TTEXT=0x80200000; OUT_FILE=kernel.elf; LINK_OFORMAT= ;;
+ *) echo "boot6-gen: unsupported arch '$ARCH'" >&2; exit 2 ;;
+esac
+
+# Kernel CFLAGS — freestanding, static, no host startfiles. We omit gcc's
+# -mgeneral-regs-only (no tcc equivalent); kernel.S enables CPACR_EL1.FPEN
+# in stext before the first `bl kmain`, so tcc's SIMD callee-saves in
+# function prologues don't trap.
+KCFLAGS='"-nostdlib" "-ffreestanding" "-static"'
+
+cat > "$OUT" <<EOF
+;; boot6 run.scm — build seed-kernel ELF with tcc3.
+;; Generated by bootprep/boot6-gen-runscm.sh; reads use in/, writes out/.
+
+(define (must r tag)
+ (if (and (car r) (= 0 (cdr r)))
+ r
+ (begin
+ (write-string stderr "boot6: step failed: ")
+ (write-string stderr tag)
+ (write-string stderr "\n")
+ (exit 1))))
+
+(write-string stdout "boot6: tcc3 -c kernel.S\n")
+(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/kernel-asm.o" "in/kernel.S")
+ "kernel.S -> kernel-asm.o")
+
+(write-string stdout "boot6: tcc3 -c kernel.c\n")
+(must (run "in/tcc3" $KCFLAGS "-Iin" "-c" "-o" "out/kernel.o" "in/kernel.c")
+ "kernel.c -> kernel.o")
+
+(write-string stdout "boot6: tcc3 -c mmu.c\n")
+(must (run "in/tcc3" $KCFLAGS "-Iin" "-c" "-o" "out/mmu.o" "in/mmu.c")
+ "mmu.c -> mmu.o")
+
+(write-string stdout "boot6: tcc3 -c mem.c\n")
+(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/mem.o" "in/mem.c")
+ "mem.c -> mem.o")
+
+(write-string stdout "boot6: tcc3 link $OUT_FILE\n")
+(must (run "in/tcc3" "-nostdlib" "-static"
+ "-Wl,-Ttext=$TTEXT"
+ $LINK_OFORMAT
+ "-o" "out/$OUT_FILE"
+ "out/kernel-asm.o" "out/kernel.o" "out/mmu.o" "out/mem.o")
+ "link $OUT_FILE")
+
+(write-string stdout "boot6: ALL-OK\n")
+(exit 0)
+EOF
diff --git a/bootprep/prep-musl.sh b/bootprep/prep-musl.sh
@@ -71,3 +71,10 @@ fi
n_remaining=$(find "$DST_MUSL" -type f | wc -l | tr -d ' ')
echo "$TAG OK filtered=$n_skip remaining=$n_remaining files in $DST_MUSL"
+
+# ── (3) enumerate musl sources + generate boot5 run.scm ───────────────
+# The filtered tree is now stable; emit the build-srcs + crt-mode that
+# boot5-gen-runscm consumes, then emit boot5.scm itself. boot5.sh just
+# reads the result.
+bootprep/boot5-enumerate.sh "$ARCH"
+bootprep/boot5-gen-runscm.sh "$ARCH"
diff --git a/bootprep/prep-src.sh b/bootprep/prep-src.sh
@@ -31,6 +31,12 @@
## skip filter on top.
## kernel/ seed-kernel sources for this arch
## test-fixtures/ boot-hello.c smoke binary
+## run/ run.scm files driving each bootN stage
+## boot3.scm static (copied from bootprep/assets/)
+## boot4.scm generated by bootprep/boot4-gen-runscm.sh
+## boot5.scm generated by bootprep/boot5-gen-runscm.sh
+## (after prep-musl.sh)
+## boot6.scm generated by bootprep/boot6-gen-runscm.sh
##
## A0 is split: prep-src.sh runs before boot0 and produces everything
## that doesn't need a working compiler. prep-musl.sh runs after boot4
@@ -105,7 +111,7 @@ cp tcc/cc/mem.c "$DST_SRC/tcc/cc/mem.c"
# Smoke binary linked by boot4 + boot5.
mkdir -p "$DST_SRC/test-fixtures"
-cp scripts/boot-hello.c "$DST_SRC/test-fixtures/boot-hello.c"
+cp bootprep/assets/boot-hello.c "$DST_SRC/test-fixtures/boot-hello.c"
# ── (3) seed-kernel sources for this arch ─────────────────────────────
mkdir -p "$DST_SRC/kernel/arch/$ARCH" "$DST_SRC/kernel/user"
@@ -191,6 +197,15 @@ if [ -e "$SKIP_COMMITTED" ]; then
cp "$SKIP_COMMITTED" "$DST_SRC/musl/skip.txt"
fi
+# ── (7) run.scm files ─────────────────────────────────────────────────
+# Static run.scm (boot3) copied verbatim; boot4/boot6 generated here.
+# boot5.scm needs the post-prep-musl tree, so it's generated by
+# prep-musl.sh.
+mkdir -p "$DST/run"
+cp bootprep/assets/boot3-run.scm "$DST/run/boot3.scm"
+bootprep/boot4-gen-runscm.sh "$ARCH"
+bootprep/boot6-gen-runscm.sh "$ARCH"
+
# ── summary ───────────────────────────────────────────────────────────
n_files=$(find "$DST" -type f | wc -l | tr -d ' ')
echo "$TAG OK -> $DST ($n_files files)"
diff --git a/docs/MUSL.md b/docs/MUSL.md
@@ -37,7 +37,7 @@ boot/boot5.sh <amd64|aarch64|riscv64>
| `vendor/musl/generated/$MUSL_ARCH/{alltypes,syscall}.h` | per-arch headers pre-generated at vendor time (replaces musl's mkalltypes.sed + `__NR_`→`SYS_` rewrite, so the container needs no awk) |
| `vendor/musl/skip-$ARCH.txt` | per-arch calibration list — sources tcc 0.9.26 cannot compile, produced by `bootprep/boot5-calibrate.sh` |
| `build/$ARCH/vendor/tcc/stdarg-bridge.h` | per-arch `__builtin_va_list` bridge (byte-identical across arches, three arches gated by `#ifdef`; produced by `bootprep/stage1-flatten.sh`) |
-| `scripts/boot-hello.c` | smoke-test source (shared with boot4) |
+| `bootprep/assets/boot-hello.c` | smoke-test source (shared with boot4) |
Architecture mapping:
@@ -174,6 +174,6 @@ hello from tcc-built libc; argc=4
strdup: works, strlen: 5
```
-(The same `hello` source, `scripts/boot-hello.c`, is also linked and
+(The same `hello` source, `bootprep/assets/boot-hello.c`, is also linked and
run by boot4 against the mes-libc closure — proving both libc closures
are exec-correct under their respective build systems.)
diff --git a/scripts/boot4-gen-runscm.sh b/scripts/boot4-gen-runscm.sh
@@ -1,142 +0,0 @@
-#!/bin/sh
-## boot4-gen-runscm.sh — emit run.scm driving boot4's tcc0→tcc1→tcc2→tcc3
-## chain inside the seed kernel. Mirrors boot/boot4.sh's per-stage shell
-## emission; per-arch values resolved on the host so the .scm body is
-## straight-line (run …) calls.
-##
-## Reads use in/<name>; writes (intermediates and exports) use out/<name>.
-##
-## Usage: boot4-gen-runscm.sh <arch> <out.scm>
-
-set -eu
-[ "$#" -eq 2 ] || { echo "usage: $0 <arch> <out.scm>" >&2; exit 2; }
-ARCH=$1; OUT=$2
-
-case "$ARCH" in
- aarch64) LIB_HELPER_SRC=lib-arm64.c; LIB_HELPER_OBJ=lib-arm64.o
- LIB_HELPER_DEFS='"-D" "HAVE_CONFIG_H=1" "-D" "TCC_TARGET_ARM64=1" "-D" "TCC_TARGET_ARM=1"'
- LIBTCC1_C_SRCS="lib-arm64.c"
- LIBTCC1_C_DEFS='"-D" "HAVE_CONFIG_H=1" "-D" "TCC_TARGET_ARM64=1" "-D" "TCC_TARGET_ARM=1"'
- LIBTCC1_ASM_SRCS="" ;;
- amd64) LIB_HELPER_SRC=va_list.c; LIB_HELPER_OBJ=va_list.o
- LIB_HELPER_DEFS='"-D" "TCC_TARGET_X86_64=1"'
- LIBTCC1_C_SRCS="libtcc1.c va_list.c"
- LIBTCC1_C_DEFS='"-D" "TCC_TARGET_X86_64=1"'
- LIBTCC1_ASM_SRCS="alloca86_64.S alloca86_64-bt.S" ;;
- riscv64) LIB_HELPER_SRC=lib-arm64.c; LIB_HELPER_OBJ=lib-arm64.o
- LIB_HELPER_DEFS='"-D" "HAVE_CONFIG_H=1" "-D" "TCC_TARGET_RISCV64=1"'
- LIBTCC1_C_SRCS="lib-arm64.c"
- LIBTCC1_C_DEFS='"-D" "HAVE_CONFIG_H=1" "-D" "TCC_TARGET_RISCV64=1"'
- LIBTCC1_ASM_SRCS="" ;;
- *) echo "boot4-gen: unknown arch $ARCH" >&2; exit 2 ;;
-esac
-
-# Per-arch link base for user binaries. tcc 0.9.26's riscv64-link.c
-# defaults to ELF_START_ADDR=0x10000, which lives below the seed
-# kernel's USER_VA_LO (0x200000). amd64 (0x400000) and aarch64
-# (0x400000) defaults already sit inside the user window, so we leave
-# them alone. Everywhere else in the chain (M0/hex2pp -B, boot6
-# -Wl,-Ttext) we link riscv64 user binaries at 0x600000; do the same
-# here so tcc-built outputs are loadable inside the seed kernel.
-case "$ARCH" in
- riscv64) LINK_TTEXT='"-Wl,-Ttext=0x600000"' ;;
- *) LINK_TTEXT= ;;
-esac
-
-# emit_helpers — cc reads .S/.c sources from in/, writes .o to out/.
-# cc_path is the cwd-relative path to the spawned compiler binary (in/tcc0
-# for round B; out/tcc1, out/tcc2 in later rounds).
-emit_helpers() {
- cc_path=$1; tag=$2
- cat <<EOF
-(must (run "$cc_path" "-nostdlib" "-c" "-o" "out/start.o" "in/start.S") "$tag start.o")
-(must (run "$cc_path" "-nostdlib" "-c" "-o" "out/sys_stubs.o" "in/sys_stubs.S") "$tag sys_stubs.o")
-(must (run "$cc_path" "-nostdlib" "-c" "-o" "out/mem.o" "in/mem.c") "$tag mem.o")
-(must (run "$cc_path" "-nostdlib" "-c" "-o" "out/libc.o" "in/libc.flat.c") "$tag libc.o")
-(must (run "$cc_path" "-nostdlib" $LIB_HELPER_DEFS "-c" "-o" "out/$LIB_HELPER_OBJ" "in/$LIB_HELPER_SRC") "$tag $LIB_HELPER_OBJ")
-EOF
-}
-
-# emit_archive — uses prefix to namespace output object names per stage.
-# pfx="s2-"/"s3-" for stage2/3. The .o objects archived into libtcc1.a
-# keep their bare basenames (lib-arm64.o, …) — tcc -ar stores basenames
-# only, so this matches podman's archive members exactly. They overwrite
-# the stage's helper-named .o files; nothing post-archive in the same
-# stage reads them as standalone .o.
-emit_archive() {
- cc_path=$1; tag=$2; pfx=$3
- echo "(must (run \"in/catm\" \"out/${pfx}crt1.o\" \"out/start.o\") \"copy crt1.o $pfx\")"
- echo "(must (run \"$cc_path\" \"-ar\" \"rcs\" \"out/${pfx}libc.a\" \"out/sys_stubs.o\" \"out/mem.o\" \"out/libc.o\") \"$tag ${pfx}libc.a\")"
- libtcc1_objs=""
- for src in $LIBTCC1_C_SRCS; do
- obj=${src%.c}.o
- echo "(must (run \"$cc_path\" \"-nostdlib\" $LIBTCC1_C_DEFS \"-c\" \"-o\" \"out/${obj}\" \"in/$src\") \"$tag lt ${obj}\")"
- libtcc1_objs="$libtcc1_objs \"out/${obj}\""
- done
- for src in $LIBTCC1_ASM_SRCS; do
- obj=${src%.S}.o
- echo "(must (run \"$cc_path\" \"-nostdlib\" \"-c\" \"-o\" \"out/${obj}\" \"in/$src\") \"$tag lt ${obj}\")"
- libtcc1_objs="$libtcc1_objs \"out/${obj}\""
- done
- echo "(must (run \"$cc_path\" \"-ar\" \"rcs\" \"out/${pfx}libtcc1.a\"$libtcc1_objs) \"$tag ${pfx}libtcc1.a\")"
-}
-
-emit_link_tcc() {
- cc_path=$1; tag=$2; pfx=$3; out=$4
- echo "(must (run \"$cc_path\" \"-nostdlib\" $LINK_TTEXT \"out/${pfx}crt1.o\" \"in/tcc.flat.c\" \"out/${pfx}libc.a\" \"out/${pfx}libtcc1.a\" \"out/${pfx}libc.a\" \"-o\" \"out/$out\") \"$tag -> $out\")"
-}
-
-{
-cat <<'PROLOGUE'
-;; boot4 run.scm — drive tcc0 -> tcc1 -> tcc2 -> tcc3 inside seed kernel.
-;; Generated by scripts/boot4-gen-runscm.sh; mirrors boot/boot4.sh's
-;; podman path stage-for-stage. Reads use in/; writes (intermediates and
-;; exports) use out/. tcc0 is staged as in/tcc0; tcc1/tcc2/tcc3 are
-;; produced and exported under out/.
-
-(define (must r tag)
- (if (and (car r) (= 0 (cdr r)))
- r
- (begin
- (write-string stderr "boot4: step failed: ")
- (write-string stderr tag)
- (write-string stderr "\n")
- (exit 1))))
-
-(write-string stdout "boot4: stage B (tcc0 helpers)\n")
-PROLOGUE
-
-# Stage B: tcc0 builds helper objects (no archive).
-emit_helpers in/tcc0 tcc0
-
-cat <<EOF
-
-(write-string stdout "boot4: stage C (tcc0 -> tcc1)\n")
-(must (run "in/tcc0" "-nostdlib" $LINK_TTEXT "out/start.o" "out/sys_stubs.o" "out/mem.o" "out/libc.o" "out/$LIB_HELPER_OBJ" "in/tcc.flat.c" "-o" "out/tcc1") "tcc0 -> tcc1")
-
-(write-string stdout "boot4: stage D (tcc1 -> tcc2)\n")
-EOF
-
-# Stage D: tcc1 rebuilds helpers + archive, links tcc2.
-emit_helpers out/tcc1 tcc1
-emit_archive out/tcc1 tcc1 "s2-"
-emit_link_tcc out/tcc1 tcc1 "s2-" tcc2
-
-cat <<EOF
-
-(write-string stdout "boot4: stage E (tcc2 -> tcc3)\n")
-EOF
-
-# Stage E: tcc2 rebuilds helpers + archive, links tcc3.
-emit_helpers out/tcc2 tcc2
-emit_archive out/tcc2 tcc2 "s3-"
-emit_link_tcc out/tcc2 tcc2 "s3-" tcc3
-
-cat <<EOF
-
-(write-string stdout "boot4: linking hello\n")
-(must (run "out/tcc2" "-nostdlib" $LINK_TTEXT "out/s3-crt1.o" "in/hello.c" "out/s3-libc.a" "out/s3-libtcc1.a" "out/s3-libc.a" "-o" "out/hello") "tcc2 -> hello")
-(write-string stdout "boot4: ALL-OK\n")
-(exit 0)
-EOF
-} > "$OUT"
diff --git a/scripts/boot5-gen-runscm.sh b/scripts/boot5-gen-runscm.sh
@@ -1,149 +0,0 @@
-#!/bin/sh
-## boot5-gen-runscm.sh — emit run.scm driving boot5's musl + hello build
-## inside the seed kernel. Mirrors boot/boot5.sh's podman-path script
-## generation step-for-step: per-source `tcc -c`, per-arch CRT, archive,
-## link hello. Source enumeration done by boot5.sh; this script consumes
-## the resulting build-srcs.txt and emits one `(run "in/tcc" …)` form per TU.
-##
-## Usage:
-## boot5-gen-runscm.sh <musl-arch> <stage-host-dir> <out.scm>
-##
-## stage-host-dir is the boot5 _host/ directory containing:
-## build-srcs.txt one path per line, relative to musl-1.2.5/
-## crt-mode "asm" or "c" — picked by boot5.sh from $MUSL_DIR
-##
-## Conventions (cwd-relative; resolves to / under seed init, /work under
-## podman bind-mount):
-## musl tree in/musl/<rel-path> (read-only; canonical
-## tree from prep-src/
-## prep-musl)
-## pre-gen hdrs in/musl/obj/include/bits/{alltypes,syscall}.h,
-## in/musl/obj/src/internal/version.h
-## .o outputs out/obj/musl/<src-with-.o> (rw; pre-mkdir'd by host)
-## tcc binary in/tcc (input)
-## libtcc1.a in/libtcc1.a (input)
-## stdarg bridge in/tcc-stdarg-bridge.h
-## hello.c in/hello.c
-## exports out/{libc.a,crt1.o,crti.o,crtn.o,hello}
-## (flat at out/ root so runscm_export pulls by basename)
-
-set -eu
-[ "$#" -eq 3 ] || { echo "usage: $0 <musl-arch> <stage-host-dir> <out.scm>" >&2; exit 2; }
-
-MUSL_ARCH=$1; STAGE_HOST=$2; OUT=$3
-SRCS=$STAGE_HOST/build-srcs.txt
-CRT_MODE=$(cat "$STAGE_HOST/crt-mode")
-[ -e "$SRCS" ] || { echo "missing $SRCS" >&2; exit 1; }
-
-CIN=in/musl
-COUT=out/obj/musl
-
-# Mirrors boot5.sh's CFLAGS_BASE exactly; the only difference is that
-# every per-arg token is quoted as its own scheme bytevector. The leading
-# "in/tcc" is the spawned binary; everything after is its argv.
-CFLAGS_BASE_QUOTED='"-std=c99" "-nostdinc" "-ffreestanding" "-fno-strict-aliasing" "-D_XOPEN_SOURCE=700"'
-CFLAGS_BASE_QUOTED="$CFLAGS_BASE_QUOTED \"-I$CIN/arch/$MUSL_ARCH\" \"-I$CIN/arch/generic\" \"-I$CIN/obj/src/internal\" \"-I$CIN/src/include\" \"-I$CIN/src/internal\" \"-I$CIN/obj/include\" \"-I$CIN/include\""
-CFLAGS_BASE_QUOTED="$CFLAGS_BASE_QUOTED \"-O2\" \"-fomit-frame-pointer\" \"-Werror=implicit-function-declaration\" \"-Werror=implicit-int\" \"-Werror=pointer-sign\" \"-Werror=pointer-arith\""
-CFLAGS_C_QUOTED="$CFLAGS_BASE_QUOTED \"-include\" \"in/tcc-stdarg-bridge.h\""
-CFLAGS_ASM_QUOTED="$CFLAGS_BASE_QUOTED"
-CRTFLAGS_C_QUOTED="$CFLAGS_C_QUOTED \"-fno-stack-protector\" \"-DCRT\""
-CRTFLAGS_ASM_QUOTED="$CFLAGS_ASM_QUOTED \"-fno-stack-protector\" \"-DCRT\""
-
-# tcc 0.9.26's riscv64-link.c default ELF_START_ADDR=0x10000 sits below
-# the seed kernel's USER_VA_LO (0x200000); land riscv64 user binaries
-# in the same 0x600000 window the rest of the chain uses. amd64
-# (0x400000) and aarch64 (0x400000) defaults already fit the window.
-case "$MUSL_ARCH" in
- riscv64) LINK_TTEXT='"-Wl,-Ttext=0x600000"' ;;
- *) LINK_TTEXT= ;;
-esac
-
-{
-cat <<'PROLOGUE'
-;; boot5 run.scm — drive musl-1.2.5 (~500 TUs) + hello.
-;; Generated by scripts/boot5-gen-runscm.sh; consumed by both DRIVER=podman
-;; (cwd=/work bind mount) and DRIVER=seed (cwd=/, cpio rootfs). The musl
-;; source tree is staged read-only at in/tmp/musl-1.2.5/...; per-source .o
-;; outputs go to out/obj/musl-1.2.5/...; final artefacts (libc.a, crt1.o,
-;; crti.o, crtn.o, hello) land at flat out/ paths so runscm_export can
-;; pull them by basename.
-
-(define (must r tag)
- (if (and (car r) (= 0 (cdr r)))
- r
- (begin
- (write-string stderr "boot5: step failed: ")
- (write-string stderr tag)
- (write-string stderr "\n")
- (exit 1))))
-
-(write-string stdout "boot5: stage A (compile sources)\n")
-PROLOGUE
-
-# Stage A: per-source compile. Each line of build-srcs.txt is a path
-# relative to musl-1.2.5/; choose flags by extension.
-awk -v CFLAGS_C="$CFLAGS_C_QUOTED" -v CFLAGS_ASM="$CFLAGS_ASM_QUOTED" -v CIN="$CIN" -v COUT="$COUT" '
-{
- src = $0
- obj = src
- sub(/\.[^.]*$/, ".o", obj)
- if (src ~ /\.c$/) flags = CFLAGS_C
- else if (src ~ /\.[sS]$/) flags = CFLAGS_ASM
- else flags = CFLAGS_C
- printf "(must (run \"in/tcc\" %s \"-c\" \"%s/%s\" \"-o\" \"%s/%s\") \"%s\")\n", \
- flags, CIN, src, COUT, obj, src
-}' "$SRCS"
-
-cat <<EOF
-
-(write-string stdout "boot5: stage B (CRT)\n")
-;; Position-independent + non-PIC CRT helpers. -fPIC objects are needed
-;; for shared-binding tools, even though our hello is fully static.
-(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-fPIC" "-c" "$CIN/crt/Scrt1.c" "-o" "$COUT/crt/Scrt1.o") "Scrt1.o")
-(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-c" "$CIN/crt/crt1.c" "-o" "$COUT/crt/crt1.o") "crt1.o")
-(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-fPIC" "-c" "$CIN/crt/rcrt1.c" "-o" "$COUT/crt/rcrt1.o") "rcrt1.o")
-EOF
-
-if [ "$CRT_MODE" = asm ]; then
- cat <<EOF
-(must (run "in/tcc" $CRTFLAGS_ASM_QUOTED "-c" "$CIN/crt/$MUSL_ARCH/crti.s" "-o" "$COUT/crt/crti.o") "crti.o")
-(must (run "in/tcc" $CRTFLAGS_ASM_QUOTED "-c" "$CIN/crt/$MUSL_ARCH/crtn.s" "-o" "$COUT/crt/crtn.o") "crtn.o")
-EOF
-else
- cat <<EOF
-(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-c" "$CIN/crt/crti.c" "-o" "$COUT/crt/crti.o") "crti.o")
-(must (run "in/tcc" $CRTFLAGS_C_QUOTED "-c" "$CIN/crt/crtn.c" "-o" "$COUT/crt/crtn.o") "crtn.o")
-EOF
-fi
-
-# Stage C: archive libc.a. tcc -ar accepts many obj args; assemble the
-# full list inline. The list is enormous (~1500 paths × ~40 chars =
-# ~60 KB on a single line) but the prelude reader handles it fine.
-{
- printf '\n(write-string stdout "boot5: stage C (libc.a)\\n")\n'
- printf '(must (run "in/tcc" "-ar" "rcs" "out/libc.a"'
- awk -v COUT="$COUT" '{
- obj = $0
- sub(/\.[^.]*$/, ".o", obj)
- printf " \"%s/%s\"", COUT, obj
- }' "$SRCS"
- printf ') "libc.a")\n'
-}
-
-cat <<EOF
-
-;; Publish CRT objects at flat out/ paths so runscm_export can pull them.
-(must (run "in/catm" "out/crt1.o" "$COUT/crt/crt1.o") "crt1.o publish")
-(must (run "in/catm" "out/crti.o" "$COUT/crt/crti.o") "crti.o publish")
-(must (run "in/catm" "out/crtn.o" "$COUT/crt/crtn.o") "crtn.o publish")
-
-(write-string stdout "boot5: stage D (link hello)\n")
-;; -Lout pulls libc.a (just built); -Lin pulls libtcc1.a (input).
-(must (run "in/tcc" "-static" "-nostdinc" "-nostdlib" "-include" "in/tcc-stdarg-bridge.h" $LINK_TTEXT
- "-I$CIN/include" "-I$CIN/arch/$MUSL_ARCH" "-I$CIN/arch/generic" "-I$CIN/obj/include"
- "out/crt1.o" "in/hello.c" "-Lout" "-lc" "-Lin" "-ltcc1" "-Lout" "-lc" "-o" "out/hello") "link hello")
-
-(write-string stdout "boot5: ALL-OK\n")
-(exit 0)
-EOF
-} > "$OUT"
diff --git a/scripts/boot6-gen-runscm.sh b/scripts/boot6-gen-runscm.sh
@@ -1,100 +0,0 @@
-#!/bin/sh
-## boot6-gen-runscm.sh — emit run.scm driving tcc3 to build seed-kernel.
-##
-## tcc3 -c each translation unit (kernel.S, kernel.c, mem.c) → .o, then
-## tcc3 links + emits a flat arm64 boot Image directly (no `ld -T
-## kernel.lds`, no objcopy). The link line is three tcc flags:
-##
-## -nostdlib -static freestanding link, no startfiles
-## -Wl,-Ttext=0x40080000 base address (arm64 RAM_BASE +
-## text_offset; QEMU's `-kernel` loads
-## us here)
-## -Wl,--oformat=binary flat-bytes output (objcopy -O binary
-## equivalent). Required because QEMU's
-## `-kernel <ELF>` path doesn't run the
-## arm64 boot wrapper that puts DTB in
-## x0 — only the flat-Image path does.
-## Detection is by `ARM\x64` magic at
-## file offset 0x38, which is in
-## kernel.S's Image header at the top of
-## `.text`.
-##
-## Everything else the kernel needs from the linker is supplied by
-## conventions tcc3 already honors:
-##
-## _start hard-coded entry symbol; kernel.S's arm64 Image
-## header is named `_start` (not `_head`) so this works
-## out of the box.
-## __bss_start auto-defined at the start of merged .bss via the
-## `bss-start-symbol` simple-patch.
-## _end auto-defined by stock tcc at the end of merged .bss
-## (tcc_add_linker_symbols). kernel.S uses it to
-## bracket the bss-zero loop and to compute the
-## Image-header `image_size` field; kernel.c uses it
-## to place the kernel heap above the loaded image.
-##
-## Usage: boot6-gen-runscm.sh <arch> <out.scm>
-
-set -eu
-[ "$#" -eq 2 ] || { echo "usage: $0 <arch> <out.scm>" >&2; exit 2; }
-ARCH=$1; OUT=$2
-
-# Per-arch link parameters. aarch64 alone needs --oformat=binary because
-# QEMU's `-kernel <ELF>` path skips the arm64 boot wrapper that puts DTB
-# in x0 — only the flat-Image path honors it (detected via the `ARM\x64`
-# magic at file offset 0x38 in kernel.S's Image header). amd64/riscv64
-# stay as ELF: QEMU's `-kernel` path on those arches consumes ELF
-# directly (PVH note for amd64, OpenSBI for riscv64).
-case "$ARCH" in
- aarch64) TTEXT=0x40080000; OUT_FILE=Image; LINK_OFORMAT='"-Wl,--oformat=binary"' ;;
- amd64) TTEXT=0x40000000; OUT_FILE=kernel.elf; LINK_OFORMAT= ;;
- riscv64) TTEXT=0x80200000; OUT_FILE=kernel.elf; LINK_OFORMAT= ;;
- *) echo "boot6-gen: unsupported arch '$ARCH'" >&2; exit 2 ;;
-esac
-
-# Kernel CFLAGS — freestanding, static, no host startfiles. We omit gcc's
-# -mgeneral-regs-only (no tcc equivalent); kernel.S enables CPACR_EL1.FPEN
-# in stext before the first `bl kmain`, so tcc's SIMD callee-saves in
-# function prologues don't trap.
-KCFLAGS='"-nostdlib" "-ffreestanding" "-static"'
-
-cat > "$OUT" <<EOF
-;; boot6 run.scm — build seed-kernel ELF with tcc3.
-;; Generated by scripts/boot6-gen-runscm.sh; reads use in/, writes out/.
-
-(define (must r tag)
- (if (and (car r) (= 0 (cdr r)))
- r
- (begin
- (write-string stderr "boot6: step failed: ")
- (write-string stderr tag)
- (write-string stderr "\n")
- (exit 1))))
-
-(write-string stdout "boot6: tcc3 -c kernel.S\n")
-(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/kernel-asm.o" "in/kernel.S")
- "kernel.S -> kernel-asm.o")
-
-(write-string stdout "boot6: tcc3 -c kernel.c\n")
-(must (run "in/tcc3" $KCFLAGS "-Iin" "-c" "-o" "out/kernel.o" "in/kernel.c")
- "kernel.c -> kernel.o")
-
-(write-string stdout "boot6: tcc3 -c mmu.c\n")
-(must (run "in/tcc3" $KCFLAGS "-Iin" "-c" "-o" "out/mmu.o" "in/mmu.c")
- "mmu.c -> mmu.o")
-
-(write-string stdout "boot6: tcc3 -c mem.c\n")
-(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/mem.o" "in/mem.c")
- "mem.c -> mem.o")
-
-(write-string stdout "boot6: tcc3 link $OUT_FILE\n")
-(must (run "in/tcc3" "-nostdlib" "-static"
- "-Wl,-Ttext=$TTEXT"
- $LINK_OFORMAT
- "-o" "out/$OUT_FILE"
- "out/kernel-asm.o" "out/kernel.o" "out/mmu.o" "out/mem.o")
- "link $OUT_FILE")
-
-(write-string stdout "boot6: ALL-OK\n")
-(exit 0)
-EOF