commit 4c8fc59ca1c924fec61a81ca04e52db3312130cd Author: Hojun-Cho Date: Sun May 10 01:24:58 2026 +0900 first commit diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..6dc008b0 --- /dev/null +++ b/CLAUDE.md @@ -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) + .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. diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 00000000..05746bec --- /dev/null +++ b/PLAN.md @@ -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/`. +- Module tests pass: `ww test ./lib/`. +- 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/_*.c`, +`test/lang//*.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. diff --git a/ref/hare b/ref/hare new file mode 160000 index 00000000..b2e3defd --- /dev/null +++ b/ref/hare @@ -0,0 +1 @@ +Subproject commit b2e3defdd8149b81966fa7b437c49a9f479abb1b diff --git a/ref/plan9front b/ref/plan9front new file mode 160000 index 00000000..6dc60ec3 --- /dev/null +++ b/ref/plan9front @@ -0,0 +1 @@ +Subproject commit 6dc60ec3a95e88d30b487d13271b042c96eb9532