first commit

This commit is contained in:
2026-05-10 01:24:58 +09:00
commit 4c8fc59ca1
4 changed files with 756 additions and 0 deletions

385
CLAUDE.md Normal file
View File

@@ -0,0 +1,385 @@
# ww
A small systems language. Plan 9 in spirit and code style, Hare in
syntax, API, and FFI, no GC, CSP at the end.
This file is the contract for the work. Read PLAN.md for the schedule.
## Identity
- Name: `ww`
- Source extension: `.ww`
- Module: a directory of `*.ww` files (Hare-style layout)
- Toolchain: Plan 9-organized. One library + one binary per role
per target architecture. Plan 9 uses a single digit per arch
(8=386, 6=amd64, 5=arm, 7=arm64, 9=power); we adopt that.
- `cmd/wwc/` — frontend library (lex, parse, check). Builds
`libwwc.a`. Not a binary.
- `cmd/6c/` — amd64 compiler. Reads `.ww`, writes `.s`
(Plan 9 amd64 asm).
- `cmd/6a/` — amd64 assembler. Reads `.s`, writes `.o`
(ELF, for C interop).
- `cmd/6l/` — amd64 linker. Reads `.o` and `.a`, writes a
static ELF binary.
- `cmd/ww/` — user-facing driver (Hare's `hare(1)` /
Plan 9's `cc(1)` analogue). Orchestrates `6c → 6a → 6l`.
Adding a new target later means a new triple (e.g. `7c`/`7a`/`7l`
for arm64). The frontend library `wwc` is shared.
## Hard rules (do not violate)
1. **No garbage collector.** Ever. Memory is allocated and freed by the
programmer. The compiler may insert defer-style cleanup, never a
tracing/reference-counting collector.
2. **No `map`.** A built-in growable hash table is unsafe without GC
(rehashing invalidates pointers). Users may build their own; it is
not a language type.
3. **No complex runtime.** The runtime is a few hundred lines. It owns:
process startup, syscalls trampoline, panic/abort, and (later) the
CSP scheduler. It does not own memory beyond a tiny bump arena for
startup.
4. **Static linking by default.** A `ww` binary is self-contained,
like Go. Dynamic linking is opt-in (`-shared`, `-l`).
5. **C FFI is first-class, Hare-style.** A body-less `fn` declaration
imports the symbol; `@symbol("name")` overrides the linker name.
We must be able to bind libcrypto/libtls/ncurses cleanly and link
them statically into the final image. The platform calling
convention (SysV amd64 on Linux) is the C ABI, so no separate
`extern "c"` marker is needed.
6. **Plan 9 toolchain. No LLVM, no QBE, no external IR.** We do not
invent a portable SSA IR. We follow Plan 9: the per-target
compiler (`6c` for amd64) reads `.ww` and writes Plan 9-style
target assembly (`.s`); the per-target assembler (`6a`) writes
ELF objects; the per-target linker (`6l`) produces a static
binary. Each tool uses Plan 9 cc's in-memory `Prog`/`Adr`
shapes — read `ref/plan9front/sys/src/cmd/cc/`, `cmd/6c/`,
`cmd/6a/`, `cmd/6l/` before writing your own.
7. **No generics, no interfaces, no tagged unions, no closures, no
lambdas.** Five forms of bloat we refuse. Polymorphism, when
genuinely needed, is a struct of function pointers plus a
`ctx: *void` (Plan 9 `Bio`, Hare `io::stream`). All functions are
declared at file scope; function values are pointers to those
named functions. No capturing. No anonymous function literals.
The compiler does no virtual dispatch; users build vtables
explicitly when they want them. If a function needs to work on
multiple types, write it multiple times, or operate on `[]u8`
and let the caller cast.
8. **Tests run after every change.** `make test` is the truth. A
change without a green `make test` is not a change.
9. **Prototype in C, then self-host.** The C bootstrap toolchain
(`libwwc`, `6c`, `6a`, `6l`, `ww`) is throwaway scaffolding.
Its job is to compile enough of `ww` to compile the ww
reimplementations of itself. Do not over-engineer the C side.
## Type system
Hare-style integer names. Fixed width, explicit signedness:
```
i8 i16 i32 i64 signed
u8 u16 u32 u64 unsigned
uint int register width (target-defined)
uintptr pointer-width unsigned
f32 f64 IEEE 754
bool one byte
rune i32, a Unicode code point
str immutable utf-8 view: { *u8, len }
void zero-sized
```
Composite:
```
*T pointer (may be nil)
[N]T fixed array
[]T slice: { *T, len, cap }
struct { x: i32, y: i32 } aggregate (Hare shape)
fn(arg: T) ret function pointer (file-scope only)
chan T CSP channel (last phase)
```
No `map`. No `interface`. No `union`. No exceptions. No generics.
No closures.
Polymorphism, when truly needed, is a struct of function pointers
plus a `ctx: *void`. See `lib/io/stream.ww` for the canonical shape.
This is how Plan 9 `Bio` and Hare `io::stream` work. It is plain
data, easy to read, and the compiler does nothing magic for it.
## Syntax
Hare-shaped. Trailing semicolons. `=` after function and type
signatures. The one departure from Hare: module paths use `.`
instead of `::`. Both module navigation and field access use the
same dot — the compiler resolves by name lookup.
```
use io;
use fmt;
use os;
def MAX_LINE: i32 = 4096;
type point = struct {
x: i32,
y: i32,
};
export fn move(p: *point, dx: i32, dy: i32) void = {
p.x += dx;
p.y += dy;
};
export fn distance(a: point, b: point) f64 = {
let dx: f64 = (a.x - b.x): f64;
let dy: f64 = (a.y - b.y): f64;
return math.sqrt(dx*dx + dy*dy);
};
export fn main() void = {
let p: point = point { x = 0, y = 0 };
move(&p, 3, 4);
for (let i: i32 = 0; i < MAX_LINE; i += 1) {
fmt.println(i);
};
};
```
Lexical rules:
- Statements end in `;`. No automatic insertion.
- Function bodies follow `=`: `fn f() T = { ... };`.
- Type definitions follow `=`: `type p = struct { ... };`.
- Visibility is the `export` keyword. No capitalization rule.
- Module paths use `.`. So does field access. Compiler disambiguates.
- Constants: `def NAME: T = lit;` (compile-time).
- Variables: `let name: T = expr;` or `let name = expr;` (inferred).
- Struct literal: `point { x = 0, y = 0 }` (Hare uses `=`).
- Type cast: `expr: T`.
- Pointers are nullable. Compare with `== nil`.
- No methods. A function on `point` is `fn move(p: *point, ...)`.
Plan 9 cc has no methods; neither do we.
- No closures, no lambdas, no anonymous functions. A function value
is a pointer to a named, file-scope function.
- No `:=`, no `make`, no `new`. Allocation is the built-in expression
form (Hare-style):
- `alloc(point { x = 1, y = 2 })` returns `*point`
- `alloc([0u8...], 16)` returns `[]u8` of len/cap 16
- `free(p)` releases a pointer or slice
- C FFI: a body-less `fn` is an external symbol. `@symbol("name")`
overrides the linker name:
```
@symbol("malloc") fn c_malloc(n: u64) *void;
@symbol("free") fn c_free(p: *void) void;
```
- Errors are plain strings. See "Errors" below.
## Errors
Plan 9 model. An error is a string. Empty means OK. Hare uses
tagged unions for errors; we don't have unions, so we drop down
to the plainer Plan 9 thing.
```
type error = str;
def eEOF : error = "eof";
def eShortRead : error = "short read";
```
Functions that can fail return `(T, error)`. The `T` is zero-valued
when the error is non-empty:
```
export fn open(name: str) (*file, error) = {
if (name == "") {
return nil, "open: empty name";
};
let fd: i32 = sys.open(name, sys.oRdonly, 0);
if (fd < 0) {
return nil, sys.errstr();
};
return alloc(file { fd = fd, name = name }), "";
};
let f, err = open("/tmp/x");
if (err != "") {
fmt.eprintln(err);
os.exit(1);
};
```
Wrapping is string concatenation: `fmt.errorf("open %s: %s", name,
err)`. Comparison is plain string compare. Sentinel errors are
package-level `def`s.
Why a string and not a struct? Because Plan 9 used errstr for
thirty years and the world did not end. Strings are concrete,
allocation-free when literal, and carry arbitrary detail without
inviting a type hierarchy. Hare's `?` postfix and `match` for
errors are also unavailable to us by rule #7.
## Naming
Plan 9 taste lowered to ww. No CamelCase anywhere in ww source.
- Package names: short, lowercase, one word. `fmt`, `io`, `bio`.
- Identifiers: lowercase, words run together. `newbuf`, `tcpsock`,
`parsefile`. Underscores allowed but discouraged.
- Visibility: the `export` keyword. Not first-letter case.
- Types: lowercase, like everything else (`point`, `lexer`, `node`).
- Constants (`def`): UPPER_SNAKE for tunables (`MAX_LINE`, `NHASH`);
lowercase for ordinary ones (`eEOF`, `eShortRead`).
- Files: short, descriptive, lowercase. `lex.c`, `parse.c`, `ir.c`,
`lex.ww`, `parse.ww`.
- C-side struct typedefs in the bootstrap mirror Plan 9 (capitalized
is the C convention there): `Node`, `Sym`, `Type`, `Prog`, `Adr`.
In ww source the same shapes are lowercase (`node`, `sym`, `prog`,
`adr`; the type-info struct is just `tinfo` to avoid the keyword).
## Standard library
Hare layout, Plan 9 names where they exist. Initial cut:
```
lib/
types/ integer limits, type info
bytes/ byte slice ops
strings/ str ops
fmt/ printf-family
io/ reader/writer/closer interfaces
bufio/ buffered io (Plan 9 'bio' equivalent)
bio/ alias of bufio for Plan 9 muscle memory
os/ process, fs, args, env
os/exec/ run subcommands
errors/ error type, sentinel values
sort/ sort.Slice, sort.Search
strconv/ number<->string
path/ path manipulation
encoding/ hex, base64, utf8
hash/ crc32, fnv, sha256
net/ dial, listen
time/ monotonic + wall clock
sync/ (post-CSP) mutex, once, waitgroup
```
C bindings live under `lib/c/`:
```
lib/c/
libc/ malloc, printf, etc. (when calling out)
tls/ libtls (or BearSSL) bindings
crypto/ libcrypto bindings
curses/ ncurses bindings
```
Bindings are thin: one `.ww` file per C header section, marked
`extern "c"`, no wrapping logic in the binding layer itself. Higher
ergonomics live in a sibling pure-`ww` package.
## Build
POSIX `make`, no autotools, no cmake.
```
make # builds libwwc, 6c, 6a, 6l, ww, stdlib
make test # runs all tests (toolchain + stdlib)
make install # installs to $PREFIX (default /usr/local)
make clean
```
Target layout under `out/`:
```
out/
bin/
ww user-facing driver
6c amd64 compiler
6a amd64 assembler
6l amd64 linker
lib/
libwwc.a frontend library (linked into 6c)
libwwrt.a runtime archive (linked into final binaries)
<pkg>.a precompiled stdlib modules
obj/... intermediate .s, .o per package
```
Static by default. `ww build foo.ww` produces a statically linked
ELF. Dynamic is `ww build -shared` or per-library `-l`.
## Testing
Three tiers, all driven by `make test`:
1. **Compiler unit tests** (`test/wwc/`): C, table-driven. Lex, parse,
typecheck, IR-gen, codegen each have their own table.
2. **Language tests** (`test/lang/*.ww`): each file is a single ww
program with a comment header declaring expected exit code and
expected stdout. The harness (`test/run`) compiles and runs.
3. **Stdlib tests** (`lib/*/+test.ha`-style, here `*_test.ww`): in-tree
tests per module. Hare convention, just renamed.
Every change must:
- Add or update a test that exercises the change.
- Leave `make test` green.
- Produce no new warnings (`-Wall -Wextra -Wpedantic` in C; the
ww typechecker is strict by default).
## Rob Pike rules (kept on the wall)
1. You can't tell where a program will spend its time. Measure.
2. Measure. Don't tune for speed without numbers.
3. Fancy algorithms are slow when n is small, and n is usually small.
4. Fancy algorithms are buggier and harder to implement. Prefer simple.
5. Data dominates. Get the structures right and the code follows.
6. There is no rule 6.
Applied to this project:
- `6c` is a single-pass-ish recursive-descent parser into a typed
AST, walked into a `Prog` list, then printed as text. No parser
generator, no LLVM, no SSA pass pipeline.
- Optimizer is intentionally absent at first. Constant fold + dead
code elim only. Add passes when a benchmark demands one.
- Data structures: `Node`, `Sym`, `Type`, `Prog`, `Adr` modeled on
Plan 9 cc. Read `ref/plan9front/sys/src/cmd/cc/cc.h` and the
per-target headers (`cmd/8c/gc.h`, `cmd/8a/`, `cmd/8l/`) before
inventing a new shape.
## What is in `ref/`
- `ref/hare/` — the Hare distribution. Read for syntax, FFI
(`@symbol`), stdlib layout, type names, error idioms.
- `ref/plan9front/` — 9front. Read `sys/src/cmd/cc/` (the shared
frontend library), `sys/src/cmd/8c/` and `sys/src/cmd/6c/` (per-
target compilers), `sys/src/cmd/8a/` (assembler), `sys/src/cmd/8l/`
(linker) for toolchain organization, `Prog`/`Adr` data shapes,
mkfile style, and naming.
When in doubt: copy Hare for surface syntax and stdlib, copy Plan 9
for toolchain organization and compiler internals.
## What we will NOT build
- A package manager. Modules are directories. Vendoring is `cp -r`.
- A formatter beyond `wwfmt` (one canonical style, no options).
- A language server in phase 0. Plain editors are fine.
- Generics, interfaces, tagged unions, closures, lambdas. Ever.
See hard rule #7.
- An async/await coloring. CSP is the concurrency story; a function
is a function.
## Working agreement (for the assistant)
When making changes:
- Prefer editing existing files to creating new ones.
- Run `make test` (or the smallest relevant slice) after any code
change. Report results.
- Match the file's existing style. C files: Plan 9 style (tabs,
K&R, short names; see `ref/plan9front/sys/src/cmd/cc/`). ww
files: Hare-shaped, formatted by `wwfmt` (one canonical style).
- If a design question is non-obvious, propose two options before
writing code.
- Keep diffs small. One concern per change.

