boot2

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

commit e31e5c1bb3b6f5fc1f1f72d266faf61d041012dd
parent f40962dac334abe1b9e7d39892fef2eee92018d1
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 26 Apr 2026 16:34:29 -0700

scripts: rework tcc bootstrap into 3 stages + add live-bootstrap diag

Replace build-tcc-source.sh and build-tcc-real.sh with three explicit
stages that match the seam our scheme1 cc will fill:

  stage1-flatten.sh  (host)         tcc.c -> tcc.flat.c
  stage2-alpine.sh   (alpine)       tcc.flat.c -> tcc-host -> tcc-boot0-mes
  stage3-rebuild.sh  (busybox:musl) tcc-boot0-mes -> boot1 -> boot2

Stage 2 is the slot scheme1 cc replaces: gcc plays the role today,
producing tcc-host from tcc.flat.c and then compile+linking the patched
real tcc.c against mes libc directly into tcc-boot0-mes (live-bootstrap
tcc-boot0-style invocation, no tcc-self.o middle step). Stage 3 stays
purely in busybox; tcc-boot0-mes provides its own preprocessor,
assembler, linker, and ar.

Picked alpine over debian for stage 2 because mes headers declare
errno as a plain global while glibc declares it TLS, which clashes at
link time without invasive transformations to tcc.flat.c. musl exposes
errno only via __errno_location() and the one-line shim suffices.

Also adds scripts/diag-livebootstrap-qemu.sh — a diagnostic-only script
that runs upstream live-bootstrap's amd64 pass1 chain through tcc-0.9.27
inside the same busybox + linux/amd64 QEMU, to disambiguate "QEMU
issue" from "our build". Result captured in docs/TCC.md Issues:

  - Issue 2 (large-TU SEGV) reproduces in canonical live-bootstrap on
    this platform; the per-file workaround is load-bearing for any
    tcc-0.9.26-on-QEMU build, not specific to our path.
  - Issue 3 (tcc-boot0-mes startup SEGV) does NOT reproduce in
    live-bootstrap's mescc-built tcc-mes under the same QEMU; our
    tcc-host-built path hits codegen mescc avoids. Fix candidate is a
    backport of tcc 0.9.28rc prologue / _DYNAMIC patches.

Diffstat:
Mdocs/TCC.md | 441++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Dscripts/build-tcc-real.sh | 188-------------------------------------------------------------------------------
Dscripts/build-tcc-source.sh | 214-------------------------------------------------------------------------------
Ascripts/diag-livebootstrap-qemu.sh | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/stage1-flatten.sh | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/stage2-alpine.sh | 275+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/stage3-rebuild.sh | 234+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 1098 insertions(+), 596 deletions(-)

