boot2

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

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:
MMakefile | 613++++++++++++++++++++++++++++++++++++-------------------------------------------
Mdocs/PLAN.md | 14+++++++++++++-
Ascripts/count-lines.sh | 32++++++++++++++++++++++++++++++++
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"