commit 118cb9efe3506aae8e345506e12688ebc8856c4d parent 34d1a969385623fc738ff411b2c83b926bcfdef6 Author: Ryan Sepassi <rsepassi@gmail.com> Date: Sat, 18 Jul 2026 10:11:55 -0700 toolchain: make installed tcc self-contained Diffstat:
25 files changed, 310 insertions(+), 90 deletions(-)
diff --git a/Makefile b/Makefile @@ -122,6 +122,7 @@ PREP_SRC_COMMON_SRCS := \ bootprep/boot5-enumerate.sh bootprep/boot5-gen-runscm.sh \ bootprep/boot6-gen-runscm.sh \ bootprep/assets/boot3-run.scm bootprep/assets/boot-hello.c \ + bootprep/assets/toolchain-hello.c \ M1pp/M1pp.P1 hex2pp/hex2pp.P1 \ P1/P1.M1pp P1/P1pp.P1pp \ P1/entry-libc.P1pp P1/entry-plain.P1pp P1/elf-end.P1pp \ @@ -131,7 +132,12 @@ PREP_SRC_COMMON_SRCS := \ tcc/cc/mem.c \ seed-kernel/kernel.c \ vendor/musl/1.2.5.tar.gz \ - vendor/musl/deletes.txt + vendor/musl/deletes.txt \ + $(wildcard vendor/tcc/patches-lb/*.before) \ + $(wildcard vendor/tcc/patches-lb/*.after) \ + $(wildcard vendor/tcc/patches/*.before) \ + $(wildcard vendor/tcc/patches/*.after) \ + $(wildcard vendor/tcc/patches/files/*) prep_src_arch_srcs = \ vendor/seed/$1/hex0-seed \ @@ -274,7 +280,7 @@ build/$1/$2/boot7/.stamp: \ build/$1/$2/boot7/toolchain/bin/tcc \ build/$1/$2/boot7/toolchain/lib/libc.a \ -build/$1/$2/boot7/toolchain/lib/libtcc1.a \ +build/$1/$2/boot7/toolchain/lib/tcc/libtcc1.a \ build/$1/$2/boot7/toolchain/lib/crt1.o \ build/$1/$2/boot7/toolchain/lib/crti.o \ build/$1/$2/boot7/toolchain/lib/crtn.o \ diff --git a/README.md b/README.md @@ -194,6 +194,16 @@ canonical generated source tree (used by every stage) is at The final static toolchain is assembled at `build/<arch>/<driver>/boot7/toolchain/`, with `bin/`, `lib/`, and `include/` subdirectories plus a complete `MANIFEST.sha256`. +`bin/tcc` discovers those directories relative to itself, so an ordinary +hosted C program needs no bootstrap-specific flags: + +```sh +build/aarch64/podman/boot7/toolchain/bin/tcc hello.c -o hello +``` + +The default link is static and automatically supplies TCC's compiler +headers, musl's public headers, the startup objects, `libc.a`, and +`libtcc1.a`. Use `-nostdinc` or `-nostdlib` only for freestanding work. ## Reproducible releases diff --git a/boot/boot5.sh b/boot/boot5.sh @@ -15,7 +15,7 @@ ## merged, deletes applied, ## alltypes.h/syscall.h generated, ## per-arch skip filter applied) -## build/$ARCH/src/src/tcc/stdarg-bridge.h +## build/$ARCH/src/src/tcc/<package>/include/ — TCC compiler headers ## build/$ARCH/src/src/test-fixtures/boot-hello.c ## ## ─── Tools ──────────────────────────────────────────────────────────── @@ -43,6 +43,8 @@ BOOT2=build/$ARCH/$DRIVER/boot2 BOOT4=build/$ARCH/$DRIVER/boot4 SRC=build/$ARCH/src MUSL_DIR=$SRC/src/musl +TCC_PKG=tcc-0.9.26-1147-gee75a10c +TCC_INCLUDE=$SRC/src/tcc/$TCC_PKG/include # ── prerequisites ───────────────────────────────────────────────────── require_prev "$BOOT4" tcc2 @@ -50,7 +52,7 @@ require_prev "$BOOT2" catm scheme1 require_file "$BOOT4/libtcc1.a" "run boot/boot4.sh $ARCH" require_file "$MUSL_DIR" "run bootprep/prep-src.sh $ARCH" require_file "$MUSL_DIR/skip.txt" "run bootprep/prep-src.sh $ARCH" -require_file "$SRC/src/tcc/stdarg-bridge.h" "run bootprep/prep-src.sh $ARCH" +require_file "$TCC_INCLUDE/stdarg.h" "run bootprep/prep-src.sh $ARCH" # ── prepare staging dirs ────────────────────────────────────────────── # $STAGE/in/ — read-only inputs (becomes /work/in or in/ in tmpfs) @@ -82,7 +84,7 @@ runscm_prelude "$SRC/src/scheme1/prelude.scm" runscm_input tcc "$BOOT4/tcc2" runscm_input libtcc1.a "$BOOT4/libtcc1.a" runscm_input catm "$BOOT2/catm" -runscm_input_from_src tcc/stdarg-bridge.h tcc-stdarg-bridge.h +runscm_input_tree tcc-include "$TCC_INCLUDE" runscm_input_from_src test-fixtures/boot-hello.c hello.c # Stage the canonical musl tree under in/musl/. Both drivers pick it diff --git a/boot/boot7.sh b/boot/boot7.sh @@ -1,15 +1,17 @@ #!/bin/sh ## boot7.sh — assemble the final static C toolchain sysroot. ## -## This stage performs no compilation. It installs the verified boot4/boot5 -## products and the canonical musl headers into one relocatable directory: +## This stage installs the verified boot4/boot5 products and canonical +## headers into one relocatable directory, then compiles and runs a temporary +## acceptance program through the installed driver: ## ## build/$ARCH/$DRIVER/boot7/toolchain/ ## bin/tcc boot4's fixed-point compiler (tcc2) -## lib/{libc.a,libtcc1.a} boot5 musl + boot4 compiler runtime +## lib/libc.a boot5 musl ## lib/crt{1,i,n}.o boot5 startup objects +## lib/tcc/libtcc1.a boot4 compiler runtime +## lib/tcc/include/ TCC compiler-owned headers ## include/ installed public musl headers -## include/tcc/stdarg-bridge.h ## MANIFEST.sha256 hash of every installed file except itself ## ## boot7 is deliberately host-side for both DRIVER values: it only copies @@ -33,27 +35,31 @@ export OUT STAGE BOOT4=build/$ARCH/$DRIVER/boot4 BOOT5=build/$ARCH/$DRIVER/boot5 MUSL=build/$ARCH/src/src/musl -BRIDGE=build/$ARCH/src/src/tcc/stdarg-bridge.h +TCC_PKG=tcc-0.9.26-1147-gee75a10c +TCC_INCLUDE=build/$ARCH/src/src/tcc/$TCC_PKG/include +SMOKE_SOURCE=build/$ARCH/src/src/test-fixtures/toolchain-hello.c for f in \ - "$BOOT4/tcc2" "$BOOT4/libtcc1.a" \ + "$BOOT4/tcc2" "$BOOT4/libtcc1.a" "$SMOKE_SOURCE" \ "$BOOT5/libc.a" "$BOOT5/crt1.o" "$BOOT5/crti.o" "$BOOT5/crtn.o" \ "$MUSL/obj/include/bits/alltypes.h" \ "$MUSL/obj/include/bits/syscall.h" \ - "$BRIDGE" + "$TCC_INCLUDE/float.h" "$TCC_INCLUDE/stdarg.h" \ + "$TCC_INCLUDE/stdbool.h" "$TCC_INCLUDE/stddef.h" \ + "$TCC_INCLUDE/varargs.h" do require_file "$f" done rm -rf "$STAGE" -mkdir -p "$STAGE/toolchain/bin" "$STAGE/toolchain/lib" \ - "$STAGE/toolchain/include/tcc" +mkdir -p "$STAGE/toolchain/bin" "$STAGE/toolchain/lib/tcc/include" \ + "$STAGE/toolchain/include" TOOLCHAIN=$STAGE/toolchain # Final built binaries and libraries. boot4's mes-libc/crt1 are bootstrap # internals; the installed libc/crt objects are boot5's musl products. cp "$BOOT4/tcc2" "$TOOLCHAIN/bin/tcc" -cp "$BOOT4/libtcc1.a" "$TOOLCHAIN/lib/libtcc1.a" +cp "$BOOT4/libtcc1.a" "$TOOLCHAIN/lib/tcc/libtcc1.a" cp "$BOOT5/libc.a" "$TOOLCHAIN/lib/libc.a" cp "$BOOT5/crt1.o" "$TOOLCHAIN/lib/crt1.o" cp "$BOOT5/crti.o" "$TOOLCHAIN/lib/crti.o" @@ -63,25 +69,50 @@ cp "$BOOT5/crtn.o" "$TOOLCHAIN/lib/crtn.o" # install-headers target: generic bits, arch bits, then generated bits. # The general include/ tree is disjoint from the installed bits/ overlay. copy_headers() { - _src=$1; _prefix=$2 + _src=$1; _dest=$2 ( cd "$_src" && find . -type f -name '*.h' | LC_ALL=C sort ) | while IFS= read -r _rel; do _rel=${_rel#./} - _dst=$TOOLCHAIN/include/$_prefix$_rel + _dst=$_dest/$_rel mkdir -p "$(dirname "$_dst")" cp "$_src/$_rel" "$_dst" done } -copy_headers "$MUSL/include" "" -copy_headers "$MUSL/arch/generic/bits" "bits/" -copy_headers "$MUSL/arch/$MUSL_ARCH/bits" "bits/" -copy_headers "$MUSL/obj/include/bits" "bits/" -cp "$BRIDGE" "$TOOLCHAIN/include/tcc/stdarg-bridge.h" +copy_headers "$MUSL/include" "$TOOLCHAIN/include" +copy_headers "$MUSL/arch/generic/bits" "$TOOLCHAIN/include/bits" +copy_headers "$MUSL/arch/$MUSL_ARCH/bits" "$TOOLCHAIN/include/bits" +copy_headers "$MUSL/obj/include/bits" "$TOOLCHAIN/include/bits" +copy_headers "$TCC_INCLUDE" "$TOOLCHAIN/lib/tcc/include" chmod 0755 "$TOOLCHAIN/bin/tcc" find "$TOOLCHAIN/lib" "$TOOLCHAIN/include" -type f -exec chmod 0644 {} + +# Acceptance is the downstream interface itself: no wrapper, include flags, +# CRT paths, or library flags. Run it in the target-architecture BusyBox +# container; boot7 stays host-side for assembly, and seed artifacts have +# already been proven byte-identical to the podman artifacts separately. +if [ "$DRIVER" = podman ]; then + IMAGE=boot2-busybox:$ARCH + if ! podman image exists "$IMAGE"; then + podman build --platform "$PLATFORM" -t "$IMAGE" \ + -f boot/containers/Containerfile.busybox boot/containers/ + fi + mkdir -p "$STAGE/smoke" + cp "$SMOKE_SOURCE" "$STAGE/smoke/hello.c" + TOOLCHAIN_ABS=$(cd "$TOOLCHAIN" && pwd) + SMOKE_ABS=$(cd "$STAGE/smoke" && pwd) + podman run --rm --pull=never --platform "$PLATFORM" \ + --network=none --read-only --tmpfs /tmp:size=16M \ + --env PATH=/toolchain/bin:/bin \ + -v "$TOOLCHAIN_ABS:/toolchain:ro" -v "$SMOKE_ABS:/work:rw" \ + -w /work "$IMAGE" tcc hello.c -o hello + podman run --rm --pull=never --platform "$PLATFORM" \ + --network=none --read-only --tmpfs /tmp:size=16M \ + -v "$SMOKE_ABS:/work:ro" -w /work "$IMAGE" /work/hello + rm -rf "$STAGE/smoke" +fi + if command -v sha256sum >/dev/null 2>&1; then sha256() { sha256sum "$1" | awk '{print $1}'; } else diff --git a/bootprep/assets/toolchain-hello.c b/bootprep/assets/toolchain-hello.c @@ -0,0 +1,27 @@ +#include <stdio.h> +#include <stdarg.h> + +static int sum(int count, ...) +{ + va_list ap; + int i, result = 0; + + va_start(ap, count); + for (i = 0; i < count; ++i) + result += va_arg(ap, int); + va_end(ap); + return result; +} + +static long double add_wide(long double left, long double right) +{ + return left + right; +} + +int main(void) +{ + int result = sum(3, 1, 2, 3); + long double wide = add_wide((long double)result, 1.0L); + printf("toolchain smoke: %d\n", result); + return result == 6 && wide == 7.0L ? 0 : 1; +} diff --git a/bootprep/boot5-calibrate.sh b/bootprep/boot5-calibrate.sh @@ -10,7 +10,7 @@ ## What it does: ## 1. Stage the same prerequisites boot5.sh stages (boot4/tcc2, ## libtcc1.a, vendored overrides + deletes, pre-generated headers, -## stdarg bridge). +## TCC compiler headers). ## 2. Run a skip-on-fail compile loop in the container over every ## musl source. Whatever tcc 0.9.26 cannot compile gets recorded. ## 3. Copy the resulting skip list out to @@ -45,7 +45,8 @@ MUSL_TARBALL=vendor/musl/1.2.5.tar.gz MUSL_OVERRIDES=vendor/musl/overrides MUSL_DELETES=vendor/musl/deletes.txt MUSL_GENERATED=vendor/musl/generated/$MUSL_ARCH -BRIDGE_FILE=build/$ARCH/vendor/tcc/stdarg-bridge.h +TCC_PKG=tcc-0.9.26-1147-gee75a10c +TCC_INCLUDE=build/$ARCH/vendor/tcc/$TCC_PKG/include SKIP_OUT=vendor/musl/skip-$ARCH.txt [ -x "$BOOT4/tcc2" ] || { echo "missing $BOOT4/tcc2 (run boot/boot4.sh $ARCH)" >&2; exit 1; } @@ -54,7 +55,7 @@ SKIP_OUT=vendor/musl/skip-$ARCH.txt [ -d "$MUSL_OVERRIDES" ] || { echo "missing $MUSL_OVERRIDES" >&2; exit 1; } [ -e "$MUSL_DELETES" ] || { echo "missing $MUSL_DELETES" >&2; exit 1; } [ -d "$MUSL_GENERATED" ] || { echo "missing $MUSL_GENERATED (run bootprep/musl-vendor.sh)" >&2; exit 1; } -[ -e "$BRIDGE_FILE" ] || { echo "missing $BRIDGE_FILE (run bootprep/stage1-flatten.sh)" >&2; exit 1; } +[ -e "$TCC_INCLUDE/stdarg.h" ] || { echo "missing $TCC_INCLUDE/stdarg.h (run bootprep/stage1-flatten.sh)" >&2; exit 1; } if ! podman image exists "$IMAGE"; then podman build --platform "$PLATFORM" -t "$IMAGE" \ @@ -72,7 +73,7 @@ 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" +cp -R "$TCC_INCLUDE" "$STAGE/in/tcc-include" cp "$MUSL_GENERATED/alltypes.h" "$STAGE/in/musl-alltypes.h" cp "$MUSL_GENERATED/syscall.h" "$STAGE/in/musl-syscall.h" @@ -102,7 +103,7 @@ CFLAGS_BASE="-std=c99 -nostdinc -ffreestanding -fno-strict-aliasing -O2 -fomit-frame-pointer -Werror=implicit-function-declaration -Werror=implicit-int -Werror=pointer-sign -Werror=pointer-arith" -CFLAGS_C="$CFLAGS_BASE -include $IN/tcc-stdarg-bridge.h" +CFLAGS_C="-I$IN/tcc-include $CFLAGS_BASE" CFLAGS_ASM="$CFLAGS_BASE" SRC_TOP="src/aio src/conf src/crypt src/ctype src/dirent diff --git a/bootprep/boot5-gen-runscm.sh b/bootprep/boot5-gen-runscm.sh @@ -19,7 +19,7 @@ ## .o outputs out/obj/musl/<src-with-.o> (rw; pre-mkdir'd by host) ## tcc binary in/tcc (input) ## libtcc1.a in/libtcc1.a (input) -## stdarg bridge in/tcc-stdarg-bridge.h +## compiler hdrs in/tcc-include/ ## hello.c in/hello.c ## exports out/{libc.a,crt1.o,crti.o,crtn.o,hello} ## (flat at out/ root so runscm_export pulls by basename) @@ -44,14 +44,14 @@ mkdir -p "$(dirname "$OUT")" CIN=in/musl COUT=out/obj/musl -# Mirrors boot5.sh's CFLAGS_BASE exactly; the only difference is that -# every per-arg token is quoted as its own scheme bytevector. The leading -# "in/tcc" is the spawned binary; everything after is its argv. -CFLAGS_BASE_QUOTED='"-std=c99" "-nostdinc" "-ffreestanding" "-fno-strict-aliasing" "-D_XOPEN_SOURCE=700"' -CFLAGS_BASE_QUOTED="$CFLAGS_BASE_QUOTED \"-I$CIN/arch/$MUSL_ARCH\" \"-I$CIN/arch/generic\" \"-I$CIN/obj/src/internal\" \"-I$CIN/src/include\" \"-I$CIN/src/internal\" \"-I$CIN/obj/include\" \"-I$CIN/include\"" -CFLAGS_BASE_QUOTED="$CFLAGS_BASE_QUOTED \"-O2\" \"-fomit-frame-pointer\" \"-Werror=implicit-function-declaration\" \"-Werror=implicit-int\" \"-Werror=pointer-sign\" \"-Werror=pointer-arith\"" -CFLAGS_C_QUOTED="$CFLAGS_BASE_QUOTED \"-include\" \"in/tcc-stdarg-bridge.h\"" -CFLAGS_ASM_QUOTED="$CFLAGS_BASE_QUOTED" +# Every per-argument token is quoted as its own Scheme bytevector. TCC's +# compiler-owned headers precede musl's source headers for C files; assembly +# inputs retain only the musl include overlay. +CFLAGS_HEAD_QUOTED='"-std=c99" "-nostdinc" "-ffreestanding" "-fno-strict-aliasing" "-D_XOPEN_SOURCE=700"' +CFLAGS_MUSL_QUOTED="\"-I$CIN/arch/$MUSL_ARCH\" \"-I$CIN/arch/generic\" \"-I$CIN/obj/src/internal\" \"-I$CIN/src/include\" \"-I$CIN/src/internal\" \"-I$CIN/obj/include\" \"-I$CIN/include\"" +CFLAGS_TAIL_QUOTED='"-O2" "-fomit-frame-pointer" "-Werror=implicit-function-declaration" "-Werror=implicit-int" "-Werror=pointer-sign" "-Werror=pointer-arith"' +CFLAGS_C_QUOTED="$CFLAGS_HEAD_QUOTED \"-Iin/tcc-include\" $CFLAGS_MUSL_QUOTED $CFLAGS_TAIL_QUOTED" +CFLAGS_ASM_QUOTED="$CFLAGS_HEAD_QUOTED $CFLAGS_MUSL_QUOTED $CFLAGS_TAIL_QUOTED" CRTFLAGS_C_QUOTED="$CFLAGS_C_QUOTED \"-fno-stack-protector\" \"-DCRT\"" CRTFLAGS_ASM_QUOTED="$CFLAGS_ASM_QUOTED \"-fno-stack-protector\" \"-DCRT\"" @@ -145,7 +145,7 @@ cat <<EOF (write-string stdout "boot5: stage D (link hello)\n") ;; -Lout pulls libc.a (just built); -Lin pulls libtcc1.a (input). -(must (run "in/tcc" "-static" "-nostdinc" "-nostdlib" "-include" "in/tcc-stdarg-bridge.h" $LINK_TTEXT +(must (run "in/tcc" "-static" "-nostdinc" "-nostdlib" "-Iin/tcc-include" $LINK_TTEXT "-I$CIN/include" "-I$CIN/arch/$MUSL_ARCH" "-I$CIN/arch/generic" "-I$CIN/obj/include" "out/crt1.o" "in/hello.c" "-Lout" "-lc" "-Lin" "-ltcc1" "-Lout" "-lc" "-o" "out/hello") "link hello") diff --git a/bootprep/headers/stdarg.h b/bootprep/headers/stdarg.h @@ -9,10 +9,9 @@ * __builtin_va_list and __builtin_va_start/arg/end as builtins) * and stock gcc/clang (where they're native). * - * Distinct from build/<arch>/vendor/tcc/stdarg-bridge.h, which is - * generated from tcc-0.9.26's own include/stdarg.h (patched) and - * prepended to libc.flat.c so that tcc itself — which has no - * __builtin_va_* keywords — can compile through the flattened libc + * Distinct from tcc-0.9.26's compiler-owned include/stdarg.h, which is + * patched and prepended to libc.flat.c so that tcc itself — which has + * no __builtin_va_* keywords — can compile through the flattened libc * by mapping __builtin_va_* onto its native __va_* intrinsics. */ #ifndef __MES_STDARG_H diff --git a/bootprep/libc-flatten.sh b/bootprep/libc-flatten.sh @@ -64,13 +64,14 @@ cp -R "$VENDOR/." "$STAGE/" # --- (2) flatten via host preprocessor -------------------------------- HOST_CC=${HOST_CC:-cc} -# Bridge file: post-patch tcc <stdarg.h>. Written by stage1-flatten.sh, -# which boot3.sh / Makefile run first. Required so we can prepend the +# Compiler header: post-patch tcc <stdarg.h>. Written by +# stage1-flatten.sh, which boot3.sh / Makefile run first. Required so we +# can prepend the # per-arch va_list typedef + __builtin_va_* → tcc __va_* mapping into -# libc.flat.c, eliminating the need for `-I /work/in/tcc-include -# -include /work/in/tcc-include/stdarg.h` on every in-container compile. -BRIDGE=$ROOT/build/$ARCH/vendor/tcc/stdarg-bridge.h -[ -e "$BRIDGE" ] || { echo "missing $BRIDGE — run bootprep/stage1-flatten.sh first" >&2; exit 1; } +# libc.flat.c. +TCC_PKG=tcc-0.9.26-1147-gee75a10c +TCC_STDARG=$ROOT/build/$ARCH/vendor/tcc/$TCC_PKG/include/stdarg.h +[ -e "$TCC_STDARG" ] || { echo "missing $TCC_STDARG — run bootprep/stage1-flatten.sh first" >&2; exit 1; } "$HOST_CC" -E -P \ -nostdinc \ @@ -82,13 +83,13 @@ BRIDGE=$ROOT/build/$ARCH/vendor/tcc/stdarg-bridge.h -D inline= \ "$STAGE/libc.c" > "$FLAT.body" -# Prepend the bridge, guarded by !CCSCM (cc.scm predefines CCSCM and +# Prepend TCC's compiler header, guarded by !CCSCM (cc.scm predefines CCSCM and # handles __builtin_va_* natively, so it must skip this block). Under -# tcc, the per-arch #ifdefs inside the bridge resolve and provide the +# tcc, the per-arch #ifdefs inside the header resolve and provide the # va_list typedef + __builtin_va_* → tcc native __va_* macros. { echo '#ifndef CCSCM' - cat "$BRIDGE" + cat "$TCC_STDARG" echo '#endif' cat "$FLAT.body" } > "$FLAT" diff --git a/bootprep/musl-vendor.sh b/bootprep/musl-vendor.sh @@ -141,6 +141,13 @@ for MARCH in aarch64 x86_64 riscv64; do print "struct timespec { time_t tv_sec; long tv_nsec; };" next } + /^#if defined\(__NEED_va_list\)/ { + print "#if defined(__TINYC__) && !defined(__DEFINED_va_list) && \\" + print " (defined(__NEED_va_list) || defined(__NEED___isoc_va_list))" + print "#include <stdarg.h>" + print "#endif" + print "" + } { print } ' > "$out/alltypes.h" # Why the post-process: tcc 0.9.26 emits "will touch memory past diff --git a/bootprep/prep-src.sh b/bootprep/prep-src.sh @@ -19,7 +19,7 @@ ## catm/ catm.P1pp ## scheme1/ scheme1.P1pp, prelude.scm ## cc/ cc.scm, main.scm -## tcc/ tcc.flat.c, stdarg-bridge.h, plus +## tcc/ tcc.flat.c plus ## tcc-0.9.26-1147-gee75a10c/{include,lib} ## tcc/libc/$ARCH/ start.S, sys_stubs.S ## tcc/cc/ mem.c (memcpy/memmove/memset/memcmp) @@ -111,6 +111,7 @@ cp tcc/cc/mem.c "$DST_SRC/tcc/cc/mem.c" # Smoke binary linked by boot4 + boot5. mkdir -p "$DST_SRC/test-fixtures" cp bootprep/assets/boot-hello.c "$DST_SRC/test-fixtures/boot-hello.c" +cp bootprep/assets/toolchain-hello.c "$DST_SRC/test-fixtures/toolchain-hello.c" # ── (3) seed-kernel sources for this arch ───────────────────────────── mkdir -p "$DST_SRC/kernel/arch/$ARCH" "$DST_SRC/kernel/user" @@ -133,13 +134,11 @@ bootprep/stage1-flatten.sh --arch "$ARCH" TCC_VENDOR=$ROOT/build/$ARCH/vendor/tcc TCC_PKG=tcc-0.9.26-1147-gee75a10c [ -e "$TCC_VENDOR/tcc.flat.c" ] || { echo "$TAG flatten produced no tcc.flat.c" >&2; exit 1; } -[ -e "$TCC_VENDOR/stdarg-bridge.h" ] || { echo "$TAG flatten produced no stdarg-bridge.h" >&2; exit 1; } [ -d "$TCC_VENDOR/$TCC_PKG/include" ] || { echo "$TAG flatten produced no $TCC_PKG/include" >&2; exit 1; } [ -d "$TCC_VENDOR/$TCC_PKG/lib" ] || { echo "$TAG flatten produced no $TCC_PKG/lib" >&2; exit 1; } mkdir -p "$DST_SRC/tcc" cp "$TCC_VENDOR/tcc.flat.c" "$DST_SRC/tcc/tcc.flat.c" -cp "$TCC_VENDOR/stdarg-bridge.h" "$DST_SRC/tcc/stdarg-bridge.h" mkdir -p "$DST_SRC/tcc/$TCC_PKG" cp -R "$TCC_VENDOR/$TCC_PKG/include" "$DST_SRC/tcc/$TCC_PKG/include" cp -R "$TCC_VENDOR/$TCC_PKG/lib" "$DST_SRC/tcc/$TCC_PKG/lib" diff --git a/bootprep/stage1-flatten.sh b/bootprep/stage1-flatten.sh @@ -297,6 +297,13 @@ apply_our_patch riscv-stdarg-fix "$SRC/include/stdarg.h" # __riscv branch already maps these names natively. See the .after # block for the full rationale. apply_our_patch stdarg-builtin-aliases "$SRC/include/stdarg.h" +apply_our_patch stdarg-musl-guard "$SRC/include/stdarg.h" + +# Installed-driver behavior. Resolve lib/tcc relative to argv[0] (with a +# PATH fallback) so the final bin/tcc needs no shell wrapper, then repeat +# libc after libtcc1 for the normal static archive link order. +apply_our_patch relocatable-driver "$SRC/tcc.c" +apply_our_patch static-runtime-libc-repeat "$SRC/tccelf.c" # Empty config.h shims — pass1.kaem creates these via `catm <out>` (line 27-28). : > "$SRC/config.h" @@ -319,9 +326,10 @@ FLAT=$WORK/tcc.flat.c -D inline= \ -D "CONFIG_TCCDIR=\"/lib/tcc\"" \ -D "CONFIG_SYSROOT=\"/\"" \ - -D "CONFIG_TCC_CRTPREFIX=\"/lib\"" \ + -D "CONFIG_TCC_CRTPREFIX=\"{B}/..\"" \ -D "CONFIG_TCC_ELFINTERP=\"/mes/loader\"" \ - -D "CONFIG_TCC_SYSINCLUDEPATHS=\"/include/mes\"" \ + -D "CONFIG_TCC_SYSINCLUDEPATHS=\"{B}/include:{B}/../../include\"" \ + -D "CONFIG_TCC_LIBPATHS=\"{B}/..\"" \ -D "TCC_LIBGCC=\"/lib/libc.a\"" \ -D CONFIG_TCC_LIBTCC1_MES=0 \ -D CONFIG_TCCBOOT=1 \ @@ -332,26 +340,16 @@ FLAT=$WORK/tcc.flat.c -D TCC_TARGET_${TCC_TARGET_DEFINE}=1 \ "$SRC/tcc.c" > "$FLAT.body" -# Publish the post-patch tcc <stdarg.h> as a per-arch bridge file -# alongside tcc.flat.c. libc-flatten.sh prepends the same bridge to -# libc.flat.c, so the boot3/boot4 container compiles no longer need -# `-I /work/in/tcc-include -include /work/in/tcc-include/stdarg.h`. -# The patched stdarg.h is byte-identical across X86_64 / ARM64 / RISCV64 -# (per-arch logic lives inside its #ifdefs); we still write a per-arch -# copy so every artifact under build/<arch>/ comes from a single -# `boot.sh <arch>` invocation, with nothing shared across arches. -BRIDGE=$WORK/stdarg-bridge.h -cp "$SRC/include/stdarg.h" "$BRIDGE" - -# Prepend the bridge into tcc.flat.c, guarded by !CCSCM so cc.scm +# Prepend TCC's patched compiler <stdarg.h> into tcc.flat.c, guarded by +# !CCSCM so cc.scm # (which has __builtin_va_list / __builtin_va_* as native frontend # keywords and predefines CCSCM) skips the whole block. Under tcc, -# the per-arch #ifdef branches inside the bridge resolve and define +# the per-arch #ifdef branches inside the header resolve and define # the va_list typedef + __builtin_va_* → tcc native __va_* macros # that flat.c needs. { echo '#ifndef CCSCM' - cat "$BRIDGE" + cat "$SRC/include/stdarg.h" echo '#endif' cat "$FLAT.body" } > "$FLAT" diff --git a/docs/LIBC.md b/docs/LIBC.md @@ -40,9 +40,9 @@ vendor/mes-libc/ bootprep/ ├── libc-flatten.sh host: cc -E -nostdinc -I bootprep/headers │ libc.c → build/$ARCH/vendor/mes-libc/libc.flat.c. -│ Prepends build/$ARCH/vendor/tcc/stdarg-bridge.h -│ (see docs/TCC.md) so tcc itself can later -│ compile through the flattened libc. +│ Prepends TCC's patched compiler-owned stdarg.h +│ so tcc itself can later compile through the +│ flattened libc. └── headers/ hand-rolled libc headers consumed at flatten time by the host preprocessor; `stdarg.h` here routes va_* through __builtin_va_*. @@ -120,8 +120,8 @@ environment pointer. `P1/entry-libc.P1pp` calls it ahead of `main`. `vendor/mes-libc/libc.c` to `build/$ARCH/vendor/mes-libc/libc-stage/`, then runs `host_cc -E -P -nostdinc -I bootprep/headers -D __linux__=1 -D __${MES_ARCH}__=1 -D __riscv_xlen=64 --D HAVE_CONFIG_H=0 -D inline=` against it and prepends -`build/$ARCH/vendor/tcc/stdarg-bridge.h` (guarded by `#ifndef CCSCM`) +-D HAVE_CONFIG_H=0 -D inline=` against it and prepends TCC's patched +`include/stdarg.h` (guarded by `#ifndef CCSCM`) to produce `build/$ARCH/vendor/mes-libc/libc.flat.c` (~27 KB). `MES_ARCH` mapping is `aarch64→riscv64`, `amd64→x86_64`, @@ -350,9 +350,7 @@ That's tracked in [TCC.md](TCC.md), not here. - Layout: `vendor/mes-libc/libc.c` is arch-agnostic. The only per-arch input feeding the libc build is the `__${MES_ARCH}__` predefine that `bootprep/libc-flatten.sh` passes through. -- The flatten-time stdarg shim (`bootprep/headers/stdarg.h`) and - the per-arch tcc stdarg bridge (generated at - `build/<arch>/vendor/tcc/stdarg-bridge.h`, prepended to - `libc.flat.c`) are *both* required and serve different consumers - — see the comment at the top of `bootprep/headers/stdarg.h` and - [docs/TCC.md](TCC.md). +- The flatten-time stdarg shim (`bootprep/headers/stdarg.h`) and TCC's + patched compiler-owned `include/stdarg.h`, prepended to `libc.flat.c`, + are both required and serve different consumers—see the comment at the + top of `bootprep/headers/stdarg.h` and [docs/TCC.md](TCC.md). diff --git a/docs/MUSL.md b/docs/MUSL.md @@ -36,7 +36,7 @@ boot/boot5.sh <amd64|aarch64|riscv64> | `vendor/musl/deletes.txt` | upstream files removed by the same patch set, one path per line | | `vendor/musl/generated/$MUSL_ARCH/{alltypes,syscall}.h` | per-arch headers pre-generated at vendor time (replaces musl's mkalltypes.sed + `__NR_`→`SYS_` rewrite, so the container needs no awk) | | `vendor/musl/skip-$ARCH.txt` | per-arch calibration list — sources tcc 0.9.26 cannot compile, produced by `bootprep/boot5-calibrate.sh` | -| `build/$ARCH/vendor/tcc/stdarg-bridge.h` | per-arch `__builtin_va_list` bridge (byte-identical across arches, three arches gated by `#ifdef`; produced by `bootprep/stage1-flatten.sh`) | +| `build/$ARCH/vendor/tcc/<package>/include/` | patched TCC compiler headers, including the target-ABI `<stdarg.h>` | | `bootprep/assets/boot-hello.c` | smoke-test source (shared with boot4) | Architecture mapping: @@ -125,7 +125,7 @@ surfaces tcc 0.9.26 cannot compile: | C99 array parameters | remove `[static N]` qualifiers tcc does not parse | | `_Complex` | stub `complex.h` and remove complex sources | | arch asm overrides | delete unsupported fenv, signal, setjmp, thread, string, math overrides as needed | -| varargs | pre-include `build/$ARCH/vendor/tcc/stdarg-bridge.h` (the post-patch tcc `<stdarg.h>`) for C translation units | +| varargs | put TCC's compiler-header directory first in the explicit `-nostdinc` build include list; generated musl `bits/alltypes.h` includes the compiler `<stdarg.h>` when TCC must supply `va_list` | Required tcc fixes live under `vendor/tcc/patches/`. The musl build depends on the aarch64 literal-address load/store fixes diff --git a/docs/TOUR.md b/docs/TOUR.md @@ -288,16 +288,18 @@ The full OS contract is [docs/OS.md](OS.md). musl `libc.a` and `crt{1,i,n}.o` from boot5, and the canonical generated musl headers. -**boot7 builds**: no new machine code. It installs those verified files +**boot7 publishes**: no new machine code. It installs those verified files into a relocatable `toolchain/` tree with conventional `bin/`, `lib/`, and -`include/` directories. The public header overlay follows musl's own -`install-headers` precedence: generic `bits/`, architecture-specific -`bits/`, then generated `alltypes.h` and `syscall.h`. +`include/` directories. It does compile and run a temporary acceptance +program, but that program is not installed. The public header overlay +follows musl's own `install-headers` precedence: generic `bits/`, +architecture-specific `bits/`, then generated `alltypes.h` and `syscall.h`. `bin/tcc` is the unwrapped `tcc2` ELF. No shell wrapper or other unbuilt -executable is added; callers pass the installed include, CRT, and library -paths directly to TCC. A separate `MANIFEST.sha256` records every installed -file. +executable is added. The compiler resolves `lib/tcc/include`, `include`, +the CRT objects, `libc.a`, and `lib/tcc/libtcc1.a` relative to its own +invocation path, so `bin/tcc hello.c -o hello` is sufficient. A separate +`MANIFEST.sha256` records every installed file. **Trust extension**: none. This is a deterministic assembly boundary that turns the proof-oriented stage outputs into the consumable product of the diff --git a/tools/release/README.md b/tools/release/README.md @@ -82,11 +82,20 @@ After verification, the ready-to-use static toolchain is at: ```text build/@ARCH@/<driver>/boot7/toolchain/ ├── bin/tcc -├── lib/{libc.a,libtcc1.a,crt1.o,crti.o,crtn.o} +├── lib/{libc.a,crt1.o,crti.o,crtn.o} +├── lib/tcc/libtcc1.a +├── lib/tcc/include/ compiler-owned headers ├── include/ └── MANIFEST.sha256 ``` +The compiler resolves this layout relative to `bin/tcc`. A normal static +compile and link therefore needs no explicit include, CRT, or library paths: + +```sh +build/@ARCH@/<driver>/boot7/toolchain/bin/tcc hello.c -o hello +``` + ## Drivers | DRIVER | runtime | prereqs | diff --git a/vendor/musl/generated/aarch64/alltypes.h b/vendor/musl/generated/aarch64/alltypes.h @@ -330,6 +330,11 @@ typedef struct _IO_FILE FILE; #endif +#if defined(__TINYC__) && !defined(__DEFINED_va_list) && \ + (defined(__NEED_va_list) || defined(__NEED___isoc_va_list)) +#include <stdarg.h> +#endif + #if defined(__NEED_va_list) && !defined(__DEFINED_va_list) typedef __builtin_va_list va_list; #define __DEFINED_va_list diff --git a/vendor/musl/generated/riscv64/alltypes.h b/vendor/musl/generated/riscv64/alltypes.h @@ -320,6 +320,11 @@ typedef struct _IO_FILE FILE; #endif +#if defined(__TINYC__) && !defined(__DEFINED_va_list) && \ + (defined(__NEED_va_list) || defined(__NEED___isoc_va_list)) +#include <stdarg.h> +#endif + #if defined(__NEED_va_list) && !defined(__DEFINED_va_list) typedef __builtin_va_list va_list; #define __DEFINED_va_list diff --git a/vendor/musl/generated/x86_64/alltypes.h b/vendor/musl/generated/x86_64/alltypes.h @@ -322,6 +322,11 @@ typedef struct _IO_FILE FILE; #endif +#if defined(__TINYC__) && !defined(__DEFINED_va_list) && \ + (defined(__NEED_va_list) || defined(__NEED___isoc_va_list)) +#include <stdarg.h> +#endif + #if defined(__NEED_va_list) && !defined(__DEFINED_va_list) typedef __builtin_va_list va_list; #define __DEFINED_va_list diff --git a/vendor/tcc/patches/relocatable-driver.after b/vendor/tcc/patches/relocatable-driver.after @@ -0,0 +1,78 @@ +/* Find the invoked compiler without relying on /proc or a shell wrapper. + An argv[0] containing a slash is already usable. Otherwise search PATH + with open(2); access(2) is intentionally unavailable in mes-libc. */ +static int tcc_find_self(char *buf, int size, const char *argv0) +{ + const char *path, *p, *q; + int n, fd, valid; + + if (strchr(argv0, '/')) { + snprintf(buf, size, "%s", argv0); + return 1; + } + path = getenv("PATH"); + if (!path) + return 0; + p = path; + for (;;) { + q = p; + while (*q && *q != ':') + ++q; + n = q - p; + valid = 0; + if (n == 0) { + if (strlen(argv0) + 3 <= size) { + strcpy(buf, "./"); + strcat(buf, argv0); + valid = 1; + } + } else if (n + strlen(argv0) + 2 <= size) { + memcpy(buf, p, n); + buf[n] = '/'; + strcpy(buf + n + 1, argv0); + valid = 1; + } + if (valid) { + fd = open(buf, 0); + if (fd >= 0) { + close(fd); + return 1; + } + } + if (!*q) + break; + p = q + 1; + } + return 0; +} + +/* The installed layout is PREFIX/bin/tcc + PREFIX/lib/tcc. Keep the + unnormalized "bin/.." component: it is valid on every runtime in the + bootstrap and avoids requiring realpath(3) or readlink(2). */ +static void tcc_set_self_lib_path(TCCState *s, const char *argv0) +{ + char executable[1024], lib_path[1024], *base; + + if (!tcc_find_self(executable, sizeof(executable), argv0)) + return; + base = tcc_basename(executable); + if (base > executable) + base[-1] = 0; + else + strcpy(executable, "."); + snprintf(lib_path, sizeof(lib_path), "%s/../lib/tcc", executable); + tcc_set_lib_path(s, lib_path); +} + +int main(int argc, char **argv) +{ + TCCState *s; + int ret, opt, n = 0; + unsigned start_time = 0; + const char *first_file; + const char *argv0 = argv[0]; + +redo: + s = tcc_new(); + tcc_set_self_lib_path(s, argv0); + opt = tcc_parse_args(s, &argc, &argv, 1); diff --git a/vendor/tcc/patches/relocatable-driver.before b/vendor/tcc/patches/relocatable-driver.before @@ -0,0 +1,10 @@ +int main(int argc, char **argv) +{ + TCCState *s; + int ret, opt, n = 0; + unsigned start_time = 0; + const char *first_file; + +redo: + s = tcc_new(); + opt = tcc_parse_args(s, &argc, &argv, 1); diff --git a/vendor/tcc/patches/static-runtime-libc-repeat.after b/vendor/tcc/patches/static-runtime-libc-repeat.after @@ -0,0 +1,10 @@ + tcc_add_support(s1, TCC_LIBTCC1); +#if CONFIG_TCC_LIBTCC1_MES + tcc_add_support(s1, TCC_LIBTCC1_MES); +#endif + /* Static archives are searched left-to-right. Runtime helpers can + introduce libc references after the first libc scan, so repeat + libc exactly as a conventional static compiler driver does. */ + if (s1->static_link) + tcc_add_library_err(s1, "c"); + /* add crt end if not memory output */ diff --git a/vendor/tcc/patches/static-runtime-libc-repeat.before b/vendor/tcc/patches/static-runtime-libc-repeat.before @@ -0,0 +1,5 @@ + tcc_add_support(s1, TCC_LIBTCC1); +#if CONFIG_TCC_LIBTCC1_MES + tcc_add_support(s1, TCC_LIBTCC1_MES); +#endif + /* add crt end if not memory output */ diff --git a/vendor/tcc/patches/stdarg-musl-guard.after b/vendor/tcc/patches/stdarg-musl-guard.after @@ -0,0 +1,9 @@ +#endif + +/* musl's bits/alltypes.h uses this guard when another compiler header has + * already supplied the target ABI's va_list definition. */ +#ifndef __DEFINED_va_list +#define __DEFINED_va_list +#endif + +#endif /* _STDARG_H */ diff --git a/vendor/tcc/patches/stdarg-musl-guard.before b/vendor/tcc/patches/stdarg-musl-guard.before @@ -0,0 +1,3 @@ +#endif + +#endif /* _STDARG_H */