commit 8dbebe704dfca3ef0d2a69b99862f1a5f7fa5f4f
parent e21919d9ee8fa9e95220ff98f3fb11f49f35cee3
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 4 May 2026 14:46:27 -0700
Move boot3 runtime closure into boot3 outputs
Diffstat:
5 files changed, 158 insertions(+), 102 deletions(-)
diff --git a/docs/MUSL.md b/docs/MUSL.md
@@ -21,7 +21,8 @@ scripts/boot4.sh <amd64|aarch64|riscv64>
| Path | Purpose |
|------|---------|
| `build/$ARCH/boot3/tcc3` | fixed-point self-host tcc from boot3 |
-| `build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/{include,lib}` | staged tcc headers and libtcc1 sources |
+| `build/$ARCH/boot3/libtcc1.a` | tcc runtime archive produced by boot3 |
+| `build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/include` | staged tcc headers |
| `vendor/upstream/musl-1.2.5.tar.gz` | pristine upstream musl source |
| `vendor/upstream/musl-1.2.5-tcc.patch` | tcc-compat musl patch |
| `scripts/boot4-musl-shim-$ARCH.h` | per-arch `__builtin_va_list` bridge |
@@ -40,7 +41,6 @@ Architecture mapping:
| File | Purpose |
|------|---------|
-| `libtcc1.a` | tcc runtime archive used when linking musl-built programs |
| `libc.a` | static musl libc archive |
| `crt1.o`, `crti.o`, `crtn.o` | static startup and init/fini CRT objects |
| `hello` | static smoke-test ELF linked by boot4 |
@@ -49,19 +49,18 @@ The staging copy under `build/$ARCH/.boot4-stage/` is disposable.
## Pipeline
-1. Copy boot3 `tcc3`, tcc headers, tcc runtime sources, musl tarball,
+1. Copy boot3 `tcc3`, boot3 `libtcc1.a`, tcc headers, musl tarball,
musl patch, and the per-arch shim into `build/$ARCH/.boot4-stage/in`.
-2. Build `libtcc1.a` with the boot3 tcc.
-3. Extract musl, apply `musl-1.2.5-tcc.patch`, and remove unsupported
+2. Extract musl, apply `musl-1.2.5-tcc.patch`, and remove unsupported
arch-specific override files so portable C fallbacks are selected
where possible.
-4. Configure musl with `CC=$TCC AR=true RANLIB=true`, `--disable-shared`,
+3. Configure musl with `CC=$TCC AR=true RANLIB=true`, `--disable-shared`,
and `--disable-wrapper`.
-5. Generate `bits/alltypes.h`, `bits/syscall.h`, and `version.h`.
-6. Compile all selected musl sources. Sources that fail to compile are
+4. Generate `bits/alltypes.h`, `bits/syscall.h`, and `version.h`.
+5. Compile all selected musl sources. Sources that fail to compile are
skipped and reported; boot4 requires the remaining closure to archive,
link, and run hello.
-7. Build CRT objects, archive `libc.a`, link static `hello`, and execute
+6. Build CRT objects, archive `libc.a`, link static `hello`, and execute
it inside the target container.
Assembler inputs must not receive the va-list shim. tcc 0.9.26 applies
diff --git a/scripts/boot.sh b/scripts/boot.sh
@@ -8,3 +8,4 @@ ARCH=$1
./scripts/boot1.sh $ARCH
./scripts/boot2.sh $ARCH
./scripts/boot3.sh $ARCH
+./scripts/boot4.sh $ARCH
diff --git a/scripts/boot3.sh b/scripts/boot3.sh
@@ -46,10 +46,15 @@
## tcc-libc/va_list_shim.h — gcc/tcc va_list bridge
## tcc-cc/mem.c — memcpy/memmove/memset/memcmp
## build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/include/** (whole tree)
+## vendor/mes-libc/include/** — mes-libc headers for hello
+## build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/lib/libtcc1.c
+## (amd64: generic compiler helper runtime)
## build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/lib/lib-arm64.c
## (aarch64 + riscv64: TFmode soft-float)
## build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/lib/va_list.c
## (amd64: __va_start / __va_arg)
+## build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/lib/alloca86_64*.S
+## (amd64: alloca helpers)
## build/tcc/$TCC_TARGET/tcc.flat.c — flattened tcc TU
## build/$ARCH/vendor/mes-libc/libc.flat.c — flattened mes-libc TU
##
@@ -66,10 +71,16 @@
## tcc-0.9.26 (see docs/TCC-ARM64-ASM.md).
##
## ─── Outputs ──────────────────────────────────────────────────────────
-## build/$ARCH/boot3/tcc0 — cc.scm-built tcc (compile 1)
-## build/$ARCH/boot3/tcc1 — tcc0-built tcc (compile 2)
-## build/$ARCH/boot3/tcc2 — tcc1-built tcc (compile 3)
-## build/$ARCH/boot3/tcc3 — tcc2-built tcc (compile 4)
+## build/$ARCH/boot3/tcc3 — final fixed-point self-host tcc
+## build/$ARCH/boot3/crt1.o
+## — tcc2-built startup object, kept outside
+## libc.a because it must lead link lines
+## build/$ARCH/boot3/libc.a
+## — tcc2-built archive of sys_stubs.o + mem.o
+## + libc.o
+## build/$ARCH/boot3/libtcc1.a
+## — tcc2-built tcc compiler helper archive
+## build/$ARCH/boot3/hello — mes-libc-linked smoke binary, run here
## tcc2 and tcc3 are byte-identical (asserted at the end of this
## script) — that equality is the fixed-point check.
##
@@ -87,17 +98,26 @@ case "$ARCH" in
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" ;;
+ 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" ;;
+ 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" ;;
+ 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
@@ -140,10 +160,15 @@ if [ ! -e "$LIBC_FLAT" ]; then
echo "[boot3 $ARCH] flatten libc.flat.c (host)"
scripts/libc-flatten.sh --arch "$ARCH"
fi
+for f in $LIBTCC1_C_SRCS $LIBTCC1_ASM_SRCS; do
+ [ -e "$TCC_DIR/lib/$f" ] || { echo "[boot3 $ARCH] missing $TCC_DIR/lib/$f" >&2; exit 1; }
+done
# ── reset staging, copy inputs explicitly ─────────────────────────────
rm -rf "$STAGE"
-mkdir -p "$STAGE/in" "$STAGE/in/tcc-include" "$STAGE/out" "$OUT"
+mkdir -p "$STAGE/in" "$STAGE/in/tcc-include" "$STAGE/in/mes-include" "$STAGE/in/tcc-lib" "$STAGE/out" "$OUT"
+rm -f "$OUT/tcc0" "$OUT/tcc1" "$OUT/tcc2" \
+ "$OUT/start.o" "$OUT/sys_stubs.o" "$OUT/mem.o" "$OUT/libc.o"
# Prior-stage binaries
cp "$BOOT1/catm" "$STAGE/in/catm"
@@ -172,6 +197,9 @@ cp tcc-cc/mem.c "$STAGE/in/mem.c"
# Per-arch libtcc1 helper source
cp "$TCC_DIR/lib/$LIB_HELPER_SRC" "$STAGE/in/$LIB_HELPER_SRC"
+for f in $LIBTCC1_C_SRCS $LIBTCC1_ASM_SRCS; do
+ cp "$TCC_DIR/lib/$f" "$STAGE/in/tcc-lib/$f"
+done
# Flattened TUs
cp "$TCC_FLAT" "$STAGE/in/tcc.flat.c"
@@ -180,6 +208,25 @@ cp "$LIBC_FLAT" "$STAGE/in/libc.flat.c"
# tcc include tree (small, < 200KB) — copied wholesale so tcc0's
# -I resolves stdarg.h etc. Recursive cp keeps directory layout.
cp -R "$TCC_DIR/include/." "$STAGE/in/tcc-include/"
+cp -R vendor/mes-libc/include/. "$STAGE/in/mes-include/"
+
+cat > "$STAGE/in/hello.c" <<'EOF'
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+extern char *strdup (char const *);
+
+int
+main (int argc, char **argv)
+{
+ printf ("hello from boot3 (tcc-built mes libc); argc=%d\n", argc);
+ char *s = strdup ("works");
+ printf ("strdup: %s, strlen: %d\n", s, (int) strlen (s));
+ free (s);
+ return 0;
+}
+EOF
# Every arch's tcc-boot2 has CONFIG_TCC_ASM and assembles .S inputs
# itself inside the container — no host cross-asm step.
@@ -198,11 +245,15 @@ podman run --rm -i --pull=never --platform "$PLATFORM" \
-e LIB_HELPER_SRC="$LIB_HELPER_SRC" \
-e LIB_HELPER_OBJ="$LIB_HELPER_OBJ" \
-e LIB_HELPER_DEFINES="$LIB_HELPER_DEFINES" \
+ -e LIBTCC1_C_SRCS="$LIBTCC1_C_SRCS" \
+ -e LIBTCC1_C_DEFS="$LIBTCC1_C_DEFS" \
+ -e LIBTCC1_ASM_SRCS="$LIBTCC1_ASM_SRCS" \
-v "$ROOT/$STAGE:/work" -w /work "$IMAGE" \
sh -eu -s <<'CONTAINER'
IN=/work/in
OUT=/work/out
TCC_INC=$IN/tcc-include
+MES_INC=$IN/mes-include
# ── Stage A.1: catm cc.scm bundle ─────────────────────────────────────
$IN/catm /tmp/cc-bundled.scm $IN/prelude.scm $IN/cc.scm $IN/main.scm
@@ -241,6 +292,41 @@ build_helpers() {
"$cc" -nostdlib -I "$TCC_INC" $LIB_HELPER_DEFINES \
-c -o "$workdir/$LIB_HELPER_OBJ" "$IN/$LIB_HELPER_SRC"
}
+build_libtcc1() {
+ cc=$1; workdir=$2; out=$3
+ mkdir -p "$workdir"
+ objs=""
+ # shellcheck disable=SC2086 # LIBTCC1_C_SRCS intentionally word-split
+ for src in $LIBTCC1_C_SRCS; do
+ obj="$workdir/${src%.c}.o"
+ # shellcheck disable=SC2086 # LIBTCC1_C_DEFS intentionally word-split
+ "$cc" -nostdlib -I "$TCC_INC" $LIBTCC1_C_DEFS \
+ -c -o "$obj" "$IN/tcc-lib/$src"
+ objs="$objs $obj"
+ done
+ # shellcheck disable=SC2086 # LIBTCC1_ASM_SRCS intentionally word-split
+ for src in $LIBTCC1_ASM_SRCS; do
+ obj="$workdir/${src%.S}.o"
+ "$cc" -nostdlib -c -o "$obj" "$IN/tcc-lib/$src"
+ objs="$objs $obj"
+ done
+ # shellcheck disable=SC2086 # archive object list intentionally word-split
+ "$cc" -ar rcs "$out" $objs
+}
+archive_runtime() {
+ cc=$1; workdir=$2
+ cp "$workdir/start.o" "$workdir/crt1.o"
+ "$cc" -ar rcs "$workdir/libc.a" \
+ "$workdir/sys_stubs.o" "$workdir/mem.o" "$workdir/libc.o"
+ build_libtcc1 "$cc" "$workdir/libtcc1-obj" "$workdir/libtcc1.a"
+}
+link_tcc_with_runtime() {
+ cc=$1; workdir=$2; out=$3
+ "$cc" -nostdlib -I "$TCC_INC" -include "$IN/va_list_shim.h" \
+ "$workdir/crt1.o" "$IN/tcc.flat.c" \
+ "$workdir/libc.a" "$workdir/libtcc1.a" "$workdir/libc.a" \
+ -o "$out"
+}
mkdir -p /tmp/stage1 /tmp/stage2 /tmp/stage3
build_asm $OUT/tcc0 /tmp/stage1
build_helpers $OUT/tcc0 /tmp/stage1
@@ -256,27 +342,21 @@ chmod +x $OUT/tcc1
# ── Stage D: tcc1 rebuilds helpers, links tcc2 ────────────────────────
build_asm $OUT/tcc1 /tmp/stage2
build_helpers $OUT/tcc1 /tmp/stage2
-$OUT/tcc1 -nostdlib -I "$TCC_INC" -include $IN/va_list_shim.h \
- /tmp/stage2/start.o /tmp/stage2/sys_stubs.o \
- /tmp/stage2/mem.o /tmp/stage2/libc.o \
- /tmp/stage2/$LIB_HELPER_OBJ \
- $IN/tcc.flat.c -o $OUT/tcc2
+archive_runtime $OUT/tcc1 /tmp/stage2
+link_tcc_with_runtime $OUT/tcc1 /tmp/stage2 $OUT/tcc2
chmod +x $OUT/tcc2
-# ── Stage E: tcc2 rebuilds helpers, links tcc3 ────────────────────────
-# Self-host idempotence check: tcc2 compiling itself with its own
-# helpers must produce a byte-identical binary. This is the real
-# bootstrap fixed point — tcc0 → tcc1 isn't expected to converge
-# because cc.scm's emitted machine code introduces subtle codegen
-# differences in tcc0's behavior, but from tcc1 onward the chain
-# is tcc compiling tcc.
+# ── Stage E: tcc2 rebuilds the runtime closure, links tcc3 ────────────
+# Self-host idempotence check: tcc2 compiling itself with the same
+# source + crt1.o + libc.a + libtcc1.a shape used for tcc2 must produce
+# a byte-identical binary. This is the real bootstrap fixed point —
+# tcc0 → tcc1 isn't expected to converge because cc.scm's emitted
+# machine code introduces subtle codegen differences in tcc0's behavior,
+# but from tcc1 onward the chain is tcc compiling tcc.
build_asm $OUT/tcc2 /tmp/stage3
build_helpers $OUT/tcc2 /tmp/stage3
-$OUT/tcc2 -nostdlib -I "$TCC_INC" -include $IN/va_list_shim.h \
- /tmp/stage3/start.o /tmp/stage3/sys_stubs.o \
- /tmp/stage3/mem.o /tmp/stage3/libc.o \
- /tmp/stage3/$LIB_HELPER_OBJ \
- $IN/tcc.flat.c -o $OUT/tcc3
+archive_runtime $OUT/tcc2 /tmp/stage3
+link_tcc_with_runtime $OUT/tcc2 /tmp/stage3 $OUT/tcc3
chmod +x $OUT/tcc3
if ! cmp -s $OUT/tcc2 $OUT/tcc3; then
@@ -285,12 +365,33 @@ if ! cmp -s $OUT/tcc2 $OUT/tcc3; then
echo "[boot3] FIXED-POINT FAIL: tcc2 ($s2) != tcc3 ($s3)" >&2
exit 1
fi
+
+# Publish the tcc2-built mes-libc link closure and prove it can link
+# and run a normal C program. tcc2 and tcc3 are byte-identical by the
+# check above, so rebuilding these artifacts with tcc3 would only repeat
+# the same fixed-point compiler cycle. crt1.o is kept separate because
+# it must lead the final link; the rest of the runtime is hidden behind
+# libc.a.
+cp /tmp/stage3/crt1.o /tmp/stage3/libc.a /tmp/stage3/libtcc1.a "$OUT/"
+
+"$OUT/tcc2" -nostdlib -I "$TCC_INC" -I "$MES_INC" \
+ "$OUT/crt1.o" "$IN/hello.c" \
+ "$OUT/libc.a" "$OUT/libtcc1.a" "$OUT/libc.a" \
+ -o "$OUT/hello"
+chmod +x "$OUT/hello"
+echo "libtcc1.a: $(wc -c <"$OUT/libtcc1.a") bytes"
+echo "libc.a: $(wc -c <"$OUT/libc.a") bytes"
+echo "hello: $(wc -c <"$OUT/hello") bytes"
+echo "--- run ---"
+"$OUT/hello" a b c
CONTAINER
# ── copy outputs to final destination ─────────────────────────────────
-for f in tcc0 tcc1 tcc2 tcc3; do
+rm -f "$OUT/tcc0" "$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"
- chmod 0700 "$OUT/$f"
done
+chmod 0700 "$OUT/tcc3" "$OUT/hello"
-echo "[boot3 $ARCH] OK -> $OUT/{tcc0, tcc1, tcc2, tcc3} (fixed point: tcc2 == tcc3)"
+echo "[boot3 $ARCH] OK -> $OUT/{tcc3, crt1.o, libc.a, libtcc1.a, hello} (fixed point: tcc2 == tcc3)"
diff --git a/scripts/boot4-musl-shim-amd64.h b/scripts/boot4-musl-shim-amd64.h
@@ -15,8 +15,8 @@
*
* Layout matches tcc/lib/va_list.c — those four fields are the SysV
* x86_64 ABI register-save struct that __va_start initializes and
- * __va_arg walks. libtcc1.a (built earlier in this script from
- * tcc/lib/va_list.c) provides __va_start and __va_arg at link time. */
+ * __va_arg walks. boot3's libtcc1.a provides __va_start and __va_arg
+ * at link time. */
typedef struct {
unsigned int gp_offset;
diff --git a/scripts/boot4.sh b/scripts/boot4.sh
@@ -1,5 +1,5 @@
#!/bin/sh
-## boot4.sh — build musl-1.2.5 with the boot3 tcc and link a hello world.
+## boot4.sh — build musl-1.2.5 with boot3 artifacts and link hello.
##
## Builds on top of boot3's verified-fixed-point tcc (tcc2 == tcc3) and
## demonstrates that the same compiler can produce a working static libc
@@ -10,7 +10,9 @@
## ─── Inputs ──────────────────────────────────────────────────────────
## build/$ARCH/boot3/tcc3
## — boot3's verified self-host tcc
-## build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/{lib,include}
+## build/$ARCH/boot3/libtcc1.a
+## — boot3's tcc runtime archive
+## build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/include
## — staged by stage1-flatten.sh during boot3
## vendor/upstream/musl-1.2.5.tar.gz
## — pristine musl source
@@ -21,15 +23,13 @@
## — per-arch __builtin_va_list bridge
##
## ─── Outputs ─────────────────────────────────────────────────────────
-## build/$ARCH/boot4/libtcc1.a
## build/$ARCH/boot4/libc.a
## build/$ARCH/boot4/{crt1.o, crti.o, crtn.o}
## build/$ARCH/boot4/hello — static, runs in the container
##
## Usage: scripts/boot4.sh <arch>
## <arch> ∈ {amd64, aarch64, riscv64}
-## Only amd64 is verified end-to-end; aarch64/riscv64 are wired up
-## but expected to fail until per-arch musl patches and shims land.
+## All three architectures are verified end-to-end.
set -eu
@@ -42,31 +42,16 @@ case "$ARCH" in
PLATFORM=linux/amd64
TCC_TARGET=X86_64
MUSL_ARCH=x86_64
- # libtcc1: libtcc1.c (with TCC_TARGET_X86_64) + alloca .S helpers + va_list.c
- 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"
;;
aarch64)
PLATFORM=linux/arm64
TCC_TARGET=ARM64
MUSL_ARCH=aarch64
- # libtcc1: lib-arm64.c only (TFmode soft-float). Upstream tcc
- # 0.9.26's OBJ-arm64 list has no .S helpers; the in-tree
- # arm64-asm.c (docs/TCC-ARM64-ASM.md) makes any musl .S file
- # assemblable in-container, no host cross-asm needed.
- 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=""
;;
riscv64)
PLATFORM=linux/riscv64
TCC_TARGET=RISCV64
MUSL_ARCH=riscv64
- # libtcc1: lib-arm64.c (the same TFmode helper covers riscv64).
- LIBTCC1_C_SRCS="lib-arm64.c"
- LIBTCC1_C_DEFS="-D HAVE_CONFIG_H=1 -D TCC_TARGET_RISCV64=1"
- LIBTCC1_ASM_SRCS=""
;;
*)
usage
@@ -87,17 +72,12 @@ SHIM_FILE=scripts/boot4-musl-shim-$ARCH.h
# ── prerequisites ─────────────────────────────────────────────────────
[ -x "$BOOT3/tcc3" ] || { echo "[boot4 $ARCH] missing $BOOT3/tcc3 (run scripts/boot3.sh $ARCH)" >&2; exit 1; }
+[ -e "$BOOT3/libtcc1.a" ] || { echo "[boot4 $ARCH] missing $BOOT3/libtcc1.a (run scripts/boot3.sh $ARCH)" >&2; exit 1; }
[ -d "$TCC_DIR/include" ] || { echo "[boot4 $ARCH] missing $TCC_DIR/include (run scripts/boot3.sh $ARCH first)" >&2; exit 1; }
[ -e "$MUSL_TARBALL" ] || { echo "[boot4 $ARCH] missing $MUSL_TARBALL" >&2; exit 1; }
[ -e "$MUSL_PATCH" ] || { echo "[boot4 $ARCH] missing $MUSL_PATCH" >&2; exit 1; }
[ -e "$SHIM_FILE" ] || { echo "[boot4 $ARCH] missing $SHIM_FILE" >&2; exit 1; }
-# Sanity-check the per-arch tcc helper sources exist before we spin up
-# the container.
-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
-
if ! podman image exists "$IMAGE"; then
echo "[boot4 $ARCH] building $IMAGE"
podman build --platform "$PLATFORM" -t "$IMAGE" \
@@ -106,13 +86,12 @@ fi
# ── stage inputs ──────────────────────────────────────────────────────
rm -rf "$STAGE"
-mkdir -p "$STAGE/in/tcc-include" "$STAGE/in/tcc-lib" "$STAGE/out" "$OUT"
+mkdir -p "$STAGE/in/tcc-include" "$STAGE/out" "$OUT"
+rm -f "$OUT/libtcc1.a"
cp "$BOOT3/tcc3" "$STAGE/in/tcc"
+cp "$BOOT3/libtcc1.a" "$STAGE/in/libtcc1.a"
cp -R "$TCC_DIR/include/." "$STAGE/in/tcc-include/"
-for f in $LIBTCC1_C_SRCS $LIBTCC1_ASM_SRCS; do
- cp "$TCC_DIR/lib/$f" "$STAGE/in/tcc-lib/$f"
-done
cp "$MUSL_TARBALL" "$STAGE/in/musl.tar.gz"
cp "$MUSL_PATCH" "$STAGE/in/musl.patch"
@@ -132,14 +111,11 @@ int main(int argc, char **argv) {
EOF
# ── run pipeline in scratch+busybox container ─────────────────────────
-echo "[boot4 $ARCH] libtcc1.a -> musl libc.a + crt -> hello"
+echo "[boot4 $ARCH] boot3/libtcc1.a + musl libc.a + crt -> hello"
podman run --rm -i --pull=never --platform "$PLATFORM" \
--tmpfs /tmp:size=1024M \
-e ARCH="$ARCH" \
-e MUSL_ARCH="$MUSL_ARCH" \
- -e LIBTCC1_C_SRCS="$LIBTCC1_C_SRCS" \
- -e LIBTCC1_C_DEFS="$LIBTCC1_C_DEFS" \
- -e LIBTCC1_ASM_SRCS="$LIBTCC1_ASM_SRCS" \
-v "$ROOT/$STAGE:/work" -w /work "$IMAGE" \
sh -eu -s <<'CONTAINER'
IN=/work/in
@@ -147,28 +123,7 @@ OUT=/work/out
TCC=$IN/tcc
TCC_INC=$IN/tcc-include
-# ── Stage A: libtcc1.a (tcc compiles its own runtime) ─────────────────
-mkdir -p /tmp/lib1
-cd /tmp/lib1
-LIBTCC1_OBJS=""
-# shellcheck disable=SC2086
-for src in $LIBTCC1_C_SRCS; do
- obj="${src%.c}.o"
- # shellcheck disable=SC2086
- $TCC -nostdlib -I "$TCC_INC" $LIBTCC1_C_DEFS -c "$IN/tcc-lib/$src" -o "$obj"
- LIBTCC1_OBJS="$LIBTCC1_OBJS $obj"
-done
-# shellcheck disable=SC2086
-for src in $LIBTCC1_ASM_SRCS; do
- obj="${src%.S}.o"
- $TCC -nostdlib -c "$IN/tcc-lib/$src" -o "$obj"
- LIBTCC1_OBJS="$LIBTCC1_OBJS $obj"
-done
-# shellcheck disable=SC2086
-$TCC -ar rcs "$OUT/libtcc1.a" $LIBTCC1_OBJS
-echo "libtcc1.a: $(wc -c <"$OUT/libtcc1.a") bytes"
-
-# ── Stage B: extract + patch musl ─────────────────────────────────────
+# ── Stage A: extract + patch musl ─────────────────────────────────────
cd /tmp
tar xzf "$IN/musl.tar.gz"
patch -p1 < "$IN/musl.patch"
@@ -220,7 +175,7 @@ rm -f musl-1.2.5/src/thread/riscv64/__unmapself.s
# __libc_start_main path; linker would fail at start otherwise.)
rm -f musl-1.2.5/src/process/riscv64/vfork.s
-# ── Stage C: configure + generate musl headers ────────────────────────
+# ── Stage B: configure + generate musl headers ────────────────────────
cd /tmp/musl-1.2.5
CC=$TCC AR=true RANLIB=true sh ./configure \
--target=$MUSL_ARCH-linux-musl --disable-shared --disable-wrapper \
@@ -237,7 +192,7 @@ sed -n -e 's/__NR_/SYS_/p' < arch/$MUSL_ARCH/bits/syscall.h.in \
>> obj/include/bits/syscall.h
echo '#define VERSION "1.2.5-tcc-boot4"' > obj/src/internal/version.h
-# ── Stage D: compile every musl source ────────────────────────────────
+# ── Stage C: compile every musl source ────────────────────────────────
# NB: -include is applied only to .c sources. tcc 0.9.26 also prepends
# -include content to .s/.S inputs, silently producing a 620-byte ELF
# with no symbols (the C content corrupts the assembler stream).
@@ -317,7 +272,7 @@ echo " compiled=$n_ok skipped=$n_skip / $n_total"
echo "--- first 30 skipped ---"
head -30 /tmp/skipped.txt
-# ── Stage E: CRT + libc.a ─────────────────────────────────────────────
+# ── Stage D: CRT + libc.a ─────────────────────────────────────────────
# musl's per-arch crti.s/crtn.s wins over the top-level crt/crti.c if
# present; riscv64 has no crt/$MUSL_ARCH/ at all, so it falls back.
mkdir -p lib obj/crt
@@ -339,12 +294,12 @@ $TCC -ar rcs lib/libc.a $OBJS
cp lib/libc.a lib/crt1.o lib/crti.o lib/crtn.o "$OUT/"
echo "libc.a: $(wc -c <"$OUT/libc.a") bytes"
-# ── Stage F: smoke test — link + run hello ────────────────────────────
+# ── Stage E: smoke test — link + run hello ────────────────────────────
$TCC -static -nostdinc -nostdlib \
-include "$IN/musl-shim.h" \
-I./include -I./arch/$MUSL_ARCH -I./arch/generic -Iobj/include \
lib/crt1.o "$IN/hello.c" \
- -L./lib -lc -L"$OUT" -ltcc1 -L./lib -lc \
+ -L./lib -lc -L"$IN" -ltcc1 -L./lib -lc \
-o "$OUT/hello"
echo "hello: $(wc -c <"$OUT/hello") bytes"
@@ -353,8 +308,8 @@ echo "--- run ---"
CONTAINER
# ── copy outputs to final destination ────────────────────────────────
-for f in libtcc1.a libc.a crt1.o crti.o crtn.o hello; do
+for f in libc.a crt1.o crti.o crtn.o hello; do
cp "$STAGE/out/$f" "$OUT/$f"
done
-echo "[boot4 $ARCH] OK -> $OUT/{libtcc1.a, libc.a, crt1.o, crti.o, crtn.o, hello}"
+echo "[boot4 $ARCH] OK -> $OUT/{libc.a, crt1.o, crti.o, crtn.o, hello}"