boot2

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

commit 54367ac4c8f926a97caf8a56ea6fef6381975169
parent a703b305fce34cd774525725b4803cf187147954
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 17 Jul 2026 11:05:21 -0700

cc: fix nested char[] string-literal init; collapse tcc fixed point to 2 bounces

cc.scm mis-compiled a char[N] array that is a struct field or array
element initialized by a string literal: it emitted a pointer to the
interned string instead of the string's bytes. The STR->char[] case
lived only in top-level parse-init-global, not in the nested
%global-init-elem / %local-init-elem walkers, so the string decayed to a
pointer via %const-init-piece.

tcc's ArHdr archive-header template (static struct of char[] fields) is
exactly this shape, so tcc0 -ar wrote raw pointers into .a headers and
produced corrupt archives. boot4 worked around this by linking tcc1 from
bare .o files (libc-first) — an asymmetric link line vs. the archive-
based later stages — which forced a third self-host bounce (the chain
converged at tcc2 == tcc3 instead of tcc1 == tcc2).

Fix %global-init-elem and %local-init-elem to encode a string-literal
initializer for a char[] field/element as bytes. With tcc0 -ar working,
boot4 stage C now uses the same archive-based link line as stage D, so
the chain is tcc0 -> tcc1 -> tcc2 and the fixed point is tcc1 == tcc2.
Drop tcc3 / stage E everywhere; rewire boot5/boot6/calibrate to tcc2.

Verified end-to-end on aarch64 (rebuilt boot3 + boot4): tcc1 == tcc2, and
the canonical tcc2 plus crt1.o / libc.a / libtcc1.a / hello are byte-
identical to the prior tcc3 build, so downstream stages are unaffected.
184 cc tests pass; new fixture 340-struct-char-array-string-init fails on
the old compiler and passes on the fixed one.

