commit 72181739d2d6abfa06af1b8e456ac86d3907fd1e
parent 7a1635af1afbf2c4008f2b43cf3762acc215f10e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 5 May 2026 12:03:32 -0700
boot{3,4,5}: unify podman+seed via lib-runscm; drop in-container shell
Replace lib-seed-runscm.sh and the per-script podman branches with a
single driver-agnostic lib-runscm.sh. Each stage now stages inputs into
<stage>/cpio/<name>, generates one run.scm with cwd-relative ./names,
and runs scheme1 either inside a scratch+busybox-free FROM-scratch
image (Containerfile.empty / boot2-empty:<arch>) or as init under the
seed kernel — same flat namespace for both.
The new image carries no /bin/sh; scheme1 spawns staged binaries
directly via argv. boot{3,4}-run.scm and boot{4,5}-gen-runscm now
emit ./catm, ./scheme1, ./tcc, etc. so the same script works under
both bind-mount /work cwd and seed-kernel cpio /.
Diffstat:
9 files changed, 463 insertions(+), 703 deletions(-)
diff --git a/scripts/Containerfile.empty b/scripts/Containerfile.empty
@@ -0,0 +1,17 @@
+## Per-arch image used by boot3/4/5.sh — fully empty rootfs (FROM scratch
+## with no copy stages). Unlike scripts/Containerfile.scratch, this image
+## does not bundle busybox: boot3/4/5 invoke scheme1 directly via argv
+## and run.scm spawns only staged binaries (catm, scheme1, M1pp, hex2pp,
+## tcc), so no in-container shell, /bin/sh, or applet tree is needed.
+##
+## Built per --platform; tag as boot2-empty:<arch>. The ARCH build-arg
+## flows into an ENV so each per-arch build produces a distinct image
+## SHA — without it, FROM-scratch-only Containerfiles dedup across
+## platforms and the second-built tag points back at the first arch's
+## image (podman has no platform check on tag resolve, so a rootless
+## --platform=linux/riscv64 lookup against an arm64-tagged SHA fails
+## with "image not known").
+FROM scratch
+ARG ARCH=unknown
+ENV CONTAINER_ARCH=$ARCH
+WORKDIR /work
diff --git a/scripts/boot3-run.scm b/scripts/boot3-run.scm
@@ -12,27 +12,27 @@
(exit 1))))
(write-string stdout "boot3: catm cc-bundle\n")
-(must (run "catm" "cc-bundled.scm" "prelude.scm" "cc.scm" "main.scm")
+(must (run "./catm" "cc-bundled.scm" "prelude.scm" "cc.scm" "main.scm")
"catm cc-bundle")
(write-string stdout "boot3: scheme1 libc\n")
-(must (run "scheme1" "cc-bundled.scm" "--lib=libc__" "libc.flat.c" "libc.P1pp")
+(must (run "./scheme1" "cc-bundled.scm" "--lib=libc__" "libc.flat.c" "libc.P1pp")
"scheme1 libc")
(write-string stdout "boot3: scheme1 tcc\n")
-(must (run "scheme1" "cc-bundled.scm" "--lib=tcc__" "tcc.flat.c" "tcc.flat.P1pp")
+(must (run "./scheme1" "cc-bundled.scm" "--lib=tcc__" "tcc.flat.c" "tcc.flat.P1pp")
"scheme1 tcc")
(write-string stdout "boot3: catm combined.M1pp\n")
-(must (run "catm" "combined.M1pp"
+(must (run "./catm" "combined.M1pp"
"backend.M1pp" "frontend.M1pp" "libp1pp.P1pp"
"entry-libc.P1pp" "libc.P1pp" "tcc.flat.P1pp" "elf-end.P1pp")
"catm combined.M1pp")
(write-string stdout "boot3: M1pp\n")
-(must (run "M1pp" "combined.M1pp" "expanded.hex2pp")
+(must (run "./M1pp" "combined.M1pp" "expanded.hex2pp")
"M1pp")
(write-string stdout "boot3: catm linked.hex2pp\n")
-(must (run "catm" "linked.hex2pp" "ELF.hex2" "expanded.hex2pp")
+(must (run "./catm" "linked.hex2pp" "ELF.hex2" "expanded.hex2pp")
"catm linked.hex2pp")
(write-string stdout "boot3: hex2pp\n")
-(must (run "hex2pp" "-B" "0x600000" "linked.hex2pp" "tcc0")
+(must (run "./hex2pp" "-B" "0x600000" "linked.hex2pp" "tcc0")
"hex2pp")
(exit 0)
diff --git a/scripts/boot3.sh b/scripts/boot3.sh
@@ -35,6 +35,9 @@
##
## ─── Tools ────────────────────────────────────────────────────────────
## In container: scratch + busybox (no libc, no /etc, no resolver).
+## scheme1 evaluates scripts/boot3-run.scm against the
+## flat staging root (cwd=/work for podman, cwd=/ for
+## the seed kernel). Same run.scm drives both.
## On host: none — Stage A is pure scheme1 + M1pp + hex2pp; no
## asm step is required.
##
@@ -63,7 +66,7 @@ ROOT=$(cd "$(dirname "$0")/.." && pwd)
cd "$ROOT"
DRIVER=${DRIVER:-podman}
-IMAGE=boot2-scratch:$ARCH
+IMAGE=boot2-empty:$ARCH
BOOT1=build/$ARCH/boot1
BOOT2=build/$ARCH/boot2
OUT=build/$ARCH/boot3
@@ -76,8 +79,8 @@ LIBC_FLAT=build/$ARCH/vendor/mes-libc/libc.flat.c
# ── ensure container image exists (podman driver only) ────────────────
if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then
echo "[boot3 $ARCH] building $IMAGE"
- podman build --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.scratch scripts/
+ podman build --platform "$PLATFORM" --build-arg "ARCH=$ARCH" -t "$IMAGE" \
+ -f scripts/Containerfile.empty scripts/
fi
if [ "$DRIVER" = seed ]; then
[ "$ARCH" = aarch64 ] || { echo "[boot3] DRIVER=seed: aarch64 only" >&2; exit 2; }
@@ -86,6 +89,7 @@ if [ "$DRIVER" = seed ]; then
[ -f "$KERNEL_IMAGE" ] || { echo "[boot3] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; }
export KERNEL_IMAGE EXTRACT
fi
+export IMAGE PLATFORM DRIVER
# ── prerequisite: prior-stage binaries ────────────────────────────────
[ -x "$BOOT1/M1pp" ] || { echo "[boot3 $ARCH] missing $BOOT1/M1pp (run scripts/boot1.sh $ARCH)" >&2; exit 1; }
@@ -113,95 +117,35 @@ ENTRY_LIBC=P1/entry-libc.P1pp
ELF_END=P1/elf-end.P1pp
ELF_HEX2=vendor/seed/$ARCH/ELF.hex2
-case "$DRIVER" in
-podman)
- # ── reset staging, copy inputs explicitly ─────────────────────────
- rm -rf "$STAGE"
- mkdir -p "$STAGE/in" "$STAGE/out" "$OUT"
- rm -f "$OUT/tcc0"
-
- cp "$BOOT1/M1pp" "$STAGE/in/M1pp"
- cp "$BOOT1/hex2pp" "$STAGE/in/hex2pp"
- cp "$BOOT2/catm" "$STAGE/in/catm"
- cp "$BOOT2/scheme1" "$STAGE/in/scheme1"
-
- cp scheme1/prelude.scm "$STAGE/in/prelude.scm"
- cp cc/cc.scm "$STAGE/in/cc.scm"
- cp cc/main.scm "$STAGE/in/main.scm"
-
- cp "$BACKEND_M1PP" "$STAGE/in/backend.M1pp"
- cp "$FRONTEND_M1PP" "$STAGE/in/frontend.M1pp"
- cp "$LIBP1PP" "$STAGE/in/libp1pp.P1pp"
- cp "$ENTRY_LIBC" "$STAGE/in/entry-libc.P1pp"
- cp "$ELF_END" "$STAGE/in/elf-end.P1pp"
- cp "$ELF_HEX2" "$STAGE/in/ELF.hex2"
-
- cp "$TCC_FLAT" "$STAGE/in/tcc.flat.c"
- cp "$LIBC_FLAT" "$STAGE/in/libc.flat.c"
-
- # ── emit flat container build script ─────────────────────────────
- # Generates a straight-line shell program: cc.scm bundle → tcc0 ELF
- # via scheme1 + M1pp + hex2pp. Container shell sees only sequential
- # exec — no functions, no for-loops, no parameter expansion.
- RUN_SCRIPT=$STAGE/in/run.sh
- {
- echo '#!/bin/sh'
- echo 'set -eu'
- echo
- echo '# Stage A: cc.scm bundle -> tcc0 ELF'
- echo '/work/in/catm /tmp/cc-bundled.scm /work/in/prelude.scm /work/in/cc.scm /work/in/main.scm'
- echo '/work/in/scheme1 /tmp/cc-bundled.scm --lib=libc__ /work/in/libc.flat.c /tmp/libc.P1pp'
- echo '/work/in/scheme1 /tmp/cc-bundled.scm --lib=tcc__ /work/in/tcc.flat.c /tmp/tcc.flat.P1pp'
- echo '/work/in/catm /tmp/combined.M1pp /work/in/backend.M1pp /work/in/frontend.M1pp /work/in/libp1pp.P1pp /work/in/entry-libc.P1pp /tmp/libc.P1pp /tmp/tcc.flat.P1pp /work/in/elf-end.P1pp'
- echo '/work/in/M1pp /tmp/combined.M1pp /tmp/expanded.hex2pp'
- echo '/work/in/catm /tmp/linked.hex2pp /work/in/ELF.hex2 /tmp/expanded.hex2pp'
- echo '/work/in/hex2pp -B 0x600000 /tmp/linked.hex2pp /work/out/tcc0'
- } > "$RUN_SCRIPT"
- chmod +x "$RUN_SCRIPT"
- echo "[boot3 $ARCH] generated run.sh: $(wc -l <"$RUN_SCRIPT") lines"
-
- echo "[boot3 $ARCH] cc.scm -> tcc0"
- podman run --rm -i --pull=never --platform "$PLATFORM" \
- --tmpfs /tmp:size=1024M \
- -v "$ROOT/$STAGE:/work" -w /work "$IMAGE" \
- sh -eu /work/in/run.sh
-
- cp "$STAGE/out/tcc0" "$OUT/tcc0"
- chmod 0700 "$OUT/tcc0"
- ;;
-seed)
- # ── seed-kernel driver: one qemu boot, scheme1 evaluates run.scm
- # against catm/scheme1/M1pp/hex2pp staged as flat tmpfs entries.
- . scripts/lib-seed-runscm.sh
- seed_runscm_init "$STAGE" "$OUT"
- seed_runscm_scheme1 "$BOOT2/scheme1"
- seed_runscm_prelude scheme1/prelude.scm
- seed_runscm_runscm scripts/boot3-run.scm
-
- seed_runscm_input catm "$BOOT2/catm"
- seed_runscm_input M1pp "$BOOT1/M1pp"
- seed_runscm_input hex2pp "$BOOT1/hex2pp"
- seed_runscm_input scheme1 "$BOOT2/scheme1"
-
- seed_runscm_input prelude.scm scheme1/prelude.scm
- seed_runscm_input cc.scm cc/cc.scm
- seed_runscm_input main.scm cc/main.scm
-
- seed_runscm_input backend.M1pp "$BACKEND_M1PP"
- seed_runscm_input frontend.M1pp "$FRONTEND_M1PP"
- seed_runscm_input libp1pp.P1pp "$LIBP1PP"
- seed_runscm_input entry-libc.P1pp "$ENTRY_LIBC"
- seed_runscm_input elf-end.P1pp "$ELF_END"
- seed_runscm_input ELF.hex2 "$ELF_HEX2"
-
- seed_runscm_input tcc.flat.c "$TCC_FLAT"
- seed_runscm_input libc.flat.c "$LIBC_FLAT"
-
- seed_runscm_export tcc0
- seed_runscm_run 1800
- ;;
-*) echo "[boot3] unknown DRIVER=$DRIVER" >&2; exit 2 ;;
-esac
+# ── stage inputs and run scheme1 + boot3-run.scm under $DRIVER ────────
+. scripts/lib-runscm.sh
+runscm_init "$STAGE" "$OUT"
+runscm_scheme1 "$BOOT2/scheme1"
+runscm_prelude scheme1/prelude.scm
+runscm_runscm scripts/boot3-run.scm
+
+runscm_input catm "$BOOT2/catm"
+runscm_input M1pp "$BOOT1/M1pp"
+runscm_input hex2pp "$BOOT1/hex2pp"
+# scheme1 binary itself is staged by runscm_run (so a `(run "scheme1" …)`
+# inside boot3-run.scm finds it at cwd-relative ./scheme1).
+
+runscm_input prelude.scm scheme1/prelude.scm
+runscm_input cc.scm cc/cc.scm
+runscm_input main.scm cc/main.scm
+
+runscm_input backend.M1pp "$BACKEND_M1PP"
+runscm_input frontend.M1pp "$FRONTEND_M1PP"
+runscm_input libp1pp.P1pp "$LIBP1PP"
+runscm_input entry-libc.P1pp "$ENTRY_LIBC"
+runscm_input elf-end.P1pp "$ELF_END"
+runscm_input ELF.hex2 "$ELF_HEX2"
+
+runscm_input tcc.flat.c "$TCC_FLAT"
+runscm_input libc.flat.c "$LIBC_FLAT"
+
+runscm_export tcc0
+runscm_run 1800
echo "[boot3 $ARCH/$DRIVER] sizes: tcc0=$(wc -c <"$OUT/tcc0")"
echo "[boot3 $ARCH/$DRIVER] OK -> $OUT/tcc0"
diff --git a/scripts/boot4-gen-runscm.sh b/scripts/boot4-gen-runscm.sh
@@ -16,17 +16,27 @@ case "$ARCH" in
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="" ;;
- *) echo "boot4-gen: only aarch64 supported under DRIVER=seed" >&2; exit 2 ;;
+ 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
emit_helpers() {
cc=$1
cat <<EOF
-(must (run "$cc" "-nostdlib" "-c" "-o" "start.o" "start.S") "$cc start.o")
-(must (run "$cc" "-nostdlib" "-c" "-o" "sys_stubs.o" "sys_stubs.S") "$cc sys_stubs.o")
-(must (run "$cc" "-nostdlib" "-c" "-o" "mem.o" "mem.c") "$cc mem.o")
-(must (run "$cc" "-nostdlib" "-c" "-o" "libc.o" "libc.flat.c") "$cc libc.o")
-(must (run "$cc" "-nostdlib" $LIB_HELPER_DEFS "-c" "-o" "$LIB_HELPER_OBJ" "$LIB_HELPER_SRC") "$cc $LIB_HELPER_OBJ")
+(must (run "./$cc" "-nostdlib" "-c" "-o" "start.o" "start.S") "$cc start.o")
+(must (run "./$cc" "-nostdlib" "-c" "-o" "sys_stubs.o" "sys_stubs.S") "$cc sys_stubs.o")
+(must (run "./$cc" "-nostdlib" "-c" "-o" "mem.o" "mem.c") "$cc mem.o")
+(must (run "./$cc" "-nostdlib" "-c" "-o" "libc.o" "libc.flat.c") "$cc libc.o")
+(must (run "./$cc" "-nostdlib" $LIB_HELPER_DEFS "-c" "-o" "$LIB_HELPER_OBJ" "$LIB_HELPER_SRC") "$cc $LIB_HELPER_OBJ")
EOF
}
@@ -38,25 +48,25 @@ EOF
# stage reads them as standalone .o.
emit_archive() {
cc=$1; pfx=$2
- echo "(must (run \"catm\" \"${pfx}crt1.o\" \"start.o\") \"copy crt1.o $pfx\")"
- echo "(must (run \"$cc\" \"-ar\" \"rcs\" \"${pfx}libc.a\" \"sys_stubs.o\" \"mem.o\" \"libc.o\") \"$cc ${pfx}libc.a\")"
+ echo "(must (run \"./catm\" \"${pfx}crt1.o\" \"start.o\") \"copy crt1.o $pfx\")"
+ echo "(must (run \"./$cc\" \"-ar\" \"rcs\" \"${pfx}libc.a\" \"sys_stubs.o\" \"mem.o\" \"libc.o\") \"$cc ${pfx}libc.a\")"
libtcc1_objs=""
for src in $LIBTCC1_C_SRCS; do
obj=${src%.c}.o
- echo "(must (run \"$cc\" \"-nostdlib\" $LIBTCC1_C_DEFS \"-c\" \"-o\" \"${obj}\" \"$src\") \"$cc lt ${obj}\")"
+ echo "(must (run \"./$cc\" \"-nostdlib\" $LIBTCC1_C_DEFS \"-c\" \"-o\" \"${obj}\" \"$src\") \"$cc lt ${obj}\")"
libtcc1_objs="$libtcc1_objs \"${obj}\""
done
for src in $LIBTCC1_ASM_SRCS; do
obj=${src%.S}.o
- echo "(must (run \"$cc\" \"-nostdlib\" \"-c\" \"-o\" \"${obj}\" \"$src\") \"$cc lt ${obj}\")"
+ echo "(must (run \"./$cc\" \"-nostdlib\" \"-c\" \"-o\" \"${obj}\" \"$src\") \"$cc lt ${obj}\")"
libtcc1_objs="$libtcc1_objs \"${obj}\""
done
- echo "(must (run \"$cc\" \"-ar\" \"rcs\" \"${pfx}libtcc1.a\"$libtcc1_objs) \"$cc ${pfx}libtcc1.a\")"
+ echo "(must (run \"./$cc\" \"-ar\" \"rcs\" \"${pfx}libtcc1.a\"$libtcc1_objs) \"$cc ${pfx}libtcc1.a\")"
}
emit_link_tcc() {
cc=$1; pfx=$2; out=$3
- echo "(must (run \"$cc\" \"-nostdlib\" \"${pfx}crt1.o\" \"tcc.flat.c\" \"${pfx}libc.a\" \"${pfx}libtcc1.a\" \"${pfx}libc.a\" \"-o\" \"$out\") \"$cc -> $out\")"
+ echo "(must (run \"./$cc\" \"-nostdlib\" \"${pfx}crt1.o\" \"tcc.flat.c\" \"${pfx}libc.a\" \"${pfx}libtcc1.a\" \"${pfx}libc.a\" \"-o\" \"$out\") \"$cc -> $out\")"
}
{
@@ -84,7 +94,7 @@ emit_helpers tcc0
cat <<EOF
(write-string stdout "boot4: stage C (tcc0 -> tcc1)\n")
-(must (run "tcc0" "-nostdlib" "start.o" "sys_stubs.o" "mem.o" "libc.o" "$LIB_HELPER_OBJ" "tcc.flat.c" "-o" "tcc1") "tcc0 -> tcc1")
+(must (run "./tcc0" "-nostdlib" "start.o" "sys_stubs.o" "mem.o" "libc.o" "$LIB_HELPER_OBJ" "tcc.flat.c" "-o" "tcc1") "tcc0 -> tcc1")
(write-string stdout "boot4: stage D (tcc1 -> tcc2)\n")
EOF
@@ -107,7 +117,7 @@ emit_link_tcc tcc2 "s3-" tcc3
cat <<'EPILOGUE'
(write-string stdout "boot4: linking hello\n")
-(must (run "tcc2" "-nostdlib" "s3-crt1.o" "hello.c" "s3-libc.a" "s3-libtcc1.a" "s3-libc.a" "-o" "hello") "tcc2 -> hello")
+(must (run "./tcc2" "-nostdlib" "s3-crt1.o" "hello.c" "s3-libc.a" "s3-libtcc1.a" "s3-libc.a" "-o" "hello") "tcc2 -> hello")
(write-string stdout "boot4: ALL-OK\n")
(exit 0)
EPILOGUE
diff --git a/scripts/boot4.sh b/scripts/boot4.sh
@@ -51,6 +51,10 @@
##
## ─── Tools ────────────────────────────────────────────────────────────
## In container: scratch + busybox (no libc, no /etc, no resolver).
+## scheme1 evaluates a host-generated run.scm (emitted
+## by scripts/boot4-gen-runscm.sh) against the flat
+## staging root (cwd=/work for podman, cwd=/ for the
+## seed kernel). Same run.scm drives both.
## On host: none — every arch has CONFIG_TCC_ASM and assembles
## .S inputs (start.S, sys_stubs.S) directly inside the
## container in stages B/D/E. The aarch64 assembler is
@@ -67,7 +71,7 @@
## + libc.o
## build/$ARCH/boot4/libtcc1.a
## — tcc2-built tcc compiler helper archive
-## build/$ARCH/boot4/hello — mes-libc-linked smoke binary, run here
+## build/$ARCH/boot4/hello — mes-libc-linked smoke binary
## tcc2 and tcc3 are byte-identical (asserted at the end of this
## script) — that equality is the fixed-point check.
##
@@ -84,27 +88,15 @@ ARCH=$1
case "$ARCH" in
aarch64) PLATFORM=linux/arm64;
TCC_TARGET=ARM64;
- LIB_HELPER_SRC=lib-arm64.c;
- LIB_HELPER_OBJ=lib-arm64.o;
- LIB_HELPER_DEFINES="-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) PLATFORM=linux/amd64;
TCC_TARGET=X86_64;
- LIB_HELPER_SRC=va_list.c;
- LIB_HELPER_OBJ=va_list.o;
- LIB_HELPER_DEFINES="-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) PLATFORM=linux/riscv64;
TCC_TARGET=RISCV64;
- LIB_HELPER_SRC=lib-arm64.c;
- LIB_HELPER_OBJ=lib-arm64.o;
- LIB_HELPER_DEFINES="-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="" ;;
*) usage ;;
esac
@@ -113,7 +105,7 @@ ROOT=$(cd "$(dirname "$0")/.." && pwd)
cd "$ROOT"
DRIVER=${DRIVER:-podman}
-IMAGE=boot2-scratch:$ARCH
+IMAGE=boot2-empty:$ARCH
BOOT2=build/$ARCH/boot2
BOOT3=build/$ARCH/boot3
OUT=build/$ARCH/boot4
@@ -126,27 +118,28 @@ LIBC_FLAT=build/$ARCH/vendor/mes-libc/libc.flat.c
# ── ensure container image exists (podman driver only) ────────────────
if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then
echo "[boot4 $ARCH] building $IMAGE"
- podman build --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.scratch scripts/
+ podman build --platform "$PLATFORM" --build-arg "ARCH=$ARCH" -t "$IMAGE" \
+ -f scripts/Containerfile.empty scripts/
fi
if [ "$DRIVER" = seed ]; then
[ "$ARCH" = aarch64 ] || { echo "[boot4] DRIVER=seed: aarch64 only" >&2; exit 2; }
KERNEL_IMAGE=$ROOT/seed-kernel/build/Image
EXTRACT=$ROOT/seed-kernel/scripts/extract-dump.sh
[ -f "$KERNEL_IMAGE" ] || { echo "[boot4] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; }
- [ -x "$BOOT2/scheme1" ] || { echo "[boot4] missing $BOOT2/scheme1 (run boot2)" >&2; exit 1; }
- [ -x "$BOOT2/catm" ] || { echo "[boot4] missing $BOOT2/catm (run boot2)" >&2; exit 1; }
export KERNEL_IMAGE EXTRACT
fi
+export IMAGE PLATFORM DRIVER
# ── prerequisite: prior-stage binaries ────────────────────────────────
-[ -x "$BOOT3/tcc0" ] || { echo "[boot4 $ARCH] missing $BOOT3/tcc0 (run scripts/boot3.sh $ARCH)" >&2; exit 1; }
+[ -x "$BOOT3/tcc0" ] || { echo "[boot4 $ARCH] missing $BOOT3/tcc0 (run scripts/boot3.sh $ARCH)" >&2; exit 1; }
+[ -x "$BOOT2/scheme1" ] || { echo "[boot4 $ARCH] missing $BOOT2/scheme1 (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
+[ -x "$BOOT2/catm" ] || { echo "[boot4 $ARCH] missing $BOOT2/catm (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
# ── prerequisite: host-flattened sources + unpacked tcc tree ──────────
# Normally these were produced by boot3 (auto-invoked by stage1-flatten
# / libc-flatten there). Re-check here so boot4 runs standalone if a
# user has tcc0 but blew away build/tcc/.
-if [ ! -e "$TCC_FLAT" ] || [ ! -d "$TCC_DIR/include" ] || [ ! -e "$TCC_DIR/lib/$LIB_HELPER_SRC" ] || [ ! -e build/tcc/stdarg-bridge.h ]; then
+if [ ! -e "$TCC_FLAT" ] || [ ! -d "$TCC_DIR/include" ] || [ ! -e "$TCC_DIR/lib/lib-arm64.c" ] || [ ! -e build/tcc/stdarg-bridge.h ]; then
echo "[boot4 $ARCH] flatten tcc.flat.c (host)"
scripts/stage1-flatten.sh --arch "$TCC_TARGET"
fi
@@ -158,213 +151,54 @@ for f in $LIBTCC1_C_SRCS $LIBTCC1_ASM_SRCS; do
[ -e "$TCC_DIR/lib/$f" ] || { echo "[boot4 $ARCH] missing $TCC_DIR/lib/$f" >&2; exit 1; }
done
-case "$DRIVER" in
-podman)
-# ── reset staging, copy inputs explicitly ─────────────────────────────
-rm -rf "$STAGE"
-mkdir -p "$STAGE/in" "$STAGE/in/tcc-lib" "$STAGE/out" "$OUT"
-rm -f "$OUT/tcc1" "$OUT/tcc2" \
- "$OUT/start.o" "$OUT/sys_stubs.o" "$OUT/mem.o" "$OUT/libc.o"
+# ── stage inputs and run scheme1 + boot4 run.scm under $DRIVER ────────
+. scripts/lib-runscm.sh
+runscm_init "$STAGE" "$OUT"
-# Prior-stage binary
-cp "$BOOT3/tcc0" "$STAGE/in/tcc0"
+RUNSCM=$STAGE/run.scm
+scripts/boot4-gen-runscm.sh "$ARCH" "$RUNSCM"
+echo "[boot4 $ARCH] generated run.scm: $(wc -l <"$RUNSCM") lines"
-# tcc-libc / tcc-cc helpers
-cp "tcc-libc/$ARCH/start.S" "$STAGE/in/start.S"
-cp "tcc-libc/$ARCH/sys_stubs.S" "$STAGE/in/sys_stubs.S"
-cp tcc-cc/mem.c "$STAGE/in/mem.c"
+runscm_scheme1 "$BOOT2/scheme1"
+runscm_prelude scheme1/prelude.scm
+runscm_runscm "$RUNSCM"
-# Per-arch libtcc1 helper sources. LIB_HELPER_SRC is always also in
-# LIBTCC1_C_SRCS (lib-arm64.c on aarch64/riscv64, va_list.c on amd64),
-# so a single staging path under tcc-lib/ covers both consumers — the
-# emit_helpers step (uses LIB_HELPER_SRC) and the emit_archive step
-# (iterates LIBTCC1_C_SRCS + LIBTCC1_ASM_SRCS).
+runscm_input tcc0 "$BOOT3/tcc0"
+runscm_input catm "$BOOT2/catm"
+
+runscm_input start.S "tcc-libc/$ARCH/start.S"
+runscm_input sys_stubs.S "tcc-libc/$ARCH/sys_stubs.S"
+runscm_input mem.c tcc-cc/mem.c
for f in $LIBTCC1_C_SRCS $LIBTCC1_ASM_SRCS; do
- cp "$TCC_DIR/lib/$f" "$STAGE/in/tcc-lib/$f"
+ runscm_input "$f" "$TCC_DIR/lib/$f"
done
-# Flattened TUs. The patched tcc <stdarg.h> bridge is already prepended
-# (under #ifndef CCSCM) into both .flat.c files by the flatten scripts,
-# so the in-container compiles need no -I/-include flags. hello.c uses
-# forward declarations (no system headers).
-cp "$TCC_FLAT" "$STAGE/in/tcc.flat.c"
-cp "$LIBC_FLAT" "$STAGE/in/libc.flat.c"
-
-cp scripts/boot-hello.c "$STAGE/in/hello.c"
-
-# Every arch's tcc-boot2 has CONFIG_TCC_ASM and assembles .S inputs
-# itself inside the container — no host cross-asm step.
-
-# ── emit flat container build script ──────────────────────────────────
-# Generates a straight-line shell program: tcc0 → tcc1 → tcc2 → tcc3
-# with all per-stage repetition unrolled and per-arch values
-# (LIB_HELPER_SRC/OBJ, LIBTCC1_C_SRCS, LIBTCC1_ASM_SRCS, etc.) resolved
-# on the host. Container shell sees only sequential exec — no
-# functions, no for-loops, no parameter expansion.
-#
-# Stage B: tcc0 builds mem.o, libc.o, helper.o.
-# Stage C: tcc0 compiles+links tcc1.
-# Stage D: tcc1 rebuilds helpers, archives, compiles+links tcc2.
-# Stage E: tcc2 rebuilds helpers, archives, compiles+links tcc3 (host
-# asserts tcc2 == tcc3 after container exits).
-
-# Helper: emit the build_asm + build_helpers + archive_runtime closure
-# for one stage. $1 = compiler (path inside container), $2 = workdir.
-emit_helpers () {
- cc=$1
- workdir=$2
- echo "$cc -nostdlib -c -o $workdir/start.o /work/in/start.S"
- echo "$cc -nostdlib -c -o $workdir/sys_stubs.o /work/in/sys_stubs.S"
- echo "$cc -nostdlib -c -o $workdir/mem.o /work/in/mem.c"
- echo "$cc -nostdlib -c -o $workdir/libc.o /work/in/libc.flat.c"
- echo "$cc -nostdlib $LIB_HELPER_DEFINES -c -o $workdir/$LIB_HELPER_OBJ /work/in/tcc-lib/$LIB_HELPER_SRC"
-}
-emit_archive () {
- cc=$1
- workdir=$2
- libtcc1_objs=""
- echo "cp $workdir/start.o $workdir/crt1.o"
- echo "$cc -ar rcs $workdir/libc.a $workdir/sys_stubs.o $workdir/mem.o $workdir/libc.o"
- echo "mkdir -p $workdir/libtcc1-obj"
- for src in $LIBTCC1_C_SRCS; do
- obj=$workdir/libtcc1-obj/${src%.c}.o
- echo "$cc -nostdlib $LIBTCC1_C_DEFS -c -o $obj /work/in/tcc-lib/$src"
- libtcc1_objs="$libtcc1_objs $obj"
- done
- for src in $LIBTCC1_ASM_SRCS; do
- obj=$workdir/libtcc1-obj/${src%.S}.o
- echo "$cc -nostdlib -c -o $obj /work/in/tcc-lib/$src"
- libtcc1_objs="$libtcc1_objs $obj"
- done
- echo "$cc -ar rcs $workdir/libtcc1.a$libtcc1_objs"
-}
-emit_link_tcc () {
- cc=$1
- workdir=$2
- out=$3
- echo "$cc -nostdlib $workdir/crt1.o /work/in/tcc.flat.c $workdir/libc.a $workdir/libtcc1.a $workdir/libc.a -o $out"
-}
-
-RUN_SCRIPT=$STAGE/in/run.sh
-{
- echo '#!/bin/sh'
- echo 'set -eu'
- echo
- echo '# Stage B: tcc0 builds helper objects (stage1)'
- echo 'mkdir -p /tmp/stage1 /tmp/stage2 /tmp/stage3'
- emit_helpers /work/in/tcc0 /tmp/stage1
- echo
- echo '# Stage C: tcc0 -> tcc1 (link with raw .o files; no archive yet)'
- echo "/work/in/tcc0 -nostdlib /tmp/stage1/start.o /tmp/stage1/sys_stubs.o /tmp/stage1/mem.o /tmp/stage1/libc.o /tmp/stage1/$LIB_HELPER_OBJ /work/in/tcc.flat.c -o /work/out/tcc1"
- echo 'chmod +x /work/out/tcc1'
- echo
- echo '# Stage D: tcc1 rebuilds helpers + archive, links tcc2'
- emit_helpers /work/out/tcc1 /tmp/stage2
- emit_archive /work/out/tcc1 /tmp/stage2
- emit_link_tcc /work/out/tcc1 /tmp/stage2 /work/out/tcc2
- echo 'chmod +x /work/out/tcc2'
- echo
- echo '# Stage E: tcc2 rebuilds helpers + archive, links tcc3.'
- echo '# (Host asserts tcc2 == tcc3 after the container exits.)'
- emit_helpers /work/out/tcc2 /tmp/stage3
- emit_archive /work/out/tcc2 /tmp/stage3
- emit_link_tcc /work/out/tcc2 /tmp/stage3 /work/out/tcc3
- echo 'chmod +x /work/out/tcc3'
- echo
- echo '# Publish the tcc2-built mes-libc link closure + smoke-test hello.'
- echo '# (tcc2 and tcc3 are byte-identical by the fixed-point check, so'
- echo '# rebuilding with tcc3 would only repeat the cycle.)'
- echo 'cp /tmp/stage3/crt1.o /tmp/stage3/libc.a /tmp/stage3/libtcc1.a /work/out/'
- echo '/work/out/tcc2 -nostdlib /work/out/crt1.o /work/in/hello.c /work/out/libc.a /work/out/libtcc1.a /work/out/libc.a -o /work/out/hello'
- echo 'chmod +x /work/out/hello'
- echo 'echo "--- run ---"'
- echo '/work/out/hello a b c'
-} > "$RUN_SCRIPT"
-chmod +x "$RUN_SCRIPT"
-echo "[boot4 $ARCH] generated run.sh: $(wc -l <"$RUN_SCRIPT") lines"
-
-# ── run flat build script in scratch+busybox container ────────────────
-echo "[boot4 $ARCH] tcc0 -> tcc1 -> tcc2 -> tcc3"
-podman run --rm -i --pull=never --platform "$PLATFORM" \
- --tmpfs /tmp:size=1024M \
- -v "$ROOT/$STAGE:/work" -w /work "$IMAGE" \
- sh -eu /work/in/run.sh
- ;;
-seed)
- # ── seed-kernel driver: one qemu boot, scheme1 evaluates a host-
- # generated run.scm against tcc0 / catm / sources flat in tmpfs.
- # Outputs (tcc1/tcc2/tcc3, s3-crt1.o, s3-libc.a, s3-libtcc1.a,
- # hello) come back via UART tmpfs dump.
- rm -f "$OUT/tcc1" "$OUT/tcc2"
-
- . scripts/lib-seed-runscm.sh
- seed_runscm_init "$STAGE" "$OUT"
+runscm_input tcc.flat.c "$TCC_FLAT"
+runscm_input libc.flat.c "$LIBC_FLAT"
+runscm_input hello.c scripts/boot-hello.c
- RUNSCM=$STAGE/run.scm
- scripts/boot4-gen-runscm.sh "$ARCH" "$RUNSCM"
- echo "[boot4 $ARCH] generated run.scm: $(wc -l <"$RUNSCM") lines"
-
- seed_runscm_scheme1 "$BOOT2/scheme1"
- seed_runscm_prelude scheme1/prelude.scm
- seed_runscm_runscm "$RUNSCM"
-
- seed_runscm_input tcc0 "$BOOT3/tcc0"
- seed_runscm_input catm "$BOOT2/catm"
- seed_runscm_input scheme1 "$BOOT2/scheme1"
-
- seed_runscm_input start.S "tcc-libc/$ARCH/start.S"
- seed_runscm_input sys_stubs.S "tcc-libc/$ARCH/sys_stubs.S"
- seed_runscm_input mem.c tcc-cc/mem.c
- for f in $LIBTCC1_C_SRCS $LIBTCC1_ASM_SRCS; do
- seed_runscm_input "$f" "$TCC_DIR/lib/$f"
- done
-
- seed_runscm_input tcc.flat.c "$TCC_FLAT"
- seed_runscm_input libc.flat.c "$LIBC_FLAT"
- seed_runscm_input hello.c scripts/boot-hello.c
-
- seed_runscm_export tcc1
- seed_runscm_export tcc2
- seed_runscm_export tcc3
- seed_runscm_export s3-crt1.o
- seed_runscm_export s3-libc.a
- seed_runscm_export s3-libtcc1.a
- seed_runscm_export hello
- seed_runscm_run 5400
- ;;
-*) echo "[boot4] unknown DRIVER=$DRIVER" >&2; exit 2 ;;
-esac
+runscm_export tcc1
+runscm_export tcc2
+runscm_export tcc3
+runscm_export s3-crt1.o
+runscm_export s3-libc.a
+runscm_export s3-libtcc1.a
+runscm_export hello
+runscm_run 5400
# ── fixed-point check (host-side) ─────────────────────────────────────
-case "$DRIVER" in
- podman) T2=$STAGE/out/tcc2; T3=$STAGE/out/tcc3 ;;
- seed) T2=$STAGE/dump/tcc2; T3=$STAGE/dump/tcc3 ;;
-esac
-if ! cmp -s "$T2" "$T3"; then
- s2=$(wc -c <"$T2")
- s3=$(wc -c <"$T3")
+if ! cmp -s "$OUT/tcc2" "$OUT/tcc3"; then
+ s2=$(wc -c <"$OUT/tcc2")
+ s3=$(wc -c <"$OUT/tcc3")
echo "[boot4 $ARCH] FIXED-POINT FAIL: tcc2 ($s2) != tcc3 ($s3)" >&2
exit 1
fi
-# ── copy outputs to final destination ─────────────────────────────────
-case "$DRIVER" in
-podman)
- rm -f "$OUT/tcc1" "$OUT/tcc2" \
- "$OUT/start.o" "$OUT/sys_stubs.o" "$OUT/mem.o" "$OUT/libc.o"
- for f in tcc3 crt1.o libc.a libtcc1.a hello; do
- cp "$STAGE/out/$f" "$OUT/$f"
- done
- ;;
-seed)
- # seed_runscm_run already published exports under $OUT; rename
- # the s3- prefix away to match podman's layout.
- [ -f "$OUT/s3-crt1.o" ] && mv "$OUT/s3-crt1.o" "$OUT/crt1.o"
- [ -f "$OUT/s3-libc.a" ] && mv "$OUT/s3-libc.a" "$OUT/libc.a"
- [ -f "$OUT/s3-libtcc1.a" ] && mv "$OUT/s3-libtcc1.a" "$OUT/libtcc1.a"
- rm -f "$OUT/tcc1" "$OUT/tcc2"
- ;;
-esac
+# ── normalize output names (drop s3- prefix; remove intermediate tccN) ─
+mv "$OUT/s3-crt1.o" "$OUT/crt1.o"
+mv "$OUT/s3-libc.a" "$OUT/libc.a"
+mv "$OUT/s3-libtcc1.a" "$OUT/libtcc1.a"
+rm -f "$OUT/tcc1" "$OUT/tcc2"
chmod 0700 "$OUT/tcc3" "$OUT/hello"
echo "[boot4 $ARCH/$DRIVER] sizes: libtcc1.a=$(wc -c <"$OUT/libtcc1.a") libc.a=$(wc -c <"$OUT/libc.a") hello=$(wc -c <"$OUT/hello")"
diff --git a/scripts/boot5-gen-runscm.sh b/scripts/boot5-gen-runscm.sh
@@ -12,17 +12,18 @@
## 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 (in seed tmpfs):
-## musl tree /tmp/musl-1.2.5/<rel-path> (staged in cpio)
-## pre-gen hdrs /tmp/musl-1.2.5/obj/include/bits/{alltypes,syscall}.h,
-## /tmp/musl-1.2.5/obj/src/internal/version.h
-## .o outputs /tmp/musl-1.2.5/obj/<src-with-.o>
-## tcc binary /tcc (basename in cpio)
-## libtcc1.a /libtcc1.a
-## stdarg bridge /tcc-stdarg-bridge.h
-## hello.c /hello.c
-## exports /libc.a, /crt1.o, /crti.o, /crtn.o, /hello (flat at
-## root so seed_runscm_export can pull them by basename)
+## Conventions (cwd-relative; resolves to / under seed init, /work under
+## podman bind-mount):
+## musl tree tmp/musl-1.2.5/<rel-path> (staged in cpio)
+## pre-gen hdrs tmp/musl-1.2.5/obj/include/bits/{alltypes,syscall}.h,
+## tmp/musl-1.2.5/obj/src/internal/version.h
+## .o outputs tmp/musl-1.2.5/obj/<src-with-.o>
+## tcc binary tcc (basename in cpio)
+## libtcc1.a libtcc1.a
+## stdarg bridge tcc-stdarg-bridge.h
+## hello.c hello.c
+## exports libc.a, crt1.o, crti.o, crtn.o, hello (flat at root
+## so runscm_export can pull them by basename)
set -eu
[ "$#" -eq 3 ] || { echo "usage: $0 <musl-arch> <stage-host-dir> <out.scm>" >&2; exit 2; }
@@ -32,7 +33,7 @@ SRCS=$STAGE_HOST/build-srcs.txt
CRT_MODE=$(cat "$STAGE_HOST/crt-mode")
[ -e "$SRCS" ] || { echo "missing $SRCS" >&2; exit 1; }
-CWORK=/tmp/musl-1.2.5
+CWORK=tmp/musl-1.2.5
# 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
@@ -40,19 +41,20 @@ CWORK=/tmp/musl-1.2.5
CFLAGS_BASE_QUOTED='"-std=c99" "-nostdinc" "-ffreestanding" "-fno-strict-aliasing" "-D_XOPEN_SOURCE=700"'
CFLAGS_BASE_QUOTED="$CFLAGS_BASE_QUOTED \"-I$CWORK/arch/$MUSL_ARCH\" \"-I$CWORK/arch/generic\" \"-I$CWORK/obj/src/internal\" \"-I$CWORK/src/include\" \"-I$CWORK/src/internal\" \"-I$CWORK/obj/include\" \"-I$CWORK/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\" \"/tcc-stdarg-bridge.h\""
+CFLAGS_C_QUOTED="$CFLAGS_BASE_QUOTED \"-include\" \"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\""
{
cat <<'PROLOGUE'
-;; boot5 run.scm — drive musl-1.2.5 (~500 TUs) + hello inside seed kernel.
-;; Generated by scripts/boot5-gen-runscm.sh; mirrors scripts/boot5.sh's
-;; podman path stage-for-stage. The musl source tree is staged in cpio at
-;; /tmp/musl-1.2.5/...; per-source .o outputs go to /tmp/musl-1.2.5/obj/...
-;; final artefacts (libc.a, crt1.o, crti.o, crtn.o, hello) land at flat
-;; root paths so the seed-runscm harness can pull them by basename.
+;; 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 at tmp/musl-1.2.5/...; per-source .o outputs go
+;; to tmp/musl-1.2.5/obj/...; final artefacts (libc.a, crt1.o, crti.o,
+;; crtn.o, hello) land at flat root paths so runscm_export can pull them
+;; by basename.
(define (must r tag)
(if (and (car r) (= 0 (cdr r)))
@@ -76,7 +78,7 @@ awk -v CFLAGS_C="$CFLAGS_C_QUOTED" -v CFLAGS_ASM="$CFLAGS_ASM_QUOTED" -v CWORK="
if (src ~ /\.c$/) flags = CFLAGS_C
else if (src ~ /\.[sS]$/) flags = CFLAGS_ASM
else flags = CFLAGS_C
- printf "(must (run \"tcc\" %s \"-c\" \"%s/%s\" \"-o\" \"%s/%s\") \"%s\")\n", \
+ printf "(must (run \"./tcc\" %s \"-c\" \"%s/%s\" \"-o\" \"%s/%s\") \"%s\")\n", \
flags, CWORK, src, CWORK, obj, src
}' "$SRCS"
@@ -85,20 +87,20 @@ 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 "tcc" $CRTFLAGS_C_QUOTED "-fPIC" "-c" "$CWORK/crt/Scrt1.c" "-o" "$CWORK/obj/crt/Scrt1.o") "Scrt1.o")
-(must (run "tcc" $CRTFLAGS_C_QUOTED "-c" "$CWORK/crt/crt1.c" "-o" "$CWORK/obj/crt/crt1.o") "crt1.o")
-(must (run "tcc" $CRTFLAGS_C_QUOTED "-fPIC" "-c" "$CWORK/crt/rcrt1.c" "-o" "$CWORK/obj/crt/rcrt1.o") "rcrt1.o")
+(must (run "./tcc" $CRTFLAGS_C_QUOTED "-fPIC" "-c" "$CWORK/crt/Scrt1.c" "-o" "$CWORK/obj/crt/Scrt1.o") "Scrt1.o")
+(must (run "./tcc" $CRTFLAGS_C_QUOTED "-c" "$CWORK/crt/crt1.c" "-o" "$CWORK/obj/crt/crt1.o") "crt1.o")
+(must (run "./tcc" $CRTFLAGS_C_QUOTED "-fPIC" "-c" "$CWORK/crt/rcrt1.c" "-o" "$CWORK/obj/crt/rcrt1.o") "rcrt1.o")
EOF
if [ "$CRT_MODE" = asm ]; then
cat <<EOF
-(must (run "tcc" $CRTFLAGS_ASM_QUOTED "-c" "$CWORK/crt/$MUSL_ARCH/crti.s" "-o" "$CWORK/obj/crt/crti.o") "crti.o")
-(must (run "tcc" $CRTFLAGS_ASM_QUOTED "-c" "$CWORK/crt/$MUSL_ARCH/crtn.s" "-o" "$CWORK/obj/crt/crtn.o") "crtn.o")
+(must (run "./tcc" $CRTFLAGS_ASM_QUOTED "-c" "$CWORK/crt/$MUSL_ARCH/crti.s" "-o" "$CWORK/obj/crt/crti.o") "crti.o")
+(must (run "./tcc" $CRTFLAGS_ASM_QUOTED "-c" "$CWORK/crt/$MUSL_ARCH/crtn.s" "-o" "$CWORK/obj/crt/crtn.o") "crtn.o")
EOF
else
cat <<EOF
-(must (run "tcc" $CRTFLAGS_C_QUOTED "-c" "$CWORK/crt/crti.c" "-o" "$CWORK/obj/crt/crti.o") "crti.o")
-(must (run "tcc" $CRTFLAGS_C_QUOTED "-c" "$CWORK/crt/crtn.c" "-o" "$CWORK/obj/crt/crtn.o") "crtn.o")
+(must (run "./tcc" $CRTFLAGS_C_QUOTED "-c" "$CWORK/crt/crti.c" "-o" "$CWORK/obj/crt/crti.o") "crti.o")
+(must (run "./tcc" $CRTFLAGS_C_QUOTED "-c" "$CWORK/crt/crtn.c" "-o" "$CWORK/obj/crt/crtn.o") "crtn.o")
EOF
fi
@@ -107,7 +109,7 @@ fi
# ~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 "tcc" "-ar" "rcs" "/libc.a"'
+ printf '(must (run "./tcc" "-ar" "rcs" "libc.a"'
awk -v CWORK="$CWORK" '{
obj = "obj/" $0
sub(/\.[^.]*$/, ".o", obj)
@@ -118,18 +120,16 @@ fi
cat <<EOF
-;; Publish CRT objects at flat root paths so seed_runscm_export can pull them.
-(must (run "catm" "/crt1.o" "$CWORK/obj/crt/crt1.o") "crt1.o publish")
-(must (run "catm" "/crti.o" "$CWORK/obj/crt/crti.o") "crti.o publish")
-(must (run "catm" "/crtn.o" "$CWORK/obj/crt/crtn.o") "crtn.o publish")
+;; Publish CRT objects at flat cwd-relative paths so runscm_export can pull them.
+(must (run "./catm" "crt1.o" "$CWORK/obj/crt/crt1.o") "crt1.o publish")
+(must (run "./catm" "crti.o" "$CWORK/obj/crt/crti.o") "crti.o publish")
+(must (run "./catm" "crtn.o" "$CWORK/obj/crt/crtn.o") "crtn.o publish")
(write-string stdout "boot5: stage D (link hello)\n")
-;; Mirrors boot5.sh's link line, with seed-tmpfs absolute paths in place
-;; of /work/in and /work/out. -L paths pick up libc.a + libtcc1.a from
-;; the flat root of the tmpfs.
-(must (run "tcc" "-static" "-nostdinc" "-nostdlib" "-include" "/tcc-stdarg-bridge.h"
+;; -L. picks up libc.a + libtcc1.a from the flat staging root (cwd).
+(must (run "./tcc" "-static" "-nostdinc" "-nostdlib" "-include" "tcc-stdarg-bridge.h"
"-I$CWORK/include" "-I$CWORK/arch/$MUSL_ARCH" "-I$CWORK/arch/generic" "-I$CWORK/obj/include"
- "/crt1.o" "/hello.c" "-L/" "-lc" "-L/" "-ltcc1" "-L/" "-lc" "-o" "/hello") "link hello")
+ "crt1.o" "hello.c" "-L." "-lc" "-L." "-ltcc1" "-L." "-lc" "-o" "hello") "link hello")
(write-string stdout "boot5: ALL-OK\n")
(exit 0)
diff --git a/scripts/boot5.sh b/scripts/boot5.sh
@@ -30,6 +30,13 @@
## (shared with boot3/boot4; one file,
## three arches gated by #ifdef inside)
##
+## ─── Tools ────────────────────────────────────────────────────────────
+## In container: scratch + busybox (no libc, no /etc, no resolver).
+## scheme1 evaluates a host-generated run.scm (emitted
+## by scripts/boot5-gen-runscm.sh) against the flat
+## staging root (cwd=/work for podman, cwd=/ for the
+## seed kernel). Same run.scm drives both.
+##
## ─── Outputs ─────────────────────────────────────────────────────────
## build/$ARCH/boot5/libc.a
## build/$ARCH/boot5/{crt1.o, crti.o, crtn.o}
@@ -63,7 +70,8 @@ if [ "$DRIVER" = seed ]; then
[ "$ARCH" = aarch64 ] || { echo "[boot5] DRIVER=seed: aarch64 only" >&2; exit 2; }
fi
-IMAGE=boot2-scratch:$ARCH
+IMAGE=boot2-empty:$ARCH
+BOOT2=build/$ARCH/boot2
BOOT4=build/$ARCH/boot4
OUT=build/$ARCH/boot5
STAGE=build/$ARCH/.boot5-stage
@@ -77,6 +85,8 @@ BRIDGE_FILE=build/tcc/stdarg-bridge.h
# ── prerequisites ─────────────────────────────────────────────────────
[ -x "$BOOT4/tcc3" ] || { echo "[boot5 $ARCH] missing $BOOT4/tcc3 (run scripts/boot4.sh $ARCH)" >&2; exit 1; }
[ -e "$BOOT4/libtcc1.a" ] || { echo "[boot5 $ARCH] missing $BOOT4/libtcc1.a (run scripts/boot4.sh $ARCH)" >&2; exit 1; }
+[ -x "$BOOT2/scheme1" ] || { echo "[boot5 $ARCH] missing $BOOT2/scheme1 (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
+[ -x "$BOOT2/catm" ] || { echo "[boot5 $ARCH] missing $BOOT2/catm (run scripts/boot2.sh $ARCH)" >&2; exit 1; }
[ -e "$MUSL_TARBALL" ] || { echo "[boot5 $ARCH] missing $MUSL_TARBALL" >&2; exit 1; }
[ -d "$MUSL_OVERRIDES" ] || { echo "[boot5 $ARCH] missing $MUSL_OVERRIDES" >&2; exit 1; }
[ -e "$MUSL_DELETES" ] || { echo "[boot5 $ARCH] missing $MUSL_DELETES" >&2; exit 1; }
@@ -86,63 +96,46 @@ BRIDGE_FILE=build/tcc/stdarg-bridge.h
if [ "$DRIVER" = podman ] && ! podman image exists "$IMAGE"; then
echo "[boot5 $ARCH] building $IMAGE"
- podman build --platform "$PLATFORM" -t "$IMAGE" \
- -f scripts/Containerfile.scratch scripts/
+ podman build --platform "$PLATFORM" --build-arg "ARCH=$ARCH" -t "$IMAGE" \
+ -f scripts/Containerfile.empty scripts/
fi
if [ "$DRIVER" = seed ]; then
KERNEL_IMAGE=$ROOT/seed-kernel/build/Image
EXTRACT=$ROOT/seed-kernel/scripts/extract-dump.sh
- BOOT2=build/$ARCH/boot2
[ -f "$KERNEL_IMAGE" ] || { echo "[boot5] missing $KERNEL_IMAGE — make in seed-kernel/" >&2; exit 1; }
- [ -x "$BOOT2/scheme1" ] || { echo "[boot5] missing $BOOT2/scheme1 (run boot2)" >&2; exit 1; }
- [ -x "$BOOT2/catm" ] || { echo "[boot5] missing $BOOT2/catm (run boot2)" >&2; exit 1; }
export KERNEL_IMAGE EXTRACT
fi
-
-# ── stage inputs ──────────────────────────────────────────────────────
-# $STAGE/in/ — exactly what the container reads (bind-mounted /work/in)
-# $STAGE/_host/ — host-side scratch (enumeration outputs, intermediates);
-# not visible to the container
-# $STAGE/out/ — container writes here
-rm -rf "$STAGE"
-mkdir -p "$STAGE/in" "$STAGE/_host" "$STAGE/out" "$OUT"
-rm -f "$OUT/libtcc1.a"
-
-cp "$BOOT4/tcc3" "$STAGE/in/tcc"
-cp "$BOOT4/libtcc1.a" "$STAGE/in/libtcc1.a"
-# (No tcc-include/ stage — boot5 compiles musl, which provides its own
-# headers via -I./include / -Iarch/$MUSL_ARCH / -Iarch/generic / -Iobj/include.
-# tcc itself is invoked with -nostdinc so it never reads CONFIG_TCCDIR.)
-
-# Extract musl on the host, then apply overrides + deletes on the host
-# too — gives us a fully-prepared tree at $STAGE/in/musl-1.2.5/ that we
-# can enumerate to drive the (kaem-friendly) flat container script.
-# The container then just `cp -R`s the staged tree into tmpfs (its
-# bind-mounted /work/in is logically read-only).
-tar xzf "$MUSL_TARBALL" -C "$STAGE/in/"
-MUSL_DIR=$STAGE/in/musl-1.2.5
+export IMAGE PLATFORM DRIVER
+
+# ── prepare staging dirs and musl tree on host ────────────────────────
+# $STAGE/cpio/ — flat staging root (becomes /work bind mount or cpio rootfs)
+# $STAGE/_host/ — host-side scratch (enumeration outputs, intermediates);
+# never visible to the container/kernel
+# runscm_init wipes $STAGE then mkdirs cpio/ and out/. Do that first so
+# we control the layout below.
+. scripts/lib-runscm.sh
+runscm_init "$STAGE" "$OUT"
+mkdir -p "$STAGE/_host"
+
+# Extract musl directly into the cpio at tmp/musl-1.2.5/, then apply
+# overrides + deletes — gives us a fully-prepared tree we can enumerate
+# to drive the (kaem-friendly) flat run.scm. The podman bind mount
+# reads it in place; the seed driver registers it via
+# runscm_register_tree below.
+MUSL_DIR=$STAGE/cpio/tmp/musl-1.2.5
+mkdir -p "$STAGE/cpio/tmp"
+tar xzf "$MUSL_TARBALL" -C "$STAGE/cpio/tmp/"
cp -R "$MUSL_OVERRIDES/." "$MUSL_DIR/"
while read -r p; do
[ -n "$p" ] && rm -rf "$MUSL_DIR/$p"
done < "$MUSL_DELETES"
-cp "$BRIDGE_FILE" "$STAGE/in/tcc-stdarg-bridge.h"
-# Pre-generated alltypes.h + syscall.h for $MUSL_ARCH; replace the
-# in-container awk that ran mkalltypes.sed and the SYS_ rewrite. Source
-# of truth is scripts/musl-vendor.sh (regenerates these files).
-cp "$MUSL_GENERATED/alltypes.h" "$STAGE/in/musl-alltypes.h"
-cp "$MUSL_GENERATED/syscall.h" "$STAGE/in/musl-syscall.h"
-# version.h is pre-staged so the container body has no `>` redirection.
-echo '#define VERSION "1.2.5-tcc-boot5"' > "$STAGE/in/musl-version.h"
-
-cp scripts/boot-hello.c "$STAGE/in/hello.c"
-
# ── enumerate musl sources on the host (kaem-friendly: no for/while/
# case/${%}/${#}/$((..)) inside the container) ───────────────────────
# Mirrors musl's Makefile rule: a per-arch override (under
# $d/$MUSL_ARCH/) replaces the same-stem base file (under $d/). We
-# subtract the calibration skip list so the container script never
-# needs an `if $TCC ...; then ok else skip fi` branch.
+# subtract the calibration skip list so the run.scm never needs an
+# `if $TCC ...; then ok else skip fi` branch.
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
@@ -202,171 +195,66 @@ awk -v SKIPF="$MUSL_SKIP" '
{ if (!($0 in skip)) print }
' "$STAGE/_host/keep.txt" > "$STAGE/_host/build-srcs.txt"
-# Per-source-dir mkdir list (unique, for `mkdir -p obj/...`).
-awk '
- {
- sub(/\.[^.]*$/, "")
- if (match($0, /\/[^\/]*$/)) print "obj/" substr($0, 1, RSTART - 1)
- }
-' "$STAGE/_host/build-srcs.txt" | sort -u > "$STAGE/_host/build-objdirs.txt"
-
n_src=$(wc -l < "$STAGE/_host/build-srcs.txt")
n_skip=$(wc -l < "$MUSL_SKIP")
echo "[boot5 $ARCH] keep=$n_src skip=$n_skip (calibrated)"
-# Record CRT mode (asm vs c) so the seed gen-runscm step can read it
-# without re-checking $MUSL_DIR. Same test as the podman branch below.
+# 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
-case "$DRIVER" in
-podman)
-# ── emit flat container build script ──────────────────────────────────
-# Generates a straight-line shell program: mkdir, cp, then one tcc
-# invocation per source, then ar, then link+run hello. No control flow
-# beyond sequential exec, no shell redirection, no `cd`; suitable for a
-# kaem-class shell. All paths are absolute.
-CWORK=/tmp/musl-1.2.5
-CFLAGS_BASE="-std=c99 -nostdinc -ffreestanding -fno-strict-aliasing -D_XOPEN_SOURCE=700 -I$CWORK/arch/$MUSL_ARCH -I$CWORK/arch/generic -I$CWORK/obj/src/internal -I$CWORK/src/include -I$CWORK/src/internal -I$CWORK/obj/include -I$CWORK/include -O2 -fomit-frame-pointer -Werror=implicit-function-declaration -Werror=implicit-int -Werror=pointer-sign -Werror=pointer-arith"
-CFLAGS_C="$CFLAGS_BASE -include /work/in/tcc-stdarg-bridge.h"
-CFLAGS_ASM="$CFLAGS_BASE"
-CRTFLAGS_C="$CFLAGS_C -fno-stack-protector -DCRT"
-CRTFLAGS_ASM="$CFLAGS_ASM -fno-stack-protector -DCRT"
-
-# Resolve the per-arch CRT branch on the host (eliminates the in-
-# container if/then/else).
-if [ -f "$MUSL_DIR/crt/$MUSL_ARCH/crti.s" ]; then
- CRT_LINES_TXT=$(printf '%s\n' \
- "/work/in/tcc $CRTFLAGS_ASM -c $CWORK/crt/$MUSL_ARCH/crti.s -o $CWORK/obj/crt/crti.o" \
- "/work/in/tcc $CRTFLAGS_ASM -c $CWORK/crt/$MUSL_ARCH/crtn.s -o $CWORK/obj/crt/crtn.o")
-else
- CRT_LINES_TXT=$(printf '%s\n' \
- "/work/in/tcc $CRTFLAGS_C -c $CWORK/crt/crti.c -o $CWORK/obj/crt/crti.o" \
- "/work/in/tcc $CRTFLAGS_C -c $CWORK/crt/crtn.c -o $CWORK/obj/crt/crtn.o")
-fi
-
-RUN_SCRIPT=$STAGE/in/run.sh
-{
- echo '#!/bin/sh'
- echo 'set -eu'
- echo
- echo '# stage A: working tree in tmpfs'
- echo "cp -R /work/in/musl-1.2.5 $CWORK"
- echo
- echo '# stage B: pre-generated headers + version stamp'
- echo "mkdir -p $CWORK/obj/include/bits $CWORK/obj/src/internal $CWORK/obj/lib $CWORK/obj/crt $CWORK/lib"
- echo "cp /work/in/musl-alltypes.h $CWORK/obj/include/bits/alltypes.h"
- echo "cp /work/in/musl-syscall.h $CWORK/obj/include/bits/syscall.h"
- echo "cp /work/in/musl-version.h $CWORK/obj/src/internal/version.h"
- echo
- echo '# per-source obj directories'
- while read -r d; do echo "mkdir -p $CWORK/$d"; done < "$STAGE/_host/build-objdirs.txt"
- echo
- echo "# stage C: compile sources ($n_src after calibration)"
- awk -v CC=/work/in/tcc -v CF="$CFLAGS_C" -v AF="$CFLAGS_ASM" -v PFX="$CWORK/" '
- {
- src = $0
- obj = "obj/" src
- sub(/\.[^.]*$/, ".o", obj)
- if (src ~ /\.c$/) flags = CF
- else if (src ~ /\.[sS]$/) flags = AF
- else flags = CF
- print CC " " flags " -c " PFX src " -o " PFX obj
- }
- ' "$STAGE/_host/build-srcs.txt"
- echo
- echo '# stage D: CRT objects'
- echo "/work/in/tcc $CRTFLAGS_C -fPIC -c $CWORK/crt/Scrt1.c -o $CWORK/obj/crt/Scrt1.o"
- echo "/work/in/tcc $CRTFLAGS_C -c $CWORK/crt/crt1.c -o $CWORK/obj/crt/crt1.o"
- echo "/work/in/tcc $CRTFLAGS_C -fPIC -c $CWORK/crt/rcrt1.c -o $CWORK/obj/crt/rcrt1.o"
- printf '%s\n' "$CRT_LINES_TXT"
- echo "cp $CWORK/obj/crt/Scrt1.o $CWORK/obj/crt/crt1.o $CWORK/obj/crt/rcrt1.o $CWORK/obj/crt/crti.o $CWORK/obj/crt/crtn.o $CWORK/lib/"
- echo
- echo '# stage E: archive libc.a'
- printf '/work/in/tcc -ar rcs %s/lib/libc.a' "$CWORK"
- awk -v PFX="$CWORK/" '{ obj = "obj/" $0; sub(/\.[^.]*$/, ".o", obj); printf " %s%s", PFX, obj }' "$STAGE/_host/build-srcs.txt"
- echo
- echo
- echo '# publish artifacts to /work/out'
- echo "cp $CWORK/lib/libc.a $CWORK/lib/crt1.o $CWORK/lib/crti.o $CWORK/lib/crtn.o /work/out/"
- echo
- echo '# stage F: link + run hello'
- echo "/work/in/tcc -static -nostdinc -nostdlib -include /work/in/tcc-stdarg-bridge.h -I$CWORK/include -I$CWORK/arch/$MUSL_ARCH -I$CWORK/arch/generic -I$CWORK/obj/include $CWORK/lib/crt1.o /work/in/hello.c -L$CWORK/lib -lc -L/work/in -ltcc1 -L$CWORK/lib -lc -o /work/out/hello"
- echo 'echo "--- run ---"'
- echo '/work/out/hello a b c'
-} > "$RUN_SCRIPT"
-chmod +x "$RUN_SCRIPT"
-echo "[boot5 $ARCH] generated run.sh: $(wc -l <"$RUN_SCRIPT") lines, $(wc -c <"$RUN_SCRIPT") bytes"
-
-# ── run pipeline in scratch+busybox container ─────────────────────────
-# The container body is a single line: source the host-generated build
-# script. All control flow (loops, conditionals, parameter expansion)
-# was resolved on the host; the container shell sees only sequential
-# `tcc -c …` / `cp` / `mkdir` lines.
-echo "[boot5 $ARCH] boot4/libtcc1.a + musl libc.a + crt -> hello"
-podman run --rm -i --pull=never --platform "$PLATFORM" \
- --tmpfs /tmp:size=1024M \
- -v "$ROOT/$STAGE:/work" -w /work "$IMAGE" \
- sh -eu /work/in/run.sh
-
- ;;
-seed)
- # ── seed-kernel driver: one qemu boot, scheme1 evaluates a host-
- # generated run.scm against tcc/libtcc1.a/musl-tree staged in tmpfs.
- # Outputs (libc.a, crt1.o, crti.o, crtn.o, hello) come back via
- # the UART tmpfs dump. ~1300 (run "tcc" …) calls; the seed
- # kernel's atomic spawn syscall avoids per-fork memcpy.
- . scripts/lib-seed-runscm.sh
- seed_runscm_init "$STAGE/seed" "$OUT"
-
- RUNSCM=$STAGE/seed/run.scm
- scripts/boot5-gen-runscm.sh "$MUSL_ARCH" "$STAGE/_host" "$RUNSCM"
- echo "[boot5 $ARCH/seed] generated run.scm: $(wc -l <"$RUNSCM") lines, $(wc -c <"$RUNSCM") bytes"
-
- seed_runscm_scheme1 "$BOOT2/scheme1"
- seed_runscm_prelude scheme1/prelude.scm
- seed_runscm_runscm "$RUNSCM"
-
- # Chain binaries staged at flat paths; matched by run.scm.
- seed_runscm_input tcc "$BOOT4/tcc3"
- seed_runscm_input libtcc1.a "$BOOT4/libtcc1.a"
- seed_runscm_input catm "$BOOT2/catm"
- seed_runscm_input scheme1 "$BOOT2/scheme1"
- seed_runscm_input tcc-stdarg-bridge.h "$BRIDGE_FILE"
- seed_runscm_input hello.c scripts/boot-hello.c
-
- # Pre-generated headers, staged at the in-tmpfs paths the run.scm
- # references via -I / -include / direct opens.
- seed_runscm_input tmp/musl-1.2.5/obj/include/bits/alltypes.h "$STAGE/in/musl-alltypes.h"
- seed_runscm_input tmp/musl-1.2.5/obj/include/bits/syscall.h "$STAGE/in/musl-syscall.h"
- seed_runscm_input tmp/musl-1.2.5/obj/src/internal/version.h "$STAGE/in/musl-version.h"
-
- # Full musl source tree (overrides + deletes already applied on host).
- seed_runscm_input_tree tmp/musl-1.2.5 "$MUSL_DIR"
-
- seed_runscm_export libc.a
- seed_runscm_export crt1.o
- seed_runscm_export crti.o
- seed_runscm_export crtn.o
- seed_runscm_export hello
-
- # boot5 has ~1300 spawns + heavy tcc work; bump qemu memory + timeout.
- QEMU_MEM=${QEMU_MEM:-3072M} seed_runscm_run "${BOOT5_TIMEOUT:-7200}"
- ;;
-*) echo "[boot5] unknown DRIVER=$DRIVER" >&2; exit 2 ;;
-esac
+# Pre-create per-source obj/ directories under the cpio so scheme1's
+# (run "tcc" -c …) doesn't need to mkdir at runtime (tcc errors out if
+# the parent dir is missing, and scheme1 has no mkdir primitive).
+awk '
+ {
+ sub(/\.[^.]*$/, "")
+ if (match($0, /\/[^\/]*$/)) print "obj/" substr($0, 1, RSTART - 1)
+ }
+' "$STAGE/_host/build-srcs.txt" | sort -u > "$STAGE/_host/build-objdirs.txt"
+mkdir -p "$MUSL_DIR/obj/include/bits" "$MUSL_DIR/obj/src/internal" "$MUSL_DIR/obj/lib" "$MUSL_DIR/obj/crt" "$MUSL_DIR/lib"
+while read -r d; do mkdir -p "$MUSL_DIR/$d"; done < "$STAGE/_host/build-objdirs.txt"
-# ── copy outputs to final destination ────────────────────────────────
-case "$DRIVER" in
- podman) SRC=$STAGE/out ;;
- seed) SRC=$STAGE/seed/dump ;;
-esac
-for f in libc.a crt1.o crti.o crtn.o hello; do
- cp "$SRC/$f" "$OUT/$f"
-done
+# Pre-generated alltypes.h + syscall.h for $MUSL_ARCH; replace the
+# in-container awk that ran mkalltypes.sed and the SYS_ rewrite.
+cp "$MUSL_GENERATED/alltypes.h" "$MUSL_DIR/obj/include/bits/alltypes.h"
+cp "$MUSL_GENERATED/syscall.h" "$MUSL_DIR/obj/include/bits/syscall.h"
+echo '#define VERSION "1.2.5-tcc-boot5"' > "$MUSL_DIR/obj/src/internal/version.h"
+
+# ── generate run.scm and stage chain binaries ─────────────────────────
+RUNSCM=$STAGE/run.scm
+scripts/boot5-gen-runscm.sh "$MUSL_ARCH" "$STAGE/_host" "$RUNSCM"
+echo "[boot5 $ARCH] generated run.scm: $(wc -l <"$RUNSCM") lines, $(wc -c <"$RUNSCM") bytes"
+
+runscm_scheme1 "$BOOT2/scheme1"
+runscm_prelude scheme1/prelude.scm
+runscm_runscm "$RUNSCM"
+
+# Chain binaries staged at flat root (cwd-relative names in run.scm).
+runscm_input tcc "$BOOT4/tcc3"
+runscm_input libtcc1.a "$BOOT4/libtcc1.a"
+runscm_input catm "$BOOT2/catm"
+runscm_input tcc-stdarg-bridge.h "$BRIDGE_FILE"
+runscm_input hello.c scripts/boot-hello.c
+
+# Musl tree is already laid out under $STAGE/cpio/tmp/musl-1.2.5/ above;
+# register names so the seed driver knows which files to pack into cpio.
+# (The podman driver bind-mounts the dir directly and ignores S_NAMES.)
+runscm_register_tree tmp/musl-1.2.5
+
+runscm_export libc.a
+runscm_export crt1.o
+runscm_export crti.o
+runscm_export crtn.o
+runscm_export hello
+
+# boot5 has ~1300 spawns + heavy tcc work; bump qemu memory + timeout for
+# the seed driver. Podman ignores QEMU_MEM and uses host memory directly.
+QEMU_MEM=${QEMU_MEM:-3072M} runscm_run "${BOOT5_TIMEOUT:-7200}"
echo "[boot5 $ARCH/$DRIVER] sizes: libc.a=$(wc -c <"$OUT/libc.a") hello=$(wc -c <"$OUT/hello")"
echo "[boot5 $ARCH/$DRIVER] OK -> $OUT/{libc.a, crt1.o, crti.o, crtn.o, hello}"
diff --git a/scripts/lib-runscm.sh b/scripts/lib-runscm.sh
@@ -0,0 +1,210 @@
+# lib-runscm.sh — driver-agnostic harness for run.scm-driven stages.
+#
+# Boot3/4/5 each drive a per-stage pipeline by invoking scheme1 against a
+# host-generated run.scm. Two transports:
+# DRIVER=podman → bind-mount the staging dir at /work in a scratch+busybox
+# container, exec scheme1 ./combined.scm with cwd=/work.
+# DRIVER=seed → pack the staging dir into a cpio on a virtio-blk read-
+# only disk, boot the seed kernel with init=scheme1 and
+# combined.scm, recover outputs via the SEEDFS dump on a
+# second virtio-blk disk.
+#
+# Both drivers see the same flat namespace: scheme1's cwd is the staging
+# root, and run.scm uses cwd-relative paths exclusively. The host stages
+# inputs once into <staging-dir>/cpio/<name>; runscm_run dispatches.
+#
+# DSL (source as `. scripts/lib-runscm.sh`):
+#
+# runscm_init <staging-dir> <out-dir>
+# runscm_scheme1 <path> # init=scheme1 (boot2)
+# runscm_prelude <path> # scheme1/prelude.scm
+# runscm_runscm <path> # the host-generated driver
+# runscm_input <name> <host-path> # repeatable; staged at cpio/<name>
+# runscm_input_tree <prefix> <src-root> # repeatable; tree under cpio/<prefix>
+# runscm_export <name> # repeatable; out file
+# runscm_run [timeout-s] # default 600s
+#
+# Required env per driver:
+# podman: IMAGE, PLATFORM
+# seed: KERNEL_IMAGE, EXTRACT, optional QEMU_MEM (default 2048M)
+# both: DRIVER=podman|seed
+
+S_STAGE_DIR=
+S_OUT_DIR=
+S_SCHEME1=
+S_PRELUDE=
+S_RUNSCM=
+S_NAMES=
+S_EXPORTS=
+
+runscm_init() {
+ S_STAGE_DIR=$1; S_OUT_DIR=$2
+ rm -rf "$S_STAGE_DIR"
+ mkdir -p "$S_STAGE_DIR/cpio" "$S_OUT_DIR"
+ S_SCHEME1=; S_PRELUDE=; S_RUNSCM=
+ S_NAMES=; S_EXPORTS=
+}
+
+runscm_scheme1() { S_SCHEME1=$1; }
+runscm_prelude() { S_PRELUDE=$1; }
+runscm_runscm() { S_RUNSCM=$1; }
+
+runscm_input() {
+ name=$1; src=$2
+ case "$name" in
+ */*) mkdir -p "$S_STAGE_DIR/cpio/$(dirname "$name")" ;;
+ esac
+ cp "$src" "$S_STAGE_DIR/cpio/$name"
+ S_NAMES="$S_NAMES
+$name"
+}
+
+# Stage every regular file under <src-root> into the cpio at <prefix>/...,
+# preserving the relative directory tree. Names are appended to S_NAMES.
+# The find pipeline runs in a subshell so it can't mutate S_NAMES directly;
+# names are collected via a tempfile then appended at the end.
+runscm_input_tree() {
+ prefix=$1; src_root=$2
+ [ -d "$src_root" ] || { echo "runscm: input_tree: $src_root not a dir" >&2; exit 2; }
+ tmp=$S_STAGE_DIR/.tree-names
+ : > "$tmp"
+ ( cd "$src_root" && find . -type f ) | sed 's|^\./||' | sort | while read -r rel; do
+ [ -n "$rel" ] || continue
+ mkdir -p "$S_STAGE_DIR/cpio/$prefix/$(dirname "$rel")"
+ cp "$src_root/$rel" "$S_STAGE_DIR/cpio/$prefix/$rel"
+ printf '%s/%s\n' "$prefix" "$rel" >> "$tmp"
+ done
+ while read -r n; do
+ S_NAMES="$S_NAMES
+$n"
+ done < "$tmp"
+ rm -f "$tmp"
+}
+
+runscm_export() {
+ S_EXPORTS="$S_EXPORTS $1"
+}
+
+# Like runscm_input_tree but the tree is already laid out under cpio/<prefix>;
+# this just enumerates the regular files so the seed driver's cpio packer
+# includes them. Use when the caller has staged a large source tree in
+# place (e.g. tar xzf … -C <staging-root>/cpio/<prefix>) and wants to
+# avoid a second filesystem copy.
+runscm_register_tree() {
+ prefix=$1
+ [ -d "$S_STAGE_DIR/cpio/$prefix" ] || {
+ echo "runscm: register_tree: cpio/$prefix not a dir" >&2; exit 2; }
+ tmp=$S_STAGE_DIR/.tree-names
+ ( cd "$S_STAGE_DIR/cpio/$prefix" && find . -type f ) | sed 's|^\./||' | sort > "$tmp"
+ while read -r rel; do
+ [ -n "$rel" ] || continue
+ S_NAMES="$S_NAMES
+$prefix/$rel"
+ done < "$tmp"
+ rm -f "$tmp"
+}
+
+runscm_run() {
+ timeout=${1:-600}
+ [ -n "$S_SCHEME1" ] || { echo "runscm: scheme1 not set" >&2; exit 2; }
+ [ -n "$S_PRELUDE" ] || { echo "runscm: prelude not set" >&2; exit 2; }
+ [ -n "$S_RUNSCM" ] || { echo "runscm: run.scm not set" >&2; exit 2; }
+ cp "$S_SCHEME1" "$S_STAGE_DIR/cpio/scheme1"
+ chmod +x "$S_STAGE_DIR/cpio/scheme1"
+ cat "$S_PRELUDE" "$S_RUNSCM" > "$S_STAGE_DIR/cpio/combined.scm"
+ cp "$S_RUNSCM" "$S_STAGE_DIR/cpio/run.scm"
+
+ case "${DRIVER:-podman}" in
+ podman) _runscm_run_podman "$timeout" ;;
+ seed) _runscm_run_seed "$timeout" ;;
+ *) echo "runscm: unknown DRIVER=$DRIVER (expected podman|seed)" >&2; exit 2 ;;
+ esac
+
+ for n in $S_EXPORTS; do
+ if [ ! -f "$S_DUMP_DIR/$n" ]; then
+ echo "[runscm/$DRIVER] FAIL: missing output '$n'" >&2
+ ls "$S_DUMP_DIR" >&2 || true
+ exit 5
+ fi
+ cp "$S_DUMP_DIR/$n" "$S_OUT_DIR/$n"
+ chmod 0700 "$S_OUT_DIR/$n"
+ done
+}
+
+# Podman: bind-mount cpio dir at /work writeable, exec scheme1 there.
+# Outputs land in $cpio/<name> directly via the bind mount.
+_runscm_run_podman() {
+ : "${IMAGE:?lib-runscm: IMAGE not set}"
+ : "${PLATFORM:?lib-runscm: PLATFORM not set}"
+ abs=$(cd "$S_STAGE_DIR/cpio" && pwd)
+ echo "[runscm/podman] scheme1 combined.scm under $IMAGE" >&2
+ podman run --rm -i --pull=never --platform "$PLATFORM" \
+ -v "$abs:/work" -w /work "$IMAGE" \
+ ./scheme1 ./combined.scm
+ S_DUMP_DIR=$S_STAGE_DIR/cpio
+}
+
+# Seed: stage cpio onto virtio-blk hd0 (read-only), boot kernel with
+# init=scheme1, recover outputs from the SEEDFS dump on hd1.
+_runscm_run_seed() {
+ timeout=$1
+ : "${KERNEL_IMAGE:?lib-runscm: KERNEL_IMAGE not set}"
+ : "${EXTRACT:?lib-runscm: EXTRACT not set}"
+ mem=${QEMU_MEM:-2048M}
+ cp "$S_STAGE_DIR/cpio/scheme1" "$S_STAGE_DIR/cpio/init"
+ chmod +x "$S_STAGE_DIR/cpio/init"
+ NAMES="init
+combined.scm
+run.scm
+scheme1$S_NAMES"
+ ( cd "$S_STAGE_DIR/cpio" && printf '%s\n' "$NAMES" | cpio -o -H newc 2>/dev/null ) > "$S_STAGE_DIR/initramfs.cpio"
+ sz=$(wc -c < "$S_STAGE_DIR/initramfs.cpio")
+ pad=$(( (512 - sz % 512) % 512 ))
+ if [ "$pad" -gt 0 ]; then
+ head -c "$pad" /dev/zero >> "$S_STAGE_DIR/initramfs.cpio"
+ fi
+ mv "$S_STAGE_DIR/initramfs.cpio" "$S_STAGE_DIR/in.img"
+ truncate -s 256M "$S_STAGE_DIR/out.img"
+
+ TRANSCRIPT=$S_STAGE_DIR/transcript.txt
+ echo "[runscm/seed] booting scheme1 + run.scm (timeout ${timeout}s)" >&2
+ qemu-system-aarch64 \
+ -machine virt,gic-version=3,accel=hvf -cpu host -m "$mem" \
+ -nographic -no-reboot \
+ -global virtio-mmio.force-legacy=false \
+ -kernel "$KERNEL_IMAGE" \
+ -drive file="$S_STAGE_DIR/in.img",if=none,format=raw,id=hd0,readonly=on \
+ -device virtio-blk-device,drive=hd0 \
+ -drive file="$S_STAGE_DIR/out.img",if=none,format=raw,id=hd1 \
+ -device virtio-blk-device,drive=hd1 \
+ -append "init combined.scm" \
+ > "$TRANSCRIPT" 2>&1 &
+ QPID=$!
+ ( sleep "$timeout"; kill -9 $QPID 2>/dev/null ) </dev/null >/dev/null 2>&1 &
+ WATCHER=$!
+ # `disown` removes the watcher from the shell's job table so that
+ # killing it on the happy path doesn't trigger bash's
+ # "Terminated: 15 PID ( sleep … )" job-status message — that
+ # message looks like a real failure but is just a noisy SIGTERM
+ # notification fired when qemu exited normally before the watcher's
+ # sleep elapsed.
+ disown $WATCHER 2>/dev/null || true
+ wait $QPID 2>/dev/null || true
+ kill $WATCHER 2>/dev/null || true
+
+ mkdir -p "$S_STAGE_DIR/dump"
+ if ! "$EXTRACT" "$S_STAGE_DIR/dump" "$S_STAGE_DIR/out.img" >/dev/null 2>&1; then
+ echo "[runscm/seed] FAIL: extract-blk failed (kernel didn't reach exit?)" >&2
+ tail -40 "$TRANSCRIPT" >&2
+ exit 3
+ fi
+ EXIT_LINE=$(grep -E "user exit_group" "$TRANSCRIPT" | tail -1 || true)
+ case "$EXIT_LINE" in
+ *"exit_group(0)"*) : ;;
+ *) echo "[runscm/seed] FAIL: driver did not exit 0: $EXIT_LINE" >&2
+ tail -40 "$TRANSCRIPT" >&2
+ exit 4 ;;
+ esac
+
+ S_DUMP_DIR=$S_STAGE_DIR/dump
+}
diff --git a/scripts/lib-seed-runscm.sh b/scripts/lib-seed-runscm.sh
@@ -1,143 +0,0 @@
-# lib-seed-runscm.sh — seed-driver harness for boot3/4/5-shaped pipelines.
-#
-# Where lib-pipeline.sh runs one qemu boot per stage (boot0/1/2 shape),
-# this harness runs ONE qemu boot whose /init is scheme1, evaluating a
-# host-generated run.scm that drives the per-bootN pipeline via
-# (spawn …) / (run …) against chain binaries staged as named files in
-# the cpio. Outputs come back through the existing UART-framed tmpfs
-# dump that extract-dump.sh decodes.
-#
-# DSL (source as `. scripts/lib-seed-runscm.sh`):
-#
-# seed_runscm_init <staging-dir> <out-dir>
-# seed_runscm_scheme1 <path> # init=scheme1 (boot2)
-# seed_runscm_prelude <path> # scheme1/prelude.scm
-# seed_runscm_runscm <path> # the host-generated driver
-# seed_runscm_input <name> <host-path> # repeatable; staged in cpio
-# seed_runscm_export <name> # repeatable; out file
-# seed_runscm_run [timeout-s] # default 600s
-#
-# Required env: KERNEL_IMAGE, EXTRACT, QEMU_MEM (default 2048M).
-
-S_STAGE_DIR=
-S_OUT_DIR=
-S_SCHEME1=
-S_PRELUDE=
-S_RUNSCM=
-S_NAMES=
-S_EXPORTS=
-
-seed_runscm_init() {
- S_STAGE_DIR=$1; S_OUT_DIR=$2
- : "${KERNEL_IMAGE:?lib-seed-runscm: KERNEL_IMAGE not set}"
- : "${EXTRACT:?lib-seed-runscm: EXTRACT not set}"
- rm -rf "$S_STAGE_DIR"
- mkdir -p "$S_STAGE_DIR/cpio" "$S_OUT_DIR"
- S_SCHEME1=; S_PRELUDE=; S_RUNSCM=
- S_NAMES=; S_EXPORTS=
-}
-
-seed_runscm_scheme1() { S_SCHEME1=$1; }
-seed_runscm_prelude() { S_PRELUDE=$1; }
-seed_runscm_runscm() { S_RUNSCM=$1; }
-
-seed_runscm_input() {
- name=$1; src=$2
- case "$name" in
- */*) mkdir -p "$S_STAGE_DIR/cpio/$(dirname "$name")" ;;
- esac
- cp "$src" "$S_STAGE_DIR/cpio/$name"
- S_NAMES="$S_NAMES
-$name"
-}
-
-# Stage every regular file under <src-root> into the cpio at <prefix>/...,
-# preserving the relative directory tree. Names are appended to S_NAMES.
-# The find pipeline runs in a subshell so it can't mutate S_NAMES directly;
-# names are collected via a tempfile then appended at the end.
-seed_runscm_input_tree() {
- prefix=$1; src_root=$2
- [ -d "$src_root" ] || { echo "seed-runscm: input_tree: $src_root not a dir" >&2; exit 2; }
- tmp=$S_STAGE_DIR/.tree-names
- : > "$tmp"
- ( cd "$src_root" && find . -type f ) | sed 's|^\./||' | sort | while read -r rel; do
- [ -n "$rel" ] || continue
- mkdir -p "$S_STAGE_DIR/cpio/$prefix/$(dirname "$rel")"
- cp "$src_root/$rel" "$S_STAGE_DIR/cpio/$prefix/$rel"
- printf '%s/%s\n' "$prefix" "$rel" >> "$tmp"
- done
- while read -r n; do
- S_NAMES="$S_NAMES
-$n"
- done < "$tmp"
- rm -f "$tmp"
-}
-
-seed_runscm_export() {
- S_EXPORTS="$S_EXPORTS $1"
-}
-
-seed_runscm_run() {
- timeout=${1:-600}
- mem=${QEMU_MEM:-2048M}
- [ -n "$S_SCHEME1" ] || { echo "seed-runscm: scheme1 not set" >&2; exit 2; }
- [ -n "$S_PRELUDE" ] || { echo "seed-runscm: prelude not set" >&2; exit 2; }
- [ -n "$S_RUNSCM" ] || { echo "seed-runscm: run.scm not set" >&2; exit 2; }
- cp "$S_SCHEME1" "$S_STAGE_DIR/cpio/init"
- chmod +x "$S_STAGE_DIR/cpio/init"
- cat "$S_PRELUDE" "$S_RUNSCM" > "$S_STAGE_DIR/cpio/combined.scm"
- cp "$S_RUNSCM" "$S_STAGE_DIR/cpio/run.scm"
- NAMES="init
-combined.scm
-run.scm$S_NAMES"
- INITRAMFS=$S_STAGE_DIR/initramfs.cpio
- ( cd "$S_STAGE_DIR/cpio" && printf '%s\n' "$NAMES" | cpio -o -H newc 2>/dev/null ) > "$INITRAMFS"
-
- TRANSCRIPT=$S_STAGE_DIR/transcript.txt
- echo "[seed-runscm] booting scheme1 + run.scm (timeout ${timeout}s)" >&2
- qemu-system-aarch64 \
- -machine virt,gic-version=3,accel=hvf -cpu host -m "$mem" \
- -nographic -no-reboot \
- -kernel "$KERNEL_IMAGE" -initrd "$INITRAMFS" \
- -append "init combined.scm dumpfs" \
- > "$TRANSCRIPT" 2>&1 &
- QPID=$!
- ( sleep "$timeout"; kill -9 $QPID 2>/dev/null ) </dev/null >/dev/null 2>&1 &
- WATCHER=$!
- # `disown` removes the watcher from the shell's job table so that
- # killing it on the happy path doesn't trigger bash's
- # "Terminated: 15 PID ( sleep … )" job-status message — that
- # message looks like a real failure but is just a noisy SIGTERM
- # notification fired when qemu exited normally before the watcher's
- # sleep elapsed.
- disown $WATCHER 2>/dev/null || true
- wait $QPID 2>/dev/null || true
- kill $WATCHER 2>/dev/null || true
-
- if ! grep -q '=== DUMP-END ===' "$TRANSCRIPT"; then
- echo "[seed-runscm] FAIL: no DUMP-END in transcript" >&2
- tail -40 "$TRANSCRIPT" >&2
- exit 3
- fi
- EXIT_LINE=$(grep -E "user exit_group" "$TRANSCRIPT" | tail -1 || true)
- case "$EXIT_LINE" in
- *"exit_group(0)"*) : ;;
- *) echo "[seed-runscm] FAIL: driver did not exit 0: $EXIT_LINE" >&2
- tail -40 "$TRANSCRIPT" >&2
- exit 4 ;;
- esac
-
- mkdir -p "$S_STAGE_DIR/dump"
- "$EXTRACT" "$S_STAGE_DIR/dump" "$TRANSCRIPT" >/dev/null 2>&1 || \
- "$EXTRACT" "$S_STAGE_DIR/dump" "$TRANSCRIPT" >&2
-
- for n in $S_EXPORTS; do
- if [ ! -f "$S_STAGE_DIR/dump/$n" ]; then
- echo "[seed-runscm] FAIL: missing output '$n'" >&2
- ls "$S_STAGE_DIR/dump" >&2 || true
- exit 5
- fi
- cp "$S_STAGE_DIR/dump/$n" "$S_OUT_DIR/$n"
- chmod 0700 "$S_OUT_DIR/$n"
- done
-}