369
PLAN.md Normal file
View File

@@ -0,0 +1,369 @@
# PLAN
A phased plan for `ww`. Each phase ends with a green `make test` and
a tagged checkpoint. Don't start phase N+1 until phase N is green.
CLAUDE.md is the rulebook. This file is the schedule.
## Phase 0 — Skeleton (C bootstrap scaffold)
Goal: a repo that builds, has a Makefile, and has a `ww` driver
that prints its version. No compilation yet.
Deliverables:
- `Makefile` (POSIX) with `all`, `test`, `clean`, `install` targets.
- `cmd/wwc/` C library skeleton (builds `libwwc.a`):
- `ww.h` — central typedefs (`Node`, `Sym`, `Type`, `Lex`)
- `mem.c` — bump arena allocator (no `malloc` in hot paths)
- `err.c``errorf`, `fatal`, `warn`
- (lex/parse/check stubs added in later phases)
- `cmd/ww/` driver skeleton: argv parsing, version print.
- `test/run` — shell test harness.
- `test/wwc/000_smoke.c` — smoke test that asserts `ww -V` prints
the version.
Per-target binaries (`6c`, `6a`, `6l`) are NOT created here. They
ship in their own phases (4, 5, 6).
Exit criteria:
- `make` produces `out/bin/ww` and `out/lib/libwwc.a`.
- `make test` runs the smoke test and passes.
## Phase 1 — Lexer (in `libwwc.a`)
Goal: tokenize ww source into a stream of `Tok` structs.
Deliverables:
- `cmd/wwc/lex.c` — hand-rolled DFA. Handles:
- identifiers, keywords (`fn`, `let`, `def`, `if`, `else`, `for`,
`switch`, `case`, `return`, `use`, `type`, `struct`, `defer`,
`break`, `continue`, `export`, `proc`, `chan`, `nil`, `true`,
`false`)
- integer literals (dec, hex, oct, bin) with suffixes (`u8`, `i32`...)
- float literals
- rune literals `'x'` and string literals `"..."` (UTF-8)
- operators and punctuation, including `@` for attributes
(`@symbol("name")`, etc.)
- explicit `;` terminators — no automatic insertion (Hare rule)
- `//` and `/* */` comments
- `cmd/wwc/tok.c` — token names, debug printer.
- `test/wwc/100_lex.c` — table-driven: input → token sequence.
- `test/lang/lex/*.ww` — small files exercised via a tiny test
harness that links against `libwwc.a`.
Exit criteria:
- A `lextool` test binary dumps a representative sample's token
stream deterministically.
- All lex tests green.
## Phase 2 — Parser & AST (in `libwwc.a`)
Goal: parse the full grammar into an AST. No types yet.
Deliverables:
- `cmd/wwc/parse.c` — recursive descent. Pratt expression parser for
precedence. No yacc.
- `cmd/wwc/ast.c``Node` constructor helpers; printer.
- Grammar coverage:
- `use` imports (paths use `.` not `::`)
- `type` declarations (struct, alias, fn type) with trailing `=`
- `def` constants
- `let` declarations (top-level and local)
- `fn` definitions with `= { ... };` body. No methods; receivers
are just first arguments.
- `export` visibility marker
- all expressions, statements, control flow
- `defer`
- body-less `fn` declarations and `@symbol("name")` attribute for
FFI imports
- `test/wwc/200_parse.c` — table-driven AST shape tests.
- `test/lang/parse/*.ww` — programs that should parse but not yet
typecheck; harness only checks parser exits 0.
Exit criteria:
- 50+ parse tests green, drawing shapes from
`ref/hare/cmd/hare/main.ha`.
- AST printer output is deterministic.
## Phase 3 — Type checker (in `libwwc.a`)
Goal: resolve names, infer/check types, produce a typed AST.
Deliverables:
- `cmd/wwc/sym.c` — symbol table. Lexical scopes. Plan 9 style hash.
- `cmd/wwc/type.c` — type representation, equality, conversion rules.
- `cmd/wwc/check.c` — type checking pass over the AST. Errors carry
source locations.
- Built-in types: all integer widths, `bool`, `f32`, `f64`, `rune`,
`str`, `void`, pointers, slices `[]T`, fixed arrays `[N]T`,
function types.
- Slice semantics: `{ ptr, len, cap }`, indexing bounds-checked at
runtime (debug builds; release may elide via flag).
- `test/wwc/300_check.c` — table-driven: src → expected error or OK.
- `test/lang/check/*.ww` — both passing and failing cases.
Exit criteria:
- All built-in types correctly checked.
- Negative tests produce stable, useful diagnostics.
## Phase 4 — `6c` amd64 compiler
Goal: turn typed AST into Plan 9-style amd64 assembly text. The
binary `6c` is a Plan 9 cc analogue for amd64. There is no separate
SSA IR file; the only on-disk intermediate is `.s`.
Deliverables:
- `cmd/6c/` (links against `libwwc.a`):
- `6.out.h` — amd64 opcode enum, register names, addressing
modes. Mirror `ref/plan9front/sys/src/cmd/6c/` shape.
- `gc.h``Prog`, `Adr`, scratch regs, stack layout.
- `cgen.c` — typed AST → `Prog` list (walking, not SSA).
- `txt.c``Prog` list → textual Plan 9 amd64 asm.
- `swt.c`, `peep.c`, `reg.c` — switch lowering, peephole pass,
naive register allocation (linear scan or spill-everywhere
first; tighten later).
- `mkfile` — Plan 9 mkfile next to the Makefile entries.
- Output suffix `.s`. Plan 9 amd64 asm syntax (not GAS).
- `test/lang/6c/*.ww` — golden tests: src → expected `.s` text.
Exit criteria:
- `6c hello.ww` produces `hello.s`.
- 20+ programs round-trip identically across runs.
- The output is consumable by phase 5's `6a`.
## Phase 5 — `6a` amd64 assembler
Goal: assemble Plan 9 amd64 asm into ELF object files. ELF (not
Plan 9 a.out) so we can interop with C archives in phase 8.
Deliverables:
- `cmd/6a/`:
- `lex.c` — tokenize Plan 9 asm.
- `parse.c` — hand-rolled grammar (Plan 9 uses yacc; we go
hand-rolled to keep rule #6 honest).
- `asm.c` — encode amd64 instructions (REX, ModR/M, SIB).
- `obj.c` — emit ELF64 relocatable objects.
- `mkfile`
- Pseudoregisters supported: `SP`, `FP`, `SB` (static base) per
Plan 9 convention, on top of `AX`, `BX`, ..., `R8`-`R15`.
- `test/wwc/500_asm.c` — round-trip known asm to known bytes.
Exit criteria:
- `6a hello.s -o hello.o` produces a valid ELF amd64 object.
- `objdump -d hello.o` matches expectations across a 20-program
corpus.
## Phase 6 — `6l` amd64 linker
Goal: link ELF objects into a static ELF executable.
Deliverables:
- `cmd/6l/`:
- `obj.c` — load ELF `.o` files and `.a` archives.
- `sym.c` — global symbol table; resolution.
- `pass.c` — section layout, relocation application.
- `out.c` — emit static ELF executable.
- `mkfile`
- Static linking only. No dynamic loader. No PT_INTERP segment.
- `lib/libwwrt.a` (built later in phase 7) is auto-included.
Exit criteria:
- `6l -o hello hello.o libwwrt.a` produces a static ELF binary.
- `ldd hello` reports "not a dynamic executable".
- The binary exits 0 with the program's intended semantics.
## Phase 7 — Runtime + driver wiring
Goal: a tiny static runtime, plus the `ww` driver wired to call
`6c → 6a → 6l` end to end.
Deliverables:
- `rt/start.s` — entry, sets up argc/argv, calls `main`.
- `rt/syscall.s` — Linux syscall trampoline.
- `rt/panic.c``panic`, `abort`, stack unwind for `defer`.
- `rt/mem.c` — small page allocator built on `mmap`. Users get
raw pages; higher-level allocators ship in `lib/`.
- `rt/slice.c` — slice helpers used by codegen
(`bounds_check`, `slice_grow` for explicit user calls).
- Build artifact: `out/lib/libwwrt.a`.
- `cmd/ww/` driver: reads `ww build foo.ww`, runs `6c` then `6a`
then `6l`, links `libwwrt.a` into the output.
Exit criteria:
- A ww program with no libc dependency runs.
- Adding `use os` pulls in syscall wrappers; still static.
## Phase 8 — C FFI + static linking C libs
Goal: bind to C libraries; produce static binaries containing them.
Deliverables:
- Body-less `fn` declarations + `@symbol("name")` attribute parser
and checker rules already in phases 2/3 — finalize codegen.
- amd64 SysV ABI: formalize struct-by-value and varargs in `6c`.
- `6l` learns to slurp static archives from a search path.
- `lib/c/libc/` — minimal libc bindings (`malloc`, `free`, `printf`,
`read`, `write`, `open`, `close`).
- `lib/c/tls/` — bindings to libtls or BearSSL (decide; BearSSL is
more aligned with our static-linking story).
- `lib/c/crypto/` — bindings to libcrypto subset (sha256, aes, hmac).
- `lib/c/curses/` — ncurses bindings (initscr, getch, mvprintw, ...).
- `ww` driver: `ww build -lcrypto -ltls -lncurses` resolves these
via a `pkg-config`-style probe, hands `.a` paths to `6l`.
Exit criteria:
- `examples/tlsclient.ww` connects to https://example.org and
prints the response. Statically linked. No `.so` deps in `ldd`.
- `examples/menu.ww` runs an ncurses TUI.
## Phase 9 — Standard library (Hare-shaped)
Goal: a usable stdlib. Each module ships with tests.
Order of build (each is its own milestone):
1. `types` limits, int helpers
2. `bytes` slice ops on `[]u8`
3. `strings` ops on `str`
4. `io` `stream` struct (function-pointer vtable, no interface);
`eof` sentinel value
5. `bufio` buffered reader/writer (Plan 9 `bio` analogue)
6. `fmt` printf-family writing through `io.stream`
7. `os` argv, env, stdin/out/err, file ops
8. `errors` `error = str` and sentinel constants
9. `strconv` itoa, atoi, parse float
10. `sort` `sort.slice`, binary search
11. `path` filepath ops
12. `encoding/utf8`, `encoding/hex`, `encoding/base64`
13. `hash/crc32`, `hash/fnv`, `hash/sha256` (pure ww)
14. `time` monotonic, wall clock, sleep
15. `net` dial/listen TCP, UDP
Each module follows the Hare layout: one directory, multiple files,
`+linux.ww`, `+test.ww` overlays where useful.
Exit criteria per module:
- Module compiles standalone with `ww build ./lib/<mod>`.
- Module tests pass: `ww test ./lib/<mod>`.
- Public API documented in `README` inside the module dir.
## Phase 10 — Self-host
Goal: rewrite `libwwc`, `6c`, `6a`, `6l`, and `ww` in ww. Drop the
C bootstrap.
Steps:
1. Translate `ww.h` and `Node`/`Sym`/`Type`/`Prog`/`Adr` to ww
structs.
2. Port `lex.c``lex.ww`. Diff token streams against C output.
3. Port `parse.c``parse.ww`. Diff ASTs.
4. Port `check.c`. Diff typed ASTs.
5. Port `6c` (cgen, txt, peep, reg, swt). Diff `.s` output byte
for byte.
6. Port `6a` and `6l`. Diff resulting `.o` and final binaries.
7. Three-stage bootstrap:
`Cstage (gcc-built tools) → ww1 → ww2 → ww3`, with
`cmp ww2 ww3` OK for each tool.
Exit criteria:
- `make bootstrap` runs the three-stage build green.
- The C `cmd/wwc/`, `cmd/6c/`, `cmd/6a/`, `cmd/6l/` C trees are
deleted in this phase's final commit.
## Phase 11 — CSP
Goal: channels and lightweight processes, without GC.
Deliverables:
- Runtime scheduler: cooperative, M:N, with stack-per-proc (start
with fixed-size stacks; segmented stacks are a research item).
- `proc f(args)` statement: spawns a process. Fire-and-forget by
default. To keep a handle, use the stdlib spawner
(`sched.spawn(f, args)` returns a `procval`). No GC means proc
lifetimes must be explicit.
- `chan T` type. `c <- v` send; `let v = <-c` receive;
`select { ... }`.
- Channel ownership: closing a channel is a single-writer
responsibility (Go rule). Buffered and unbuffered both supported.
- `sync` package: `mutex`, `waitgroup`, `once`.
Concurrency-safety constraints (because no GC):
- Sending a pointer over a channel transfers ownership unless the
type is explicitly marked `shared`. The checker enforces this.
- No data races by construction in the safe subset; `unsafe` pkg
exists for power users.
Exit criteria:
- Classic CSP examples (ping-pong, prime sieve, fan-in/fan-out)
compile and run.
- Race detector under `-race` (later sub-phase) catches a planted
race in tests.
## Test cadence
Every phase has its own test directory (`test/wwc/<phase>_*.c`,
`test/lang/<phase-area>/*.ww`). `make test` runs them all in order
of phase. CI is just `make test` in a clean tree.
## Versioning checkpoints
Tag the tree at each phase boundary:
- `v0.0` end of phase 0
- `v0.1` end of phase 1
- ...
- `v0.10` end of phase 10 (self-hosted)
- `v1.0` end of phase 11 (CSP merged, ABI declared stable enough
to start caring)
## Risks and mitigations
- **`Prog`/`Adr` design churn.** Mitigate by reading Plan 9 cc and
per-target compilers before we write our own. Don't invent until
you've copied.
- **Writing a linker is hard.** ELF is documented, static linking
is the small subset. Read `ref/plan9front/sys/src/cmd/8l/` and
the ELF spec before writing `6l`. Start with hello-world and grow.
- **Static linking against C libs.** OpenSSL is a pain to build
static; BearSSL or LibreTLS are more pleasant. Pick early.
- **Self-host bootstrap divergence.** Diff outputs at every stage,
not just the final binary. Token streams, ASTs, and `.s` text
must match.
- **CSP without GC.** Channel-of-pointer ownership rules are the
hard part. Prototype the checker with `unsafe`-allowed first;
tighten later.
## Non-goals (restated)
- No package manager.
- No generics, no interfaces, no tagged unions, no closures, no
lambdas.
- No async/await.
- No `map`.
- No GC.
- No LLVM, no QBE, no external compiler infrastructure. The
toolchain is `cc/8c/8a/8l`-shaped (Plan 9), per target.

1
ref/hare Submodule

Submodule ref/hare added at b2e3defdd8

1
ref/plan9front Submodule

Submodule ref/plan9front added at 6dc60ec3a9