Files
ww/BOOTSTRAP.md
Hojun-Cho edfc4273f6 nocc: stage-0 bootstrap path (binaries gitignored)
Sets up the cc-free fresh-checkout flow without yet committing the
stage-0 binaries. The pieces are in place; flipping the switch is
one `git add -f` away when the compiler is judged stable.

  bootstrap/<arch>/{ww,6c,6a,6l}   stage-0 binaries (gitignored)
  bootstrap/README.md              layout + workflow

Two new targets:

  make bootstrap-snapshot   populates bootstrap/$(ARCH)/ from the
                            currently-built wwstage
  make nocc                 starts from bootstrap/$(ARCH)/, rebuilds
                            the wwstage from source, gates on
                            stage-0 == rebuilt byte-for-byte. Lands
                            in $(OUT)/nocc/ so $(OUT)/ stays
                            untouched. cc is never invoked.

The fixed-point gate has the same shape as `make bootstrap`, just
the entry point flipped: that target trusts cstage; nocc trusts
the checked-in (or local-snapshot) stage-0 binaries.

Implementation notes:

- libwwrt.a is assembled with stage-0 6a, members ordered to match
  $(RT_OBJ) so the embedded symbol table reproduces the cstage
  build byte-for-byte.
- The driver's default lib path is $self_dir/../../lib, which under
  $(NOCC_BIN) resolves to $(NOCC_OUT)/lib (libwwrt only). Each
  ww-build invocation passes -I $(CURDIR)/lib so `use os` etc.
  resolve to source.
- Stage-0 binaries are duplicated under both natural names (ww/6c/
  6a/6l) and _ww-suffixed names so the wwstage driver's
  hard-coded join_path_lit(self_dir, "6c_ww") still finds them.
  When cmd/wwc/ gets deleted at v1.0, that duplication and the
  _ww suffix in the driver both go away.
2026-05-11 11:51:34 +09:00

3.8 KiB
Raw Blame History

BOOTSTRAP

How ww builds itself.

ww is self-hosted: the ww-side compiler/assembler/linker/driver live under selfhost/cmd/ and reach a byte-identical fixed point under make bootstrap. But the first ww binary on a fresh checkout has to come from somewhere. Today, that "somewhere" is a small C bootstrap toolchain — the Cstage:

cmd/wwc/   frontend library (lex, parse, check)  → libwwc.a
cmd/6c/    amd64 compiler   .ww → .s
cmd/6a/    amd64 assembler  .s  → .o
cmd/6l/    amd64 linker     .o  → static ELF
cmd/ww/    user-facing driver (orchestrates 6c → 6a → 6l)

Cstage is built with cc. Once it exists, it compiles the ww-rewritten tools (the wwstage) under selfhost/cmd/, which then self-compile to a byte-identical fixed point. After that, no C is involved.

Build flow

cstage     (cc → C tools, one-time)
  │
  ├──→ ww1   wwstage tools, built by Cstage
  │     │
  │     └──→ ww2   wwstage self-compiles
  │            │
  │            └──→ ww3   ww2 self-compiles
  │                   │
  │                   ↓
  │              cmp ww2 ww3   →   fixed point ✓
  ↑
  └ from here on, no C is invoked

Make targets

make                builds cstage + wwstage + libs
make cstage         Cstage only (cc → ww, 6c, 6a, 6l, wwdump, libs)
make wwstage        wwstage only (assumes cstage)
make bootstrap      three-stage build; gates on cmp ww2 == ww3
make test           runs all tests, including 990995 (ww-side
                    tools vs C-side tools, byte-for-byte, plus
                    the wwstage rebuilding itself in 995)

Without cc:

make bootstrap-snapshot   populate bootstrap/$(ARCH)/ from the
                          currently-built wwstage (gitignored
                          by default)
make nocc                 cold-start from bootstrap/$(ARCH)/,
                          rebuild the wwstage from source, gate
                          on `stage-0 == rebuilt` byte-for-byte.
                          cc is never invoked.

Status

Phase 10 of PLAN.md is at its first exit criterion: the bootstrap is green and selfhost/cmd/{wwc,6c,6a,6l,ww} are byte-identical to their Cstage counterparts. Test 995 pins the stronger property — the wwstage rebuilds every one of its own tools through ww_ww + 6c_ww + 6a_ww + 6l_ww, byte-for-byte.

make nocc is wired and verifies stage-0 self-reproduction locally; the stage-0 binaries under bootstrap/$(ARCH)/ are gitignored until ready to commit. The second exit criterion — deleting the C trees — is still deferred to v1.0: shipping the stage-0 binaries is a one-way door (they become the project's new trust surface), and the compiler is still churning. A git clone today still requires cc unless the local working copy has already done make bootstrap-snapshot once.

What "no C" looks like

The pieces are already wired; only the binary commit is held back. When the compiler stabilises:

  1. On each supported arch, run make wwstage && make bootstrap-snapshot to populate bootstrap/<arch>/{ww,6c,6a,6l} from the current wwstage. Verify with make nocc.
  2. git add -f bootstrap/<arch>/{ww,6c,6a,6l} — explicit because the dir is gitignored. The binary SHAs become the new trust surface.
  3. Delete cmd/wwc/, cmd/6c/, cmd/6a/, cmd/6l/, cmd/ww/. Drop CC/AR C-tool dependencies from the top-level Makefile.
  4. Move selfhost/cmd/* to cmd/* and make nocc becomes the default make.
  5. Build path: stage-0 binary → ww1 → ww2 → fixed point, same as today, just without cc.

This is the standard self-hosting story (Go, Rust, OCaml all do variants of it). The bootstrap binaries become the new "trusting trust" surface.