toolchain: banner purge + WHY-only comment sweep (rule 8)

selfhost/, cmd/, internal/ join the tree-wide sweep: every section
banner dies (91 selfhost + the cmd C-style dividers -> 0); narration
and stale contracts deleted (pre-#22 bundler notes, retired
single-PT_LOAD and no-archive claims, superseded ABI tables); every
ref/harec/qbe cite, task cite, encoding/ELF contract, and rule-10
twin pointer kept; lost lifetime/rationale lines restored where the
sweep over-cut (elf_globals ownership, kwtab linear-scan). Comment-
only proven: all five wwstage tool binaries byte-identical across
the sweep; test-commit, test-byteid (161+1399, 0 pinned-divergent),
and test-bootstrap (fixed point + 991-995 byte-id) all exit 0.
The read-through banked 66 latent-bug leads (checkpoint).
This commit is contained in:
2026-08-08 23:14:03 +09:00
parent 83f5956df2
commit 62b9d20383
60 changed files with 232 additions and 1045 deletions

View File

@@ -1,9 +1,5 @@
/*
* ast.c — Node constructor + s-expression printer.
*
* Constructor zeroes everything past kind/pos. Printer is rigid and
* deterministic so golden tests can diff. One node per logical line,
* children indented by 2 spaces.
* Printer output is rigid and deterministic so golden tests can diff.
*/
#include "ww.h"
#include <string.h>

View File

