commit 49dcd6e7b024c3b4921442ba79db41bb510e5cc3
parent 8bee01c9d52db4681e63f4f36b3b5ff8517cf023
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 21 Apr 2026 07:55:31 -0700
reorg: docs/ arch/ src/ tests/
Root was flat with ~20 files. Group by role:
docs/ — PLAN, SEED, P1, C1, LISP
arch/ — ELF-*.hex2, p1_gen.py, generated p1_<arch>.M1
src/ — lisp.M1, kaem-minimal.M1 (real programs)
tests/ — hello.M1, demo.M1, kaem.run (smoke; lisp/ fixtures unchanged)
Makefile resolves PROG.M1 against src/ then tests/; hard-errors if
neither has it. RUN_ARGS_lisp now points at tests/lisp/00-identity.scm
(hello.scm was a byte-identical duplicate, removed). Added
RUN_ARGS_kaem-minimal := tests/kaem.run.
p1_gen.py already writes relative to its own dir, so moving it into
arch/ needed no code change.
Diffstat:
18 files changed, 47 insertions(+), 32 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,4 +1,4 @@
build/
-p1_aarch64.M1
-p1_amd64.M1
-p1_riscv64.M1
+arch/p1_aarch64.M1
+arch/p1_amd64.M1
+arch/p1_riscv64.M1
diff --git a/Makefile b/Makefile
@@ -23,10 +23,20 @@
PROG ?= hello
ARCH ?= aarch64
+# Programs live in src/ (real: lisp, kaem-minimal) or tests/ (smoke:
+# hello, demo). Resolve PROG.M1 against both so the user doesn't have
+# to remember which bucket a program is in.
+PROG_SRC := $(firstword $(wildcard src/$(PROG).M1 tests/$(PROG).M1))
+ifeq ($(PROG_SRC),)
+ $(error PROG '$(PROG)' not found — no src/$(PROG).M1 or tests/$(PROG).M1)
+endif
+
# Per-program runtime arguments. The lisp interpreter reads its script
-# from argv[1] (LISP.md step 8); supply hello.scm by default so
-# `make run-all` stays a smoke test.
-RUN_ARGS_lisp ?= hello.scm
+# from argv[1] (docs/LISP.md step 8); kaem-minimal reads its build
+# script. Default each to its smoke fixture so `make run-all` stays a
+# smoke test.
+RUN_ARGS_lisp ?= tests/lisp/00-identity.scm
+RUN_ARGS_kaem-minimal ?= tests/kaem.run
RUN_ARGS := $(RUN_ARGS_$(PROG))
# Map P1 ARCH -> Linux-platform tag for the container.
@@ -95,10 +105,10 @@ $(TOOLS_DIR)/M0 $(TOOLS_DIR)/hex2-0 $(TOOLS_DIR)/catm $(TOOLS_DIR)/hex0 $(TOOLS_
# M0 takes a single positional input (no -f flag), so we catm the two
# sources together first. The intermediate .combined.M1 is kept in OUT_DIR
# so it gets cleaned along with everything else.
-$(OUT_DIR)/$(PROG).hex2: $(PROG).M1 p1_$(ARCH).M1 lint.sh $(TOOLS_DIR)/M0 $(TOOLS_DIR)/catm | $(OUT_DIR)
- ./lint.sh p1_$(ARCH).M1 $(PROG).M1
+$(OUT_DIR)/$(PROG).hex2: $(PROG_SRC) arch/p1_$(ARCH).M1 lint.sh $(TOOLS_DIR)/M0 $(TOOLS_DIR)/catm | $(OUT_DIR)
+ ./lint.sh arch/p1_$(ARCH).M1 $(PROG_SRC)
$(PODMAN) sh -ec ' \
- $(TOOLS_DIR)/catm $(OUT_DIR)/$(PROG).combined.M1 p1_$(ARCH).M1 $(PROG).M1 ; \
+ $(TOOLS_DIR)/catm $(OUT_DIR)/$(PROG).combined.M1 arch/p1_$(ARCH).M1 $(PROG_SRC) ; \
$(TOOLS_DIR)/M0 $(OUT_DIR)/$(PROG).combined.M1 $(OUT_DIR)/$(PROG).hex2'
# Link: prepend the ELF header and feed to hex2-0.
@@ -107,9 +117,9 @@ $(OUT_DIR)/$(PROG).hex2: $(PROG).M1 p1_$(ARCH).M1 lint.sh $(TOOLS_DIR)/M0 $(TOOL
# base address 0x00600000 (no --base-address flag), which is why the ELF
# header references `&ELF_base` symbolically rather than baking in a
# concrete VA — the header travels to whatever base the linker chose.
-$(OUT_DIR)/$(PROG): $(OUT_DIR)/$(PROG).hex2 ELF-$(ARCH).hex2 $(TOOLS_DIR)/hex2-0 $(TOOLS_DIR)/catm
+$(OUT_DIR)/$(PROG): $(OUT_DIR)/$(PROG).hex2 arch/ELF-$(ARCH).hex2 $(TOOLS_DIR)/hex2-0 $(TOOLS_DIR)/catm
$(PODMAN) sh -ec ' \
- $(TOOLS_DIR)/catm $(OUT_DIR)/$(PROG).linked.hex2 ELF-$(ARCH).hex2 $(OUT_DIR)/$(PROG).hex2 ; \
+ $(TOOLS_DIR)/catm $(OUT_DIR)/$(PROG).linked.hex2 arch/ELF-$(ARCH).hex2 $(OUT_DIR)/$(PROG).hex2 ; \
$(TOOLS_DIR)/hex2-0 $(OUT_DIR)/$(PROG).linked.hex2 $(OUT_DIR)/$(PROG)'
run: $(OUT_DIR)/$(PROG)
@@ -122,7 +132,7 @@ run-all:
-$(MAKE) --no-print-directory PROG=$(PROG) ARCH=amd64 run
-$(MAKE) --no-print-directory PROG=$(PROG) ARCH=riscv64 run
-# Test harness (LISP.md step 9). Each fixtures pair in tests/lisp/ is a
+# Test harness (docs/LISP.md step 9). Each fixtures pair in tests/lisp/ is a
# <name>.scm program plus a <name>.expected file holding its exact
# stdout. test-lisp runs every fixture on $(ARCH); test-lisp-all runs
# the suite on all three arches — cross-arch consistency falls out by
@@ -158,11 +168,12 @@ test-lisp-all:
$(MAKE) --no-print-directory ARCH=riscv64 test-lisp
clean:
- rm -rf build/ p1_aarch64.M1 p1_amd64.M1 p1_riscv64.M1
-
-# Generate all three per-arch DEFINE tables from p1_gen.py in a single
-# shot. Grouped target (&:) because p1_gen.py writes all three files
-# unconditionally. These are build artifacts — gitignored; the build
-# regenerates them on any p1_gen.py edit so there's no staleness risk.
-p1_aarch64.M1 p1_amd64.M1 p1_riscv64.M1 &: p1_gen.py
- python3 p1_gen.py
+ rm -rf build/ arch/p1_aarch64.M1 arch/p1_amd64.M1 arch/p1_riscv64.M1
+
+# Generate all three per-arch DEFINE tables from arch/p1_gen.py in a
+# single shot. Grouped target (&:) because p1_gen.py writes all three
+# files unconditionally (next to itself, i.e. into arch/). These are
+# build artifacts — gitignored; the build regenerates them on any
+# p1_gen.py edit so there's no staleness risk.
+arch/p1_aarch64.M1 arch/p1_amd64.M1 arch/p1_riscv64.M1 &: arch/p1_gen.py
+ python3 arch/p1_gen.py
diff --git a/README.md b/README.md
@@ -3,27 +3,33 @@
Experimental alternative bootstrap path: a small Lisp written directly in
M1 assembly + a C compiler written in that Lisp, replacing the
`M2-Planet → mes → MesCC → nyacc` stack between M1 asm and tcc-boot.
-Goal is a 4–6× shrink in auditable LOC. See [PLAN.md](PLAN.md).
+Goal is a 4–6× shrink in auditable LOC. See [docs/PLAN.md](docs/PLAN.md).
## Status
-Stage 0: hello-world in the P1 portable pseudo-ISA (see [P1.md](P1.md)),
+Stage 0: hello-world in the P1 portable pseudo-ISA (see [docs/P1.md](docs/P1.md)),
assembled and run inside a pristine alpine container on all three target
-arches (aarch64, amd64, riscv64). The same `hello.M1` source assembles
-for every arch; only the backing `p1_<arch>.M1` defs file varies.
+arches (aarch64, amd64, riscv64). The same `tests/hello.M1` source assembles
+for every arch; only the backing `arch/p1_<arch>.M1` defs file varies.
Toolchain (M1, hex2) builds statically from the upstream mescc-tools C
source.
## Layout
```
-PLAN.md design doc (Lisp-in-M1 → C compiler path)
-hello.M1 portable P1 hello, unchanged across arches
-p1_<arch>.M1 per-arch P1 defs (aarch64, amd64, riscv64)
-ELF-<arch>.hex2 per-arch ELF header template
-Containerfile builder image: alpine + gcc + musl-dev
+docs/ design docs (PLAN, SEED, P1, C1, LISP)
+src/ real programs (lisp.M1, kaem-minimal.M1)
+tests/ smoke programs (hello.M1, demo.M1) + fixtures
+ lisp/ lisp test fixtures (*.scm + *.expected)
+ kaem.run smoke input for kaem-minimal
+arch/ per-arch defs + ELF headers
+ p1_gen.py generator for p1_<arch>.M1
+ p1_<arch>.M1 per-arch P1 defs (gitignored, generated)
+ ELF-<arch>.hex2 per-arch ELF header template
+bootstrap.sh hex0-seed → M0/hex2-0/catm toolchain build
+lint.sh M1 undefined-token guard
Makefile podman-driven build, ARCH-parameterized
-build/<arch>/ per-arch outputs (hello.hex2, hello)
+build/<arch>/ per-arch outputs + toolchain
```
## Build & run
diff --git a/ELF-aarch64.hex2 b/arch/ELF-aarch64.hex2
diff --git a/ELF-amd64.hex2 b/arch/ELF-amd64.hex2
diff --git a/ELF-riscv64.hex2 b/arch/ELF-riscv64.hex2
diff --git a/p1_gen.py b/arch/p1_gen.py
diff --git a/C1.md b/docs/C1.md
diff --git a/LISP.md b/docs/LISP.md
diff --git a/P1.md b/docs/P1.md
diff --git a/PLAN.md b/docs/PLAN.md
diff --git a/SEED.md b/docs/SEED.md
diff --git a/hello.scm b/hello.scm
@@ -1,2 +0,0 @@
-(define id (lambda (x) x))
-(id 42)
diff --git a/kaem-minimal.M1 b/src/kaem-minimal.M1
diff --git a/lisp.M1 b/src/lisp.M1
diff --git a/demo.M1 b/tests/demo.M1
diff --git a/hello.M1 b/tests/hello.M1
diff --git a/kaem.run b/tests/kaem.run