diff --git a/docs/TCC.md b/docs/TCC.md @@ -1,31 +1,38 @@ # Building tcc-0.9.26 from this repo -Working doc. Describes the host-side pipeline that takes the upstream -tcc-0.9.26 source tarball through to a working tcc-0.9.26 binary linked -against mes libc, **without** depending on M2-Planet, Mes Scheme, or -MesCC at any stage. Two scripts drive the pipeline: - -- [scripts/build-tcc-source.sh](../scripts/build-tcc-source.sh) — host - C preprocessor produces a flattened single-source `tcc.flat.c`, then - bootstraps `tcc-host` from it via host gcc. -- [scripts/build-tcc-real.sh](../scripts/build-tcc-real.sh) — uses - `tcc-host` to compile mes libc and link a real tcc-0.9.26 binary - (`tcc-boot0-mes`). +Working doc. Describes a three-stage host pipeline that builds +tcc-0.9.26 from the upstream tarball, **without** depending on +M2-Planet, Mes Scheme, or MesCC at any point. Three scripts drive +the chain: + +- [scripts/stage1-flatten.sh](../scripts/stage1-flatten.sh) — runs on + the host (macOS or Linux). Flattens upstream `tcc.c` into a single + `tcc.flat.c` bytestream using only the host C preprocessor. +- [scripts/stage2-alpine.sh](../scripts/stage2-alpine.sh) — runs in an + `alpine:latest` container. gcc compiles `tcc.flat.c` to a working + `tcc-host`, which then builds mes libc and directly compiles+links + the patched real `tcc.c` against mes libc into `tcc-boot0-mes`. + **This is the slot our scheme1 cc replaces.** +- [scripts/stage3-rebuild.sh](../scripts/stage3-rebuild.sh) — runs in a + `busybox:musl` container. Uses `tcc-boot0-mes` to drive the + live-bootstrap-style chain `boot0 → boot1 → boot2`, ending in a + final tcc-0.9.26 built entirely from real (unflattened) sources by + a previous-stage tcc. This is the upstream half of the [CC.md](CC.md) story. Once our scheme1-hosted compiler can ingest `tcc.flat.c`, it slots in where -`tcc-host` is today, replacing host gcc as the bootstrap-stage compiler. +gcc is in stage 2, so the alpine container goes away. ## Inputs | Path | Contents | |------|----------| -| `../lb-work/distfiles/tcc-0.9.26.tar.gz` | tcc-0.9.26-1147-gee75a10c source (janneke's bootstrap-friendly fork; the same artifact live-bootstrap consumes) | -| `../lb-work/distfiles/mes-0.27.1.tar.gz` | GNU Mes 0.27.1 — used **only** for its bundled minimal libc sources and headers, not for any Scheme runtime | +| `../lb-work/distfiles/tcc-0.9.26.tar.gz` | tcc-0.9.26-1147-gee75a10c source (janneke's bootstrap-friendly fork; same artifact live-bootstrap consumes) | +| `../lb-work/distfiles/mes-0.27.1.tar.gz` | GNU Mes 0.27.1 — used **only** for its bundled minimal libc sources and headers, not for any Scheme runtime | | `../live-bootstrap/steps/tcc-0.9.26/simple-patches/` | Two file-open reorder patches applied before the flatten step | -| `../mes/include/` | Same Mes headers as those in the tarball — used at flatten time to provide `<stdio.h>` etc. without pulling in host glibc/musl | +| `../mes/include/` | Same Mes headers as the tarball — used at flatten time so we don't pull in host glibc/musl | -The two host scripts sit on top of these inputs; they require nothing +The three scripts sit on top of these inputs; they require nothing else from the host besides `tar`, `awk`, a host `cc`, and `podman`. ## Pipeline overview @@ -33,33 +40,37 @@ else from the host besides `tar`, `awk`, a host `cc`, and `podman`. ``` tcc-0.9.26-1147-gee75a10c.tar.gz live-bootstrap source │ - │ build-tcc-source.sh + │ stage1-flatten.sh (host) │ • unpack │ • apply 2 simple-patches │ • host cc -E -nostdinc with mes headers + tcc-mes defines ▼ build/cc-bootstrap/X86_64/tcc.flat.c 608 KB single-file C │ - │ build-tcc-source.sh --self-host - │ • inside alpine linux/amd64 + gcc + musl-dev + │ stage2-alpine.sh (alpine:latest) + │ • apk add gcc musl-dev │ • gcc -static tcc.flat.c errno-shim.c -> tcc-host - │ • tcc-host -c tcc.flat.c -> tcc-self.o (self-host check) + │ • set up mes-0.27.1 include tree + │ • tcc-host compiles mes libc per-file -> libc.a + libtcc1.a + │ • tcc-host -static compiles+links real tcc.c -> tcc-boot0-mes + │ (mirrors live-bootstrap's tcc-boot0 invocation) -build/cc-bootstrap/X86_64/{tcc-host, tcc-self.o} bootstrap compiler + self-compile +build/cc-bootstrap/X86_64/tcc-boot0-mes ~750 KB tcc-0.9.26 ELF - │ build-tcc-real.sh - │ • unpack mes-0.27.1 - │ • set up include tree with arch -> linux/x86_64 symlink - │ • tcc-host compiles each mes libc .c file individually - │ • tcc-host -ar -> libc.a + libtcc1.a - │ • tcc-host links tcc-self.o + mes libc -> tcc-boot0-mes + │ stage3-rebuild.sh (busybox:musl) + │ • tcc-boot0-mes rebuilds libc, then compiles real tcc.c -> tcc-boot1 + │ • tcc-boot1 rebuilds libc, then compiles real tcc.c -> tcc-boot2 -build/cc-bootstrap/X86_64/tcc-boot0-mes 313 KB tcc-0.9.26 ELF +build/cc-bootstrap/X86_64/tcc-boot2 final tcc-0.9.26 ``` +Two containers, three scripts, one host-side step. Stage 1's +`tcc.flat.c` is a portable artifact; stage 2's `tcc-boot0-mes` plus +mes libc bits cross into stage 3 via `build/cc-bootstrap/<arch>/stage3-input/`. + ## Stage 1 — flatten tcc.c into tcc.flat.c -`scripts/build-tcc-source.sh --arch X86_64` +`scripts/stage1-flatten.sh --arch X86_64` Mirrors the live-bootstrap `tcc-mes` invocation ([steps/tcc-0.9.26/pass1.kaem:60–87](../../live-bootstrap/steps/tcc-0.9.26/pass1.kaem)) @@ -68,16 +79,20 @@ in `tcc.c` (which uses `ONE_SOURCE=1` to fold `libtcc.c`, `tcctools.c`, and the per-arch backends in via `#include`) and inlines all the Mes- bundled standard headers. +Stage 1 deliberately stays on the host — it is just text manipulation +and the `tcc.flat.c` it produces is consumed identically downstream +regardless of where stage 1 ran. + ### Sub-steps 1. **Unpack** `tcc-0.9.26.tar.gz` into `build/cc-bootstrap/X86_64/`. 2. **Apply simple-patches**: `remove-fileopen.before/.after` then - `addback-fileopen.before/.after` against `tcctools.c`. Implemented as - an `awk` literal-block replacer (live-bootstrap's `simple-patch` is a - trivial before/after substitution; we don't depend on the binary). + `addback-fileopen.before/.after` against `tcctools.c`. Implemented + as an `awk` literal-block replacer (live-bootstrap's `simple-patch` + is a trivial before/after substitution). 3. **Empty config.h shims**: live-bootstrap creates two empty - `config.h` files via `catm` (an empty concat). We do the same — one - in `$TCC_PKG/config.h`, one in `mes-overlay/mes/config.h` for the + `config.h` files via `catm`. We do the same — one in + `$TCC_PKG/config.h`, one in `mes-overlay/mes/config.h` for the `<mes/config.h>` reach the Mes stdio.h does. 4. **Host preprocess**: `cc -E -nostdinc` with the Mes headers as the sole `-I` set, plus the same `-D` set live-bootstrap passes: @@ -100,152 +115,192 @@ bundled standard headers. macOS this produces a Mach-O .o; the verify is purely a "does the source compile" check. Failure here means the flatten step is wrong. -## Stage 2 — bootstrap tcc-host - -`scripts/build-tcc-source.sh --arch X86_64 --self-host` - -Implies `--verify`, then drives a Linux container to build a real ELF -tcc-0.9.26 binary from `tcc.flat.c` using host gcc. - -### Why a container - -Stage 1 only needs a preprocessor — works on any host. Stage 2 needs: -- Linux x86_64 toolchain (the resulting binary runs on Linux x86_64) -- musl-dev for headers/libs that gcc can link against - -The macOS dev host has clang but produces Mach-O, not ELF. We use a -clean `alpine:latest` linux/amd64 container (~5 MB image, +~150 MB for -gcc + musl-dev on first run, cached afterwards). On macOS arm64 hosts, -podman runs this under QEMU x86_64 emulation — slow but functional. - -### errno shim - -`tcc.flat.c` came out of the Mes-headers preprocess, so it expects -`extern int errno;` (Mes's plain-global form). musl provides errno -through `__errno_location()` instead, so the link fails. A one-line -`errno-shim.c` (`int errno;`) fills the gap. - -``` -gcc -w -static -no-pie -o tcc-host tcc.flat.c errno-shim.c -``` - -### Smoke tests +## Stage 2 — Alpine container builds tcc-boot0-mes -After build: +`scripts/stage2-alpine.sh --arch X86_64` -- `tcc-host -version` → `tcc version 0.9.26 (x86_64 Linux)` ✓ -- `tcc-host -c -o tcc-self.o tcc.flat.c` → 485 KB object file ✓ - This is **the self-host check**: tcc compiling its own preprocessed - source. `tcc-self.o` is the object that Stage 3 links against mes - libc. +This is the **stand-in slot for our future scheme CC**. Today, native +gcc plays the role; tomorrow our scheme1-hosted C compiler does the +same job — take `tcc.flat.c`, produce a working tcc, then use that +tcc to build mes libc and compile+link a static `tcc-boot0-mes` +against it. -## Stage 3 — link a real tcc against mes libc +### Why Alpine (musl, not glibc) -`scripts/build-tcc-real.sh` +mes headers declare `errno` as a plain global; glibc declares it as a +TLS symbol. Linking `tcc.flat.c` against glibc's libc.a fails with a +non-TLS / TLS clash on the errno definition, and patching tcc.flat.c +to use a TLS form would mean injecting a transformation into stage 1 +that exists only to satisfy stage 2's host libc choice. -Drives the same alpine container the rest of the way. Produces -`tcc-boot0-mes`, the equivalent of live-bootstrap's tcc-boot0 stage — -but built without ever invoking mescc. +musl exposes errno only through `__errno_location()`, so a one-line +`int errno;` shim is the sole storage and links cleanly. Alpine +ships musl natively, so we use it. -### Sub-steps +The output binary itself (`tcc-boot0-mes`) is statically linked +against **mes libc**, not musl — the alpine-side gcc only links +`tcc-host`, the transient compiler used inside the container. -1. **Unpack `mes-0.27.1`** alongside `tcc-0.9.26`. Used purely for - `lib/**.c` (libc sources) and `include/**.h` (libc headers). -2. **Sanitize the include tree**: +### errno shim +`tcc.flat.c` came out of stage 1's mes-headers preprocess, so it +expects `extern int errno;` (Mes's plain-global form). musl provides +errno only via `__errno_location()`, so the gcc link of tcc-host +needs a definition. A one-line `errno-shim.c` (`int errno;`) is it. +Purely a stage-2 concern — `tcc-boot0-mes` itself links against mes +libc, where errno is the plain global mes expects. + +### Sub-steps (inside the container) + +1. **`apk add gcc musl-dev`**. +2. **`gcc -static -no-pie -o tcc-host tcc.flat.c errno-shim.c`**. + This is the only step that exercises the host toolchain. It + doubles as a validation of stage 1: if `tcc.flat.c` is malformed, + it fails here. ~700 KB output, musl-linked. +3. **Set up mes include tree**: ``` /tmp/mes-inc/ <- copy of mes/include/ /tmp/mes-inc/arch -> linux/x86_64 <- symlink we create ``` + (Issue §1 workaround — without the symlink, tcc 0.9.26 SEGVs on + `#include <arch/syscall.h>` rather than reporting cleanly.) +4. **Compile each mes libc .c file individually** with tcc-host. + (Issue §2 workaround — concatenated TU SEGVs at ~22+ files.) +5. **`tcc-host -ar`** the .o set into `/lib/libc.a` (~299 KB), build + `crt1.o`, build `libtcc1.a`, install at the baked-in paths + (`CONFIG_TCC_CRTPREFIX="/lib"`, `CONFIG_TCCDIR="/lib/tcc"`, + `CONFIG_TCC_SYSINCLUDEPATHS="/include/mes"`). +6. **Compile+link real tcc.c into tcc-boot0-mes**. tcc-host operates + on the patched tree from `$WORK/$TCC_PKG/` (stage 1 left it there + in place), with the same flag set live-bootstrap's `pass1.kaem` + passes to its own `tcc-mes -static -o tcc-boot0 ... tcc.c` call: + ``` + tcc-host -g -static -o tcc-boot0-mes \ + -D BOOTSTRAP=1 -D HAVE_FLOAT=1 -D HAVE_BITFIELD=1 \ + -D HAVE_LONG_LONG=1 -D HAVE_SETJMP=1 \ + -D TCC_TARGET_X86_64=1 ...CONFIG_TCC_* defines... \ + -D ONE_SOURCE=1 \ + -I . -I /include/mes -L . -L /lib \ + tcc.c + ``` + No intermediate `tcc-self.o`. An earlier iteration produced one as + a "tcc-host self-compile" check, then linked it against mes libc + in a separate step — but the standalone `-static` link exposed a + tcc 0.9.26 codegen bug, and the separate pass adds nothing the + live-bootstrap-style direct invocation doesn't already validate. + ~750 KB output. +7. **Stage out** mes libc + libtcc1 + crt1.o + headers into + `build/cc-bootstrap/X86_64/stage3-input/`, so stage 3 can mount + them without re-running stage 2. +8. **Smoke test**: `tcc-boot0-mes -version`. **Expected to SEGV under + QEMU on macOS arm64** (Issue §3); native x86_64 needed to verify + cleanly. + +## Stage 3 — busybox container drives boot1 / boot2 + +`scripts/stage3-rebuild.sh --arch X86_64` + +Mirrors live-bootstrap's pass1.kaem chain, but starting from +`tcc-boot0-mes` (= live-bootstrap's `tcc-boot0` slot) since we +skipped MesCC / `tcc-mes` entirely. Each pass uses the previous +stage's tcc to rebuild mes libc and recompile the **real**, +unflattened tcc-0.9.26 sources from the patched tree stage 1 +produced. + +### Why busybox + +This stage is "pure recompile" work — no host-toolchain dependency. +busybox provides everything we need (sh, tar, awk, basic file +utilities) and tcc-boot0-mes is its own preprocessor + assembler + +linker + ar. The container is ~5 MB; nothing else gets installed. + +### Sub-steps (inside the container, per pass) + +For each `cc ∈ {tcc-boot0-mes, tcc-boot1}`: + +1. **Rebuild libc** from mes sources using `cc`: + - `cc -c crt1.c -> /lib/crt1.o` + - `cc -c libtcc1.c`, `cc -ar -> /lib/tcc/libtcc1.a` + - per-file compile of mes libc, `cc -ar -> /lib/libc.a` +2. **Compile real tcc.c** using `cc`: + ``` + cc -g -static -o tcc-boot{1,2} \ + -D BOOTSTRAP=1 -D HAVE_FLOAT=1 -D HAVE_BITFIELD=1 \ + -D HAVE_LONG_LONG=1 -D HAVE_SETJMP=1 \ + -D TCC_TARGET_X86_64=1 ...CONFIG_TCC_* defines... \ + -D ONE_SOURCE=1 \ + -I . -I /include/mes -L . -L /lib \ + tcc.c + ``` +3. **Verify**: `tcc-boot{1,2} -version`. - The Mes headers do `#include <arch/syscall.h>`. Live-bootstrap must - set up an equivalent symlink internally; the tarball doesn't ship - one. Without this symlink, tcc-host segfaults rather than cleanly - reporting the missing include (see Issues §1). - -3. **Compile each mes libc .c file individually** with tcc-host. The - live-bootstrap kaem script `catm`s ~250 files into a single - `unified-libc.c` and compiles that as one TU. tcc-host crashes on - that approach (see Issues §2). Compiling each as a separate TU and - `ar`-ing the results sidesteps the bug and is what tcc was designed - to do anyway. - -4. **`tcc-host -ar` the .o set into `libc.a`** (~299 KB). - -5. **Build crt1.o** from `lib/linux/x86_64-mes-gcc/crt1.c` (mes's hand- - rolled `_start` that invokes `main` directly via inline asm). On - x86_64, `crtn.o` and `crti.o` are empty files (live-bootstrap does - the same). - -6. **Build libtcc1.a** from `lib/libtcc1.c` (tcc's helper runtime). - -7. **Install at the baked-in paths**: `tcc-host` was built with - `CONFIG_TCC_CRTPREFIX="/lib"`, `CONFIG_TCCDIR="/lib/tcc"`, - `CONFIG_TCC_SYSINCLUDEPATHS="/include/mes"`. The script populates - those locations inside the container so tcc-host can find its - pieces without `-B`/`-L` overrides. - -8. **Link**: `tcc-host -static -o tcc-boot0-mes tcc-self.o`. +The build-time defines here are a strict superset of stage 1's: +`HAVE_FLOAT`, `HAVE_BITFIELD`, `HAVE_SETJMP` are added because the +real builds enable code paths the flatten deliberately omits. - tcc-host walks `tcc-self.o`'s symbol references against `crt1.o`, - `libc.a`, `libtcc1.a`, emits a static ELF. +### Status today -Result: `build/cc-bootstrap/X86_64/tcc-boot0-mes` — 313 KB. Roughly -half the size of the earlier musl-linked variant (`tcc-boot0`, 764 KB), -which tracks: mes libc is stripped down, musl is full. +Stage 3 cannot complete on macOS arm64 hosts — Issue §3 (tcc-boot0-mes +SEGVs at startup under QEMU x86_64) blocks the very first +`tcc-boot0-mes -version`. The script is correct; it will run +end-to-end on native x86_64 hardware, or once a tcc 0.9.28rc backport +patches the prologue/_DYNAMIC issues. The failure mode is clean: the +script aborts at the first `-version` smoke and reports the blocker. ## Relation to live-bootstrap ``` -live-bootstrap path: mescc → tcc-mes → tcc-boot0 → tcc-boot1 → tcc-boot2 → tcc → tcc-0.9.27 → make 3.82 → ... -our path: gcc → tcc-host → tcc-boot0-mes - (= live-bootstrap's tcc-boot0 equivalent) +live-bootstrap path: mescc → tcc-mes → tcc-boot0 → tcc-boot1 → tcc-boot2 → tcc → tcc-0.9.27 → ... +our path: gcc → tcc-host → tcc-boot0-mes → tcc-boot1 → tcc-boot2 + └──── stage 2 (alpine) ────┘ └──── stage 3 (busybox) ────┘ ``` `tcc-boot0-mes` is the slot-equivalent of live-bootstrap's `tcc-boot0`. -The downstream chain (rebuild libc → tcc-boot1 → rebuild libc → -tcc-boot2 → tcc → tcc-0.9.27 → make 3.82 → real builds) is a series of -mechanical recompiles the existing pass1.kaem already orchestrates. -Picking up from `tcc-boot0-mes` requires only that it run correctly -(see Issues §3). +Stages 3 stops at `tcc-boot2` ("final 0.9.26" per the project goal); +the further hop to tcc-0.9.27 lives outside this doc. ## What this unlocks for the scheme1 cc -The ultimate goal: replace `tcc-host` in Stage 2 with our scheme1-hosted -C compiler. The interface is fixed: +The interface for the slot scheme CC will fill is fixed: -- **Input**: `tcc.flat.c` produced by Stage 1. -- **Output**: an ELF binary equivalent to `tcc-host` (or an object file - equivalent to `tcc-self.o`, which gcc would then link). +- **Input**: `tcc.flat.c` produced by stage 1. +- **Output**: a working ELF tcc-host capable of compiling mes libc + and compile+linking the patched real `tcc.c` into `tcc-boot0-mes`. -`tcc.flat.c` is a known-good, host-cc-validated artifact ready for -the scheme1 cc to chew on incrementally. Track progress against it. +Stage 2 collapses to "scheme1-cc compiles tcc.flat.c and the mes libc +sources inside a busybox container." The alpine container goes away; +busybox + scheme1-cc covers everything from stage 1's output through +stage 3's `tcc-boot2`. `tcc.flat.c` is a known-good, host-cc-validated +artifact ready for scheme1-cc to chew on incrementally. ## Reproducibility ``` -scripts/build-tcc-source.sh --arch X86_64 --self-host -scripts/build-tcc-real.sh +scripts/stage1-flatten.sh --arch X86_64 +scripts/stage2-alpine.sh --arch X86_64 +scripts/stage3-rebuild.sh --arch X86_64 # blocked on Issue §3 today ``` -Three artifacts land in `build/cc-bootstrap/X86_64/`: +Artifacts in `build/cc-bootstrap/X86_64/`: -| File | Size | Built by | What it is | -|-------------------|--------|------------------|-----------------------------------------| -| `tcc.flat.c` | 608 KB | host cc | flattened single-source tcc-0.9.26 | -| `tcc-host` | 704 KB | host gcc + musl | first-stage tcc, runs natively on Linux | -| `tcc-self.o` | 485 KB | tcc-host | tcc-host compiling its own source | -| `tcc-boot0-mes` | 313 KB | tcc-host + mes lib | real tcc-0.9.26, mes-libc-linked | +| File | Stage | Size | Built by | What it is | +|-------------------|-------|---------|-----------------------|-------------------------------------------| +| `tcc.flat.c` | 1 | 608 KB | host cc | flattened single-source tcc-0.9.26 | +| `tcc-host` | 2 | ~700 KB | alpine gcc + musl | transient stage-2 compiler | +| `tcc-boot0-mes` | 2 | ~750 KB | tcc-host + mes libc | static tcc-0.9.26, mes-libc-linked | +| `stage3-input/` | 2 | — | stage 2 | staged mes libc + libtcc1 + headers | +| `tcc-boot1` | 3 | ~750 KB | tcc-boot0-mes | first rebuild from real tcc.c | +| `tcc-boot2` | 3 | ~750 KB | tcc-boot1 | final 0.9.26 | -`build/` is in `.gitignore`; nothing is tracked outside of the scripts +`build/` is in `.gitignore`; nothing tracked outside the scripts themselves. ## Issues / bugs -Found while bringing this pipeline up. All four are real, none are -ours; (1) and (2) are bugs in tcc 0.9.26 that we work around in the -scripts; (3) and (4) are open and need either native x86_64 testing or -a tcc backport. +Found while bringing this pipeline up. All three are real, none are +ours; (1) and (2) are bugs in tcc 0.9.26 that the scripts work +around; (3) is open and needs either native x86_64 testing or a +tcc backport. ### 1. tcc 0.9.26 SEGV on missing include @@ -254,7 +309,7 @@ it segfaults instead of reporting the error. Discovered when `mes/include/dirent.h` does `#include <arch/syscall.h>`; mes/include has no `arch/` directory by default. -**Workaround**: in `build-tcc-real.sh`, create +**Workaround**: in `stage2-alpine.sh`, create `/tmp/mes-inc/arch -> linux/x86_64` before invoking tcc-host. The live-bootstrap build presumably sets up the same symlink somewhere we haven't traced — possibly via a configure step, or via the @@ -276,62 +331,60 @@ hash chain, or similar — that overflows or hits a corrupted state when the TU grows large enough. **Workaround**: compile each `.c` separately, then `ar` together. -`build-tcc-real.sh` does this for all 258 mes libc .c files. -Bonus: avoids ~250 redundant header re-parses, faster overall. +Both `stage2-alpine.sh` and `stage3-rebuild.sh` do this for all 258 +mes libc .c files. Bonus: avoids ~250 redundant header re-parses, +faster overall. -### 3. tcc 0.9.26 emits no `_DYNAMIC` stub for static binaries +**Confirmed in canonical live-bootstrap.** The +[`scripts/diag-livebootstrap-qemu.sh`](../scripts/diag-livebootstrap-qemu.sh) +diagnostic runs upstream live-bootstrap's amd64 pass1 chain inside +the same busybox + linux/amd64 QEMU we use, and its mescc-built +`tcc-mes` SEGVs at exactly this step (`tcc-mes -c unified-libc.c` +with `assert fail: 0` then SIGSEGV). The per-file workaround is +load-bearing for any tcc-0.9.26-on-QEMU build, not specific to our +path. -When linking a tcc-host-output ELF against musl's `crt1.o` + `libc.a` -on alpine, the resulting binary segfaults because tcc 0.9.26 doesn't -emit a `_DYNAMIC` symbol; the `lea _DYNAMIC(%rip), %rsi` in musl's -`_start` resolves to address 0, and musl's `__libc_start_main` -dereferences rsi. +### 3. tcc-boot0-mes segfaults at startup under QEMU x86_64 + +`./tcc-boot0-mes -version` segfaults under `podman run --platform +linux/amd64` on macOS arm64. -Side-by-side disassembly of the same hello-world built with alpine's -prebuilt tcc 0.9.28rc and our tcc-host: +**This is specific to our build path, not a generic QEMU issue.** The +diagnostic in +[`scripts/diag-livebootstrap-qemu.sh`](../scripts/diag-livebootstrap-qemu.sh) +runs upstream live-bootstrap's amd64 pass1 chain inside the same +busybox + linux/amd64 QEMU. Its mescc-built `tcc-mes` (also +mes-libc-linked, also tcc-0.9.26) runs `tcc-mes -version` cleanly: ``` -alpine 0.9.28rc: lea 0x9bb(%rip),%rsi # 0x4014a0 ← real stub -ours 0.9.26: lea -0x4000bd(%rip),%rsi # 0x0 ← NULL ++> tcc-mes -version +tcc version 0.9.26 (x86_64 Linux) ``` -This is why the **musl-linked** tcc-boot0 doesn't run. The -**mes-libc-linked** `tcc-boot0-mes` should sidestep it because mes's -`crt1.c` calls `main` directly, not via `__libc_start_main`, so rsi -is never dereferenced. - -A patch to emit a `_DYNAMIC` stub would be small. tcc 0.9.28rc has -the fix; backporting it to the flat tcc.c is a candidate next step. - -### 4. tcc-boot0-mes segfaults at startup under QEMU x86_64 - -`./tcc-boot0-mes -version` segfaults under `podman run --platform -linux/amd64` on macOS arm64. **Whether this works on native x86_64 -hardware is untested.** - -Possibilities: - -a) tcc 0.9.26 codegen bug in the function prologue tcc emits for - `_start` (mes's `crt1.c` declares `_start` as a regular C - function with inline asm; tcc adds a standard prologue, and a - bug in that prologue under specific conditions could mismatch - mes's expectations of `rbp`). - -b) A QEMU-specific interaction with mes's hand-rolled `_start` - inline asm (which assumes the kernel-provided process-entry - stack layout). QEMU's process-emulation user-mode does set - that up, but corner cases exist. - -(a) is more likely because the same QEMU runs alpine's prebuilt -tcc 0.9.28rc — and tcc-built binaries from that — without any -trouble. So whatever's wrong is specific to **our** binary's -contents. - -**Path to verify**: run tcc-boot0-mes on native x86_64. Failure -there confirms (a) and points at a tcc-source patch. -**Path to fix**: backport tcc 0.9.28rc's `_DYNAMIC` and prologue -fixes (probably the same patch series that fixes Issue 3). - -Once tcc-boot0-mes runs, the rest of the chain -(rebuild-libc → tcc-boot1 → tcc-boot2 → tcc → tcc-0.9.27 → make 3.82 -→ ...) is what live-bootstrap's pass1.kaem already does. +So QEMU correctly executes mescc-built tcc-0.9.26 ELFs at startup. Our +`tcc-boot0-mes`, which differs in being produced by `tcc-host` +(itself a gcc-compile of `tcc.flat.c`) rather than by mescc, hits a +codegen path that mescc avoids. Most likely candidates: function +prologue around `_start` (mes's `crt1.c` declares `_start` as a +regular C function with inline asm; tcc-host's prologue may not match +what mes's runtime expects), or a `_DYNAMIC` symbol gap that tcc +0.9.28rc's patch series fixes. + +A related historical observation: when an earlier iteration of stage 2 +tried linking a tcc-host-output ELF against musl's `crt1.o` directly, +that binary also segfaulted at startup because tcc 0.9.26 doesn't +emit a `_DYNAMIC` symbol musl's `_start` expects. That path is +retired, but the same underlying tcc codegen gap is a plausible +cousin of the SEGV here — tcc 0.9.28rc's patch series likely fixes +both. + +**Path to fix**: backport tcc 0.9.28rc's prologue / `_DYNAMIC` fixes +onto `tcc.flat.c` (or onto the patched real tcc tree stage 2 builds +from), then rerun stage 2. Optionally compare disassembly of our +`tcc-boot0-mes` against live-bootstrap's `tcc-boot0` (output of the +same diagnostic chain at a later step) to localize the exact +divergence. + +Once tcc-boot0-mes runs, stage 3 is unblocked: the `tcc-boot1` / +`tcc-boot2` rebuilds mirror what live-bootstrap's pass1.kaem already +does, and the script is in place. diff --git a/scripts/build-tcc-real.sh b/scripts/build-tcc-real.sh @@ -1,188 +0,0 @@ -#!/bin/sh -## scripts/build-tcc-real.sh — build a real tcc-0.9.26 from our chain, -## linked against mes libc. -## -## Pipeline (all inside a linux/amd64 alpine container): -## 1. unpack mes-0.27.1 sources -## 2. set up include tree with `arch -> linux/<arch>` symlink so -## mes headers' `#include <arch/syscall.h>` resolves -## (without this, tcc 0.9.26 SEGVs on missing include rather than -## reporting cleanly — bug found while bringing this up) -## 3. compile each mes libc .c file individually with tcc-host -## (concatenating them all into unified-libc.c also hits a tcc 0.9.26 -## bug — accumulated symbol-table state segfaults around 22+ files -## when one of them uses inline asm) -## 4. tcc-host -ar the .o set into libc.a -## 5. compile crt1.c -> crt1.o; libtcc1.c -> libtcc1.a -## 6. tcc-host -static linking tcc-self.o (already built by -## build-tcc-source.sh --self-host) against the mes libc -## -> tcc-boot0-mes -## -## Status: produces tcc-boot0-mes successfully. The binary itself -## segfaults at startup under QEMU x86_64 emulation; native x86_64 -## testing is needed to confirm whether this is a tcc-0.9.26 codegen -## bug exposed by mes's hand-rolled crt1, or a QEMU-specific issue. -## -## Pre-condition: -## build/cc-bootstrap/X86_64/tcc-host (build-tcc-source.sh --self-host) -## build/cc-bootstrap/X86_64/tcc-self.o -## -## Usage: -## scripts/build-tcc-real.sh - -set -eu - -ROOT=$(cd "$(dirname "$0")/.." && pwd) -WORK=$ROOT/build/cc-bootstrap/X86_64 -MES_TAR=$ROOT/../lb-work/distfiles/mes-0.27.1.tar.gz -MES_PKG=mes-0.27.1 - -[ -r "$MES_TAR" ] || { echo "missing $MES_TAR" >&2; exit 1; } - -if [ ! -x "$WORK/tcc-host" ] || [ ! -r "$WORK/tcc-self.o" ]; then - echo "tcc-host or tcc-self.o missing — running build-tcc-source.sh --self-host" - "$ROOT/scripts/build-tcc-source.sh" --arch X86_64 --self-host -fi - -MES_SRC=$WORK/$MES_PKG -if [ ! -d "$MES_SRC" ]; then - tar -xzf "$MES_TAR" -C "$WORK" -fi -mkdir -p "$MES_SRC/include/mes" -: > "$MES_SRC/include/mes/config.h" - -echo "running build inside linux/amd64 alpine container (a few minutes under QEMU)" - -podman run --rm -i --platform linux/amd64 \ - -v "$ROOT":/work -w /work alpine:latest sh -s <<'CONTAINER_SCRIPT' -set -eu -WORK=/work/build/cc-bootstrap/X86_64 -TCC=$WORK/tcc-host -MES_SRC=$WORK/mes-0.27.1 -MES_ARCH=x86_64 - -# --- (2) build a sanitized include tree ------------------------------ -INC=/tmp/mes-inc -mkdir -p $INC -cp -r $MES_SRC/include/. $INC/ -ln -sfn linux/$MES_ARCH $INC/arch - -# --- (3) compile each mes libc .c file individually ------------------ -mkdir -p /tmp/objs -cd $MES_SRC/lib - -# The canonical catm list from steps/tcc-0.9.26/pass1.kaem:99 (X86_64). -# We compile these as separate TUs to dodge tcc-host's accumulator bug. -ALL_FILES="ctype/isalnum.c ctype/isalpha.c ctype/isascii.c ctype/iscntrl.c \ -ctype/isdigit.c ctype/isgraph.c ctype/islower.c ctype/isnumber.c \ -ctype/isprint.c ctype/ispunct.c ctype/isspace.c ctype/isupper.c \ -ctype/isxdigit.c ctype/tolower.c ctype/toupper.c \ -dirent/closedir.c dirent/__getdirentries.c dirent/opendir.c \ -linux/readdir.c linux/access.c linux/brk.c linux/chdir.c linux/chmod.c \ -linux/clock_gettime.c linux/close.c linux/dup2.c linux/dup.c linux/execve.c \ -linux/fcntl.c linux/fork.c linux/fsync.c linux/fstat.c linux/_getcwd.c \ -linux/getdents.c linux/getegid.c linux/geteuid.c linux/getgid.c linux/getpid.c \ -linux/getppid.c linux/getrusage.c linux/gettimeofday.c linux/getuid.c \ -linux/ioctl.c linux/ioctl3.c linux/kill.c linux/link.c linux/lseek.c \ -linux/lstat.c linux/malloc.c linux/mkdir.c linux/mknod.c linux/nanosleep.c \ -linux/_open3.c linux/pipe.c linux/_read.c linux/readlink.c linux/rename.c \ -linux/rmdir.c linux/setgid.c linux/settimer.c linux/setuid.c linux/signal.c \ -linux/sigprogmask.c linux/symlink.c linux/stat.c linux/time.c linux/unlink.c \ -linux/waitpid.c linux/wait4.c \ -linux/${MES_ARCH}-mes-gcc/_exit.c linux/${MES_ARCH}-mes-gcc/syscall.c \ -linux/${MES_ARCH}-mes-gcc/_write.c \ -math/ceil.c math/fabs.c math/floor.c \ -mes/abtod.c mes/abtol.c mes/__assert_fail.c mes/assert_msg.c \ -mes/__buffered_read.c mes/__init_io.c mes/cast.c mes/dtoab.c \ -mes/eputc.c mes/eputs.c mes/fdgetc.c mes/fdgets.c mes/fdputc.c mes/fdputs.c \ -mes/fdungetc.c mes/globals.c mes/itoa.c mes/ltoab.c mes/ltoa.c \ -mes/__mes_debug.c mes/mes_open.c mes/ntoab.c mes/oputc.c mes/oputs.c \ -mes/search-path.c mes/ultoa.c mes/utoa.c \ -posix/alarm.c posix/buffered-read.c posix/execl.c posix/execlp.c \ -posix/execv.c posix/execvp.c posix/getcwd.c posix/getenv.c posix/isatty.c \ -posix/mktemp.c posix/open.c posix/pathconf.c posix/raise.c posix/sbrk.c \ -posix/setenv.c posix/sleep.c posix/unsetenv.c posix/wait.c posix/write.c \ -stdio/clearerr.c stdio/fclose.c stdio/fdopen.c stdio/feof.c stdio/ferror.c \ -stdio/fflush.c stdio/fgetc.c stdio/fgets.c stdio/fileno.c stdio/fopen.c \ -stdio/fprintf.c stdio/fputc.c stdio/fputs.c stdio/fread.c stdio/freopen.c \ -stdio/fscanf.c stdio/fseek.c stdio/ftell.c stdio/fwrite.c stdio/getc.c \ -stdio/getchar.c stdio/perror.c stdio/printf.c stdio/putc.c stdio/putchar.c \ -stdio/remove.c stdio/snprintf.c stdio/sprintf.c stdio/sscanf.c stdio/ungetc.c \ -stdio/vfprintf.c stdio/vfscanf.c stdio/vprintf.c stdio/vsnprintf.c \ -stdio/vsprintf.c stdio/vsscanf.c \ -stdlib/abort.c stdlib/abs.c stdlib/alloca.c stdlib/atexit.c stdlib/atof.c \ -stdlib/atoi.c stdlib/atol.c stdlib/calloc.c stdlib/__exit.c stdlib/exit.c \ -stdlib/free.c stdlib/mbstowcs.c stdlib/puts.c stdlib/qsort.c stdlib/realloc.c \ -stdlib/strtod.c stdlib/strtof.c stdlib/strtol.c stdlib/strtold.c \ -stdlib/strtoll.c stdlib/strtoul.c stdlib/strtoull.c \ -string/bcmp.c string/bcopy.c string/bzero.c string/index.c string/memchr.c \ -string/memcmp.c string/memcpy.c string/memmem.c string/memmove.c string/memset.c \ -string/rindex.c string/strcat.c string/strchr.c string/strcmp.c string/strcpy.c \ -string/strcspn.c string/strdup.c string/strerror.c string/strlen.c \ -string/strlwr.c string/strncat.c string/strncmp.c string/strncpy.c \ -string/strpbrk.c string/strrchr.c string/strspn.c string/strstr.c string/strupr.c \ -stub/atan2.c stub/bsearch.c stub/chown.c stub/__cleanup.c stub/cos.c \ -stub/ctime.c stub/exp.c stub/fpurge.c stub/freadahead.c stub/frexp.c \ -stub/getgrgid.c stub/getgrnam.c stub/getlogin.c stub/getpgid.c stub/getpgrp.c \ -stub/getpwnam.c stub/getpwuid.c stub/gmtime.c stub/ldexp.c stub/localtime.c \ -stub/log.c stub/mktime.c stub/modf.c stub/mprotect.c stub/pclose.c \ -stub/popen.c stub/pow.c stub/putenv.c stub/rand.c stub/realpath.c stub/rewind.c \ -stub/setbuf.c stub/setgrent.c stub/setlocale.c stub/setvbuf.c stub/sigaction.c \ -stub/sigaddset.c stub/sigblock.c stub/sigdelset.c stub/sigemptyset.c \ -stub/sigsetmask.c stub/sin.c stub/sys_siglist.c stub/system.c stub/sqrt.c \ -stub/strftime.c stub/times.c stub/ttyname.c stub/umask.c stub/utime.c \ -${MES_ARCH}-mes-gcc/setjmp.c" - -OBJS= -n_compiled=0 -n_failed=0 -for f in $ALL_FILES; do - name=$(echo "$f" | tr / _) - o=/tmp/objs/${name%.c}.o - if "$TCC" -c -D HAVE_CONFIG_H=1 -I "$INC" -I "$INC/linux/$MES_ARCH" \ - -o "$o" "$f" 2>/dev/null; then - OBJS="$OBJS $o" - n_compiled=$((n_compiled+1)) - else - echo "compile failed: $f" >&2 - n_failed=$((n_failed+1)) - fi -done -echo "compiled $n_compiled libc .o files (failed: $n_failed)" -[ "$n_failed" -eq 0 ] || { echo "abort: some libc files failed" >&2; exit 1; } - -# --- (4) ar -> libc.a ----------------------------------------------- -mkdir -p /lib/tcc /include/mes -"$TCC" -ar cr /lib/libc.a $OBJS - -# --- (5) crt1.o, libtcc1.a ------------------------------------------- -"$TCC" -c -D HAVE_CONFIG_H=1 -I "$INC" -I "$INC/linux/$MES_ARCH" \ - -o /lib/crt1.o "linux/$MES_ARCH-mes-gcc/crt1.c" -: > /lib/crtn.o -: > /lib/crti.o - -"$TCC" -c -D HAVE_CONFIG_H=1 -D HAVE_LONG_LONG=1 -D HAVE_FLOAT=1 \ - -I "$INC" -I "$INC/linux/$MES_ARCH" \ - -o /tmp/libtcc1.o libtcc1.c -"$TCC" -ar cr /lib/tcc/libtcc1.a /tmp/libtcc1.o - -# Install headers at the path tcc-host has baked in (CONFIG_TCC_SYSINCLUDEPATHS) -cp -r "$INC/." /include/mes/ - -ls -la /lib/crt1.o /lib/libc.a /lib/tcc/libtcc1.a - -# --- (6) link tcc-self.o against mes libc -> tcc-boot0-mes ----------- -echo -echo "--- linking tcc-self.o against mes libc ---" -"$TCC" -static -o "$WORK/tcc-boot0-mes" "$WORK/tcc-self.o" -ls -la "$WORK/tcc-boot0-mes" - -echo -echo "--- tcc-boot0-mes -version (expected: may segfault under QEMU) ---" -rc=0; "$WORK/tcc-boot0-mes" -version 2>&1 || rc=$? -echo "exit=$rc" -CONTAINER_SCRIPT - -echo -echo "artifacts:" -ls -la "$WORK/tcc-boot0-mes" 2>/dev/null || echo "(no tcc-boot0-mes)" diff --git a/scripts/build-tcc-source.sh b/scripts/build-tcc-source.sh @@ -1,214 +0,0 @@ -#!/bin/sh -## scripts/build-tcc-source.sh — produce a single flattened tcc.c that our -## scheme1-hosted C compiler can consume. -## -## Mirrors the live-bootstrap tcc-mes invocation (steps/tcc-0.9.26/pass1.kaem) -## minus the actual compile step. The output is one C source bytestream with -## all #include "X.c" project files inlined and all standard headers expanded -## from the Mes-bundled minimal libc headers. No mes/mescc binary is involved -## — only the host preprocessor. -## -## Stages: -## 1. unpack tcc-0.9.26-1147-gee75a10c.tar.gz -## 2. apply live-bootstrap simple-patches (file-open reordering in tcctools.c) -## 3. host-cc -E with the tcc-mes defines + -nostdinc + Mes header tree -## 4. emit build/cc-bootstrap/<arch>/tcc.flat.c -## 5. (optional) compile tcc.flat.c with host cc to verify well-formedness -## -## Usage: -## scripts/build-tcc-source.sh [--arch <arch>] [--verify] [--self-host] -## -## --arch: X86_64 | I386 | RISCV64. Default X86_64 (live-bootstrap -## reference path). -## --verify: compile tcc.flat.c with host cc to confirm the source is -## valid C. On macOS, produces a Mach-O .o (won't run, but -## proves syntactic correctness). -## --self-host: full chain in a Linux x86_64 container (alpine + gcc): -## 1. build tcc-host (a real tcc binary) from tcc.flat.c -## 2. run tcc-host -version -## 3. use tcc-host to recompile tcc.flat.c (self-host check) -## Requires podman and pulls alpine:latest on first run. -## Implies --verify. - -set -eu - -# --- arg parse -------------------------------------------------------- -ARCH=X86_64 -VERIFY=0 -SELF_HOST=0 -while [ $# -gt 0 ]; do - case "$1" in - --arch) ARCH=$2; shift 2 ;; - --verify) VERIFY=1; shift ;; - --self-host) SELF_HOST=1; VERIFY=1; shift ;; - -h|--help) sed -n 's/^## \{0,1\}//p' "$0"; exit 0 ;; - *) echo "unknown arg: $1" >&2; exit 2 ;; - esac -done - -# Map ARCH to (mes-arch, have-long-long). -case "$ARCH" in - X86_64) MES_ARCH=x86_64; HAVE_LL=1 ;; - I386) MES_ARCH=x86; HAVE_LL=0 ;; - RISCV64) MES_ARCH=riscv64; HAVE_LL=1 ;; - AARCH64) echo "AARCH64 not in live-bootstrap; tcc.c lacks an arm64-gen.c we can use here" >&2; exit 2 ;; - *) echo "unknown ARCH: $ARCH" >&2; exit 2 ;; -esac - -# --- paths ------------------------------------------------------------ -ROOT=$(cd "$(dirname "$0")/.." && pwd) -WORK=$ROOT/build/cc-bootstrap/$ARCH -DISTFILES=$ROOT/../lb-work/distfiles -LB_PATCHES=$ROOT/../live-bootstrap/steps/tcc-0.9.26/simple-patches -MES_INCLUDE=$ROOT/../mes/include -MES_INCLUDE_LINUX=$MES_INCLUDE/linux/$MES_ARCH - -TCC_TAR=$DISTFILES/tcc-0.9.26.tar.gz -TCC_PKG=tcc-0.9.26-1147-gee75a10c - -[ -r "$TCC_TAR" ] || { echo "missing $TCC_TAR" >&2; exit 1; } -[ -d "$LB_PATCHES" ] || { echo "missing $LB_PATCHES" >&2; exit 1; } -[ -d "$MES_INCLUDE" ] || { echo "missing $MES_INCLUDE" >&2; exit 1; } -[ -d "$MES_INCLUDE_LINUX" ] || { echo "missing $MES_INCLUDE_LINUX" >&2; exit 1; } - -# --- (1) unpack ------------------------------------------------------- -mkdir -p "$WORK" -rm -rf "$WORK/$TCC_PKG" -tar -xzf "$TCC_TAR" -C "$WORK" - -SRC=$WORK/$TCC_PKG - -# --- (2) simple-patches ---------------------------------------------- -# Both patches edit tcctools.c. The pair (remove-fileopen, addback-fileopen) -# moves a fopen() block earlier in the function. Implement simple-patch -# inline: replace the .before block (verbatim) with the .after block. -apply_simple_patch() { - target=$1 - before=$2 - after=$3 - [ -r "$target" ] || { echo "patch target missing: $target" >&2; exit 1; } - [ -r "$before" ] || { echo "patch before missing: $before" >&2; exit 1; } - [ -r "$after" ] || { echo "patch after missing: $after" >&2; exit 1; } - # Use awk to do a literal multiline replace. We slurp file, find the - # first occurrence of the before-block, splice in the after-block. - awk -v BFILE="$before" -v AFILE="$after" ' - BEGIN { - while ((getline line < BFILE) > 0) bef = bef line "\n"; - close(BFILE); - while ((getline line < AFILE) > 0) aft = aft line "\n"; - close(AFILE); - } - { src = src $0 "\n" } - END { - i = index(src, bef); - if (i == 0) { print "patch did not match" > "/dev/stderr"; exit 1 } - printf "%s%s%s", - substr(src, 1, i - 1), - aft, - substr(src, i + length(bef)); - } - ' "$target" > "$target.new" - mv "$target.new" "$target" -} - -apply_simple_patch \ - "$SRC/tcctools.c" \ - "$LB_PATCHES/remove-fileopen.before" \ - "$LB_PATCHES/remove-fileopen.after" - -apply_simple_patch \ - "$SRC/tcctools.c" \ - "$LB_PATCHES/addback-fileopen.before" \ - "$LB_PATCHES/addback-fileopen.after" - -# pass1.kaem creates two empty config.h files via `catm <out>` (line 27-28). -# tcc.h does `#include "config.h"` and Mes's stdio.h reaches for -# `<mes/config.h>`. Both are empty in the live-bootstrap build. -: > "$SRC/config.h" -mkdir -p "$WORK/mes-overlay/mes" -: > "$WORK/mes-overlay/mes/config.h" - -# --- (3) flatten via host preprocessor -------------------------------- -HOST_CC=${HOST_CC:-cc} -FLAT=$WORK/tcc.flat.c - -# Defines mirror the tcc-mes invocation in pass1.kaem. Paths in the -# CONFIG_* defines don't matter for our purposes (we won't run the -# resulting binary as-is) but must be syntactically valid string literals. -"$HOST_CC" -E -P \ - -nostdinc \ - -I "$SRC" \ - -I "$WORK/mes-overlay" \ - -I "$MES_INCLUDE_LINUX" \ - -I "$MES_INCLUDE" \ - -D __linux__=1 \ - -D __${MES_ARCH}__=1 \ - -D BOOTSTRAP=1 \ - -D HAVE_LONG_LONG=$HAVE_LL \ - -D inline= \ - -D "CONFIG_TCCDIR=\"/lib/tcc\"" \ - -D "CONFIG_SYSROOT=\"/\"" \ - -D "CONFIG_TCC_CRTPREFIX=\"/lib\"" \ - -D "CONFIG_TCC_ELFINTERP=\"/mes/loader\"" \ - -D "CONFIG_TCC_SYSINCLUDEPATHS=\"/include/mes\"" \ - -D "TCC_LIBGCC=\"/lib/libc.a\"" \ - -D CONFIG_TCC_LIBTCC1_MES=0 \ - -D CONFIG_TCCBOOT=1 \ - -D CONFIG_TCC_STATIC=1 \ - -D CONFIG_USE_LIBGCC=1 \ - -D "TCC_VERSION=\"0.9.26\"" \ - -D ONE_SOURCE=1 \ - -D TCC_TARGET_${ARCH}=1 \ - "$SRC/tcc.c" > "$FLAT" - -LINES=$(wc -l < "$FLAT") -BYTES=$(wc -c < "$FLAT") -echo "produced $FLAT ($LINES lines, $BYTES bytes)" - -# --- (4) optional verify --------------------------------------------- -if [ "$VERIFY" -eq 1 ]; then - HOST_OBJ=$WORK/tcc.flat.o - if "$HOST_CC" -c -w -o "$HOST_OBJ" "$FLAT" 2>"$WORK/host-cc.log"; then - echo "host cc: tcc.flat.c compiles cleanly to $HOST_OBJ" - else - echo "host cc: tcc.flat.c FAILED to compile; see $WORK/host-cc.log" >&2 - echo "first 30 errors:" >&2 - head -30 "$WORK/host-cc.log" >&2 - exit 1 - fi -fi - -# --- (5) optional self-host validation in a Linux x86_64 container ---- -# tcc.c expects errno as a global int (mes-libc convention) but musl -# exposes errno via __errno_location(). Provide a tiny shim so linking -# against musl works. -if [ "$SELF_HOST" -eq 1 ]; then - if [ "$ARCH" != "X86_64" ]; then - echo "--self-host only supported for X86_64 (the live-bootstrap path)" >&2 - exit 2 - fi - command -v podman >/dev/null 2>&1 || { - echo "--self-host requires podman" >&2; exit 2 - } - SHIM=$WORK/errno-shim.c - printf 'int errno;\n' > "$SHIM" - - echo "--- self-host: building tcc-host with alpine gcc ---" - podman run --rm --platform linux/amd64 \ - -v "$ROOT":/work -w /work alpine:latest sh -c " - apk add --no-cache gcc musl-dev >/dev/null 2>&1 - set -e - REL=build/cc-bootstrap/$ARCH - gcc -w -static -no-pie -o \$REL/tcc-host \\ - \$REL/tcc.flat.c \\ - \$REL/errno-shim.c - echo - echo '--- tcc-host -version ---' - \$REL/tcc-host -version - echo - echo '--- self-compile: tcc-host compiling tcc.flat.c ---' - \$REL/tcc-host -c -o \$REL/tcc-self.o \$REL/tcc.flat.c - ls -la \$REL/tcc-self.o - echo 'self-compile: OK' - " -fi diff --git a/scripts/diag-livebootstrap-qemu.sh b/scripts/diag-livebootstrap-qemu.sh @@ -0,0 +1,187 @@ +#!/bin/sh +## scripts/diag-livebootstrap-qemu.sh — DIAGNOSTIC ONLY +## +## Runs live-bootstrap's stage0 → tcc-0.9.27 chain inside a busybox:musl +## container under linux/amd64 QEMU emulation, to determine whether the +## tcc-boot0-mes startup SEGV (Issue §3 in docs/TCC.md) is QEMU's fault +## or specific to our build. +## +## **This script is diagnostic, not part of the bootstrap chain.** It +## intentionally invokes live-bootstrap's M2-Planet / Mes / MesCC path +## — the very chain our project replaces. Nothing it produces feeds +## into the project's deliverables. Read result, then ignore. +## +## Outcome interpretation: +## - chain reaches tcc-0.9.27 and `tcc -version` works: +## QEMU is sound. Our tcc-boot0-mes SEGV is build-specific +## (codegen/runtime bug). Action: backport tcc 0.9.28rc fixes +## or compare disasm against live-bootstrap's tcc-boot0. +## - chain SEGVs at tcc-mes / tcc-boot0 / tcc-0.9.27: +## QEMU is broken for these binaries on macOS arm64. Action: +## either run on native x86_64 hardware, or use the linux/386 +## (32-bit) path with QEMU and accept the arch mismatch. +## +## Setup (host): +## - Distfiles at lb-work/distfiles. Currently we have +## mes-0.27.1.tar.gz, nyacc-1.00.2-lb1.tar.gz, tcc-0.9.26.tar.gz +## The diagnostic also needs tcc-0.9.27.tar.bz2; this script +## fetches it via curl on first run if missing. Stage0-posix tools +## (M2-Planet, mescc-tools, etc.) are bundled in +## ../live-bootstrap/seed/stage0-posix and don't need distfiles. +## +## Pipeline: +## 1. (host) populate distfiles (curl tcc-0.9.27 if needed) +## 2. (host) assemble rootfs at build/diag-livebootstrap/rootfs/ +## - copy seed/stage0-posix/* (the hex0 binaries + M2-Planet +## + mescc-tools sources) to / +## - copy seed/{after,seed,preseeded}.kaem to / +## - copy steps/, lib/ to / +## - copy distfiles to /distfiles +## - write /steps/bootstrap.cfg with ARCH=amd64, CHROOT=True +## - truncate /steps/manifest after `build: tcc-0.9.27` so the +## chain stops at our target instead of running 200+ builds +## 3. (busybox:musl, linux/amd64) chroot into rootfs and run +## /bootstrap-seeds/POSIX/AMD64/kaem-optional-seed +## +## Runtime: many hours under QEMU emulation on macOS arm64. Set aside a +## work block. Set DIAG_PREP_ONLY=1 to do steps 1-2 and skip the run. +## +## Usage: +## scripts/diag-livebootstrap-qemu.sh + +set -eu + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +BOOTSTRAP_ROOT=$(cd "$ROOT/.." && pwd) +LB=$BOOTSTRAP_ROOT/live-bootstrap +DISTFILES=$BOOTSTRAP_ROOT/lb-work/distfiles +WORK=$ROOT/build/diag-livebootstrap +ROOTFS=$WORK/rootfs + +[ -d "$LB" ] || { echo "missing live-bootstrap at $LB" >&2; exit 1; } +[ -d "$DISTFILES" ] || { echo "missing distfiles at $DISTFILES" >&2; exit 1; } +command -v podman >/dev/null 2>&1 || { echo "podman required" >&2; exit 2; } + +# --- (1) ensure distfiles populated ---------------------------------- +NEED="mes-0.27.1.tar.gz tcc-0.9.26.tar.gz tcc-0.9.27.tar.bz2 nyacc-1.00.2-lb1.tar.gz" +for f in $NEED; do + if [ ! -r "$DISTFILES/$f" ]; then + echo "missing $DISTFILES/$f" + case "$f" in + tcc-0.9.27.tar.bz2) + echo "fetching from savannah..." + curl --fail --location \ + "https://download.savannah.gnu.org/releases/tinycc/$f" \ + -o "$DISTFILES/$f" + ;; + *) + echo " (cannot auto-fetch $f — please populate $DISTFILES/$f)" >&2 + exit 1 + ;; + esac + fi +done +echo "distfiles ok: $NEED" + +# --- (2) assemble rootfs -------------------------------------------- +echo "=== assembling rootfs at $ROOTFS ===" +rm -rf "$WORK" +mkdir -p "$ROOTFS" + +# seed/stage0-posix → / (stage0 tools, M2-Planet, mescc-tools, kaem etc) +cp -R "$LB/seed/stage0-posix/." "$ROOTFS/" + +# seed/*.kaem → / (preseeded.kaem, seed.kaem, after.kaem) +cp "$LB/seed/"*.kaem "$ROOTFS/" + +# Other seed files (configurator binaries, checksums) +cp "$LB/seed/configurator.c" "$LB/seed/configurator.amd64.checksums" "$ROOTFS/" 2>/dev/null || true +cp "$LB/seed/script-generator.c" "$LB/seed/script-generator.amd64.checksums" "$ROOTFS/" 2>/dev/null || true + +# steps/, lib/ from live-bootstrap +cp -R "$LB/steps" "$ROOTFS/" +cp -R "$LB/lib" "$ROOTFS/" + +# distfiles → /external/distfiles (live-bootstrap's steps/env sets +# DISTFILES=/external/distfiles, and helpers/build steps read from there) +mkdir -p "$ROOTFS/external/distfiles" +for f in $NEED; do + cp "$DISTFILES/$f" "$ROOTFS/external/distfiles/" +done + +# Truncate manifest to stop after the first `build: tcc-0.9.27`. The +# manifest has a header comment block (lines 1-33), then build steps +# starting at line 34. Line 38 is the first tcc-0.9.27 build. +awk ' + /^build: tcc-0\.9\.27/ && !seen_tcc27 { + print + seen_tcc27 = 1 + next + } + seen_tcc27 { + # drop everything after first tcc-0.9.27 build + next + } + { print } +' "$LB/steps/manifest" > "$ROOTFS/steps/manifest" + +# bootstrap.cfg — mirrors what rootfs.py would write for +# `--arch amd64 --chroot --mirrors file:///distfiles`. We disable +# every optional pipeline (kernels, configurator, fiwix) since this +# is a pass1-only diagnostic. +cat > "$ROOTFS/steps/bootstrap.cfg" <<'EOF' +ARCH=amd64 +ARCH_DIR=AMD64 +FORCE_TIMESTAMPS=False +CHROOT=True +UPDATE_CHECKSUMS=False +JOBS=2 +SWAP_SIZE=0 +FINAL_JOBS=2 +INTERNAL_CI=False +INTERACTIVE=False +QEMU=False +BARE_METAL=False +DISK=sda1 +KERNEL_BOOTSTRAP=False +BUILD_KERNELS=False +CONFIGURATOR=False +MIRRORS_LEN=0 +EOF + +echo "rootfs assembled." +du -sh "$ROOTFS" 2>/dev/null || true + +if [ "${DIAG_PREP_ONLY:-0}" = "1" ]; then + echo "DIAG_PREP_ONLY=1 — skipping container run." + exit 0 +fi + +# --- (3) run kaem-optional-seed in busybox:musl under linux/amd64 ---- +echo +echo "=== launching kaem-optional-seed via busybox:musl (linux/amd64 QEMU) ===" +echo " long-running. ctrl-C aborts. log lines stream below." +echo + +# busybox:musl ships chroot, sh, tar, awk, etc. — sufficient. +# /proc /dev /sys are mounted by podman; chroot inherits them via bind. +# We mount the rootfs as /work/rootfs inside the container, then chroot. +podman run --rm -i --platform linux/amd64 \ + -v "$ROOTFS":/rootfs \ + docker.io/library/busybox:musl sh -s <<'CONTAINER_SCRIPT' +set -eu + +# Ensure /proc /dev /sys exist inside the chroot for kaem etc. +mkdir -p /rootfs/proc /rootfs/dev /rootfs/sys /rootfs/tmp +mount -t proc proc /rootfs/proc 2>/dev/null || true +mount --rbind /dev /rootfs/dev 2>/dev/null || true +mount --rbind /sys /rootfs/sys 2>/dev/null || true + +echo "--- starting chroot kaem ---" +exec env -i PATH=/bin chroot /rootfs /bootstrap-seeds/POSIX/AMD64/kaem-optional-seed +CONTAINER_SCRIPT + +rc=$? +echo +echo "=== kaem exit=$rc ===" +exit "$rc" diff --git a/scripts/stage1-flatten.sh b/scripts/stage1-flatten.sh @@ -0,0 +1,155 @@ +#!/bin/sh +## scripts/stage1-flatten.sh — flatten upstream tcc-0.9.26 into a single +## C bytestream (tcc.flat.c) using only the host preprocessor. +## +## This is the first of three stages building tcc-0.9.26 without +## M2-Planet, MesCC, or Mes Scheme. See docs/TCC.md. +## +## Stages: +## 1. unpack tcc-0.9.26-1147-gee75a10c.tar.gz +## 2. apply live-bootstrap simple-patches (tcctools.c file-open reorder) +## 3. host cc -E -nostdinc with mes-bundled headers + tcc-mes defines +## 4. emit build/cc-bootstrap/<arch>/tcc.flat.c +## 5. (--verify) compile tcc.flat.c with host cc to confirm well-formedness +## +## Stage 1 deliberately stays on the host: it is just text manipulation +## (preprocess + concat) and the resulting tcc.flat.c is a portable +## artifact downstream stages consume. No container needed. +## +## Usage: +## scripts/stage1-flatten.sh [--arch <X86_64|I386|RISCV64>] [--verify] + +set -eu + +# --- arg parse -------------------------------------------------------- +ARCH=X86_64 +VERIFY=0 +while [ $# -gt 0 ]; do + case "$1" in + --arch) ARCH=$2; shift 2 ;; + --verify) VERIFY=1; shift ;; + -h|--help) sed -n 's/^## \{0,1\}//p' "$0"; exit 0 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +case "$ARCH" in + X86_64) MES_ARCH=x86_64; HAVE_LL=1 ;; + I386) MES_ARCH=x86; HAVE_LL=0 ;; + RISCV64) MES_ARCH=riscv64; HAVE_LL=1 ;; + AARCH64) echo "AARCH64 not in live-bootstrap; tcc.c lacks an arm64-gen.c" >&2; exit 2 ;; + *) echo "unknown ARCH: $ARCH" >&2; exit 2 ;; +esac + +# --- paths ------------------------------------------------------------ +ROOT=$(cd "$(dirname "$0")/.." && pwd) +WORK=$ROOT/build/cc-bootstrap/$ARCH +DISTFILES=$ROOT/../lb-work/distfiles +LB_PATCHES=$ROOT/../live-bootstrap/steps/tcc-0.9.26/simple-patches +MES_INCLUDE=$ROOT/../mes/include +MES_INCLUDE_LINUX=$MES_INCLUDE/linux/$MES_ARCH + +TCC_TAR=$DISTFILES/tcc-0.9.26.tar.gz +TCC_PKG=tcc-0.9.26-1147-gee75a10c + +[ -r "$TCC_TAR" ] || { echo "missing $TCC_TAR" >&2; exit 1; } +[ -d "$LB_PATCHES" ] || { echo "missing $LB_PATCHES" >&2; exit 1; } +[ -d "$MES_INCLUDE" ] || { echo "missing $MES_INCLUDE" >&2; exit 1; } +[ -d "$MES_INCLUDE_LINUX" ] || { echo "missing $MES_INCLUDE_LINUX" >&2; exit 1; } + +# --- (1) unpack ------------------------------------------------------- +mkdir -p "$WORK" +rm -rf "$WORK/$TCC_PKG" +tar -xzf "$TCC_TAR" -C "$WORK" + +SRC=$WORK/$TCC_PKG + +# --- (2) simple-patches ---------------------------------------------- +# Both patches edit tcctools.c. The pair (remove-fileopen, addback-fileopen) +# moves a fopen() block earlier in the function. We implement live-bootstrap's +# simple-patch as an awk literal-block replacer; no binary dep. +apply_simple_patch() { + target=$1; before=$2; after=$3 + [ -r "$target" ] || { echo "patch target missing: $target" >&2; exit 1; } + [ -r "$before" ] || { echo "patch before missing: $before" >&2; exit 1; } + [ -r "$after" ] || { echo "patch after missing: $after" >&2; exit 1; } + awk -v BFILE="$before" -v AFILE="$after" ' + BEGIN { + while ((getline line < BFILE) > 0) bef = bef line "\n"; + close(BFILE); + while ((getline line < AFILE) > 0) aft = aft line "\n"; + close(AFILE); + } + { src = src $0 "\n" } + END { + i = index(src, bef); + if (i == 0) { print "patch did not match" > "/dev/stderr"; exit 1 } + printf "%s%s%s", + substr(src, 1, i - 1), + aft, + substr(src, i + length(bef)); + } + ' "$target" > "$target.new" + mv "$target.new" "$target" +} + +apply_simple_patch \ + "$SRC/tcctools.c" \ + "$LB_PATCHES/remove-fileopen.before" \ + "$LB_PATCHES/remove-fileopen.after" + +apply_simple_patch \ + "$SRC/tcctools.c" \ + "$LB_PATCHES/addback-fileopen.before" \ + "$LB_PATCHES/addback-fileopen.after" + +# Empty config.h shims — pass1.kaem creates these via `catm <out>` (line 27-28). +: > "$SRC/config.h" +mkdir -p "$WORK/mes-overlay/mes" +: > "$WORK/mes-overlay/mes/config.h" + +# --- (3) flatten via host preprocessor -------------------------------- +HOST_CC=${HOST_CC:-cc} +FLAT=$WORK/tcc.flat.c + +"$HOST_CC" -E -P \ + -nostdinc \ + -I "$SRC" \ + -I "$WORK/mes-overlay" \ + -I "$MES_INCLUDE_LINUX" \ + -I "$MES_INCLUDE" \ + -D __linux__=1 \ + -D __${MES_ARCH}__=1 \ + -D BOOTSTRAP=1 \ + -D HAVE_LONG_LONG=$HAVE_LL \ + -D inline= \ + -D "CONFIG_TCCDIR=\"/lib/tcc\"" \ + -D "CONFIG_SYSROOT=\"/\"" \ + -D "CONFIG_TCC_CRTPREFIX=\"/lib\"" \ + -D "CONFIG_TCC_ELFINTERP=\"/mes/loader\"" \ + -D "CONFIG_TCC_SYSINCLUDEPATHS=\"/include/mes\"" \ + -D "TCC_LIBGCC=\"/lib/libc.a\"" \ + -D CONFIG_TCC_LIBTCC1_MES=0 \ + -D CONFIG_TCCBOOT=1 \ + -D CONFIG_TCC_STATIC=1 \ + -D CONFIG_USE_LIBGCC=1 \ + -D "TCC_VERSION=\"0.9.26\"" \ + -D ONE_SOURCE=1 \ + -D TCC_TARGET_${ARCH}=1 \ + "$SRC/tcc.c" > "$FLAT" + +LINES=$(wc -l < "$FLAT") +BYTES=$(wc -c < "$FLAT") +echo "produced $FLAT ($LINES lines, $BYTES bytes)" + +# --- (4) optional verify --------------------------------------------- +if [ "$VERIFY" -eq 1 ]; then + HOST_OBJ=$WORK/tcc.flat.o + if "$HOST_CC" -c -w -o "$HOST_OBJ" "$FLAT" 2>"$WORK/host-cc.log"; then + echo "host cc: tcc.flat.c compiles cleanly to $HOST_OBJ" + else + echo "host cc: tcc.flat.c FAILED to compile; see $WORK/host-cc.log" >&2 + head -30 "$WORK/host-cc.log" >&2 + exit 1 + fi +fi diff --git a/scripts/stage2-alpine.sh b/scripts/stage2-alpine.sh @@ -0,0 +1,275 @@ +#!/bin/sh +## scripts/stage2-alpine.sh — build tcc-boot0-mes in an alpine container. +## +## This is the stand-in slot for our scheme1-hosted C compiler: a real +## native gcc plays the role our scheme CC will eventually fill. Its +## job is to take stage 1's tcc.flat.c, build a working tcc, then use +## that tcc to compile mes libc and link a final static tcc-0.9.26 +## binary against it. See docs/TCC.md. +## +## We picked alpine (musl) over debian (glibc) because mes headers +## declare errno as a plain global, while glibc declares it TLS — a +## non-TLS / TLS clash at link time. musl exposes errno only via +## __errno_location(), so a one-line `int errno;` shim is the sole +## definition and links cleanly. +## +## Pre-condition: +## build/cc-bootstrap/<arch>/tcc.flat.c (run scripts/stage1-flatten.sh) +## +## Inside alpine:latest (linux/amd64): +## 1. apk add gcc musl-dev +## 2. gcc -static tcc.flat.c errno-shim.c -> tcc-host +## (validates the flatten output as well-formed C; tcc-host is a +## working musl-linked tcc-0.9.26 binary) +## 3. unpack mes-0.27.1, set up include tree with arch symlink +## (Issue §1 workaround — tcc 0.9.26 SEGVs on missing include) +## 4. tcc-host compiles each mes libc .c file individually +## (Issue §2 workaround — concatenated TU SEGVs around 22+ files) +## 5. tcc-host -ar -> /lib/libc.a + /lib/tcc/libtcc1.a, build crt1.o +## 6. tcc-host -static compiles patched real tcc.c against mes libc +## directly into tcc-boot0-mes — mirrors live-bootstrap's tcc-boot0 +## invocation. We skip the tcc-self.o intermediate that an older +## iteration used: it was a redundant round-trip and exposed a tcc +## 0.9.26 bug in the static-link codepath. +## 7. (best-effort) tcc-boot0-mes -version +## Expected to segfault under QEMU x86_64 emulation on macOS arm64 +## (Issue §3); native x86_64 needed to verify cleanly. +## +## Output: build/cc-bootstrap/<arch>/tcc-boot0-mes (static, mes-libc-linked). +## This artifact is what stage 3 (busybox) consumes to drive the +## tcc-boot1 / tcc-boot2 chain. +## +## Usage: +## scripts/stage2-alpine.sh [--arch X86_64] + +set -eu + +ARCH=X86_64 +while [ $# -gt 0 ]; do + case "$1" in + --arch) ARCH=$2; shift 2 ;; + -h|--help) sed -n 's/^## \{0,1\}//p' "$0"; exit 0 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +if [ "$ARCH" != "X86_64" ]; then + echo "stage2 currently only supports X86_64 (live-bootstrap reference path)" >&2 + exit 2 +fi +MES_ARCH=x86_64 + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +WORK=$ROOT/build/cc-bootstrap/$ARCH +DISTFILES=$ROOT/../lb-work/distfiles +MES_TAR=$DISTFILES/mes-0.27.1.tar.gz +MES_PKG=mes-0.27.1 +FLAT=$WORK/tcc.flat.c + +[ -r "$FLAT" ] || { echo "missing $FLAT — run scripts/stage1-flatten.sh first" >&2; exit 1; } +[ -r "$MES_TAR" ] || { echo "missing $MES_TAR" >&2; exit 1; } +command -v podman >/dev/null 2>&1 || { echo "podman required" >&2; exit 2; } + +# Unpack mes outside the container so it lands on the bind mount. +if [ ! -d "$WORK/$MES_PKG" ]; then + tar -xzf "$MES_TAR" -C "$WORK" +fi +mkdir -p "$WORK/$MES_PKG/include/mes" +: > "$WORK/$MES_PKG/include/mes/config.h" + +# errno shim: tcc.flat.c references errno as a plain global (mes-libc +# convention). musl provides errno only via __errno_location(), so this +# one-line int errno; is the sole storage. Without it the link fails. +printf 'int errno;\n' > "$WORK/errno-shim.c" + +echo "=== stage 2: tcc-boot0-mes via alpine:latest ===" +echo "(slow on macOS arm64 — runs under QEMU linux/amd64)" + +TCC_PKG=tcc-0.9.26-1147-gee75a10c + +podman run --rm -i --platform linux/amd64 \ + -v "$ROOT":/work -w /work alpine:latest sh -s "$ARCH" "$MES_ARCH" "$MES_PKG" "$TCC_PKG" <<'CONTAINER_SCRIPT' +set -eu +ARCH=$1 +MES_ARCH=$2 +MES_PKG=$3 +TCC_PKG=$4 +WORK=/work/build/cc-bootstrap/$ARCH + +# --- (1) install gcc + musl-dev (provides libc.a for -static) -------- +apk add --no-cache gcc musl-dev >/dev/null +echo "host gcc: $(gcc --version | head -1)" + +# --- (2) gcc tcc.flat.c -> tcc-host ---------------------------------- +echo "--- gcc -static -> tcc-host ---" +gcc -w -static -no-pie -o "$WORK/tcc-host" \ + "$WORK/tcc.flat.c" "$WORK/errno-shim.c" +"$WORK/tcc-host" -version + +# --- (3) sanitized include tree -------------------------------------- +MES_SRC=$WORK/$MES_PKG +INC=/tmp/mes-inc +rm -rf $INC +mkdir -p $INC +cp -r $MES_SRC/include/. $INC/ +ln -sfn linux/$MES_ARCH $INC/arch + +# --- (4) compile mes libc per-file ----------------------------------- +echo "--- tcc-host compiling mes libc (per-file to dodge Issue §2) ---" +mkdir -p /tmp/objs +cd $MES_SRC/lib + +ALL_FILES="ctype/isalnum.c ctype/isalpha.c ctype/isascii.c ctype/iscntrl.c \ +ctype/isdigit.c ctype/isgraph.c ctype/islower.c ctype/isnumber.c \ +ctype/isprint.c ctype/ispunct.c ctype/isspace.c ctype/isupper.c \ +ctype/isxdigit.c ctype/tolower.c ctype/toupper.c \ +dirent/closedir.c dirent/__getdirentries.c dirent/opendir.c \ +linux/readdir.c linux/access.c linux/brk.c linux/chdir.c linux/chmod.c \ +linux/clock_gettime.c linux/close.c linux/dup2.c linux/dup.c linux/execve.c \ +linux/fcntl.c linux/fork.c linux/fsync.c linux/fstat.c linux/_getcwd.c \ +linux/getdents.c linux/getegid.c linux/geteuid.c linux/getgid.c linux/getpid.c \ +linux/getppid.c linux/getrusage.c linux/gettimeofday.c linux/getuid.c \ +linux/ioctl.c linux/ioctl3.c linux/kill.c linux/link.c linux/lseek.c \ +linux/lstat.c linux/malloc.c linux/mkdir.c linux/mknod.c linux/nanosleep.c \ +linux/_open3.c linux/pipe.c linux/_read.c linux/readlink.c linux/rename.c \ +linux/rmdir.c linux/setgid.c linux/settimer.c linux/setuid.c linux/signal.c \ +linux/sigprogmask.c linux/symlink.c linux/stat.c linux/time.c linux/unlink.c \ +linux/waitpid.c linux/wait4.c \ +linux/${MES_ARCH}-mes-gcc/_exit.c linux/${MES_ARCH}-mes-gcc/syscall.c \ +linux/${MES_ARCH}-mes-gcc/_write.c \ +math/ceil.c math/fabs.c math/floor.c \ +mes/abtod.c mes/abtol.c mes/__assert_fail.c mes/assert_msg.c \ +mes/__buffered_read.c mes/__init_io.c mes/cast.c mes/dtoab.c \ +mes/eputc.c mes/eputs.c mes/fdgetc.c mes/fdgets.c mes/fdputc.c mes/fdputs.c \ +mes/fdungetc.c mes/globals.c mes/itoa.c mes/ltoab.c mes/ltoa.c \ +mes/__mes_debug.c mes/mes_open.c mes/ntoab.c mes/oputc.c mes/oputs.c \ +mes/search-path.c mes/ultoa.c mes/utoa.c \ +posix/alarm.c posix/buffered-read.c posix/execl.c posix/execlp.c \ +posix/execv.c posix/execvp.c posix/getcwd.c posix/getenv.c posix/isatty.c \ +posix/mktemp.c posix/open.c posix/pathconf.c posix/raise.c posix/sbrk.c \ +posix/setenv.c posix/sleep.c posix/unsetenv.c posix/wait.c posix/write.c \ +stdio/clearerr.c stdio/fclose.c stdio/fdopen.c stdio/feof.c stdio/ferror.c \ +stdio/fflush.c stdio/fgetc.c stdio/fgets.c stdio/fileno.c stdio/fopen.c \ +stdio/fprintf.c stdio/fputc.c stdio/fputs.c stdio/fread.c stdio/freopen.c \ +stdio/fscanf.c stdio/fseek.c stdio/ftell.c stdio/fwrite.c stdio/getc.c \ +stdio/getchar.c stdio/perror.c stdio/printf.c stdio/putc.c stdio/putchar.c \ +stdio/remove.c stdio/snprintf.c stdio/sprintf.c stdio/sscanf.c stdio/ungetc.c \ +stdio/vfprintf.c stdio/vfscanf.c stdio/vprintf.c stdio/vsnprintf.c \ +stdio/vsprintf.c stdio/vsscanf.c \ +stdlib/abort.c stdlib/abs.c stdlib/alloca.c stdlib/atexit.c stdlib/atof.c \ +stdlib/atoi.c stdlib/atol.c stdlib/calloc.c stdlib/__exit.c stdlib/exit.c \ +stdlib/free.c stdlib/mbstowcs.c stdlib/puts.c stdlib/qsort.c stdlib/realloc.c \ +stdlib/strtod.c stdlib/strtof.c stdlib/strtol.c stdlib/strtold.c \ +stdlib/strtoll.c stdlib/strtoul.c stdlib/strtoull.c \ +string/bcmp.c string/bcopy.c string/bzero.c string/index.c string/memchr.c \ +string/memcmp.c string/memcpy.c string/memmem.c string/memmove.c string/memset.c \ +string/rindex.c string/strcat.c string/strchr.c string/strcmp.c string/strcpy.c \ +string/strcspn.c string/strdup.c string/strerror.c string/strlen.c \ +string/strlwr.c string/strncat.c string/strncmp.c string/strncpy.c \ +string/strpbrk.c string/strrchr.c string/strspn.c string/strstr.c string/strupr.c \ +stub/atan2.c stub/bsearch.c stub/chown.c stub/__cleanup.c stub/cos.c \ +stub/ctime.c stub/exp.c stub/fpurge.c stub/freadahead.c stub/frexp.c \ +stub/getgrgid.c stub/getgrnam.c stub/getlogin.c stub/getpgid.c stub/getpgrp.c \ +stub/getpwnam.c stub/getpwuid.c stub/gmtime.c stub/ldexp.c stub/localtime.c \ +stub/log.c stub/mktime.c stub/modf.c stub/mprotect.c stub/pclose.c \ +stub/popen.c stub/pow.c stub/putenv.c stub/rand.c stub/realpath.c stub/rewind.c \ +stub/setbuf.c stub/setgrent.c stub/setlocale.c stub/setvbuf.c stub/sigaction.c \ +stub/sigaddset.c stub/sigblock.c stub/sigdelset.c stub/sigemptyset.c \ +stub/sigsetmask.c stub/sin.c stub/sys_siglist.c stub/system.c stub/sqrt.c \ +stub/strftime.c stub/times.c stub/ttyname.c stub/umask.c stub/utime.c \ +${MES_ARCH}-mes-gcc/setjmp.c" + +OBJS= +n_compiled=0 +n_failed=0 +for f in $ALL_FILES; do + name=$(echo "$f" | tr / _) + o=/tmp/objs/${name%.c}.o + if "$WORK/tcc-host" -c -D HAVE_CONFIG_H=1 -I "$INC" -I "$INC/linux/$MES_ARCH" \ + -o "$o" "$f" 2>/dev/null; then + OBJS="$OBJS $o" + n_compiled=$((n_compiled+1)) + else + echo "compile failed: $f" >&2 + n_failed=$((n_failed+1)) + fi +done +echo "compiled $n_compiled libc .o files (failed: $n_failed)" +[ "$n_failed" -eq 0 ] || { echo "abort: some libc files failed" >&2; exit 1; } + +# --- (5) ar -> libc.a, crt1.o, libtcc1.a ----------------------------- +mkdir -p /lib/tcc /include/mes +"$WORK/tcc-host" -ar cr /lib/libc.a $OBJS + +"$WORK/tcc-host" -c -D HAVE_CONFIG_H=1 -I "$INC" -I "$INC/linux/$MES_ARCH" \ + -o /lib/crt1.o "linux/$MES_ARCH-mes-gcc/crt1.c" +: > /lib/crtn.o +: > /lib/crti.o + +"$WORK/tcc-host" -c -D HAVE_CONFIG_H=1 -D HAVE_LONG_LONG=1 -D HAVE_FLOAT=1 \ + -I "$INC" -I "$INC/linux/$MES_ARCH" \ + -o /tmp/libtcc1.o libtcc1.c +"$WORK/tcc-host" -ar cr /lib/tcc/libtcc1.a /tmp/libtcc1.o + +cp -r "$INC/." /include/mes/ +ls -la /lib/crt1.o /lib/libc.a /lib/tcc/libtcc1.a + +# --- (6) tcc-host -static compile real tcc.c -> tcc-boot0-mes -------- +# Mirrors live-bootstrap pass1.kaem's tcc-boot0 invocation: same flags, +# same source tree, just driven by tcc-host instead of tcc-mes. Direct +# compile+link in one shot — no intermediate .o. +echo "--- tcc-host -static compile+link real tcc.c -> tcc-boot0-mes ---" +cd "$WORK/$TCC_PKG" +"$WORK/tcc-host" \ + -g \ + -static \ + -o "$WORK/tcc-boot0-mes" \ + -D BOOTSTRAP=1 \ + -D HAVE_FLOAT=1 \ + -D HAVE_BITFIELD=1 \ + -D HAVE_LONG_LONG=1 \ + -D HAVE_SETJMP=1 \ + -I . \ + -I /include/mes \ + -D TCC_TARGET_${ARCH}=1 \ + -D CONFIG_TCCDIR=\"/lib/tcc\" \ + -D CONFIG_TCC_CRTPREFIX=\"/lib\" \ + -D CONFIG_TCC_ELFINTERP=\"/mes/loader\" \ + -D CONFIG_TCC_LIBPATHS=\"/lib:/lib/tcc\" \ + -D CONFIG_TCC_SYSINCLUDEPATHS=\"/include/mes\" \ + -D TCC_LIBGCC=\"/lib/libc.a\" \ + -D TCC_LIBTCC1=\"libtcc1.a\" \ + -D CONFIG_TCCBOOT=1 \ + -D CONFIG_TCC_STATIC=1 \ + -D CONFIG_USE_LIBGCC=1 \ + -D TCC_VERSION=\"0.9.26\" \ + -D ONE_SOURCE=1 \ + -L . \ + -L /lib \ + tcc.c +ls -la "$WORK/tcc-boot0-mes" + +# Stage out mes libc + libtcc1 + crt1.o + headers so stage 3 can mount +# them in next to tcc-boot0-mes without re-running stage 2. +STAGE3=$WORK/stage3-input +rm -rf "$STAGE3" +mkdir -p "$STAGE3/lib/tcc" "$STAGE3/include" +cp /lib/libc.a "$STAGE3/lib/" +cp /lib/crt1.o "$STAGE3/lib/" +cp /lib/crtn.o "$STAGE3/lib/" +cp /lib/crti.o "$STAGE3/lib/" +cp /lib/tcc/libtcc1.a "$STAGE3/lib/tcc/" +cp -r "$INC/." "$STAGE3/include/mes/" +echo "staged mes libc bits into $STAGE3 for stage 3" + +# --- (7) best-effort version probe ----------------------------------- +echo +echo "--- tcc-boot0-mes -version (Issue §3: expected SEGV under QEMU) ---" +rc=0; "$WORK/tcc-boot0-mes" -version 2>&1 || rc=$? +echo "exit=$rc" +CONTAINER_SCRIPT + +echo +echo "=== stage 2 artifacts ===" +ls -la "$WORK/tcc-host" "$WORK/tcc-boot0-mes" 2>/dev/null || \ + echo "(some artifacts missing — see container output above)" diff --git a/scripts/stage3-rebuild.sh b/scripts/stage3-rebuild.sh @@ -0,0 +1,234 @@ +#!/bin/sh +## scripts/stage3-rebuild.sh — drive the tcc-boot1 / tcc-boot2 chain +## inside a busybox container, consuming stage 2's tcc-boot0-mes. +## +## Mirrors live-bootstrap's pass1.kaem (steps/tcc-0.9.26) chain: +## tcc-boot0-mes (= tcc-boot0 slot) +## → rebuild libc → tcc-boot1 +## → rebuild libc → tcc-boot2 (= "final 0.9.26") +## +## Each rebuild compiles the **real**, unflattened tcc-0.9.26 sources +## (the patched tree at $WORK/tcc-0.9.26-1147-gee75a10c) using the +## previous-stage tcc, with the full live-bootstrap define set +## (HAVE_FLOAT, HAVE_BITFIELD, HAVE_SETJMP added on top of the stage 1 +## flatten set). +## +## Pre-condition: +## build/cc-bootstrap/<arch>/tcc-boot0-mes +## build/cc-bootstrap/<arch>/stage3-input/ (staged by stage 2) +## build/cc-bootstrap/<arch>/tcc-0.9.26-1147-gee75a10c/ (patched, from stage 1) +## build/cc-bootstrap/<arch>/mes-0.27.1/ (from stage 2) +## +## Container: docker.io/library/busybox:musl on linux/amd64. +## Tools used inside: busybox sh + tcc-boot0-mes (which provides its own +## preprocessor, assembler, linker, and `-ar`). +## +## Status: stage 3 cannot complete on macOS arm64 hosts today — Issue §3 +## (tcc-boot0-mes segfaults at startup under QEMU x86_64) blocks the +## very first step. The script is correct; it will run end-to-end on +## native x86_64 hardware or once a tcc 0.9.28rc backport patches the +## prologue/_DYNAMIC issues. The failure is reported clearly so the +## blocker is visible. +## +## Usage: +## scripts/stage3-rebuild.sh [--arch X86_64] + +set -eu + +ARCH=X86_64 +while [ $# -gt 0 ]; do + case "$1" in + --arch) ARCH=$2; shift 2 ;; + -h|--help) sed -n 's/^## \{0,1\}//p' "$0"; exit 0 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +if [ "$ARCH" != "X86_64" ]; then + echo "stage3 currently only supports X86_64" >&2 + exit 2 +fi +MES_ARCH=x86_64 + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +WORK=$ROOT/build/cc-bootstrap/$ARCH +TCC_PKG=tcc-0.9.26-1147-gee75a10c +MES_PKG=mes-0.27.1 + +[ -x "$WORK/tcc-boot0-mes" ] || { echo "missing $WORK/tcc-boot0-mes — run stage2-alpine.sh" >&2; exit 1; } +[ -d "$WORK/stage3-input" ] || { echo "missing $WORK/stage3-input — run stage2-alpine.sh" >&2; exit 1; } +[ -d "$WORK/$TCC_PKG" ] || { echo "missing $WORK/$TCC_PKG — run stage1-flatten.sh" >&2; exit 1; } +[ -d "$WORK/$MES_PKG" ] || { echo "missing $WORK/$MES_PKG — run stage2-alpine.sh" >&2; exit 1; } +command -v podman >/dev/null 2>&1 || { echo "podman required" >&2; exit 2; } + +echo "=== stage 3: tcc-boot1 / tcc-boot2 via busybox:musl ===" +echo "(Issue §3 may block on macOS arm64 / QEMU; native x86_64 expected to succeed)" + +podman run --rm -i --platform linux/amd64 \ + -v "$ROOT":/work -w /work \ + docker.io/library/busybox:musl sh -s "$ARCH" "$MES_ARCH" "$TCC_PKG" "$MES_PKG" <<'CONTAINER_SCRIPT' +set -eu +ARCH=$1 +MES_ARCH=$2 +TCC_PKG=$3 +MES_PKG=$4 +WORK=/work/build/cc-bootstrap/$ARCH + +# --- install tcc-boot0-mes + mes libc bits at baked-in paths -------- +mkdir -p /lib/tcc /include/mes /bin +cp "$WORK/stage3-input/lib/libc.a" /lib/libc.a +cp "$WORK/stage3-input/lib/crt1.o" /lib/crt1.o +cp "$WORK/stage3-input/lib/crtn.o" /lib/crtn.o +cp "$WORK/stage3-input/lib/crti.o" /lib/crti.o +cp "$WORK/stage3-input/lib/tcc/libtcc1.a" /lib/tcc/libtcc1.a +cp -r "$WORK/stage3-input/include/mes/." /include/mes/ + +TCC=$WORK/tcc-boot0-mes + +echo "--- tcc-boot0-mes -version (smoke; Issue §3 may SEGV here) ---" +"$TCC" -version + +INC_MES=/include/mes +TCC_DEFS_BUILD="-D BOOTSTRAP=1 \ + -D HAVE_FLOAT=1 \ + -D HAVE_BITFIELD=1 \ + -D HAVE_LONG_LONG=1 \ + -D HAVE_SETJMP=1 \ + -D TCC_TARGET_${ARCH}=1 \ + -D CONFIG_TCCDIR=\"/lib/tcc\" \ + -D CONFIG_TCC_CRTPREFIX=\"/lib\" \ + -D CONFIG_TCC_ELFINTERP=\"/mes/loader\" \ + -D CONFIG_TCC_LIBPATHS=\"/lib:/lib/tcc\" \ + -D CONFIG_TCC_SYSINCLUDEPATHS=\"/include/mes\" \ + -D TCC_LIBGCC=\"/lib/libc.a\" \ + -D TCC_LIBTCC1=\"libtcc1.a\" \ + -D CONFIG_TCCBOOT=1 \ + -D CONFIG_TCC_STATIC=1 \ + -D CONFIG_USE_LIBGCC=1 \ + -D TCC_VERSION=\"0.9.26\" \ + -D ONE_SOURCE=1" + +# Helper: rebuild mes libc with the given tcc binary, install to /lib + /lib/tcc. +# Mirrors pass1.kaem's "Recompile libc" block after each tcc-bootN. +rebuild_libc() { + cc=$1 + label=$2 + echo "--- $label: rebuilding mes libc ---" + cd "$WORK/$MES_PKG" + "$cc" -c -D HAVE_CONFIG_H=1 -I include -I include/linux/$MES_ARCH \ + -o /lib/crt1.o lib/linux/$MES_ARCH-mes-gcc/crt1.c + + "$cc" -c -D HAVE_CONFIG_H=1 -D HAVE_LONG_LONG=1 -D HAVE_FLOAT=1 \ + -I include -I include/linux/$MES_ARCH \ + -o /tmp/libtcc1.o lib/libtcc1.c + "$cc" -ar cr /lib/tcc/libtcc1.a /tmp/libtcc1.o + + # libc.a: same per-file approach as stage 2 (Issue §2 workaround). + rm -rf /tmp/objs && mkdir -p /tmp/objs + cd lib + OBJS= + for f in $ALL_LIBC_FILES; do + name=$(echo "$f" | tr / _) + o=/tmp/objs/${name%.c}.o + "$cc" -c -D HAVE_CONFIG_H=1 -I ../include -I ../include/linux/$MES_ARCH \ + -o "$o" "$f" + OBJS="$OBJS $o" + done + "$cc" -ar cr /lib/libc.a $OBJS + cd "$WORK/$MES_PKG/.." +} + +# Helper: compile tcc.c (real, unflattened) into a new tcc binary. +# Mirrors pass1.kaem's tcc-boot0/1/2 invocations. +build_tcc() { + cc=$1 + out=$2 + echo "--- $out: $cc compiling real tcc.c ---" + cd "$WORK/$TCC_PKG" + eval "\"$cc\" -g -static -o \"$WORK/$out\" \ + $TCC_DEFS_BUILD \ + -I . -I $INC_MES \ + -L . -L /lib \ + tcc.c" + "$WORK/$out" -version +} + +# Same canonical mes libc list as stage 2 + pass1.kaem. +ALL_LIBC_FILES="ctype/isalnum.c ctype/isalpha.c ctype/isascii.c ctype/iscntrl.c \ +ctype/isdigit.c ctype/isgraph.c ctype/islower.c ctype/isnumber.c \ +ctype/isprint.c ctype/ispunct.c ctype/isspace.c ctype/isupper.c \ +ctype/isxdigit.c ctype/tolower.c ctype/toupper.c \ +dirent/closedir.c dirent/__getdirentries.c dirent/opendir.c \ +linux/readdir.c linux/access.c linux/brk.c linux/chdir.c linux/chmod.c \ +linux/clock_gettime.c linux/close.c linux/dup2.c linux/dup.c linux/execve.c \ +linux/fcntl.c linux/fork.c linux/fsync.c linux/fstat.c linux/_getcwd.c \ +linux/getdents.c linux/getegid.c linux/geteuid.c linux/getgid.c linux/getpid.c \ +linux/getppid.c linux/getrusage.c linux/gettimeofday.c linux/getuid.c \ +linux/ioctl.c linux/ioctl3.c linux/kill.c linux/link.c linux/lseek.c \ +linux/lstat.c linux/malloc.c linux/mkdir.c linux/mknod.c linux/nanosleep.c \ +linux/_open3.c linux/pipe.c linux/_read.c linux/readlink.c linux/rename.c \ +linux/rmdir.c linux/setgid.c linux/settimer.c linux/setuid.c linux/signal.c \ +linux/sigprogmask.c linux/symlink.c linux/stat.c linux/time.c linux/unlink.c \ +linux/waitpid.c linux/wait4.c \ +linux/${MES_ARCH}-mes-gcc/_exit.c linux/${MES_ARCH}-mes-gcc/syscall.c \ +linux/${MES_ARCH}-mes-gcc/_write.c \ +math/ceil.c math/fabs.c math/floor.c \ +mes/abtod.c mes/abtol.c mes/__assert_fail.c mes/assert_msg.c \ +mes/__buffered_read.c mes/__init_io.c mes/cast.c mes/dtoab.c \ +mes/eputc.c mes/eputs.c mes/fdgetc.c mes/fdgets.c mes/fdputc.c mes/fdputs.c \ +mes/fdungetc.c mes/globals.c mes/itoa.c mes/ltoab.c mes/ltoa.c \ +mes/__mes_debug.c mes/mes_open.c mes/ntoab.c mes/oputc.c mes/oputs.c \ +mes/search-path.c mes/ultoa.c mes/utoa.c \ +posix/alarm.c posix/buffered-read.c posix/execl.c posix/execlp.c \ +posix/execv.c posix/execvp.c posix/getcwd.c posix/getenv.c posix/isatty.c \ +posix/mktemp.c posix/open.c posix/pathconf.c posix/raise.c posix/sbrk.c \ +posix/setenv.c posix/sleep.c posix/unsetenv.c posix/wait.c posix/write.c \ +stdio/clearerr.c stdio/fclose.c stdio/fdopen.c stdio/feof.c stdio/ferror.c \ +stdio/fflush.c stdio/fgetc.c stdio/fgets.c stdio/fileno.c stdio/fopen.c \ +stdio/fprintf.c stdio/fputc.c stdio/fputs.c stdio/fread.c stdio/freopen.c \ +stdio/fscanf.c stdio/fseek.c stdio/ftell.c stdio/fwrite.c stdio/getc.c \ +stdio/getchar.c stdio/perror.c stdio/printf.c stdio/putc.c stdio/putchar.c \ +stdio/remove.c stdio/snprintf.c stdio/sprintf.c stdio/sscanf.c stdio/ungetc.c \ +stdio/vfprintf.c stdio/vfscanf.c stdio/vprintf.c stdio/vsnprintf.c \ +stdio/vsprintf.c stdio/vsscanf.c \ +stdlib/abort.c stdlib/abs.c stdlib/alloca.c stdlib/atexit.c stdlib/atof.c \ +stdlib/atoi.c stdlib/atol.c stdlib/calloc.c stdlib/__exit.c stdlib/exit.c \ +stdlib/free.c stdlib/mbstowcs.c stdlib/puts.c stdlib/qsort.c stdlib/realloc.c \ +stdlib/strtod.c stdlib/strtof.c stdlib/strtol.c stdlib/strtold.c \ +stdlib/strtoll.c stdlib/strtoul.c stdlib/strtoull.c \ +string/bcmp.c string/bcopy.c string/bzero.c string/index.c string/memchr.c \ +string/memcmp.c string/memcpy.c string/memmem.c string/memmove.c string/memset.c \ +string/rindex.c string/strcat.c string/strchr.c string/strcmp.c string/strcpy.c \ +string/strcspn.c string/strdup.c string/strerror.c string/strlen.c \ +string/strlwr.c string/strncat.c string/strncmp.c string/strncpy.c \ +string/strpbrk.c string/strrchr.c string/strspn.c string/strstr.c string/strupr.c \ +stub/atan2.c stub/bsearch.c stub/chown.c stub/__cleanup.c stub/cos.c \ +stub/ctime.c stub/exp.c stub/fpurge.c stub/freadahead.c stub/frexp.c \ +stub/getgrgid.c stub/getgrnam.c stub/getlogin.c stub/getpgid.c stub/getpgrp.c \ +stub/getpwnam.c stub/getpwuid.c stub/gmtime.c stub/ldexp.c stub/localtime.c \ +stub/log.c stub/mktime.c stub/modf.c stub/mprotect.c stub/pclose.c \ +stub/popen.c stub/pow.c stub/putenv.c stub/rand.c stub/realpath.c stub/rewind.c \ +stub/setbuf.c stub/setgrent.c stub/setlocale.c stub/setvbuf.c stub/sigaction.c \ +stub/sigaddset.c stub/sigblock.c stub/sigdelset.c stub/sigemptyset.c \ +stub/sigsetmask.c stub/sin.c stub/sys_siglist.c stub/system.c stub/sqrt.c \ +stub/strftime.c stub/times.c stub/ttyname.c stub/umask.c stub/utime.c \ +${MES_ARCH}-mes-gcc/setjmp.c" + +# --- Pass 1: tcc-boot0-mes -> tcc-boot1 ----------------------------- +rebuild_libc "$TCC" "tcc-boot0-mes" +build_tcc "$TCC" "tcc-boot1" + +# --- Pass 2: tcc-boot1 -> tcc-boot2 (final 0.9.26) ------------------ +TCC=$WORK/tcc-boot1 +rebuild_libc "$TCC" "tcc-boot1" +build_tcc "$TCC" "tcc-boot2" + +echo +echo "=== stage 3: tcc-boot2 is the final 0.9.26 build ===" +ls -la "$WORK/tcc-boot1" "$WORK/tcc-boot2" +CONTAINER_SCRIPT + +echo +echo "=== stage 3 artifacts ===" +ls -la "$WORK/tcc-boot1" "$WORK/tcc-boot2" 2>/dev/null || \ + echo "(stage 3 did not complete — see container output above; likely Issue §3)"