@@ -1,6 +1,4 @@
/*
* check.c — name resolution + type checking pass.
*
* Two-stage:
* 1) collect: walk top-level decls and install Syms with stub types.
* 2) resolve: expand types, check fn bodies and def initialisers.
@@ -1046,8 +1044,6 @@ resolve_type(Checker *c, Node *n)
}
}
/* ---- expressions -------------------------------------------------- */
static Type *
unify_arith(Checker *c, Pos p, Type *a, Type *b)
{
@@ -2007,7 +2003,6 @@ cexpr(Checker *c, Node *n)
(void)cexpr(c, n->rhs);
return n->type = ty_void;
}
/* Reject assignment to a const-bound name. */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
n->lhs->str);
@@ -2387,8 +2382,6 @@ cexpr(Checker *c, Node *n)
}
}
/* ---- statements --------------------------------------------------- */
static void
clet(Checker *c, Node *n)
{
@@ -2731,8 +2724,6 @@ cstmt(Checker *c, Node *n)
}
}
/* ---- top-level ---------------------------------------------------- */
static Type *
build_fn_type(Checker *c, Node *fn)
{

View File

@@ -1,10 +1,4 @@
/*
* err.c — diagnostics.
*
* fatal prints, sets exit(1).
* errorf prints with source location, increments nerrors.
* warnf prints with source location, increments nwarnings.
*
* Plan 9 style: short, no levels beyond fatal/error/warn, no colour.
*/
#include "ww.h"

View File

@@ -1,16 +1,4 @@
/*
* lex.c — hand-rolled DFA. UTF-8 source, ASCII operators.
*
* Comments: //... and (slash-star ... star-slash). Both stripped.
* Whitespace: space, tab, CR, NL.
* Identifiers: [A-Za-z_][A-Za-z0-9_]* — also matches keywords; we
* look up the kw table after lexing the run.
* Integer: 0x[0-9a-fA-F_]+, 0o[0-7_]+, 0b[01_]+, [0-9][0-9_]*
* Float: [0-9]+'.'[0-9]+([eE][+-]?[0-9]+)?
* Rune: 'x' with C-like escapes
* String: "..." with C-like escapes
* Operators: longest match.
*
* No automatic semicolon insertion (Hare rule). The lexer only emits
* what is in the source; the parser is responsible for non-empty rules.
*/
@@ -81,7 +69,6 @@ ishex(int c)
(c >= 'A' && c <= 'F');
}
/* skip whitespace and comments. returns 0 on EOF, else 1. */
static int
skipws(Lex *l)
{
@@ -94,7 +81,7 @@ skipws(Lex *l)
continue;
}
if (c == '/' && lpeek(l, 1) == '/') {
lget(l); lget(l); /* consume '//' */
lget(l); lget(l);
/* #16 option-B: the driver emits `//ww:module-reset`
* before a package-less file's bytes; recognize the
* whole-line directive (without consuming differently)
@@ -379,10 +366,7 @@ lexnum(Lex *l, Pos start)
}
}
/* Typed suffix: i8/i16/i32/i64, u8/u16/u32/u64, f32/f64.
* Must be glued (no whitespace) to the digits. We grab the
* adjacent identifier-like run and accept it only if it's one
* of the recognised type names. */
/* A typed suffix must be glued (no whitespace) to the digits. */
if (isidstart(lpeek(l, 0))) {
u64 sb = l->pos;
while (isidcont(lpeek(l, 0))) lget(l);

View File

@@ -1,11 +1,8 @@
/*
* mem.c — arena allocator. No free per allocation; freearena releases
* the whole chain. Aligned to 16 so structs with 8-byte fields and
* doubles are happy.
*
* Hot allocations in the compiler land in arenas: tokens, AST nodes,
* symbols, types. The chunk size doubles up to a cap so we don't
* fragment on huge inputs.
* No free per allocation; freearena releases the whole chain.
* Aligned to 16 so structs with 8-byte fields and doubles are happy.
* The chunk size doubles up to a cap so we don't fragment on huge
* inputs.
*/
#include "ww.h"
#include <stdlib.h>
@@ -43,7 +40,6 @@ grow(Arena *a, u64 need)
if (ncap < need)
ncap = roundup(need, ALIGN);
/* push current chunk onto chain, allocate fresh head */
Arena *old = malloc(sizeof *old);
if (old == NULL)
fatal("arena: oom");

View File

@@ -100,8 +100,6 @@ static Node *parsetype(Parser *p);
static Node *parseblock(Parser *p);
static Node *parsestmt(Parser *p);
/* ------- type expressions ------------------------------------------ */
static Node *
parseparams(Parser *p)
{
@@ -376,8 +374,6 @@ parsetype(Parser *p)
}
}
/* ------- expressions (Pratt) ---------------------------------------- */
/* binary precedence; 0 = not a binary op */
static int
bprec(Tkind k)
@@ -663,10 +659,9 @@ parseprimary(Parser *p)
n->str = t.text;
n->strlen = t.tlen;
advance(p);
/* dotted ident chain folded into one IDENT for type-ish refs */
while (p->cur.kind == TK_DOT && peek(p).kind == TK_IDENT) {
advance(p);
n = (Node*)n; /* keep stable */
n = (Node*)n;
Node *mr = newnode(p->a, N_DOT, pp);
mr->lhs = n;
mr->str = p->cur.text;
@@ -674,7 +669,6 @@ parseprimary(Parser *p)
advance(p);
n = mr;
}
/* struct literal: ident '{' ... '}' (only if ident-shaped) */
if (p->cur.kind == TK_LBRACE) {
/* #76: bare `Foo{}` keeps the N_IDENT fast-path; a
* qualified `pkg.Type{}` (N_DOT chain) flattens first. */
@@ -902,8 +896,6 @@ parseexpr_top(Parser *p)
return parseexpr(p);
}
/* ------- statements ------------------------------------------------- */
static Node *
parselet(Parser *p, int top)
{
@@ -944,7 +936,6 @@ parselet(Parser *p, int top)
return m;
}
/* parse first binding */
Pos lp = p->cur.pos;
Node *first = newnode(p->a, N_LET, lp);
first->str = expectbindname(p);
@@ -952,7 +943,6 @@ parselet(Parser *p, int top)
first->lhs = parsetype(p);
if (p->cur.kind == TK_COMMA) {
/* multi-let: collect (name, type) pairs, then '=' rhs */
Node *m = newnode(p->a, N_MLET, pp);
Node *head = first, *tail = first;
while (accept(p, TK_COMMA)) {
@@ -1038,7 +1028,7 @@ parsefor(Parser *p)
* Tuple destructure: `let (a, b) .. expr`. */
Tok save_cur = p->cur;
(void)save_cur;
advance(p); /* consume LET */
advance(p);
if (p->cur.kind == TK_LPAREN) {
advance(p);
Node *names = NULL, *tail = NULL;
@@ -1067,8 +1057,8 @@ parsefor(Parser *p)
int isunder = p->cur.kind == TK_UNDER;
Tok la = peek(p);
if (la.kind == TK_DOTDOT) {
advance(p); /* consume IDENT/UNDER */
advance(p); /* consume DOTDOT */
advance(p);
advance(p);
Node *rng = newnode(p->a, N_FORRANGE, pp);
rng->str = isunder ? "" : nm;
rng->lhs = parseexpr(p);
@@ -1283,8 +1273,6 @@ parseblock(Parser *p)
return n;
}
/* ------- top-level decls ------------------------------------------- */
/* `import encoding.utf8;` — the driver resolves the dotted path to a
* directory; the checker only needs the leaf (`utf8`) as the module
* bareword for n_use→decl disambiguation, mirroring Hare's

View File

@@ -1,9 +1,3 @@
/*
* sym.c — symbol table. Plan 9-flavoured: a per-scope hashtable
* chained to the parent scope. Lookup walks up. Duplicate definitions
* within the same scope are flagged by the caller (we just refuse the
* insert and return the first one).
*/
#include "ww.h"
#include <string.h>
@@ -54,13 +48,6 @@ scope_lookup(Scope *s, const char *name)
}
/*
* scope_lookup_in_module — module-filtered chain walk.
*
* Same FNV bucket + hashnext chain + parent walk as scope_lookup,
* plus a (b->mod != NULL && strcmp(b->mod, mod) == 0) filter. When
* `mod` is NULL we fall back to unfiltered scope_lookup semantics,
* so callers that don't care about disambiguation get the default.
*
* Used by resolve_typename and the cexpr N_DOT branch to pick the
* right same-leaf-name type when two imports each export it
* (`bufio.stream` vs `io.stream`).
@@ -80,19 +67,13 @@ scope_lookup_in_module(Scope *s, const char *mod, const char *name)
}
/*
* scope_lookup_prefer — bare-leaf lookup with same-module preference.
*
* Walks the same FNV bucket + hashnext chain + parent walk scope_lookup
* uses. Within each scope's bucket: Pass 1 prefers entries whose
* `sym.mod` matches the caller's `mod`; Pass 2 falls back to the first
* match regardless of mod (the existing scope_lookup semantics). We
* only descend to the parent scope when the current scope has no
* matching entry at all — so a local binding in a closer scope still
* shadows a same-name fn from a parent scope, even when the parent
* entry mod-matches.
*
* When `mod` is NULL we just call scope_lookup — there's no module
* identity to prefer.
* Within each scope's bucket: Pass 1 prefers entries whose `sym.mod`
* matches the caller's `mod`; Pass 2 falls back to the first match
* regardless of mod (the existing scope_lookup semantics). We only
* descend to the parent scope when the current scope has no matching
* entry at all — so a local binding in a closer scope still shadows a
* same-name fn from a parent scope, even when the parent entry
* mod-matches.
*
* Used at bare-leaf lookup sites inside a known current module so that
* a bare `read` inside lib/os resolves to os.read rather than the
@@ -117,15 +98,12 @@ scope_lookup_prefer(Scope *s, const char *mod, const char *name)
}
/*
* scope_lookup_type — kind-filtered bare-leaf lookup for type position.
*
* Same FNV bucket + hashnext chain + parent walk and same-module
* preference as scope_lookup_prefer, but skips every Sym whose kind
* isn't SK_TYPE and KEEPS scanning — so it returns the innermost
* SK_TYPE of `name`, looking past a same-named value binding (SK_VAR/
* SK_PARAM/SK_FN) that shadows it in a closer scope. ww keeps type and
* value namespaces separate (wwstage already does; #225 conformance
* gap): a param `off` must not hide the global `type off`.
* Skips every Sym whose kind isn't SK_TYPE and KEEPS scanning — so it
* returns the innermost SK_TYPE of `name`, looking past a same-named
* value binding (SK_VAR/SK_PARAM/SK_FN) that shadows it in a closer
* scope. ww keeps type and value namespaces separate (wwstage already
* does; #225 conformance gap): a param `off` must not hide the global
* `type off`.
*/
Sym *
scope_lookup_type(Scope *s, const char *mod, const char *name)
@@ -151,10 +129,8 @@ scope_define(Scope *s, const char *name, Skind k, Type *t, Node *decl)
}
/*
* scope_define_in_module — bucket insert with per-mod dedup.
*
* Same insertion as scope_define, but the duplicate-rejection key is
* (name, mod) rather than name alone. This lets two imports each
* The duplicate-rejection key is (name, mod) rather than name alone.
* This lets two imports each
* register their own `stream` SK_TYPE in the flat scope, and lets the
* primary register `stream` (mod=NULL) alongside imported `stream`s.
*

View File

@@ -1,10 +1,3 @@
/*
* tok.c — token names, keyword lookup, debug printer.
*
* One table-of-records keyed by kind. The keyword subset is also
* scanned linearly during lexing — fewer than 25 entries, a hash
* isn't worth it.
*/
#include "ww.h"
#include <string.h>
@@ -13,7 +6,8 @@ struct kwent {
Tkind kind;
};
/* keep alphabetised, so kwlookup is easy to read. */
/* keep alphabetised, so kwlookup is easy to read. Scanned linearly:
* fewer than 25 entries, a hash isn't worth it. */
static const struct kwent kwtab[] = {
{ "as", TK_AS },
{ "break", TK_BREAK },

View File

@@ -1,10 +1,6 @@
/*
* type.c — Type values and structural equality.
*
* Built-in types are constructed once and exposed as globals so the
* rest of the compiler can `==`-compare them. Compound types (ptr,
* slice, array, fn, struct, chan) are constructed on demand and
* de-duplicated when equality is cheap (only ptr/slice for now).
* rest of the compiler can `==`-compare them.
*/
#include "ww.h"
#include <string.h>
@@ -147,8 +143,7 @@ type_named(Arena *a, const char *name, Type *under)
return t;
}
/* type_chase_named — walk the TY_NAMED.under chain to the deepest non-
* named type. Chain-of-aliases (#22): `type b = a; type a = struct;`
/* Chain-of-aliases (#22): `type b = a; type a = struct;`
* stacks two TY_NAMED layers — a single peel leaves `t` pointing at
* the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT,
* TY_SLICE, TY_TAGGED, TY_PTR) miss and the consumer silently falls
@@ -372,7 +367,6 @@ type_assignable(Type *dst, Type *src)
}
}
/* Untyped → typed: only if the typed kind can hold the value. */
if (type_isuntyped(src)) {
Type *du = type_chase_named(dst);
if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1;

View File

@@ -1,6 +1,4 @@
/*
* ww.h — central header for libwcc.a (the ww frontend library).
*
* Plan 9 in spirit. This file mirrors cc/cc.h's role: one shared
* header that declares everything every translation unit in the
* frontend cares about.
@@ -19,7 +17,6 @@
/* version banner — printed by `ww -V` */
#define WW_VERSION "0.0"
/* short integer aliases, Plan 9 / Hare-flavoured */
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
@@ -29,7 +26,6 @@ typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
/* forward decls — concrete shapes appear in their phases. */
typedef struct Tok Tok;
typedef struct Lex Lex;
typedef struct Node Node;
@@ -54,8 +50,6 @@ char *astrndup(Arena*, const char*, u64);
char *aprintf(Arena*, const char*, ...);
void freearena(Arena*);
/* err.c — diagnostics. Phase 0 has only fatal/warn; later phases add
* source-location-bearing variants. */
typedef struct Pos Pos;
struct Pos {
const char *file;
@@ -72,15 +66,12 @@ void fatal(const char*, ...) __attribute__((noreturn, format(printf, 1, 2)));
void errorf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
void warnf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
/* tiny helpers */
#define nelem(a) ((sizeof(a) / sizeof((a)[0])))
/* ---- lexer (lex.c, tok.c) ----------------------------------------- */
typedef enum {
/* zero is "no token" so memset-zero structs read sane */
TK_NONE = 0,
/* trivial */
TK_EOF,
TK_ERR,
TK_IDENT,
@@ -117,7 +108,6 @@ typedef enum {
TK_CONST, /* const binding */
TK_UNDER, /* bare '_' discard */
/* punct + operators */
TK_LPAREN, /* ( */
TK_RPAREN, /* ) */
TK_LBRACE, /* { */
@@ -233,7 +223,6 @@ const char *tokname(Tkind); /* canonical spelling, e.g. "fn", "+=" */
void tokprint(FILE*, Tok); /* one line, "%s:%d:%d: %s %q" */
Tkind kwlookup(const char *s, u64 n); /* TK_NONE if not a keyword */
/* ---- AST (ast.c, parse.c) ----------------------------------------- */
typedef enum {
N_NONE = 0,
@@ -399,7 +388,6 @@ void parserinit(Parser*, Arena*, Lex*);
Node *parsefile(Parser*);
Node *parseexpr_top(Parser*); /* for testing: parse one expression */
/* ---- types (type.c) ----------------------------------------------- */
typedef enum {
TY_NONE = 0,
TY_VOID,
@@ -525,7 +513,6 @@ int type_isuntyped(Type *t);
int type_assignable(Type *dst, Type *src);
Type *type_default(Type *t); /* untyped → default concrete */
/* ---- symbols (sym.c) ---------------------------------------------- */
typedef enum {
SK_NONE = 0,
SK_VAR,
@@ -580,7 +567,6 @@ Sym *scope_lookup_in_module(Scope*, const char *mod, const char *name);
Sym *scope_lookup_prefer(Scope*, const char *mod, const char *name);
Sym *scope_lookup_type(Scope*, const char *mod, const char *name);
/* ---- checker (check.c) -------------------------------------------- */
typedef struct Checker Checker;
struct Checker {
Arena *a;