riscv64 has a separate, unrelated cc.scm bug (immediate-fold miscompile
in riscv64-gen.c) that independently breaks tcc1 == tcc2; it needs
TCC_BOOTSTRAP_RELAX_FIXEDPOINT=1 until fixed (see docs/TCC.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014dTQvpe9XBV8fNdqkTGW81

Diffstat:
MMakefile | 6+++---
Mboot/boot3.sh | 6++----
Mboot/boot4.sh | 57+++++++++++++++++++++++++++++----------------------------
Mboot/boot5.sh | 8++++----
Mboot/boot6.sh | 10+++++-----
Mboot/lib-arch.sh | 2+-
Mbootprep/boot4-gen-runscm.sh | 40++++++++++++++++++----------------------
Mbootprep/boot5-calibrate.sh | 6+++---
Mbootprep/boot6-gen-runscm.sh | 30+++++++++++++++---------------
Mcc/cc.scm | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdocs/MUSL.md | 4++--
Mdocs/TCC.md | 23++++++++++++-----------
Mdocs/TOUR.md | 29++++++++++++++---------------
Mseed-kernel/arch/aarch64/kernel.S | 8++++----
Mseed-kernel/arch/aarch64/kernel.lds | 4++--
Mseed-kernel/arch/amd64/kernel.S | 2+-
Atests/cc/340-struct-char-array-string-init.c | 42++++++++++++++++++++++++++++++++++++++++++
Atests/cc/340-struct-char-array-string-init.expected-exit | 1+
Mtests/seed-accept.sh | 8++++----
Mtools/mkrelease.sh | 1-
Mtools/release/README.md | 2+-
Mvendor/mes-libc/libc.c | 2+-
22 files changed, 229 insertions(+), 127 deletions(-)

diff --git a/Makefile b/Makefile @@ -222,7 +222,7 @@ build/$1/$2/boot3/.stamp: \ build/$1/$2/boot3/tcc0 build/$1/$2/boot3/libc.P1pp \ build/$1/$2/boot3/tcc.flat.P1pp: build/$1/$2/boot3/.stamp ; -# boot4: tcc0 -> tcc1 -> tcc2 -> tcc3 self-host chain (+ libc.a, libtcc1.a, hello) +# boot4: tcc0 -> tcc1 -> tcc2 self-host chain (+ libc.a, libtcc1.a, hello) build/$1/$2/boot4/.stamp: \ build/$1/$2/boot3/.stamp build/$1/$2/boot2/.stamp \ boot/boot4.sh boot/lib-arch.sh boot/lib-runscm.sh \ @@ -231,7 +231,7 @@ build/$1/$2/boot4/.stamp: \ @touch $$@ build/$1/$2/boot4/tcc1 build/$1/$2/boot4/tcc2 \ -build/$1/$2/boot4/tcc3 build/$1/$2/boot4/hello \ +build/$1/$2/boot4/hello \ build/$1/$2/boot4/crt1.o build/$1/$2/boot4/libc.a \ build/$1/$2/boot4/libtcc1.a: build/$1/$2/boot4/.stamp ; @@ -248,7 +248,7 @@ build/$1/$2/boot5/libc.a build/$1/$2/boot5/crt1.o \ build/$1/$2/boot5/crti.o build/$1/$2/boot5/crtn.o \ build/$1/$2/boot5/hello: build/$1/$2/boot5/.stamp ; -# boot6: seed-kernel ELF/Image, built with boot4's tcc3 +# boot6: seed-kernel ELF/Image, built with boot4's tcc2 build/$1/$2/boot6/.stamp: \ build/$1/$2/boot4/.stamp build/$1/$2/boot2/.stamp \ boot/boot6.sh boot/lib-arch.sh boot/lib-runscm.sh \ diff --git a/boot/boot3.sh b/boot/boot3.sh @@ -1,15 +1,13 @@ #!/bin/sh ## boot3.sh — bootstrap tcc0 from cc.scm. ## -## Stage A of the four-stage tcc chain: cc.scm compiles tcc.flat.c into +## Stage A of the tcc self-host chain: cc.scm compiles tcc.flat.c into ## tcc0. boot4 picks up tcc0 and self-hosts the rest of the chain -## (tcc0 → tcc1 → tcc2 → tcc3, with tcc2 == tcc3 as the fixed-point -## check). +## (tcc0 → tcc1 → tcc2, with tcc1 == tcc2 as the fixed-point check). ## ## tcc0 = tcc-source compiled by cc.scm ← produced here ## tcc1 = tcc-source compiled by tcc0 ← boot4 ## tcc2 = tcc-source compiled by tcc1 ← boot4 -## tcc3 = tcc-source compiled by tcc2 ← boot4 ## ## ─── Inputs (sources, from canonical tree) ─────────────────────────── ## build/$ARCH/src/src/scheme1/prelude.scm scheme bundle diff --git a/boot/boot4.sh b/boot/boot4.sh @@ -2,18 +2,17 @@ ## boot4.sh — self-host tcc rebuild stages on top of boot3's tcc0. ## ## boot3 produced tcc0 (cc.scm-built bootstrap). boot4 runs the rest of -## the four-stage chain: tcc0 → tcc1 → tcc2 → tcc3. The bootstrap -## fixed-point check is `tcc2 == tcc3`: once tcc is compiling itself -## with no help from cc.scm, the chain reaches a byte-identical fixed -## point. cc.scm and tcc emit different — but both correct — code from -## the same source, so tcc0 ≠ tcc1. tcc1 and tcc2 are both built by tcc, -## but tcc1 is itself a cc.scm-shaped binary, so its codegen choices in -## tcc2 need one more bounce to reach the tcc-shaped fixed point. +## the three-binary chain: tcc0 → tcc1 → tcc2. The bootstrap fixed-point +## check is `tcc1 == tcc2`: tcc0 ≠ tcc1 because cc.scm and tcc emit +## different — but both correct — code from the same source, but once tcc +## is compiling itself the output is byte-identical. Stages C and D use +## the same archive-based link line, so tcc0(tcc.c) and tcc1(tcc.c) +## differ only where tcc0's runtime behavior differs from tcc1's — so the +## check doubles as a proof that cc.scm compiled tcc.c faithfully. ## ## tcc0 = tcc-source compiled by cc.scm ← boot3 ## tcc1 = tcc-source compiled by tcc0 ← produced here ## tcc2 = tcc-source compiled by tcc1 ← produced here -## tcc3 = tcc-source compiled by tcc2 ← produced here ## ## ─── Inputs (sources, from canonical tree) ─────────────────────────── ## build/$ARCH/src/src/tcc/libc/$ARCH/{start.S, sys_stubs.S} @@ -37,25 +36,27 @@ ## bootprep/stage1-flatten.sh, around the arm64-asm patch block). ## ## ─── Outputs ────────────────────────────────────────────────────────── -## build/$ARCH/$DRIVER/boot4/{tcc1, tcc2, tcc3} -## tcc2 and tcc3 are byte-identical (asserted +## build/$ARCH/$DRIVER/boot4/{tcc1, tcc2} +## tcc1 and tcc2 are byte-identical (asserted ## below) — that equality is the fixed-point. ## build/$ARCH/$DRIVER/boot4/crt1.o -## tcc2-built startup object, kept outside +## tcc1-built startup object, kept outside ## libc.a because it must lead link lines. ## build/$ARCH/$DRIVER/boot4/libc.a -## tcc2-built archive of sys_stubs.o + mem.o +## tcc1-built archive of sys_stubs.o + mem.o ## + libc.o ## build/$ARCH/$DRIVER/boot4/libtcc1.a -## tcc2-built tcc compiler helper archive +## tcc1-built tcc compiler helper archive ## build/$ARCH/$DRIVER/boot4/hello — mes-libc-linked smoke binary ## ## ─── Env knobs ──────────────────────────────────────────────────────── ## TCC_BOOTSTRAP_RELAX_FIXEDPOINT=1 -## After a codegen-altering tcc patch, the two-stage rule needs a -## third bounce to converge. Set this to accept tcc3 even when -## tcc2 != tcc3; the next boot4 run, started from this run's -## tcc3, will reach tcc2 == tcc3 with no extra knob. +## After a codegen-altering tcc patch, tcc0 (still built by the old +## cc.scm) may lag tcc1 by a bounce. Set this to accept tcc2 even +## when tcc1 != tcc2; the next boot4 run, started from this run's +## tcc2, will reach tcc1 == tcc2 with no extra knob. riscv64 also +## needs this today: cc.scm miscompiles an immediate-fold predicate +## in riscv64-gen.c, so tcc0 ≠ tcc1 there (see docs/TCC.md). ## ## Usage: boot/boot4.sh <arch> ## <arch> ∈ {aarch64, amd64, riscv64} for either DRIVER (default podman). @@ -109,28 +110,28 @@ runscm_input_from_src tcc/tcc.flat.c runscm_input_from_src libc/libc.flat.c runscm_input_from_src test-fixtures/boot-hello.c hello.c -runscm_export tcc1 tcc2 tcc3 s3-crt1.o s3-libc.a s3-libtcc1.a hello +runscm_export tcc1 tcc2 s2-crt1.o s2-libc.a s2-libtcc1.a hello runscm_run "${BOOT4_TIMEOUT:-5400}" # ── fixed-point check (host-side) ───────────────────────────────────── -if ! cmp -s "$OUT/tcc2" "$OUT/tcc3"; then +if ! cmp -s "$OUT/tcc1" "$OUT/tcc2"; then + s1=$(wc -c <"$OUT/tcc1") s2=$(wc -c <"$OUT/tcc2") - s3=$(wc -c <"$OUT/tcc3") if [ "${TCC_BOOTSTRAP_RELAX_FIXEDPOINT:-0}" = 1 ]; then - echo "[$BOOT_TAG] WARN: tcc2 ($s2) != tcc3 ($s3); TCC_BOOTSTRAP_RELAX_FIXEDPOINT=1, accepting tcc3" >&2 + echo "[$BOOT_TAG] WARN: tcc1 ($s1) != tcc2 ($s2); TCC_BOOTSTRAP_RELAX_FIXEDPOINT=1, accepting tcc2" >&2 else - echo "[$BOOT_TAG] FIXED-POINT FAIL: tcc2 ($s2) != tcc3 ($s3)" >&2 + echo "[$BOOT_TAG] FIXED-POINT FAIL: tcc1 ($s1) != tcc2 ($s2)" >&2 exit 1 fi fi -# ── normalize output names (drop s3- prefix) ────────────────────────── +# ── normalize output names (drop s2- prefix) ────────────────────────── # tcc1 / tcc2 are kept on disk: the test path (tcc-cc / tcc-libc suites) # uses them as stage-2 / stage-3 self-built tcc binaries. -mv "$OUT/s3-crt1.o" "$OUT/crt1.o" -mv "$OUT/s3-libc.a" "$OUT/libc.a" -mv "$OUT/s3-libtcc1.a" "$OUT/libtcc1.a" -chmod 0700 "$OUT/tcc1" "$OUT/tcc2" "$OUT/tcc3" "$OUT/hello" +mv "$OUT/s2-crt1.o" "$OUT/crt1.o" +mv "$OUT/s2-libc.a" "$OUT/libc.a" +mv "$OUT/s2-libtcc1.a" "$OUT/libtcc1.a" +chmod 0700 "$OUT/tcc1" "$OUT/tcc2" "$OUT/hello" echo "[$BOOT_TAG] sizes: libtcc1.a=$(wc -c <"$OUT/libtcc1.a") libc.a=$(wc -c <"$OUT/libc.a") hello=$(wc -c <"$OUT/hello")" -echo "[$BOOT_TAG] OK -> $OUT/{tcc3, crt1.o, libc.a, libtcc1.a, hello} (fixed point: tcc2 == tcc3)" +echo "[$BOOT_TAG] OK -> $OUT/{tcc2, crt1.o, libc.a, libtcc1.a, hello} (fixed point: tcc1 == tcc2)" diff --git a/boot/boot5.sh b/boot/boot5.sh @@ -1,14 +1,14 @@ #!/bin/sh ## boot5.sh — build musl-1.2.5 with boot4 artifacts and link hello. ## -## Builds on top of boot4's verified-fixed-point tcc (tcc2 == tcc3) and +## Builds on top of boot4's verified-fixed-point tcc (tcc1 == tcc2) and ## demonstrates that the same compiler can produce a working static libc ## from upstream musl source — patched only as far as needed to work ## around tcc's missing GCC extensions (register-asm-variable syscalls, ## attribute(alias) weak refs, _Complex, x86_64 SSE/x87 inline asm). ## ## ─── Inputs ────────────────────────────────────────────────────────── -## build/$ARCH/$DRIVER/boot4/tcc3 — boot4's verified self-host tcc +## build/$ARCH/$DRIVER/boot4/tcc2 — boot4's verified self-host tcc ## build/$ARCH/$DRIVER/boot4/libtcc1.a — boot4's tcc runtime archive ## build/$ARCH/$DRIVER/boot2/{catm, scheme1} ## build/$ARCH/src/src/musl/ — canonical musl tree (overrides @@ -45,7 +45,7 @@ SRC=build/$ARCH/src MUSL_DIR=$SRC/src/musl # ── prerequisites ───────────────────────────────────────────────────── -require_prev "$BOOT4" tcc3 +require_prev "$BOOT4" tcc2 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" @@ -79,7 +79,7 @@ runscm_scheme1 "$BOOT2/scheme1" runscm_prelude "$SRC/src/scheme1/prelude.scm" # Chain binaries staged at flat in/ root (cwd-relative names in run.scm). -runscm_input tcc "$BOOT4/tcc3" +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 diff --git a/boot/boot6.sh b/boot/boot6.sh @@ -1,7 +1,7 @@ #!/bin/sh -## boot6.sh — build the seed-kernel ELF/Image with boot4's tcc3. +## boot6.sh — build the seed-kernel ELF/Image with boot4's tcc2. ## -## Drives tcc3 to compile + link the seed kernel directly: no `ld -T +## Drives tcc2 to compile + link the seed kernel directly: no `ld -T ## kernel.lds`, no objcopy. aarch64 emits the flat Image QEMU expects; ## amd64/riscv64 emit the ELF consumed by QEMU's -kernel path. ## @@ -11,7 +11,7 @@ ## build/$ARCH/src/src/tcc/cc/mem.c ## ## ─── Inputs (binaries from prior stages) ────────────────────────────── -## build/$ARCH/$DRIVER/boot4/tcc3 +## build/$ARCH/$DRIVER/boot4/tcc2 ## build/$ARCH/$DRIVER/boot2/scheme1 ## ## ─── Tools ──────────────────────────────────────────────────────────── @@ -43,7 +43,7 @@ BOOT4=build/$ARCH/$DRIVER/boot4 SRC=build/$ARCH/src # ── prerequisites ───────────────────────────────────────────────────── -require_prev "$BOOT4" tcc3 +require_prev "$BOOT4" tcc2 require_prev "$BOOT2" scheme1 for f in kernel/arch/$ARCH/kernel.S kernel/arch/$ARCH/mmu.c kernel/arch/$ARCH/arch.h kernel/kernel.c tcc/cc/mem.c; do require_file "$SRC/src/$f" @@ -57,7 +57,7 @@ runscm_runscm "$SRC/run/boot6.scm" runscm_scheme1 "$BOOT2/scheme1" runscm_prelude "$SRC/src/scheme1/prelude.scm" -runscm_input tcc3 "$BOOT4/tcc3" +runscm_input tcc "$BOOT4/tcc2" runscm_input_from_src "kernel/arch/$ARCH/kernel.S" runscm_input_from_src kernel/kernel.c runscm_input_from_src "kernel/arch/$ARCH/arch.h" diff --git a/boot/lib-arch.sh b/boot/lib-arch.sh @@ -124,7 +124,7 @@ driver_init() { ;; seed) # DRIVER=seed always consumes the podman-built boot6 kernel — - # tcc3 is platform-agnostic but we settled on a single canonical + # tcc2 is platform-agnostic but we settled on a single canonical # build location to reduce surface area. KERNEL_IMAGE=$ROOT/build/$ARCH/podman/boot6/$KERNEL_NAME EXTRACT=$ROOT/seed-kernel/scripts/extract-blk.sh diff --git a/bootprep/boot4-gen-runscm.sh b/bootprep/boot4-gen-runscm.sh @@ -1,5 +1,5 @@ #!/bin/sh -## boot4-gen-runscm.sh — emit run.scm driving boot4's tcc0→tcc1→tcc2→tcc3 +## boot4-gen-runscm.sh — emit run.scm driving boot4's tcc0→tcc1→tcc2 ## chain inside the seed kernel. Mirrors boot/boot4.sh's per-stage shell ## emission; per-arch values resolved on the host so the .scm body is ## straight-line (run …) calls. @@ -61,7 +61,7 @@ EOF } # emit_archive — uses prefix to namespace output object names per stage. -# pfx="s2-"/"s3-" for stage2/3. The .o objects archived into libtcc1.a +# pfx="s1-"/"s2-" for stage C/D. The .o objects archived into libtcc1.a # keep their bare basenames (lib-arm64.o, …) — tcc -ar stores basenames # only, so this matches podman's archive members exactly. They overwrite # the stage's helper-named .o files; nothing post-archive in the same @@ -91,11 +91,15 @@ emit_link_tcc() { { cat <<'PROLOGUE' -;; boot4 run.scm — drive tcc0 -> tcc1 -> tcc2 -> tcc3 inside seed kernel. +;; boot4 run.scm — drive tcc0 -> tcc1 -> tcc2 inside seed kernel. ;; Generated by bootprep/boot4-gen-runscm.sh; mirrors boot/boot4.sh's ;; podman path stage-for-stage. Reads use in/; writes (intermediates and -;; exports) use out/. tcc0 is staged as in/tcc0; tcc1/tcc2/tcc3 are -;; produced and exported under out/. +;; exports) use out/. tcc0 is staged as in/tcc0; tcc1/tcc2 are produced +;; and exported under out/. +;; +;; Stages C and D use the SAME archive-based link line, so tcc0's output +;; (tcc1) is directly comparable to tcc1's output (tcc2): once tcc0 is a +;; faithful compilation of tcc.c, tcc1 == tcc2 is the fixed point. (define (must r tag) (if (and (car r) (= 0 (cdr r))) @@ -106,39 +110,31 @@ cat <<'PROLOGUE' (write-string stderr "\n") (exit 1)))) -(write-string stdout "boot4: stage B (tcc0 helpers)\n") +(write-string stdout "boot4: stage C (tcc0 -> tcc1)\n") PROLOGUE -# Stage B: tcc0 builds helper objects (no archive). +# Stage C: tcc0 builds helpers + archive, links tcc1 — same link line as +# stage D so tcc1 is directly comparable to tcc2. emit_helpers in/tcc0 tcc0 +emit_archive in/tcc0 tcc0 "s1-" +emit_link_tcc in/tcc0 tcc0 "s1-" tcc1 cat <<EOF -(write-string stdout "boot4: stage C (tcc0 -> tcc1)\n") -(must (run "in/tcc0" "-nostdlib" $LINK_TTEXT "out/start.o" "out/sys_stubs.o" "out/mem.o" "out/libc.o" "out/$LIB_HELPER_OBJ" "in/tcc.flat.c" "-o" "out/tcc1") "tcc0 -> tcc1") - (write-string stdout "boot4: stage D (tcc1 -> tcc2)\n") EOF -# Stage D: tcc1 rebuilds helpers + archive, links tcc2. +# Stage D: tcc1 rebuilds helpers + archive, links tcc2. tcc1 == tcc2 is +# the bootstrap fixed point (asserted host-side in boot4.sh). The s2- +# archives + crt1.o built here are boot4's exported deliverables. emit_helpers out/tcc1 tcc1 emit_archive out/tcc1 tcc1 "s2-" emit_link_tcc out/tcc1 tcc1 "s2-" tcc2 cat <<EOF -(write-string stdout "boot4: stage E (tcc2 -> tcc3)\n") -EOF - -# Stage E: tcc2 rebuilds helpers + archive, links tcc3. -emit_helpers out/tcc2 tcc2 -emit_archive out/tcc2 tcc2 "s3-" -emit_link_tcc out/tcc2 tcc2 "s3-" tcc3 - -cat <<EOF - (write-string stdout "boot4: linking hello\n") -(must (run "out/tcc2" "-nostdlib" $LINK_TTEXT "out/s3-crt1.o" "in/hello.c" "out/s3-libc.a" "out/s3-libtcc1.a" "out/s3-libc.a" "-o" "out/hello") "tcc2 -> hello") +(must (run "out/tcc1" "-nostdlib" $LINK_TTEXT "out/s2-crt1.o" "in/hello.c" "out/s2-libc.a" "out/s2-libtcc1.a" "out/s2-libc.a" "-o" "out/hello") "tcc1 -> hello") (write-string stdout "boot4: ALL-OK\n") (exit 0) EOF diff --git a/bootprep/boot5-calibrate.sh b/bootprep/boot5-calibrate.sh @@ -8,7 +8,7 @@ ## the vendored musl artifacts. ## ## What it does: -## 1. Stage the same prerequisites boot5.sh stages (boot4/tcc3, +## 1. Stage the same prerequisites boot5.sh stages (boot4/tcc2, ## libtcc1.a, vendored overrides + deletes, pre-generated headers, ## stdarg bridge). ## 2. Run a skip-on-fail compile loop in the container over every @@ -48,7 +48,7 @@ MUSL_GENERATED=vendor/musl/generated/$MUSL_ARCH BRIDGE_FILE=build/$ARCH/vendor/tcc/stdarg-bridge.h SKIP_OUT=vendor/musl/skip-$ARCH.txt -[ -x "$BOOT4/tcc3" ] || { echo "missing $BOOT4/tcc3 (run boot/boot4.sh $ARCH)" >&2; exit 1; } +[ -x "$BOOT4/tcc2" ] || { echo "missing $BOOT4/tcc2 (run boot/boot4.sh $ARCH)" >&2; exit 1; } [ -e "$BOOT4/libtcc1.a" ] || { echo "missing $BOOT4/libtcc1.a" >&2; exit 1; } [ -e "$MUSL_TARBALL" ] || { echo "missing $MUSL_TARBALL" >&2; exit 1; } [ -d "$MUSL_OVERRIDES" ] || { echo "missing $MUSL_OVERRIDES" >&2; exit 1; } @@ -64,7 +64,7 @@ fi rm -rf "$STAGE" mkdir -p "$STAGE/in" "$STAGE/out" -cp "$BOOT4/tcc3" "$STAGE/in/tcc" +cp "$BOOT4/tcc2" "$STAGE/in/tcc" cp "$BOOT4/libtcc1.a" "$STAGE/in/libtcc1.a" tar xzf "$MUSL_TARBALL" -C "$STAGE/in/" MUSL_DIR=$STAGE/in/musl-1.2.5 diff --git a/bootprep/boot6-gen-runscm.sh b/bootprep/boot6-gen-runscm.sh @@ -1,8 +1,8 @@ #!/bin/sh -## boot6-gen-runscm.sh — emit run.scm driving tcc3 to build seed-kernel. +## boot6-gen-runscm.sh — emit run.scm driving tcc2 to build seed-kernel. ## -## tcc3 -c each translation unit (kernel.S, kernel.c, mem.c) → .o, then -## tcc3 links + emits a flat arm64 boot Image directly (no `ld -T +## tcc2 -c each translation unit (kernel.S, kernel.c, mem.c) → .o, then +## tcc2 links + emits a flat arm64 boot Image directly (no `ld -T ## kernel.lds`, no objcopy). The link line is three tcc flags: ## ## -nostdlib -static freestanding link, no startfiles @@ -20,7 +20,7 @@ ## `.text`. ## ## Everything else the kernel needs from the linker is supplied by -## conventions tcc3 already honors: +## conventions tcc2 already honors: ## ## _start hard-coded entry symbol; kernel.S's arm64 Image ## header is named `_start` (not `_head`) so this works @@ -62,7 +62,7 @@ esac KCFLAGS='"-nostdlib" "-ffreestanding" "-static"' cat > "$OUT" <<EOF -;; boot6 run.scm — build seed-kernel ELF with tcc3. +;; boot6 run.scm — build seed-kernel ELF with tcc2. ;; Generated by bootprep/boot6-gen-runscm.sh; reads use in/, writes out/. (define (must r tag) @@ -74,24 +74,24 @@ cat > "$OUT" <<EOF (write-string stderr "\n") (exit 1)))) -(write-string stdout "boot6: tcc3 -c kernel.S\n") -(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/kernel-asm.o" "in/kernel.S") +(write-string stdout "boot6: tcc2 -c kernel.S\n") +(must (run "in/tcc" $KCFLAGS "-c" "-o" "out/kernel-asm.o" "in/kernel.S") "kernel.S -> kernel-asm.o") -(write-string stdout "boot6: tcc3 -c kernel.c\n") -(must (run "in/tcc3" $KCFLAGS "-Iin" "-c" "-o" "out/kernel.o" "in/kernel.c") +(write-string stdout "boot6: tcc2 -c kernel.c\n") +(must (run "in/tcc" $KCFLAGS "-Iin" "-c" "-o" "out/kernel.o" "in/kernel.c") "kernel.c -> kernel.o") -(write-string stdout "boot6: tcc3 -c mmu.c\n") -(must (run "in/tcc3" $KCFLAGS "-Iin" "-c" "-o" "out/mmu.o" "in/mmu.c") +(write-string stdout "boot6: tcc2 -c mmu.c\n") +(must (run "in/tcc" $KCFLAGS "-Iin" "-c" "-o" "out/mmu.o" "in/mmu.c") "mmu.c -> mmu.o") -(write-string stdout "boot6: tcc3 -c mem.c\n") -(must (run "in/tcc3" $KCFLAGS "-c" "-o" "out/mem.o" "in/mem.c") +(write-string stdout "boot6: tcc2 -c mem.c\n") +(must (run "in/tcc" $KCFLAGS "-c" "-o" "out/mem.o" "in/mem.c") "mem.c -> mem.o") -(write-string stdout "boot6: tcc3 link $OUT_FILE\n") -(must (run "in/tcc3" "-nostdlib" "-static" +(write-string stdout "boot6: tcc2 link $OUT_FILE\n") +(must (run "in/tcc" "-nostdlib" "-static" "-Wl,-Ttext=$TTEXT" $LINK_OFORMAT "-o" "out/$OUT_FILE" diff --git a/cc/cc.scm b/cc/cc.scm @@ -5672,6 +5672,32 @@ ((equal? (car (car fields)) fname) (cdr fields)) (else (%init-drop-thru-field (cdr fields) fname)))) +;; #t when TY is an array of i8/u8 — a char[] a string literal may +;; initialize directly (C11 §6.7.9 ¶14). +(define (%char-arr-type? t) + (and (eq? (ctype-kind t) 'arr) + (let ((et (car (ctype-ext t)))) + (or (eq? et %t-i8) (eq? et %t-u8))))) + +;; A char[] field/element written as `= "..."`: consume the STR token +;; and return a byte-vector piece of the array's declared size, copying +;; the string bytes (truncating an over-long string, zero-padding a +;; short one). The nested-aggregate analogue of parse-init-global's +;; top-level STR arm; `t` always has a declared length here (only a +;; top-level array may infer its length from the string). +(define (%str-arr-piece ps t) + (let* ((s (tok-value (peek ps))) + (slen (bytevector-length s)) + (final (ctype-size t)) + (bv (make-bytevector final 0))) + (advance ps) + (let loop ((i 0)) + (cond + ((or (= i slen) (>= i final)) bv) + (else + (bytevector-u8-set! bv i (bytevector-u8-ref s i)) + (loop (+ i 1))))))) + ;; Element/field dispatch for global aggregate initializers. ELIDE? = #f ;; means caller has just consumed `{` for this element and we own the ;; matching `}`; ELIDE? = #t is C99 §6.7.8 ¶22 brace elision (the @@ -5680,6 +5706,20 @@ (define (%global-init-elem ps t elide?) (let ((k (ctype-kind t))) (cond + ;; char[] initialized by a string literal (`{"..."}` or, under + ;; brace elision, a bare `"..."`). Without this, the `arr` arm + ;; below would treat the string as an element-list and encode it + ;; as a pointer to the string pool instead of the bytes. + ((and (eq? k 'arr) + (eq? (tok-kind (peek ps)) 'STR) + (%char-arr-type? t)) + (let ((p (%str-arr-piece ps t))) + (cond + (elide? (list p)) + (else + (cond ((at-punct? ps 'comma) (advance ps))) + (expect-punct ps 'rbrace) + (list p))))) ((eq? k 'arr) (let-values (((p _c) (cond (elide? (%parse-init-array-list/mode ps t #f)) @@ -5705,6 +5745,31 @@ (define (%local-init-elem ps sm eoff t elide?) (let ((k (ctype-kind t))) (cond + ;; char[] field/element initialized by a string literal — emit the + ;; bytes into the frame slot. Mirrors %global-init-elem's STR arm; + ;; without it the `arr` arm would parse the string as a scalar + ;; expression and store one byte of its decayed pointer. + ((and (eq? k 'arr) + (eq? (tok-kind (peek ps)) 'STR) + (%char-arr-type? t)) + (let* ((s (tok-value (peek ps))) + (slen (bytevector-length s)) + (final (ctype-size t))) + (advance ps) + (let loop ((i 0)) + (cond + ((>= i final) 0) + (else + (let ((b (cond ((< i slen) (bytevector-u8-ref s i)) (else 0)))) + (%push-frame-elem-lval ps (+ eoff i) %t-u8) + (cg-push-imm (ps-cg ps) %t-u8 b) + (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)) + (loop (+ i 1)))))) + (cond + (elide? 0) + (else + (cond ((at-punct? ps 'comma) (advance ps))) + (expect-punct ps 'rbrace))))) ((eq? k 'arr) (cond (elide? (%parse-init-local-array-list/mode ps sm eoff t #f)) diff --git a/docs/MUSL.md b/docs/MUSL.md @@ -29,7 +29,7 @@ boot/boot5.sh <amd64|aarch64|riscv64> | Path | Purpose | |------|---------| -| `build/$ARCH/boot4/tcc3` | fixed-point self-host tcc from boot4 | +| `build/$ARCH/boot4/tcc2` | fixed-point self-host tcc from boot4 | | `build/$ARCH/boot4/libtcc1.a` | tcc runtime archive produced by boot4 | | `vendor/musl/1.2.5.tar.gz` | pristine upstream musl source | | `vendor/musl/overrides/` | post-patch files vendored as a tree (replaces the old patch + `patch` binary) | @@ -69,7 +69,7 @@ The entire `.boot5-stage` tree is disposable; every `boot5.sh` run rebuilds it. ## Pipeline -1. **Stage inputs (host)**. Copy boot4 `tcc3` and `libtcc1.a` to `in/`. +1. **Stage inputs (host)**. Copy boot4 `tcc2` and `libtcc1.a` to `in/`. Extract the musl tarball into `in/musl-1.2.5/`. Overlay the vendored `musl-1.2.5-overrides/` tree on top of it. Remove every path listed in `musl-1.2.5-deletes.txt`. The result is the post-patch tree that diff --git a/docs/TCC.md b/docs/TCC.md @@ -204,22 +204,23 @@ For now: known limitation, document, move on. The scalar codegen elsewhere on riscv64 is fine — only u32 narrowing of a wider source trips it. -### riscv64: tcc0 → tcc1 is not a fixed point (cc.scm behavioral bug) +### riscv64: tcc1 ≠ tcc2 (cc.scm behavioral bug) -`boot3.sh` + `boot4.sh` produce four staged compilers: +`boot3.sh` + `boot4.sh` produce three staged compilers: - `tcc0` = tcc-source compiled by cc.scm (boot3 output) - `tcc1` = tcc-source compiled by tcc0 (boot4) - `tcc2` = tcc-source compiled by tcc1 (boot4) -- `tcc3` = tcc-source compiled by tcc2 (boot4) -The fixed-point check is **`tcc2 == tcc3`** (asserted at the end of -`boot4.sh`, verified on aarch64, amd64, riscv64). On riscv64 the -weaker `tcc1 == tcc2` does *not* hold: `tcc0(tcc.flat.c)` produces -a 616100-byte `.o` while `tcc1(tcc.flat.c)` and `tcc2(tcc.flat.c)` -produce a byte-identical 615892-byte `.o` — 208 bytes larger from -tcc0 (200 in `.text` + 8 ripple in symtab/reloc offsets). amd64 and -aarch64 satisfy `tcc1 == tcc2`; only riscv64 diverges. +The fixed-point check is **`tcc1 == tcc2`** (asserted at the end of +`boot4.sh`). It holds on aarch64 and amd64: `tcc0(tcc.flat.c)` and +`tcc1(tcc.flat.c)` are byte-identical, so the two stages' identical +archive-based link lines yield identical binaries. On riscv64 it does +*not* hold: `tcc0(tcc.flat.c)` produces a 616100-byte `.o` while +`tcc1(tcc.flat.c)` and `tcc2(tcc.flat.c)` produce a byte-identical +615892-byte `.o` — 208 bytes larger from tcc0 (200 in `.text` + 8 +ripple in symtab/reloc offsets). Until this is fixed, riscv64 boot4 +needs `TCC_BOOTSTRAP_RELAX_FIXEDPOINT=1`. This is a **bug to investigate**, not just a "fatter code" observation. cc.scm should be a *faithful* (semantics-preserving) @@ -315,4 +316,4 @@ patches in `tccelf.c`: After AT.2 the post-link `elf-pvh-note.c` tool, the amd64-only branch in `boot6.sh`, and the amd64 fixup block in `boot6-gen-runscm.sh` are all gone. The amd64 kernel is emitted -ready-to-boot by tcc3. +ready-to-boot by tcc2. diff --git a/docs/TOUR.md b/docs/TOUR.md @@ -21,7 +21,7 @@ prior stages produced, plus source from the canonical | 1 | [boot1.sh](../boot/boot1.sh) | `M1pp`, `hex2pp` | first programs in the portable P1 pseudo-ISA | | 2 | [boot2.sh](../boot/boot2.sh) | `catm` (rebuilt), `scheme1` | seed `catm` retired; Scheme interpreter arrives | | 3 | [boot3.sh](../boot/boot3.sh) | `tcc0` | C arrives — `cc.scm` (in scheme1) compiles upstream tcc | -| 4 | [boot4.sh](../boot/boot4.sh) | `tcc1`, `tcc2`, `tcc3`, `libc.a`, `libtcc1.a` | tcc self-host, byte-identical fixed point `tcc2 == tcc3`, minimal libc | +| 4 | [boot4.sh](../boot/boot4.sh) | `tcc1`, `tcc2`, `libc.a`, `libtcc1.a` | tcc self-host, byte-identical fixed point `tcc1 == tcc2`, minimal libc | | 5 | [boot5.sh](../boot/boot5.sh) | `libc.a`, `crt{1,i,n}.o` | musl-1.2.5 built by the self-hosted tcc | | 6 | [boot6.sh](../boot/boot6.sh) | `Image` (aarch64) / `kernel.elf` | a minimal kernel that can host the chain (`DRIVER=seed`) | @@ -194,28 +194,27 @@ smallest end-to-end exercise of the whole pipeline. **You arrive with**: `tcc0` from boot3. -**boot4 builds**: `tcc1`, `tcc2`, `tcc3`, plus `crt1.o`, +**boot4 builds**: `tcc1`, `tcc2`, plus `crt1.o`, `libc.a` (mes-libc), and `libtcc1.a`. -**How**: tcc compiles tcc, three more times. +**How**: tcc compiles tcc, twice more. ``` tcc0 = tcc-source compiled by cc.scm ← boot3 tcc1 = tcc-source compiled by tcc0 ← here tcc2 = tcc-source compiled by tcc1 ← here -tcc3 = tcc-source compiled by tcc2 ← here ``` -After the third bounce, the script asserts `tcc2 == tcc3` byte for +After the second bounce, the script asserts `tcc1 == tcc2` byte for byte. That's the fixed point: tcc compiling itself with no help from cc.scm reaches a stable image, and any future build that walks through -this stage will produce the same `tcc3` from the same sources. +this stage will produce the same `tcc2` from the same sources. -Why four stages, not two? `cc.scm` and tcc's own codegen produce +Why three stages, not two? `cc.scm` and tcc's own codegen produce different — but both correct — code for the same source, so `tcc0` -and `tcc1` differ. `tcc1` and `tcc2` are both built by tcc, but `tcc1` -itself was a cc.scm-shaped binary, so its codegen choices in `tcc2` -need one more bounce to reach the tcc-shaped fixed point. +and `tcc1` differ. But both stages compile+link tcc the same way, so +`tcc1 == tcc2` holds as soon as `tcc0` is a faithful compilation of +tcc.c — the check thus also proves cc.scm compiled tcc.c correctly. **Trust extension**: a self-built tcc that demonstrates determinism under self-application. Plus mes-libc, a small libc that's good @@ -223,12 +222,12 @@ enough for tcc's own runtime, and `libtcc1.a` — tcc's helper archive (division/intrinsics that tcc emits calls to). **Worth reading**: the fixed-point check in -[boot4.sh:114–123](../boot/boot4.sh) is the audit point of the entire +[boot4.sh:115–124](../boot/boot4.sh) is the audit point of the entire chain. ## §6. boot5 — a real libc -**You arrive with**: `tcc3` and `libtcc1.a` from boot4; `catm` and +**You arrive with**: `tcc2` and `libtcc1.a` from boot4; `catm` and `scheme1` from boot2. **boot5 builds**: `libc.a` and `crt{1,i,n}.o` from upstream @@ -245,7 +244,7 @@ static archive. **Trust extension**: a real libc. The chain no longer depends on mes-libc for anything but the seed-kernel's `hello` link path; every -later binary you build with this `tcc3` can use musl. +later binary you build with this `tcc2` can use musl. **Worth reading**: the musl skip / patch lists under [vendor/musl/](../vendor/musl/) and the calibration script @@ -254,12 +253,12 @@ Background: [docs/MUSL.md](MUSL.md), [docs/LIBC.md](LIBC.md). ## §7. boot6 — a kernel that runs the chain -**You arrive with**: `tcc3` from boot4 and `scheme1` from boot2. +**You arrive with**: `tcc2` from boot4 and `scheme1` from boot2. **boot6 builds**: a minimal kernel image — `Image` on aarch64, `kernel.elf` on amd64 / riscv64. -**How**: tcc3 compiles `seed-kernel/kernel.c` plus the per-arch entry +**How**: tcc2 compiles `seed-kernel/kernel.c` plus the per-arch entry in `seed-kernel/arch/<arch>/{kernel.S, mmu.c, arch.h}` and `tcc/cc/mem.c`, linking through tcc directly — no separate linker, no objcopy. On aarch64, tcc's flat-binary mode produces an arm64 `Image`. diff --git a/seed-kernel/arch/aarch64/kernel.S b/seed-kernel/arch/aarch64/kernel.S @@ -16,8 +16,8 @@ _start: * section ordering required. * * Entry symbol is `_start` (not `_head`) so a script-less linker - * — tcc3 in particular — picks us up via its hard-coded default. - * Image-size end marker is `_end`, which both gnu ld and tcc3 + * — tcc2 in particular — picks us up via its hard-coded default. + * Image-size end marker is `_end`, which both gnu ld and tcc2 * auto-define at the end of `.bss`. */ b stext .long 0 @@ -44,7 +44,7 @@ stext: * SPSR=EL1h with DAIF masked, ELR=in_el1, eret. * * Address loads use `ldr Xn, =sym` rather than `adrp/add :lo12:` so this - * file assembles under tcc3 (the MOVW_UABS_G{0..3} reloc chain that + * file assembles under tcc2 (the MOVW_UABS_G{0..3} reloc chain that * `ldr =sym` lowers to is the same chain `arm64-gen.c` uses for every * compiler-emitted symbol load). */ mov x9, #(1 << 31) @@ -80,7 +80,7 @@ in_el1: isb /* Zero BSS. `__bss_start` and `_end` are auto-defined by both - * gnu ld (via the kernel.lds bracket) and tcc3 (via the + * gnu ld (via the kernel.lds bracket) and tcc2 (via the * `bss-start-symbol` simple-patch + tcc's existing `_end` def). */ ldr x1, =__bss_start ldr x2, =_end diff --git a/seed-kernel/arch/aarch64/kernel.lds b/seed-kernel/arch/aarch64/kernel.lds @@ -15,7 +15,7 @@ SECTIONS { * at its top), and it is linked first, so its contributions land at * file offset 0 — exactly where QEMU's arm64 boot protocol expects * the `ARM\x64` magic at byte 0x38. No `.head.text` indirection - * needed; a script-less linker (tcc3) reaches the same layout via + * needed; a script-less linker (tcc2) reaches the same layout via * its default input-order section concatenation. */ .text : ALIGN(8) { *(.text .text.*) @@ -33,7 +33,7 @@ SECTIONS { * plain `.bss` content ending at `kstack_top`. kernel.S references * `__bss_start` and `_end` to bracket the bss-zero loop; both names * are de-facto linker conventions (gnu ld defines them via this - * script, and tcc3 synthesizes them automatically via the + * script, and tcc2 synthesizes them automatically via the * bss-start-symbol simple-patch + its built-in `_end` def). */ .bss : ALIGN(16) { __bss_start = .; diff --git a/seed-kernel/arch/amd64/kernel.S b/seed-kernel/arch/amd64/kernel.S @@ -72,7 +72,7 @@ long_mode: movw %ax, %ss movq $kstack_top, %rsp - /* Enable SSE/SSE2 for user mode. tcc-emitted user binaries (tcc3, + /* Enable SSE/SSE2 for user mode. tcc-emitted user binaries (tcc2, * scheme1) save callee-saved xmm regs in every function prologue, * so the first user `movq %xmm7, ...` would otherwise trap with * #UD or #NM. CR0.MP=1, EM=0; CR4.OSFXSR=1, OSXMMEXCPT=1. The diff --git a/tests/cc/340-struct-char-array-string-init.c b/tests/cc/340-struct-char-array-string-init.c @@ -0,0 +1,42 @@ +/* tests/cc/340-struct-char-array-string-init.c + * + * Regression: a char[N] array that is a *struct field* or *array + * element* initialized by a string literal must be encoded as the + * string's BYTES, not a pointer to the string pool. This is the shape + * of tcc's ArHdr archive-header template (static struct of char[] + * fields); mis-encoding it as pointers produced corrupt `.a` files and + * cost the tcc self-host an extra fixed-point bounce. + */ + +typedef struct { char a[16]; char b[6]; char c[6]; char e[2]; } Hdr; + +/* static (global) struct — bare and braced string field forms. */ +static Hdr gbare = { "/x", "0", "0", "`\n" }; +static Hdr gbrace = { {"/x"}, {"0"}, "0", "`\n" }; + +/* array of char[] strings, each element a string literal. */ +static char rows[3][4] = { "ab", "cd", "ef" }; + +static int check(const Hdr *h) { + if (h->a[0] != '/' || h->a[1] != 'x' || h->a[2] != 0) return 1; + if (h->b[0] != '0' || h->b[1] != 0) return 2; + if (h->c[0] != '0' || h->c[1] != 0) return 3; + if (h->e[0] != '`' || h->e[1] != '\n') return 4; + return 0; +} + +int main(void) { + /* local struct with char[] string fields */ + struct { char a[8]; char b[4]; } loc = { "hello", "wx" }; + + if (check(&gbare)) return 10; + if (check(&gbrace)) return 20; + + if (rows[0][0] != 'a' || rows[1][0] != 'c' || rows[2][1] != 'f') return 30; + if (rows[0][2] != 0) return 31; /* zero-padded tail */ + + if (loc.a[0] != 'h' || loc.a[4] != 'o' || loc.a[5] != 0) return 40; + if (loc.b[0] != 'w' || loc.b[1] != 'x' || loc.b[2] != 0) return 41; + + return 0; +} diff --git a/tests/cc/340-struct-char-array-string-init.expected-exit b/tests/cc/340-struct-char-array-string-init.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/seed-accept.sh b/tests/seed-accept.sh @@ -72,8 +72,8 @@ if [ "$MODE" = "boot34" ]; then exit 0 fi - [ -x $PODMAN/boot4/tcc3 ] || { - echo "$PODMAN/boot4/tcc3 missing — run boot/boot4.sh aarch64 under podman first" >&2 + [ -x $PODMAN/boot4/tcc2 ] || { + echo "$PODMAN/boot4/tcc2 missing — run boot/boot4.sh aarch64 under podman first" >&2 exit 1 } @@ -86,7 +86,7 @@ if [ "$MODE" = "boot34" ]; then # tcc patch (simple-patches/tcc-0.9.26/) drops the /work/in/[tcc-lib/] # mount prefix from STT_FILE entries, so seed's flat-basename staging # and podman's /work/in/ mounts produce identical .o relocations. - for f in tcc3 hello crt1.o libc.a libtcc1.a; do + for f in tcc2 hello crt1.o libc.a libtcc1.a; do if ! cmp -s $SEED/boot4/$f $PODMAN/boot4/$f; then s_seed=$(wc -c < $SEED/boot4/$f) s_ref=$(wc -c < $PODMAN/boot4/$f) @@ -95,7 +95,7 @@ if [ "$MODE" = "boot34" ]; then fi done [ $fail -eq 0 ] || exit 4 - echo "[seed-accept boot34] boot4 PASS — tcc3/hello/crt1.o/libc.a/libtcc1.a byte-identical vs podman" + echo "[seed-accept boot34] boot4 PASS — tcc2/hello/crt1.o/libc.a/libtcc1.a byte-identical vs podman" exit 0 fi diff --git a/tools/mkrelease.sh b/tools/mkrelease.sh @@ -129,7 +129,6 @@ boot3/libc.P1pp boot3/tcc.flat.P1pp boot4/tcc1 boot4/tcc2 -boot4/tcc3 boot4/hello boot4/crt1.o boot4/libc.a diff --git a/tools/release/README.md b/tools/release/README.md @@ -103,7 +103,7 @@ If you've already built and just want to re-check the manifest: | var | default | meaning | | ---------------- | ------- | ---------------------------------- | | `BOOT3_TIMEOUT` | 1800 | scheme1-driven boot3, seconds | -| `BOOT4_TIMEOUT` | 5400 | tcc1/tcc2/tcc3 self-host chain | +| `BOOT4_TIMEOUT` | 5400 | tcc1/tcc2 self-host chain | | `BOOT5_TIMEOUT` | 7200 | musl build | | `BOOT6_TIMEOUT` | 1200 | seed-kernel link | | `QEMU_MEM` | 3072M | guest RAM for `DRIVER=seed` | diff --git a/vendor/mes-libc/libc.c b/vendor/mes-libc/libc.c @@ -412,7 +412,7 @@ realloc (void *p, size_t size) /* Internal parser shared by atoi / strtol / strtoul / strtoull and the width/precision parser inside vformat_. `long long` accumulator so values that don't fit in 32-bit signed don't sign-extend through the - parse — tcc3 needs this for `-Wl,-Ttext=0x80200000` and the like. */ + parse — tcc2 needs this for `-Wl,-Ttext=0x80200000` and the like. */ static long long parse_int_ (char const **p, int base) {