commit 97c83d00c094c070d8dae05e5a06ec7cd3968dcc
parent fb0a4e33c70a5965ed86b9c838e2b83df3827e93
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 6 May 2026 12:37:07 -0700
A4: Makefile drives bootN.sh with path-based deps
prep-src + boot{0..6} are now make targets per (arch, driver). Each
stage's .stamp is the recipe peg; real artifacts depend on the stamp
via empty-recipe declarations. `make build/<arch>/<driver>/bootN/<file>`
walks the full chain — no env-var dance. DRIVER=seed stages pick up
build/<arch>/podman/boot6/<kernel-name> as a dep so a clean-tree seed
build pulls in the podman side automatically.
Single-target rules (not grouped `&:`) — GNU make 4.4 doesn't propagate
"must remake of prereq" through grouped explicit targets across multi-
level chains, which would skip the boot6 rebuild after a deeper change.
Drops the m1pp/hex2pp/scheme1/cc/tcc-flat/tcc-boot2/tcc-tcc/tcc-tcc-tcc/
tcc-gcc phony aliases (path is the name), the dead TOOLS_M0/TOOLS_CATM
vars, and inlines the tcc-version string into a single TCC_PKG. cloc
moves out to scripts/count-lines.sh. Test infrastructure stays inline
until AX migrates it to tests/Makefile.
Diffstat:
3 files changed, 322 insertions(+), 337 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,124 +1,257 @@
-# boot2 — P1 portable pseudo-ISA + M1pp.
+# boot2 — Make-driven bootstrap pipeline.
#
-# Make declares all build dependencies and ensures every input + tool
-# exists before any script runs. Scripts in scripts/ are pure
-# transformations: they assume their inputs are present, do one thing,
-# write outputs. Any script that runs inside podman starts with `boot`;
-# the rest run on the host.
+# The bootN.sh scripts under scripts/ are the canonical builders. This
+# Makefile is dependency tracking: every target is a real output path,
+# and the rule body invokes the right bootN.sh (or prep-src.sh /
+# prep-musl.sh) script with the appropriate ARCH and DRIVER.
#
-# Bootstrap inputs are vendored in vendor/seed/<arch>/. The only image
-# used is per-arch boot2-busybox:<arch>, built from
-# scripts/Containerfile.busybox.
+# Output layout: build/<arch>/<driver>/boot{0..6}/<artifacts>.
+# <arch> ∈ {aarch64, amd64, riscv64}
+# <driver> ∈ {podman, seed}
#
-# Common entrypoints:
-# make all (m1pp + hex2pp for ARCH)
-# make m1pp build the m1pp expander for ARCH
-# make hex2pp build the hex2pp assembler/linker for ARCH
-# make scheme1 build the scheme1 interpreter for ARCH
-# make cc catm the cc compiler source for ARCH
-# make tcc-flat flatten upstream tcc.c into one TU
-# make tcc-boot2 cc.scm + P1pp pipeline → tcc-boot2 ELF
-# make tcc-gcc same flatten output, built with stock gcc
-# (sanity check; ARCH in {aarch64, amd64})
-# make tcc-tcc second-stage tcc: tcc-boot2 compiles
-# tcc.flat.c into a self-built tcc
-# (ARCH in {aarch64, amd64, riscv64})
-# make tcc-tcc-tcc third-stage tcc: tcc-tcc compiles
-# tcc.flat.c. README endpoint —
-# `(define tcc (tcc1 tcc.c))`. The
-# tcc-cc / tcc-libc suites run twice,
-# stage 2 then stage 3
-# (ARCH in {aarch64, amd64, riscv64})
-# make test every suite, every arch
-# make test SUITE=m1pp m1pp suite, every arch
-# make test SUITE=p1 ARCH=amd64 p1 suite, one arch
-# make test SUITE=scheme1 scheme1 .scm fixtures, every arch
-# make test SUITE=tcc-cc tcc-tcc compiles + runs tests/cc
-# (ARCH in {aarch64, amd64, riscv64})
-# make test SUITE=tcc-libc tcc-boot2 builds mes-libc, tcc-tcc
-# compiles + links tests/cc-libc against it
-# (ARCH in {aarch64, amd64, riscv64})
-# make test SUITE=cc-ext vendored c-testsuite (broad coverage,
-# opt-in: not part of `make test`)
-# make image build the per-arch container image
-# make tools bootstrap M0/hex2-0/catm for ARCH
-# (seed-only: used to build m1pp + hex2pp)
-# make tables regen pre-pruned P1/P1-<arch>.M1 tables
-# make tools-native build host-native M1/hex2/m1pp/hex2pp (opt-in)
-# make cloc line counts for the core sources
-# make clean rm -rf build/
+# Path-based builds:
+# make build/aarch64/podman/boot6/Image
+# make build/amd64/seed/boot6/kernel.elf
+# make build/riscv64/podman/boot1/M1pp # only prep-src + boot0 + boot1
#
-# Output layout: every binary lives at build/<arch>/<src-path-without-ext>,
-# mirroring the source path under the repo root (e.g. M1pp/M1pp.P1 ->
-# build/<arch>/M1pp/M1pp; hex2pp/hex2pp.P1 -> build/<arch>/hex2pp/hex2pp;
-# tests/cc/foo.c -> build/<arch>/tests/cc/foo).
-# Per-source intermediates land under build/<arch>/.work/<src-path>/.
+# Convenience entrypoints (default ARCH=aarch64, DRIVER=podman):
+# make all build boot6 kernel for ARCH × DRIVER
+# make test SUITE=<name> run a test suite (see SUITE list at bottom)
+# make image build the per-arch container image used by tests
+# make cloc line counts for the bootstrap sources
+# make clean rm -rf build/
#
-# Bootstrap chain:
-# 1. seed (vendored hex0-seed) -> M0 + hex2-0 + catm (mk-seed-tools.sh)
-# 2. seed M0 + hex2-0 -> M1pp ELF (boot-build-p1.sh)
-# 2. seed M0 + hex2-0 -> hex2pp ELF (boot-build-p1.sh)
-# 3. M1pp + hex2pp -> every other ELF (.P1pp pipeline) (boot-build-p1pp.sh)
-#
-# Standalone host-side entrypoints (not used by this Makefile, but they
-# build the same artifacts in build/$ARCH/$DRIVER/bootN/ for inspection;
-# DRIVER ∈ {podman, seed}, default podman):
-# scripts/boot0.sh <arch> -> hex2 / M0 / catm
-# scripts/boot1.sh <arch> -> m1pp / hex2pp
-# scripts/boot2.sh <arch> -> scheme1
-# scripts/boot3.sh <arch> -> tcc0 (cc.scm-built bootstrap)
-# scripts/boot4.sh <arch> -> tcc1 / tcc2 / tcc3 + libtcc1.a + libc.a + hello
-# scripts/boot5.sh <arch> -> static musl libc + hello
-# The seed M0/hex2-0/catm participate ONLY in step 2 (building the two
-# new tools from their .P1 sources). Once both binaries exist, no
-# downstream user/test/scheme/cc target ever invokes them again.
-
-ARCH ?= aarch64
-
-ALL_ARCHES := aarch64 amd64 riscv64
-
-PLATFORM_aarch64 := linux/arm64
-PLATFORM_amd64 := linux/amd64
-PLATFORM_riscv64 := linux/riscv64
+# DRIVER=seed bootN stages depend on the podman-built boot6 kernel
+# (build/<arch>/podman/boot6/<kernel-name>) since the seed driver runs
+# scheme1 inside seed-kernel under QEMU. The seed-side dependency on
+# the podman side is encoded in the rules.
+
+# ── Config ───────────────────────────────────────────────────────────────
+
+ARCH ?= aarch64
+DRIVER ?= podman
+
+ALL_ARCHES := aarch64 amd64 riscv64
+ALL_DRIVERS := podman seed
ifeq ($(filter $(ARCH),$(ALL_ARCHES)),)
$(error ARCH '$(ARCH)' not supported — use one of $(ALL_ARCHES))
endif
+ifeq ($(filter $(DRIVER),$(ALL_DRIVERS)),)
+ $(error DRIVER '$(DRIVER)' not supported — use one of $(ALL_DRIVERS))
+endif
-OUT_DIR := build/$(ARCH)
-TOOLS_DIR := $(OUT_DIR)/tools
-IMAGE_STAMP := $(OUT_DIR)/.image
+# Per-arch metadata mirrored from scripts/lib-arch.sh.
+PLATFORM_aarch64 := linux/arm64
+PLATFORM_amd64 := linux/amd64
+PLATFORM_riscv64 := linux/riscv64
-# All container invocations go through this. Just env/args + one named
-# script in scripts/; never inline shell. Every script reads ARCH from
-# the env and derives all its fixed paths (P1 table, ELF header, tools
-# dir, etc.) from that — only per-call args (source, output) are passed
-# positionally.
-#
-# --tmpfs /tmp: backstop for the stage0 tools' per-byte fputc/fgetc. Without
-# it /tmp falls through to the container overlay (still backed by virtiofs
-# on Apple Silicon), and a 1MB M0 input pays a virtiofs round-trip per
-# byte. Real RAM tmpfs collapses that to local memory access.
-PODMAN = podman run --rm --pull=never --platform $(PLATFORM_$(1)) \
- --tmpfs /tmp:size=512M \
- -e ARCH=$(1) \
- -v $(CURDIR):/work -w /work boot2-busybox:$(1)
+KERNEL_NAME_aarch64 := Image
+KERNEL_NAME_amd64 := kernel.elf
+KERNEL_NAME_riscv64 := kernel.elf
+
+MUSL_ARCH_aarch64 := aarch64
+MUSL_ARCH_amd64 := x86_64
+MUSL_ARCH_riscv64 := riscv64
+
+TCC_PKG := tcc-0.9.26-1147-gee75a10c
+
+OUT_DIR := build/$(ARCH)/$(DRIVER)
-# --- Targets --------------------------------------------------------------
+.SUFFIXES:
-.PHONY: all m1pp hex2pp scheme1 cc test image tools tables \
- tools-native cloc clean help tcc-boot2 tcc-flat tcc-gcc \
- tcc-tcc tcc-tcc-tcc
+.PHONY: all help clean image cloc test tools tools-native tables \
+ audit prep-src prep-musl
-all: m1pp hex2pp
+# ── Top-level targets ────────────────────────────────────────────────────
+
+all: build/$(ARCH)/$(DRIVER)/boot6/$(KERNEL_NAME_$(ARCH))
help:
- @sed -n '/^# Common entrypoints:/,/^$$/p' Makefile | sed 's/^# *//'
+ @echo 'Targets (default ARCH=$(ARCH) DRIVER=$(DRIVER)):'
+ @echo ' make all build boot6 kernel'
+ @echo ' make build/<arch>/<driver>/boot6/<kn> full chain (kn = Image | kernel.elf)'
+ @echo ' make build/<arch>/<driver>/bootN/<file> any single artifact'
+ @echo ' make test SUITE=<suite> test suite (NAMES=<filter> optional)'
+ @echo ' make image per-arch tests container image'
+ @echo ' make cloc line counts for bootstrap sources'
+ @echo ' make clean rm -rf build/'
+
+clean:
+ rm -rf build/
+
+# ── prep-src + boot0..boot6 chain (rules per arch × driver) ──────────────
+#
+# The .stamp files are the make-rule pegs. Each rule lists its real
+# outputs as additional targets so single-binary builds work — `make
+# build/aarch64/podman/boot1/M1pp` resolves to the boot1 grouped target
+# and only walks back through prep-src + boot0.
+#
+# Per-arch source deps for prep-src.sh. The file list mirrors what
+# prep-src.sh actually copies, so a vendor/seed change re-triggers the
+# canonical-tree build.
+
+PREP_SRC_COMMON_SRCS := \
+ scripts/prep-src.sh scripts/lib-arch.sh \
+ scripts/stage1-flatten.sh scripts/libc-flatten.sh \
+ M1pp/M1pp.P1 hex2pp/hex2pp.P1 \
+ P1/P1.M1pp P1/P1pp.P1pp \
+ P1/entry-libc.P1pp P1/entry-plain.P1pp P1/elf-end.P1pp \
+ catm/catm.P1pp \
+ scheme1/scheme1.P1pp scheme1/prelude.scm \
+ cc/cc.scm cc/main.scm \
+ tcc-cc/mem.c \
+ scripts/boot-hello.c \
+ seed-kernel/kernel.c \
+ vendor/upstream/musl-1.2.5.tar.gz \
+ vendor/upstream/musl-1.2.5-deletes.txt
+
+prep_src_arch_srcs = \
+ vendor/seed/$1/hex0-seed \
+ vendor/seed/$1/ELF.hex2 \
+ vendor/seed/$1/hex0.hex0 vendor/seed/$1/hex1.hex0 vendor/seed/$1/hex2.hex1 \
+ vendor/seed/$1/catm.hex2 vendor/seed/$1/M0.hex2 \
+ P1/P1-$1.M1 P1/P1-$1.M1pp \
+ tcc-libc/$1/start.S tcc-libc/$1/sys_stubs.S \
+ $(wildcard seed-kernel/arch/$1/*) \
+ $(wildcard seed-kernel/user/*) \
+ vendor/upstream/musl-1.2.5-generated/$(MUSL_ARCH_$1)/alltypes.h \
+ vendor/upstream/musl-1.2.5-generated/$(MUSL_ARCH_$1)/syscall.h \
+ $(wildcard vendor/upstream/musl-1.2.5-skip-$1.txt) \
+ $(shell find vendor/upstream/musl-1.2.5-overrides -type f 2>/dev/null) \
+ $(shell find vendor/mes-libc -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null) \
+ $(wildcard vendor/mes-libc/patches/*.before) \
+ $(wildcard vendor/mes-libc/patches/*.after) \
+ $(wildcard vendor/upstream/tcc-0.9.26.tar.gz)
+
+# DRIVER=seed bootN stages run scheme1 under QEMU using the podman-built
+# boot6 kernel. Add that as a make dep so a clean-tree seed build pulls
+# in the podman side automatically. Empty for DRIVER=podman.
+seed_kernel_dep = $(if $(filter seed,$2),build/$1/podman/boot6/$(KERNEL_NAME_$1))
+
+# Per-arch prep-src + prep-musl rules. Driver-independent.
+define PREP_RULES
+build/$1/src/.stamp: $$(PREP_SRC_COMMON_SRCS) $$(call prep_src_arch_srcs,$1)
+ scripts/prep-src.sh $1
+ @touch $$@
+
+# Filtered musl tree. Depends on the canonical tree from prep-src and
+# (when present) the committed per-arch skip list. If the skip list is
+# missing, prep-musl.sh runs scripts/boot5-calibrate.sh, which drives
+# its own boot4 build outside this make graph — keep make's deps simple.
+build/$1/src/musl/.stamp: build/$1/src/.stamp \
+ scripts/prep-musl.sh scripts/lib-arch.sh \
+ $$(wildcard vendor/upstream/musl-1.2.5-skip-$1.txt)
+ scripts/prep-musl.sh $1
+ @touch $$@
+endef
+
+$(foreach a,$(ALL_ARCHES),$(eval $(call PREP_RULES,$a)))
+
+# Per-(arch, driver) boot0..boot6 rules.
+#
+# Each stage's .stamp is the recipe peg; each real artifact is declared
+# as a target depending on the .stamp with an empty recipe (`;`). We
+# avoid grouped targets (`&:`) — GNU make 4.4 does not propagate "must
+# remake of prereq" through grouped targets across multi-level chains,
+# so a path-based request like `make .../boot6/Image` would skip the
+# boot6 rebuild after a deeper change. Single-target rules with stamp-
+# anchored declarations propagate correctly.
+define BOOT_CHAIN_RULES
+# boot0: hex0-seed -> hex2 / M0 / catm
+build/$1/$2/boot0/.stamp: \
+ build/$1/src/.stamp \
+ scripts/boot0.sh scripts/lib-arch.sh scripts/lib-pipeline.sh \
+ $$(call seed_kernel_dep,$1,$2)
+ DRIVER=$2 scripts/boot0.sh $1
+ @touch $$@
+
+build/$1/$2/boot0/hex2 build/$1/$2/boot0/M0 build/$1/$2/boot0/catm: \
+ build/$1/$2/boot0/.stamp ;
+
+# boot1: M1pp.P1 + hex2pp.P1 -> M1pp + hex2pp
+build/$1/$2/boot1/.stamp: \
+ build/$1/$2/boot0/.stamp \
+ scripts/boot1.sh scripts/lib-arch.sh scripts/lib-pipeline.sh \
+ $$(call seed_kernel_dep,$1,$2)
+ DRIVER=$2 scripts/boot1.sh $1
+ @touch $$@
+
+build/$1/$2/boot1/M1pp build/$1/$2/boot1/hex2pp: build/$1/$2/boot1/.stamp ;
+
+# boot2: catm.P1pp + scheme1.P1pp -> catm + scheme1
+build/$1/$2/boot2/.stamp: \
+ build/$1/$2/boot1/.stamp build/$1/$2/boot0/.stamp \
+ scripts/boot2.sh scripts/lib-arch.sh scripts/lib-pipeline.sh \
+ $$(call seed_kernel_dep,$1,$2)
+ DRIVER=$2 scripts/boot2.sh $1
+ @touch $$@
+
+build/$1/$2/boot2/catm build/$1/$2/boot2/scheme1: build/$1/$2/boot2/.stamp ;
+
+# boot3: cc.scm-built bootstrap tcc (tcc0)
+build/$1/$2/boot3/.stamp: \
+ build/$1/$2/boot2/.stamp build/$1/$2/boot1/.stamp \
+ scripts/boot3.sh scripts/lib-arch.sh scripts/lib-runscm.sh \
+ scripts/boot3-run.scm \
+ $$(call seed_kernel_dep,$1,$2)
+ DRIVER=$2 scripts/boot3.sh $1
+ @touch $$@
+
+build/$1/$2/boot3/tcc0: build/$1/$2/boot3/.stamp ;
+
+# boot4: tcc0 -> tcc1 -> tcc2 -> tcc3 self-host chain (+ libc.a, libtcc1.a, hello)
+build/$1/$2/boot4/.stamp: \
+ build/$1/$2/boot3/.stamp build/$1/$2/boot2/.stamp \
+ scripts/boot4.sh scripts/boot4-gen-runscm.sh \
+ scripts/lib-arch.sh scripts/lib-runscm.sh \
+ $$(call seed_kernel_dep,$1,$2)
+ DRIVER=$2 scripts/boot4.sh $1
+ @touch $$@
+
+build/$1/$2/boot4/tcc3 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 ;
+
+# boot5: musl libc + hello (consumes prep-musl output)
+build/$1/$2/boot5/.stamp: \
+ build/$1/$2/boot4/.stamp build/$1/$2/boot2/.stamp \
+ build/$1/src/musl/.stamp \
+ scripts/boot5.sh scripts/boot5-gen-runscm.sh \
+ scripts/lib-arch.sh scripts/lib-runscm.sh \
+ $$(call seed_kernel_dep,$1,$2)
+ DRIVER=$2 scripts/boot5.sh $1
+ @touch $$@
+
+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
+build/$1/$2/boot6/.stamp: \
+ build/$1/$2/boot4/.stamp build/$1/$2/boot2/.stamp \
+ scripts/boot6.sh scripts/boot6-gen-runscm.sh \
+ scripts/lib-arch.sh scripts/lib-runscm.sh \
+ $$(call seed_kernel_dep,$1,$2)
+ DRIVER=$2 scripts/boot6.sh $1
+ @touch $$@
+
+build/$1/$2/boot6/$$(KERNEL_NAME_$1): build/$1/$2/boot6/.stamp ;
+endef
+
+$(foreach a,$(ALL_ARCHES),$(foreach d,$(ALL_DRIVERS),$(eval $(call BOOT_CHAIN_RULES,$a,$d))))
+
+# Phony aliases for the active ARCH × DRIVER pair, useful for scripting.
+prep-src: build/$(ARCH)/src/.stamp
+prep-musl: build/$(ARCH)/src/musl/.stamp
+
+# ── cloc ─────────────────────────────────────────────────────────────────
+#
+# Default covers every arch; `make cloc ARCH=<a>` narrows the per-arch
+# pieces (P1-<a>.M1pp/.M1, vendor/seed/<a>/*) to one arch.
CLOC_SEED_BASES := hex0.hex0 hex1.hex0 hex2.hex1 catm.hex2 M0.hex2 ELF.hex2
-# `make cloc` covers every arch by default; `make cloc ARCH=<a>` narrows
-# the per-arch sources (P1-<a>.M1pp/.M1 + vendor/seed/<a>/*) to one arch.
ifeq ($(origin ARCH),file)
CLOC_ARCHES := $(ALL_ARCHES)
else
@@ -126,32 +259,24 @@ else
endif
CLOC_FILES := \
- $(foreach a,$(CLOC_ARCHES), \
- $(foreach f,$(CLOC_SEED_BASES),vendor/seed/$(a)/$(f))) \
- $(foreach a,$(CLOC_ARCHES),P1/P1-$(a).M1) \
- M1pp/M1pp.P1 \
- hex2pp/hex2pp.P1 \
- $(foreach a,$(CLOC_ARCHES),vendor/seed/$(a)/ELF.hex2) \
- $(foreach a,$(CLOC_ARCHES),P1/P1-$(a).M1pp) \
- P1/P1.M1pp \
- P1/P1pp.P1pp \
- scheme1/scheme1.P1pp \
- scheme1/prelude.scm \
- cc/cc.scm
+ $(foreach a,$(CLOC_ARCHES),$(foreach f,$(CLOC_SEED_BASES),vendor/seed/$(a)/$(f))) \
+ $(foreach a,$(CLOC_ARCHES),P1/P1-$(a).M1) \
+ M1pp/M1pp.P1 \
+ hex2pp/hex2pp.P1 \
+ $(foreach a,$(CLOC_ARCHES),vendor/seed/$(a)/ELF.hex2) \
+ $(foreach a,$(CLOC_ARCHES),P1/P1-$(a).M1pp) \
+ P1/P1.M1pp \
+ P1/P1pp.P1pp \
+ scheme1/scheme1.P1pp \
+ scheme1/prelude.scm \
+ cc/cc.scm
cloc:
- @total=0; for f in $(CLOC_FILES); do \
- n=$$(grep -v "^ZERO.*" $$f | grep -v "^\s*#.*" | grep -v "^\s*;.*" | grep -v "^$$" | wc -l); \
- printf '%6d %s\n' $$n $$f; \
- total=$$((total + n)); \
- done; \
- printf '%6d total\n' $$total
-
-clean:
- rm -rf build/
+ @sh scripts/count-lines.sh $(CLOC_FILES)
-# --- Per-arch container image ---------------------------------------------
+# ── Tests container image (boot2-busybox; AX-bound) ──────────────────────
+IMAGE_STAMP := build/$(ARCH)/.image
IMAGE_STAMPS := $(foreach a,$(ALL_ARCHES),build/$(a)/.image)
image: $(IMAGE_STAMP)
@@ -162,19 +287,21 @@ $(IMAGE_STAMPS): build/%/.image: scripts/Containerfile.busybox
-f scripts/Containerfile.busybox scripts/
@touch $@
-# --- Bootstrap toolchain (per arch) ---------------------------------------
+# ── Test infrastructure (KEEP for now; AX moves to tests/Makefile) ───────
#
-# Stage 1: vendored hex0-seed -> M0/hex2-0/catm in the container.
-
-TOOLS_M0 := $(foreach a,$(ALL_ARCHES),build/$(a)/tools/M0)
-TOOLS_CATM := $(foreach a,$(ALL_ARCHES),build/$(a)/tools/catm)
+# All rules below this line build artifacts under the legacy layout
+# (build/<arch>/{tools,M1pp,hex2pp,scheme1,cc,vendor,tcc-*}/...) used by
+# scripts/run-tests.sh + scripts/boot-run-tests.sh. They are NOT part of
+# the bootN.sh chain. The plan is to move them out of this Makefile in
+# phase AX (see docs/PLAN.md). Kept here unchanged so `make test` keeps
+# working between phases.
-tools: $(TOOLS_DIR)/M0
+PODMAN = podman run --rm --pull=never --platform $(PLATFORM_$(1)) \
+ --tmpfs /tmp:size=512M \
+ -e ARCH=$(1) \
+ -v $(CURDIR):/work -w /work boot2-busybox:$(1)
-# mk-seed-tools.sh produces M0, hex2-0, and catm in one shot. Grouped
-# targets (`&:`) tell make they're all outputs of a single recipe
-# execution, so downstream rules can depend on whichever tool they
-# actually invoke (e.g. cc/scheme1 tests need only catm, not M0/hex2-0).
+# Stage 1: vendored hex0-seed -> M0/hex2-0/catm in the container.
build/%/tools/M0 build/%/tools/catm build/%/tools/hex2-0 &: \
scripts/mk-seed-tools.sh build/%/.image \
vendor/seed/%/hex0-seed vendor/seed/%/hex0.hex0 \
@@ -183,14 +310,10 @@ build/%/tools/M0 build/%/tools/catm build/%/tools/hex2-0 &: \
vendor/seed/%/ELF.hex2
$(call PODMAN,$*) sh scripts/mk-seed-tools.sh
-# --- Pre-pruned P1 backend tables -----------------------------------------
-#
-# Generated once from P1/gen/p1_gen.py and pruned to just the DEFINEs
-# referenced by M1pp/.M1 fixtures. Result is checked in to
-# P1/P1-<arch>.M1; routine builds skip the prune step. Re-run `make
-# tables` after editing P1/gen/*.py or any of the prune-source files
-# below, then commit the updated P1/*.M1.
+tools: $(foreach a,$(ALL_ARCHES),build/$(a)/tools/M0)
+# Pre-pruned P1 backend tables. Generated once from P1/gen/p1_gen.py and
+# committed under P1/P1-<arch>.M1; routine builds skip the prune step.
P1_PRUNE_SRCS := M1pp/M1pp.P1 hex2pp/hex2pp.P1 $(wildcard tests/P1/*.P1)
tables: $(foreach a,$(ALL_ARCHES),P1/P1-$(a).M1)
@@ -199,49 +322,30 @@ build/%/P1/P1.M1: $(wildcard P1/gen/*.py)
mkdir -p $(@D)
python3 P1/gen/p1_gen.py --arch $* --out $@
-# Keep the unpruned per-arch tables around after the prune step.
.SECONDARY: $(foreach a,$(ALL_ARCHES),build/$(a)/P1/P1.M1)
P1/P1-%.M1: build/%/P1/P1.M1 scripts/prune-p1-table.sh $(P1_PRUNE_SRCS)
sh scripts/prune-p1-table.sh $< $@ $(P1_PRUNE_SRCS)
-# --- Programs (per arch) --------------------------------------------------
-
+# Legacy per-arch programs (used by tests).
M1PP_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/M1pp/M1pp)
HEX2PP_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/hex2pp/hex2pp)
SCHEME1_SRC := scheme1/scheme1.P1pp
SCHEME1_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/scheme1/scheme1)
-# Catm'd cc compiler source. Per-arch only because catm runs in the
-# per-arch container; the resulting .scm is identical across arches but
-# we keep it under build/$arch/ for naming consistency.
CC_SRCS := scheme1/prelude.scm cc/cc.scm cc/main.scm
CC_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/cc/cc.scm)
-m1pp: $(OUT_DIR)/M1pp/M1pp
-hex2pp: $(OUT_DIR)/hex2pp/hex2pp
-scheme1: $(OUT_DIR)/scheme1/scheme1
-cc: $(OUT_DIR)/cc/cc.scm
-
-# Per-arch deps for the seed-built .P1 -> ELF chain. Used ONLY to build
-# the two new self-hosted tools (M1pp.P1, hex2pp.P1) from their pure-P1
-# sources via vendored M0 + hex2-0. After that, no other target reaches
-# back to the seed M0/hex2 path.
P1_BUILD_DEPS = scripts/lint.sh scripts/boot-build-p1.sh \
build/%/.image build/%/tools/M0 \
vendor/seed/%/ELF.hex2 P1/P1-%.M1
-# Per-arch deps for .P1pp builds (M1pp expansion + libp1pp + hex2pp link).
-# The seed M0/hex2 tools are deliberately absent: this chain is
-# end-to-end M1pp + hex2pp.
P1PP_BUILD_DEPS = scripts/boot-build-p1pp.sh \
build/%/.image \
build/%/M1pp/M1pp build/%/hex2pp/hex2pp \
vendor/seed/%/ELF.hex2 \
P1/P1-%.M1pp P1/P1.M1pp P1/P1pp.P1pp
-# M1pp and hex2pp are both built from their self-hosted .P1 sources via
-# the seed M0+hex2-0 chain (boot-build-p1.sh).
ALPINE_GCC_IMAGES := $(foreach a,$(ALL_ARCHES),build/$(a)/.image-alpine-gcc)
$(ALPINE_GCC_IMAGES): build/%/.image-alpine-gcc: scripts/Containerfile.alpine-gcc
@@ -267,22 +371,11 @@ $(HEX2PP_BINS): build/%/hex2pp/hex2pp: hex2pp/hex2pp.P1 $(P1_BUILD_DEPS)
$(SCHEME1_BINS): build/%/scheme1/scheme1: $(SCHEME1_SRC) $(P1PP_BUILD_DEPS)
$(call PODMAN,$*) sh scripts/boot-build-p1pp.sh $@ $(SCHEME1_SRC)
-# cc.scm: catm prelude + cc.scm + main.scm entry into one source the
-# scheme1 interpreter can run. Catm runs inside the per-arch container;
-# only catm (not M0/hex2-0) is needed.
$(CC_BINS): build/%/cc/cc.scm: $(CC_SRCS) build/%/.image build/%/tools/catm
mkdir -p $(@D)
$(call PODMAN,$*) build/$*/tools/catm $@ $(CC_SRCS)
-# --- tcc-boot2 end-to-end harness -----------------------------------------
-#
-# Drives stage1-flatten.sh (host preprocessor only — no container) to
-# produce build/$(ARCH)/vendor/tcc/tcc.flat.c, then runs cc.scm inside
-# the per-arch container against the flattened TU, then assembles the
-# resulting P1pp into a runnable ELF using the standard P1pp pipeline.
-# The resulting binary embeds tcc's $(TCC_TARGET) codegen; ARCH and
-# TCC_TARGET track 1:1 (aarch64↔ARM64, amd64↔X86_64, riscv64↔RISCV64)
-# so generated programs run natively in the per-arch container.
+# ── tcc-boot2 chain (tests-only; AX-bound) ───────────────────────────────
TCC_TARGET_aarch64 := ARM64
TCC_TARGET_amd64 := X86_64
@@ -295,25 +388,14 @@ TCC_FLATS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/tcc/tcc.flat.c)
TCC_BOOT2_BINS := $(foreach a,$(ALL_ARCHES),build/$(a)/tcc-boot2/tcc-boot2)
TCC_BOOT2_P1PPS := $(foreach a,$(ALL_ARCHES),build/$(a)/tcc-boot2/tcc.flat.P1pp)
-# Vendored mes-libc, flattened on the host then compiled by cc.scm,
-# linked into tcc-boot2 itself so the unresolved libc symbols (printf,
-# malloc, fopen, …) resolve against our own libc.P1pp instead of the
-# host's. Phase A of docs/LIBC.md.
LIBC_FLATS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/mes-libc/libc.flat.c)
LIBC_P1PPS := $(foreach a,$(ALL_ARCHES),build/$(a)/vendor/mes-libc/libc.P1pp)
-tcc-flat: $(TCC_FLAT)
-tcc-boot2: $(OUT_DIR)/tcc-boot2/tcc-boot2
-
$(TCC_FLATS): build/%/vendor/tcc/tcc.flat.c: scripts/stage1-flatten.sh
sh scripts/stage1-flatten.sh --arch $*
-# stage1-flatten.sh writes this as a side effect — the post-patch tcc
-# <stdarg.h>, used as a per-arch bridge prepended into both .flat.c
-# files (see comments in stage1-flatten.sh / libc-flatten.sh).
build/%/vendor/tcc/stdarg-bridge.h: build/%/vendor/tcc/tcc.flat.c
-# Catalog of inputs the host preprocessor reads when flattening libc.
LIBC_VENDOR_SRCS := $(shell find vendor/mes-libc -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null) \
$(wildcard vendor/mes-libc/patches/*.before) \
$(wildcard vendor/mes-libc/patches/*.after)
@@ -322,15 +404,6 @@ $(LIBC_FLATS): build/%/vendor/mes-libc/libc.flat.c: \
scripts/libc-flatten.sh build/%/vendor/tcc/stdarg-bridge.h $(LIBC_VENDOR_SRCS)
sh scripts/libc-flatten.sh --arch $*
-# libc and tcc.flat are both compiled with --lib= so they omit the
-# entry stub and trailing :ELF_end (provided by P1/entry-libc.P1pp
-# and P1/elf-end.P1pp at link time). Distinct prefixes keep the
-# anonymous string labels (cc__str_N) from colliding when both TUs
-# end up in the same catm chain.
-# CC_DEBUG=1 / CC_TRACE_EMIT=1 forward to scripts/boot-build-cc.sh,
-# which threads them as --cc-debug / --cc-trace-emit on the cc.scm
-# command line. Both are opt-in so the default tcc-boot2 build stays
-# clean; the trace emit fattens output by one libp1pp call per fn.
CC_DEBUG ?= 0
CC_TRACE_EMIT ?= 0
@@ -349,9 +422,6 @@ $(TCC_BOOT2_P1PPS): build/%/tcc-boot2/tcc.flat.P1pp: \
CC_DEBUG=$(CC_DEBUG) CC_TRACE_EMIT=$(CC_TRACE_EMIT) \
sh scripts/boot-build-cc.sh build/$*/vendor/tcc/tcc.flat.c $@
-# tcc-boot2 link: pure catm chain — entry stub, libc, client TU,
-# elf terminator. boot-build-p1pp.sh concatenates them in order
-# ahead of the M1pp expander + hex2pp pipeline.
$(TCC_BOOT2_BINS): build/%/tcc-boot2/tcc-boot2: \
build/%/tcc-boot2/tcc.flat.P1pp build/%/vendor/mes-libc/libc.P1pp \
P1/entry-libc.P1pp P1/elf-end.P1pp \
@@ -361,23 +431,12 @@ $(TCC_BOOT2_BINS): build/%/tcc-boot2/tcc-boot2: \
P1/entry-libc.P1pp build/$*/vendor/mes-libc/libc.P1pp \
$< P1/elf-end.P1pp
-# --- tcc-gcc: same flatten, stock gcc -------------------------------------
-#
-# Sanity-check sibling of tcc-boot2. Compiles the *same* tcc.flat.c +
-# libc.flat.c through stock gcc + libgcc inside an Alpine image, with
-# our hand-rolled _start / sys_* shim (tcc-gcc/<arch>/) replacing
-# musl's crt0 + libc. If tcc-gcc runs and tcc-boot2 doesn't, the bug
-# is downstream of the C source — i.e. in cc.scm or the P1 pipeline.
-#
-# aarch64 and amd64 are wired today; bring up another arch by adding
-# a tcc-gcc/<arch>/ variant of start.S + sys_stubs.c. The cc.scm bug
-# we're chasing is arch-agnostic, so one validation arch is enough.
+# ── tcc-gcc: same flatten, stock gcc (tests-only sanity check) ───────────
+
TCC_GCC_BIN := build/$(ARCH)/tcc-gcc/tcc-gcc
TCC_GCC_IMAGE := build/$(ARCH)/.image-alpine-gcc
TCC_GCC_HARNESS := tcc-gcc/$(ARCH)/start.S tcc-gcc/$(ARCH)/sys_stubs.c
-tcc-gcc: $(TCC_GCC_BIN)
-
$(TCC_GCC_BIN): $(TCC_FLAT) build/$(ARCH)/vendor/mes-libc/libc.flat.c \
$(TCC_GCC_HARNESS) scripts/build-tcc-gcc.sh $(TCC_GCC_IMAGE)
mkdir -p $(@D)
@@ -387,35 +446,16 @@ $(TCC_GCC_BIN): $(TCC_FLAT) build/$(ARCH)/vendor/mes-libc/libc.flat.c \
sh scripts/build-tcc-gcc.sh $@ $(TCC_FLAT) \
build/$(ARCH)/vendor/mes-libc/libc.flat.c
-# --- tcc-cc test harness support -----------------------------------------
-#
-# The ARM64-targeted tcc-boot2 can compile/link tests/cc directly, but
-# its ARM64 build does not accept .S inputs yet. Build the one tiny
-# _start object with the host cross assembler, then let tcc link it
-# with each test fixture under the tcc-cc suite.
+# ── tcc-cc / tcc-libc / tcc-tcc / tcc-tcc-tcc test infrastructure ────────
HOST_CC ?= cc
-# tcc-cc / tcc-libc / tcc-tcc share an arch with the building tcc-boot2
-# (the binary the suites drive). Three arches are wired today: aarch64,
-# amd64, riscv64. ARCH selects which one. The cross-asm target string
-# (or container) and the per-target tcc include path follow ARCH.
TCC_HARNESS_ARCHES := aarch64 amd64 riscv64
HOST_CC_TARGET_aarch64 := aarch64-linux-gnu
HOST_CC_TARGET_amd64 := x86_64-linux-gnu
HOST_CC_TARGET = $(HOST_CC_TARGET_$(ARCH))
-# Cross-assembler for the per-arch .S harness inputs.
-# aarch64 — tcc-boot2 itself assembles via the in-tree arm64-asm.c
-# (phase 1; see docs/TCC-ARM64-ASM.md). Run inside the
-# scratch container since tcc-boot2 is a Linux/aarch64 ELF.
-# amd64 — host clang's `-target x86_64-linux-gnu` (works out of the
-# box on macOS).
-# riscv64 — alpine-gcc:riscv64 because Apple's clang ships without a
-# RISC-V backend.
-# Both forms consume (out, src) positionals so the call sites are arch-
-# agnostic.
ifeq ($(ARCH),aarch64)
TCC_ASM_DEPS := build/$(ARCH)/tcc-boot2/tcc-boot2 build/$(ARCH)/.image
TCC_ASM = $(call PODMAN,$(ARCH)) build/$(ARCH)/tcc-boot2/tcc-boot2 -nostdlib -c -o $(1) $(2)
@@ -428,62 +468,29 @@ TCC_ASM = $(HOST_CC) -target $(HOST_CC_TARGET) -c -o $(1) -x assembler $(2
endif
TCC_CC_START := build/$(ARCH)/tcc-cc/start.o
-TCC_CC_MEM := build/$(ARCH)/tcc-cc/mem.o
-TCC_CC_TCC_INCLUDE = $(TCC_VENDOR)/tcc-0.9.26-1147-gee75a10c/include
-TCC_CC_TCC_LIBDIR = $(TCC_VENDOR)/tcc-0.9.26-1147-gee75a10c/lib
-
-# x86_64-only: tcc emits calls to __va_start / __va_arg for variadic
-# functions. Upstream tcc supplies these via lib/va_list.c → va_list.o
-# (OBJ-x86_64 in lib/Makefile). The tcc-cc / tcc-libc suites link
-# -nostdlib so we compile this object with tcc-boot2 and add it to
-# every link. On other arches this variable is empty: aarch64 / riscv64
-# tcc lower va_arg without out-of-line helpers.
+TCC_CC_MEM := build/$(ARCH)/tcc-cc/mem.o
+TCC_CC_TCC_INCLUDE = $(TCC_VENDOR)/$(TCC_PKG)/include
+TCC_CC_TCC_LIBDIR = $(TCC_VENDOR)/$(TCC_PKG)/lib
+
ifeq ($(ARCH),amd64)
TCC_CC_VA_LIST := build/$(ARCH)/tcc-cc/va_list.o
else
TCC_CC_VA_LIST :=
endif
-# tcc-libc suite: tcc-boot2 (built by cc.scm) compiles mes-libc into
-# libc.o, then for each tests/cc-libc fixture, links fixture +
-# start.o + sys_stubs.o + mem.o + libc.o into a runnable ELF.
-# End-to-end exercise of "tcc as a real compiler" against the same
-# libc the cc.scm + libc.P1pp pipeline uses.
TCC_LIBC_DIR := build/$(ARCH)/tcc-libc
TCC_LIBC_START := $(TCC_LIBC_DIR)/start.o
TCC_LIBC_SYS_STUBS := $(TCC_LIBC_DIR)/sys_stubs.o
TCC_LIBC_MEM := $(TCC_LIBC_DIR)/mem.o
TCC_LIBC_LIBC := $(TCC_LIBC_DIR)/libc.o
-# tcc-tcc: second-stage tcc. tcc-boot2 (cc.scm-built) compiles
-# tcc.flat.c and links it against the tcc-libc runtime objects to
-# produce a self-built tcc. The tcc-cc and tcc-libc suites run their
-# fixtures through tcc-tcc, not tcc-boot2 — so a regression in
-# cc.scm's emitted code surfaces as a tcc-tcc misbehavior on a
-# fixture, and the test set spans tcc compiling itself.
TCC_TCC_BIN := build/$(ARCH)/tcc-tcc/tcc-tcc
-
-# tcc-tcc-tcc: third-stage tcc. tcc-tcc compiles tcc.flat.c through
-# the same boot-build-tcc-tcc.sh recipe (parameterized on the input
-# compiler) into a fresh binary. This is the README's
-# `(define tcc (tcc1 tcc.c))` — the first tcc whose machine code was
-# emitted by an actual tcc rather than cc.scm. The tcc-cc / tcc-libc
-# suites run twice: once through tcc-tcc (stage 2), once through
-# tcc-tcc-tcc (stage 3). Bootstrap fixed-point check: if the two
-# stages diverge on any fixture, the codegen is non-idempotent.
TCC_TCC_TCC_BIN := build/$(ARCH)/tcc-tcc-tcc/tcc-tcc-tcc
$(TCC_CC_START): tcc-cc/$(ARCH)/start.S $(TCC_ASM_DEPS)
mkdir -p $(@D)
$(call TCC_ASM,$@,$<)
-# Tiny mem* runtime: tcc emits calls to memcpy/memmove/memset for
-# struct copies and bulk zero-init past its inline thresholds, but
-# its libtcc1 doesn't define them — upstream expects libc to. The
-# tcc-cc suite links -nostdlib, so we compile this fallback with
-# tcc-boot2 itself and link it alongside start.o. memcmp lives here
-# too, as a plain compiler-builtin for fixtures that reach it via
-# `extern int memcmp(...)`.
$(TCC_CC_MEM): tcc-cc/mem.c \
build/$(ARCH)/tcc-boot2/tcc-boot2 \
build/$(ARCH)/.image
@@ -492,26 +499,16 @@ $(TCC_CC_MEM): tcc-cc/mem.c \
build/$(ARCH)/tcc-boot2/tcc-boot2 \
-nostdlib -I $(TCC_CC_TCC_INCLUDE) -c -o $@ $<
-# x86_64 va_list runtime: defines __va_start / __va_arg, the intrinsics
-# tcc's x86_64 codegen lowers `va_start` / `va_arg` to. The Makefile
-# rule is gated on amd64 via TCC_CC_VA_LIST being non-empty above.
build/amd64/tcc-cc/va_list.o: \
build/amd64/tcc-boot2/tcc-boot2 \
build/amd64/.image build/amd64/vendor/tcc/tcc.flat.c
mkdir -p $(@D)
$(call PODMAN,amd64) \
build/amd64/tcc-boot2/tcc-boot2 \
- -nostdlib -I build/amd64/vendor/tcc/tcc-0.9.26-1147-gee75a10c/include \
+ -nostdlib -I build/amd64/vendor/tcc/$(TCC_PKG)/include \
-D TCC_TARGET_X86_64=1 \
- -c -o $@ build/amd64/vendor/tcc/tcc-0.9.26-1147-gee75a10c/lib/va_list.c
+ -c -o $@ build/amd64/vendor/tcc/$(TCC_PKG)/lib/va_list.c
-# --- tcc-libc test harness inputs ----------------------------------------
-#
-# start.o threads __libc_init in front of main and exits with main's
-# return value. sys_stubs.o implements the libp1pp-shaped sys_*
-# wrappers via raw syscalls. Both are assembled by TCC_ASM (arch-
-# routed in the block above): tcc-boot2 for aarch64, host clang for
-# amd64, alpine-gcc for riscv64.
$(TCC_LIBC_START): tcc-libc/$(ARCH)/start.S $(TCC_ASM_DEPS)
mkdir -p $(@D)
$(call TCC_ASM,$@,$<)
@@ -520,9 +517,6 @@ $(TCC_LIBC_SYS_STUBS): tcc-libc/$(ARCH)/sys_stubs.S $(TCC_ASM_DEPS)
mkdir -p $(@D)
$(call TCC_ASM,$@,$<)
-# tcc-libc reuses tcc-cc/mem.c for the compiler-builtin mem* runtime,
-# but rebuilds it under build/$(ARCH)/tcc-libc/ to keep the suite's
-# outputs cleanly separated from the tcc-cc tree.
$(TCC_LIBC_MEM): tcc-cc/mem.c \
build/$(ARCH)/tcc-boot2/tcc-boot2 \
build/$(ARCH)/.image
@@ -531,13 +525,6 @@ $(TCC_LIBC_MEM): tcc-cc/mem.c \
build/$(ARCH)/tcc-boot2/tcc-boot2 \
-nostdlib -I $(TCC_CC_TCC_INCLUDE) -c -o $@ $<
-# libc.o: tcc-boot2 compiles the same flatten output cc.scm consumes.
-# Force-including tcc's <stdarg.h> brings in the gcc/clang spelling
-# bridge appended by the stdarg-builtin-aliases patch in
-# stage1-flatten.sh: it aliases gcc's __builtin_va_* names onto tcc's
-# native va_* macros (tcc has no __builtin_va_list keyword on
-# amd64/aarch64). Without that bridge the host-preprocessed flatten
-# won't compile back through tcc-boot2.
$(TCC_LIBC_LIBC): build/$(ARCH)/vendor/mes-libc/libc.flat.c \
build/$(ARCH)/tcc-boot2/tcc-boot2 \
build/$(ARCH)/.image
@@ -548,14 +535,6 @@ $(TCC_LIBC_LIBC): build/$(ARCH)/vendor/mes-libc/libc.flat.c \
-include $(TCC_CC_TCC_INCLUDE)/stdarg.h \
-c -o $@ $<
-# --- tcc-tcc: second-stage tcc -------------------------------------------
-#
-# Build inputs come straight from the tcc-libc setup: same start /
-# sys_stubs / mem / libc objects. The aarch64 build also pulls in
-# lib-arm64.o (TFmode soft-float helpers) — that's materialized
-# inside the build script alongside the link step.
-tcc-tcc: $(TCC_TCC_BIN)
-
$(TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \
$(TCC_FLAT) \
build/$(ARCH)/tcc-boot2/tcc-boot2 \
@@ -566,14 +545,6 @@ $(TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \
$(call PODMAN,$(ARCH)) \
sh scripts/boot-build-tcc-tcc.sh $@
-# --- tcc-tcc-tcc: third-stage tcc ----------------------------------------
-#
-# Same recipe as tcc-tcc, but the input compiler is tcc-tcc rather
-# than tcc-boot2. boot-build-tcc-tcc.sh accepts the compiler as its
-# second arg; lib-arm64.o / va_list.o are rebuilt by the new compiler
-# into $(@D), so each stage owns its own helpers.
-tcc-tcc-tcc: $(TCC_TCC_TCC_BIN)
-
$(TCC_TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \
$(TCC_FLAT) \
$(TCC_TCC_BIN) \
@@ -584,7 +555,7 @@ $(TCC_TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \
$(call PODMAN,$(ARCH)) \
sh scripts/boot-build-tcc-tcc.sh $@ $(TCC_TCC_BIN)
-# --- Native tools (opt-in dev-loop helpers) -------------------------------
+# ── Native tools (opt-in dev-loop helpers) ───────────────────────────────
NATIVE_TOOLS := build/native-tools/M1 build/native-tools/hex2 \
build/native-tools/m1pp build/native-tools/hex2pp
@@ -603,14 +574,7 @@ build/native-tools/m1pp: scripts/build-native-tools.sh M1pp/M1pp.c
build/native-tools/hex2pp: scripts/build-native-tools.sh hex2pp/hex2pp.c
sh scripts/build-native-tools.sh hex2pp
-# --- Tests ----------------------------------------------------------------
-#
-# `make test` runs every suite. SUITE selects one; ARCH restricts to one
-# arch. Make ensures every dependency (images, tools, tables, m1pp
-# expanders) exists before scripts/run-tests.sh is invoked. The host
-# runner starts one container per arch and hands off to
-# scripts/boot-run-tests.sh, which loops the fixtures, builds
-# per-fixture binaries via the boot-* scripts, runs them, and diffs.
+# ── test target (proxies to scripts/run-tests.sh) ────────────────────────
SUITE ?=
@@ -622,38 +586,18 @@ else
TEST_ARCHES := $(ARCH)
endif
-# m1pp suite per-arch deps: image, expander, hex2pp (the suite pipes
-# M1pp output through hex2pp as a smoke test in addition to the
-# text-diff against .expected).
TEST_M1PP_DEPS := $(foreach a,$(TEST_ARCHES), \
build/$(a)/.image build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \
vendor/seed/$(a)/ELF.hex2)
-# p1 suite per-arch deps: image, table, expander, hex2pp, ELF header.
-# Raw .P1 fixtures still go through the seed M0+hex2 chain (boot-build-p1.sh)
-# because they share the legacy M1 backend; .P1pp fixtures go through
-# the new M1pp + hex2pp chain via boot-build-p1pp.sh.
TEST_P1_DEPS := $(foreach a,$(TEST_ARCHES), \
build/$(a)/.image build/$(a)/tools/M0 P1/P1-$(a).M1 \
build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp vendor/seed/$(a)/ELF.hex2)
-# scheme1 suite per-arch deps: image, expander, hex2pp, scheme1 binary,
-# and catm (boot-run-scheme1.sh prepends prelude.scm with catm).
-# (run-tests.sh runs the pre-built binary against each .scm fixture; it
-# does not rebuild the interpreter per fixture.) M0/hex2-0 are not in
-# the runtime test path — they're pulled in transitively only as build
-# inputs to M1pp/hex2pp.
TEST_SCHEME1_DEPS := $(foreach a,$(TEST_ARCHES), \
build/$(a)/.image build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \
build/$(a)/scheme1/scheme1 build/$(a)/tools/catm)
-# cc-* suites: scheme1 + M1pp + hex2pp cover everything. cc-util /
-# cc-lex / cc-pp byte-diff their pure transformations; cc-cg / cc
-# compile the emitted P1pp through the P1pp toolchain (M1pp + hex2pp)
-# and run the resulting ELF. cc.scm is only needed by the cc suite
-# (it invokes the catm'd compiler against a .c fixture); the rest
-# catm their own per-suite layer list. The runtime path uses only catm
-# from build/$(a)/tools/ — M0/hex2-0 are not invoked.
TEST_CC_UNIT_DEPS := $(foreach a,$(TEST_ARCHES), \
build/$(a)/.image build/$(a)/tools/catm \
build/$(a)/M1pp/M1pp build/$(a)/hex2pp/hex2pp \
@@ -662,9 +606,6 @@ TEST_CC_UNIT_DEPS := $(foreach a,$(TEST_ARCHES), \
TEST_CC_DEPS := $(TEST_CC_UNIT_DEPS) \
$(foreach a,$(TEST_ARCHES),build/$(a)/cc/cc.scm)
-# cc-libc: cc deps + the pre-built libc.P1pp the suite catm's into every
-# fixture, plus the entry/elf-end fragments the catm chain depends on.
-# Targeted red-green TDD on the cc.scm + libc combination.
TEST_CC_LIBC_DEPS := $(TEST_CC_DEPS) \
$(foreach a,$(TEST_ARCHES),build/$(a)/vendor/mes-libc/libc.P1pp) \
P1/entry-libc.P1pp P1/elf-end.P1pp
diff --git a/docs/PLAN.md b/docs/PLAN.md
@@ -160,11 +160,23 @@ flatten/musl scripts are no longer triggered from within a boot stage.
---
-### A4. Makefile drives `bootN.sh` with path-based deps
+### [DONE] A4. Makefile drives `bootN.sh` with path-based deps
**Goal.** Outputs are targets. `make build/aarch64/seed/boot6/Image`
walks the chain: prep-src → boot0 → boot1 → … → boot6.
+**Status note.** Implemented with single-target rules anchored on per-
+stage `.stamp` files (real artifacts depend on the stamp via empty
+recipes). GNU make 4.4 does not propagate "must remake of prereq"
+through grouped explicit targets (`&:`) across multi-level chains, so
+the rule shape sketched below — combining real outputs and `.stamp`
+into one grouped target — would skip the boot6 rebuild after a deep
+source change. The single-target form keeps the stamp the recipe peg
+and makes the artifact files declarative-only, which restores correct
+propagation. The `tests/Makefile` include and the `boot-build-p1*.sh`
+retirement are deferred to AX, which migrates the test infrastructure
+that still depends on them.
+
**Rule shape.**
```
build/$(ARCH)/$(DRIVER)/boot1/M1pp \
diff --git a/scripts/count-lines.sh b/scripts/count-lines.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+## count-lines.sh — line counts for the core sources.
+##
+## Skips ZERO/comment/blank lines per the existing cloc convention.
+## Pass file paths as arguments, or pass none to read from stdin
+## (one path per line). Prints `<count> <path>` per file plus a
+## trailing total.
+##
+## Usage:
+## sh scripts/count-lines.sh file1 file2 …
+## printf '%s\n' file1 file2 | sh scripts/count-lines.sh
+
+set -eu
+
+if [ "$#" -gt 0 ]; then
+ FILES="$*"
+else
+ FILES=$(cat)
+fi
+
+total=0
+for f in $FILES; do
+ [ -e "$f" ] || { echo "count-lines: missing $f" >&2; exit 1; }
+ n=$(grep -v "^ZERO.*" "$f" \
+ | grep -v "^[[:space:]]*#.*" \
+ | grep -v "^[[:space:]]*;.*" \
+ | grep -v "^$" \
+ | wc -l)
+ printf '%6d %s\n' "$n" "$f"
+ total=$((total + n))
+done
+printf '%6d total\n' "$total"