boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

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:
Ascripts/Containerfile.empty | 17+++++++++++++++++
Mscripts/boot3-run.scm | 14+++++++-------
Mscripts/boot3.sh | 128++++++++++++++++++++++---------------------------------------------------------
Mscripts/boot4-gen-runscm.sh | 38++++++++++++++++++++++++--------------
Mscripts/boot4.sh | 262+++++++++++++++----------------------------------------------------------------
Mscripts/boot5-gen-runscm.sh | 74+++++++++++++++++++++++++++++++++++++-------------------------------------
Mscripts/boot5.sh | 280++++++++++++++++++++++++-------------------------------------------------------
Ascripts/lib-runscm.sh | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dscripts/lib-seed-runscm.sh | 143-------------------------------------------------------------------------------
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 -}