From 62b9d20383c184e1d0211ee1309bfececbd25ba9 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 23:14:03 +0900 Subject: [PATCH] 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). --- cmd/w6a/a.h | 13 +---- cmd/w6a/asm.c | 25 +-------- cmd/w6a/lex.c | 5 -- cmd/w6a/main.c | 3 - cmd/w6a/obj.c | 13 +---- cmd/w6a/parse.c | 14 ----- cmd/w6c/6.out.h | 4 +- cmd/w6c/cgen.c | 40 +++++--------- cmd/w6c/gc.h | 9 +-- cmd/w6c/main.c | 6 -- cmd/w6c/peep.c | 6 +- cmd/w6c/reg.c | 8 +-- cmd/w6c/swt.c | 6 +- cmd/w6c/txt.c | 2 - cmd/w6c/wwi.c | 14 ++--- cmd/w6l/dyn.c | 9 +-- cmd/w6l/dynout.c | 38 ++----------- cmd/w6l/l.h | 6 -- cmd/w6l/main.c | 6 -- cmd/w6l/obj.c | 26 ++------- cmd/w6l/out.c | 10 ---- cmd/w6l/pass.c | 17 +----- cmd/w6l/sym.c | 4 +- cmd/wcc/ast.c | 6 +- cmd/wcc/check.c | 9 --- cmd/wcc/err.c | 6 -- cmd/wcc/lex.c | 20 +------ cmd/wcc/mem.c | 12 ++-- cmd/wcc/parse.c | 20 ++----- cmd/wcc/sym.c | 54 +++++------------- cmd/wcc/tok.c | 10 +--- cmd/wcc/type.c | 10 +--- cmd/wcc/ww.h | 14 ----- cmd/ww/main.c | 97 +++++++++++---------------------- cmd/wwdump/main.c | 11 ++-- internal/wwpackage/package.ww | 1 - selfhost/cmd/w6a/asm.ww | 42 +++----------- selfhost/cmd/w6a/lex.ww | 10 +--- selfhost/cmd/w6a/main.ww | 9 +-- selfhost/cmd/w6a/obj.ww | 28 ++-------- selfhost/cmd/w6a/opcodes.ww | 13 ++--- selfhost/cmd/w6a/parse.ww | 28 ++-------- selfhost/cmd/w6c/main.ww | 12 +--- selfhost/cmd/w6l/dyn.ww | 23 +------- selfhost/cmd/w6l/dynout.ww | 32 ++--------- selfhost/cmd/w6l/main.ww | 13 +---- selfhost/cmd/w6l/obj.ww | 53 ++++-------------- selfhost/cmd/w6l/out.ww | 20 ++----- selfhost/cmd/w6l/pass.ww | 9 +-- selfhost/cmd/w6l/sym.ww | 6 +- selfhost/cmd/wcc/cgen.ww | 88 ++++-------------------------- selfhost/cmd/wcc/cgendecl.ww | 19 ------- selfhost/cmd/wcc/cgenexpr.ww | 26 --------- selfhost/cmd/wcc/cgenstmt.ww | 38 +------------ selfhost/cmd/wcc/cgenutil.ww | 39 ++----------- selfhost/cmd/wcc/check.ww | 52 +----------------- selfhost/cmd/wcc/err.ww | 7 +-- selfhost/cmd/wcc/wwi.ww | 35 ++++-------- selfhost/cmd/ww/main.ww | 100 +++++----------------------------- selfhost/cmd/wwdump/main.ww | 21 ++----- 60 files changed, 232 insertions(+), 1045 deletions(-) diff --git a/cmd/w6a/a.h b/cmd/w6a/a.h index 32c4cf40..1febfe13 100644 --- a/cmd/w6a/a.h +++ b/cmd/w6a/a.h @@ -1,10 +1,7 @@ /* - * a.h — w6a-private header. Modelled on Plan 9 cmd/6a/a.h, trimmed - * to the instruction subset that w6c emits. - * - * w6a is line-oriented and has no preprocessor: each non-blank, non- - * label line is one instruction. We read the whole file into a list - * of `Aprog`s, then encode and emit ELF64. + * Modelled on Plan 9 cmd/6a/a.h, trimmed to the instruction subset + * that w6c emits. Line-oriented, no preprocessor: each non-blank, + * non-label line is one instruction. */ #ifndef SIX_A_H #define SIX_A_H @@ -69,17 +66,14 @@ struct Areloc { }; struct Asm { - /* parser state */ const char *file; const char *src; u64 srclen; u64 pos; int line; - /* program list */ Aprog *head, *tail; - /* output text section */ u8 *text; u64 textcap, textlen; @@ -89,7 +83,6 @@ struct Asm { u8 *data; u64 datacap, datalen; - /* symbols */ Asym *syms; Areloc *relocs; diff --git a/cmd/w6a/asm.c b/cmd/w6a/asm.c index 035a95cb..81a59a3d 100644 --- a/cmd/w6a/asm.c +++ b/cmd/w6a/asm.c @@ -1,8 +1,4 @@ /* - * asm.c — encode the parsed Aprog list into amd64 machine bytes, - * appending to Asm.text. Relocations for CALL/branch targets that - * resolve to externals are queued in Asm.relocs. - * * Encoding subset: the instructions cgen emits today. Operand shapes * we accept: * MOVQ $imm, reg — C7 /0 imm32 (REX.W) [imm fits in i32] @@ -95,8 +91,6 @@ a_addreloc_data(Asm *a, u64 off, int kind, Asym *s, i64 add) a->relocs = r; } -/* ------ register codes ------------------------------------------- */ - /* low 3 bits of register encoding */ static int rcode(int r) @@ -137,14 +131,12 @@ is_xmm(int r) return r >= D_X0 && r <= D_X15; } -/* ModR/M byte */ static u8 modrm(int mod, int reg, int rm) { return (u8)(((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)); } -/* emit REX with W=1 plus optional R/B for high regs */ static void emit_rex(Asm *a, int regbit, int rmbit, int w) { @@ -155,9 +147,7 @@ emit_rex(Asm *a, int regbit, int rmbit, int w) if (b != 0x40 || w) a_emit_byte(a, b); } -/* encode mod/disp for [base+disp]; returns 0 on ok. - * Special-cases SP (needs SIB) and BP (forces disp). - */ +/* Special-cases SP (needs SIB) and BP (forces disp). */ static void emit_modrm_mem(Asm *a, int reg_field, int base, i64 disp) { @@ -191,7 +181,6 @@ encode_rr(Asm *a, u8 opcode, int src, int dst) a_emit_byte(a, modrm(3, rcode(src), rcode(dst))); } -/* MOVQ src reg → mem(base, disp). opcode = 0x89 */ static void encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp) { @@ -200,7 +189,6 @@ encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp) emit_modrm_mem(a, rcode(src_reg), base, disp); } -/* MOVQ mem(base, disp) → reg. opcode = 0x8B */ static void encode_mr(Asm *a, u8 opcode, int dst_reg, int base, i64 disp) { @@ -250,7 +238,7 @@ sse_mr_load(Asm *a, u8 prefix, u8 op2, int reg_op, int base, i64 disp) emit_modrm_mem(a, rcode(reg_op), base, disp); } -/* like sse_mr_load but encoded with REX.W (used by CVTTSD2SI / CVTSI2SD +/* like sse_rr but encoded with REX.W (used by CVTTSD2SI / CVTSI2SD * which target/source 64-bit integer regs) */ static void sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op) @@ -262,8 +250,6 @@ sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op) a_emit_byte(a, modrm(3, rcode(reg_op), rcode(rm_op))); } -/* ------ second-pass helper: resolve labels to addresses ---------- */ - static u64 resolve_label(Asm *a, const char *name) { @@ -281,8 +267,6 @@ label_defined(Asm *a, const char *name) return 0; } -/* ------ first pass: encode ---------------------------------------- */ - /* For local labels, we record a "fixup" — an offset in .text that * needs to be patched once the label is resolved at end of pass. */ typedef struct Fixup Fixup; @@ -307,10 +291,9 @@ int a_encode(Asm *a) { fixups = NULL; - const char *cur_text = NULL; /* current TEXT name */ + const char *cur_text = NULL; (void)cur_text; for (Aprog *p = a->head; p; p = p->link) { - /* Define any pending label at the current PC */ if (p->label) { Asym *s = a_intern(a, p->label); s->defined = 1; @@ -800,7 +783,6 @@ a_encode(Asm *a) /* R_X86_64_PLT32 (4); addend -4 */ a_addreloc(a, reloff, 4, s, -4); } else if (p->to.type == D_BRANCH) { - /* local call to a label */ a_emit_byte(a, 0xE8); add_fixup(a->textlen, p->to.sym); a_emit_u32(a, 0); @@ -846,7 +828,6 @@ a_encode(Asm *a) } } - /* second pass: patch fixups */ for (Fixup *f = fixups; f; f = f->next) { if (!label_defined(a, f->label)) { fprintf(stderr, "w6a: undefined label '%s'\n", f->label); diff --git a/cmd/w6a/lex.c b/cmd/w6a/lex.c index 8b4c1619..68e46315 100644 --- a/cmd/w6a/lex.c +++ b/cmd/w6a/lex.c @@ -1,8 +1,3 @@ -/* - * lex.c — character-level helpers for w6a's line-oriented parser. - * The parser itself lives in parse.c; here we keep the tokenisers - * for identifiers and numbers so parse.c stays focused on syntax. - */ #include "a.h" #include #include diff --git a/cmd/w6a/main.c b/cmd/w6a/main.c index 55343226..fdc2072c 100644 --- a/cmd/w6a/main.c +++ b/cmd/w6a/main.c @@ -1,6 +1,3 @@ -/* - * w6a — amd64 assembler driver. Read .s, parse, encode, emit ELF .o. - */ #include "a.h" #include #include diff --git a/cmd/w6a/obj.c b/cmd/w6a/obj.c index 3088ece5..81dcf917 100644 --- a/cmd/w6a/obj.c +++ b/cmd/w6a/obj.c @@ -1,6 +1,4 @@ /* - * obj.c — emit a tiny ELF64 relocatable object. - * * Layout (in file order): * [0] ELF header * [1] Section .text (program bytes) @@ -24,7 +22,6 @@ #include #include -/* ELF constants */ #define ELFMAG "\x7f""ELF" #define ELFCLASS64 2 #define ELFDATA2LSB 1 @@ -54,7 +51,6 @@ #define R_X86_64_PLT32 4 #define ELF64_R_INFO(s,t) (((u64)(s) << 32) | ((u64)(t) & 0xffffffff)) -/* growable byte buffer */ typedef struct Buf Buf; struct Buf { u8 *p; size_t n, cap; }; @@ -157,10 +153,9 @@ a_emit_elf(Asm *a, FILE *f) bput(&sym, &z, sizeof z); } - /* Build symbols (defined = global; undefined = global UND). Data - * symbols carry STT_OBJECT and st_shndx=SH_DATA; everything else - * keeps the legacy STT_FUNC/SH_TEXT shape so non-DATAW outputs - * stay byte-identical. */ + /* Data symbols carry STT_OBJECT and st_shndx=SH_DATA; everything + * else keeps the legacy STT_FUNC/SH_TEXT shape so non-DATAW + * outputs stay byte-identical. */ int idx = 1; for (Asym *s = a->syms; s; s = s->next) { Sym64 e = {0}; @@ -193,7 +188,6 @@ a_emit_elf(Asm *a, FILE *f) bput(r->section == 1 ? &relad : &rela, &re, sizeof re); } - /* Layout offsets in the file */ u64 off = sizeof(Ehdr); u64 off_text = off; off += a->textlen; u64 off_rela = off; off += rela.n; @@ -202,7 +196,6 @@ a_emit_elf(Asm *a, FILE *f) u64 off_sym = off; off += sym.n; u64 off_str = off; off += str.n; u64 off_shstr= off; off += shstr.n; - /* align to 8 */ while (off % 8) off++; u64 off_shdr = off; const int NSECT = has_data diff --git a/cmd/w6a/parse.c b/cmd/w6a/parse.c index da1259ab..cd254d93 100644 --- a/cmd/w6a/parse.c +++ b/cmd/w6a/parse.c @@ -1,6 +1,4 @@ /* - * parse.c — line-oriented parser for the asm subset emitted by w6c. - * * Grammar: * line := blank | comment | label | text | instr * blank := /^\s*$/ @@ -52,7 +50,6 @@ a_intern(Asm *a, const char *name) return s; } -/* ------------------------------------------------------------------ */ /* line iterator: returns the next line as a NUL-terminated buffer in * line/llen pointers, advances pos. Returns 0 on EOF. */ @@ -70,7 +67,6 @@ nextline(Asm *a, char **line, size_t *llen, char *buf, size_t bufsz) return 1; } -/* skip leading whitespace */ static const char * skipws(const char *p) { @@ -168,7 +164,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out) return 0; } - /* (REG) form */ if (*s == '(') { s++; char rbuf[8] = {0}; @@ -183,7 +178,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out) return 0; } - /* number(REG) form, or label form, or REG */ const char *p = s; int sign = 1; if (*p == '-') { sign = -1; p++; } @@ -208,7 +202,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out) return 0; } - /* IDENT — register or symbol-or-label */ if (a_isidstart((unsigned char)*s)) { char buf[256] = {0}; int n = 0; @@ -226,7 +219,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out) s = end; } - /* ID(SB) means external symbol */ if (*s == '(') { char rbuf[8] = {0}; int rn = 0; @@ -253,7 +245,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out) out->type = r; return 0; } - /* otherwise it's a branch target */ out->type = D_BRANCH; out->sym = strdup(buf); return 0; @@ -281,7 +272,6 @@ a_parse(Asm *a) continue; } - /* label? */ if (a_isidstart((unsigned char)*p) && line[0] != '\t') { const char *q = p; while (a_isidcont((unsigned char)*q)) q++; @@ -308,7 +298,6 @@ a_parse(Asm *a) } } - /* TEXT or instruction */ const char *m = p; char mnem[16] = {0}; int n = 0; @@ -332,7 +321,6 @@ a_parse(Asm *a) const char *rest = m; if (op == A_TEXT) { - /* TEXT name,$framesize */ char nbuf[256] = {0}; int nn = 0; while (*m && *m != ',' && nn < 255) nbuf[nn++] = *m++; @@ -363,7 +351,6 @@ a_parse(Asm *a) prg->nbytes = 0; } else { m++; - /* parse escapes into a fresh buffer */ size_t cap = 32, len = 0; u8 *buf = malloc(cap); while (*m && *m != '"') { @@ -398,7 +385,6 @@ a_parse(Asm *a) prg->nbytes = len; } } else { - /* split rest at top-level comma */ const char *comma = NULL; for (const char *q = rest; *q; q++) if (*q == ',' && comma == NULL) comma = q; diff --git a/cmd/w6c/6.out.h b/cmd/w6c/6.out.h index f4ad071d..d4e1a128 100644 --- a/cmd/w6c/6.out.h +++ b/cmd/w6c/6.out.h @@ -114,7 +114,7 @@ enum { A_LAST }; -const char *anames(int); /* opcode -> mnemonic */ -const char *rnames(int); /* register -> name */ +const char *anames(int); +const char *rnames(int); #endif diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index ec1637e8..ab987cf0 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1366,9 +1366,9 @@ let_emit_size(Type *t) } } -/* Is the unwrapped type a str? Used by the load/store paths so the - * (AX, BX) pair convention is preserved for str globals, mirroring - * what we already do for str locals. */ +/* Used by the load/store paths so the (AX, BX) pair convention is + * preserved for str globals, mirroring what we already do for str + * locals. */ static int let_isstr(Type *t) { @@ -1377,8 +1377,8 @@ let_isstr(Type *t) return u && u->kind == TY_STR; } -/* Is the unwrapped type a slice? Slice globals flow as the (AX, BX, - * CX) triple — same as the local ABI. */ +/* Slice globals flow as the (AX, BX, CX) triple — same as the local + * ABI. */ static int let_isslice(Type *t) { @@ -1387,9 +1387,9 @@ let_isslice(Type *t) return u && u->kind == TY_SLICE; } -/* Is the unwrapped type a struct? Struct globals only support field - * access (read + plain `=` write for scalar fields). Whole-struct - * by-value flow through expressions isn't wired. */ +/* Struct globals only support field access (read + plain `=` write + * for scalar fields). Whole-struct by-value flow through expressions + * isn't wired. */ static int let_isstruct(Type *t) { @@ -1398,9 +1398,8 @@ let_isstruct(Type *t) return u && u->kind == TY_STRUCT; } -/* Is the unwrapped type a fixed-length array? Array globals are - * zero-init DATAW slots; cgindex addresses them as LEAQ name(SB) - * and lets the element load/store run as usual. */ +/* Array globals are zero-init DATAW slots; cgindex addresses them as + * LEAQ name(SB) and lets the element load/store run as usual. */ static int let_isarray(Type *t) { @@ -1409,9 +1408,9 @@ let_isarray(Type *t) return u && u->kind == TY_ARRAY; } -/* Is the unwrapped type a float (f32 or f64)? Float globals flow - * through X0 — load/store goes LEAQ name(SB),CX → MOVSS/MOVSD via the - * indirect, since the asm has no D_EXTERN form for SSE moves yet. */ +/* Float globals flow through X0 — load/store goes LEAQ name(SB),CX → + * MOVSS/MOVSD via the indirect, since the asm has no D_EXTERN form + * for SSE moves yet. */ static int let_isfloat(Type *t) { @@ -1722,7 +1721,6 @@ let_var_type(const char *name) return NULL; } -/* Glue `.` into a fresh arena buffer. */ static const char * mod_join(Cg *c, const char *mod, const char *ident) { @@ -1944,9 +1942,6 @@ cgslicehdr(Cg *c, int base) else if (base == D_AX) ins2(c, A_MOVQ, amem(base, 0), areg(D_AX)); } -/* ------------------------------------------------------------------ */ -/* per-fn local table: name → stack offset (positive = below FP) */ - typedef struct Local Local; struct Local { const char *name; @@ -2074,8 +2069,7 @@ cg_base_cap(Cg *c, Node *base, Type *bu, Local *locals, int dst) return 0; } -/* ------------------------------------------------------------------ */ -/* expressions: result lands in AX. Returns 1 on success. */ +/* expressions: result lands in AX. */ static void cgexpr(Cg*, Node*, Local*); static void cgstmt(Cg*, Node*, Local**, int*); @@ -4678,7 +4672,6 @@ cgexpr(Cg *c, Node *n, Local *locals) } } if (amped) break; - /* Fall through to silent-drop fallback below. */ } if (opnd && opnd->kind == N_INDEX) { /* &base[i] = base + i*esz, no dereference. @@ -5436,7 +5429,6 @@ cgexpr(Cg *c, Node *n, Local *locals) } after_dot_assign: if (u && u->kind == TY_STRUCT) { - /* find field metadata */ Tfield *f = NULL; for (Tfield *fl = u->fields; fl; fl = fl->next) if (strcmp(fl->name, n->lhs->str) == 0) @@ -5725,7 +5717,6 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } - /* now store AX into target */ if (via_ptr) { if (boff == 0 && let_islet(base->str)) { /* #47 (inverse): a GLOBAL *struct @@ -15147,7 +15138,6 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) baseoff = localoff(c, locals, bname, 8, frame); } - /* allocate per-name slots */ struct { int off, sz, foff; Type *ftype; } binds[8] = {0}; int nbinds = 0; if (destruct) { @@ -15733,7 +15723,6 @@ cgfn(Cg *c, FILE *out, Node *fn) { if (fn->body == NULL) return; /* extern decl, no body */ - /* fresh per-fn state */ c->head = c->tail = NULL; c->fnname = fn->str; c->cur_mod = (fn->module && fn->module[0]) ? fn->module : NULL; @@ -15788,7 +15777,6 @@ cgfn(Cg *c, FILE *out, Node *fn) text->from.offset = 0; /* framesize patched below */ emit(c, text); - /* prologue */ ins1(c, A_PUSHQ, areg(D_BP)); ins2(c, A_MOVQ, areg(D_SP), areg(D_BP)); Prog *subsp = newprog(c, A_SUBQ); diff --git a/cmd/w6c/gc.h b/cmd/w6c/gc.h index f8799405..595d50fe 100644 --- a/cmd/w6c/gc.h +++ b/cmd/w6c/gc.h @@ -1,7 +1,4 @@ -/* - * gc.h — w6c-private header: Prog/Adr structs, scratch register set, - * stack-frame state. Plan 9 cmd/6c/gc.h shape, trimmed. - */ +/* Plan 9 cmd/6c/gc.h shape, trimmed. */ #ifndef SIX_GC_H #define SIX_GC_H @@ -73,9 +70,7 @@ void emit(Cg*, Prog*); /* txt.c */ void txt_emit(FILE*, Prog *head); -/* wwi.c — `.wwi` export-data producer (w6c -I). M2 dead-code: writes a - * re-parseable ww-prototype rendering of the package's exported surface. - * Returns non-zero if check_exported_type rejects a dangling export. */ +/* wwi.c — non-zero return: check_exported_type rejected a dangling export. */ int wwi_emit(Checker *c, FILE *of, Node *file); /* swt.c, peep.c, reg.c — placeholders for now */ diff --git a/cmd/w6c/main.c b/cmd/w6c/main.c index 30213857..dc0fdd9c 100644 --- a/cmd/w6c/main.c +++ b/cmd/w6c/main.c @@ -1,9 +1,3 @@ -/* - * w6c — amd64 compiler driver. Reads a .ww source file, runs the - * libwcc frontend (lex → parse → check), then walks the typed AST - * via cgen.c and writes Plan 9-flavoured amd64 asm to stdout (or - * the file given by -o). - */ #include "gc.h" #include #include diff --git a/cmd/w6c/peep.c b/cmd/w6c/peep.c index 837a2f52..9b74c2cb 100644 --- a/cmd/w6c/peep.c +++ b/cmd/w6c/peep.c @@ -1,7 +1,7 @@ /* - * peep.c — peephole pass. Currently a no-op; reserved for the kind of - * cleanup Plan 9 6c does (folding adjacent moves, removing redundant - * compares). Wire in `peephole(c)` from cgen.c after the main walk. + * Currently a no-op; reserved for the kind of cleanup Plan 9 6c does + * (folding adjacent moves, removing redundant compares). Wire in + * `peephole(c)` from cgen.c after the main walk. */ #include "gc.h" diff --git a/cmd/w6c/reg.c b/cmd/w6c/reg.c index 48bd95ba..340c6b81 100644 --- a/cmd/w6c/reg.c +++ b/cmd/w6c/reg.c @@ -1,8 +1,8 @@ /* - * reg.c — register allocator. The current cgen pins everything to AX - * with BX as a scratch top-of-stack — no real allocation. This file - * is the seam where a linear-scan or graph-colouring pass would land - * later; today it's empty. + * The current cgen pins everything to AX with BX as a scratch + * top-of-stack — no real allocation. This file is the seam where a + * linear-scan or graph-colouring pass would land later; today it's + * empty. */ #include "gc.h" diff --git a/cmd/w6c/swt.c b/cmd/w6c/swt.c index df8fe5d8..a4347072 100644 --- a/cmd/w6c/swt.c +++ b/cmd/w6c/swt.c @@ -1,7 +1,7 @@ /* - * swt.c — switch-statement lowering. Stub for now: cgen falls - * through to a no-op for N_SWITCH. When we add a real lowering, it - * will live here, mirroring Plan 9 6c's pswt.c. + * Stub for now: cgen falls through to a no-op for N_SWITCH. When we + * add a real lowering, it will live here, mirroring Plan 9 6c's + * pswt.c. */ #include "gc.h" diff --git a/cmd/w6c/txt.c b/cmd/w6c/txt.c index 0942dae7..58d13e66 100644 --- a/cmd/w6c/txt.c +++ b/cmd/w6c/txt.c @@ -1,6 +1,4 @@ /* - * txt.c — print a Prog list as Plan 9-flavoured amd64 asm text. - * * Format we emit (and that w6a expects): * TEXT name * MOVQ $1, AX diff --git a/cmd/w6c/wwi.c b/cmd/w6c/wwi.c index 2d3877e6..2a0b5b04 100644 --- a/cmd/w6c/wwi.c +++ b/cmd/w6c/wwi.c @@ -35,11 +35,11 @@ wwi_primary(Node *n) return n && n->imported == 0; } -/* --- check_exported_type (drew) ------------------------------------- * - * Resolve an N_TNAME to its type sym WITHOUT the side effects of - * resolve_typename (no on-demand resolve, no double "unknown type" - * error). A primitive/keyword resolves to no SK_TYPE → leaf. By the - * time the producer runs, check_file has finished and c->cur == c->top. +/* check_exported_type (drew): resolve an N_TNAME to its type sym WITHOUT + * the side effects of resolve_typename (no on-demand resolve, no double + * "unknown type" error). A primitive/keyword resolves to no SK_TYPE → + * leaf. By the time the producer runs, check_file has finished and + * c->cur == c->top. */ static Sym * wwi_typesym(Checker *c, const char *nm) @@ -141,7 +141,7 @@ wwi_check_decl(Checker *c, Node *d) return bad; } -/* --- type-expr + const-expr unparser (rob §2.2/§2.4) ---------------- */ +/* type-expr unparse per rob §2.2; const-expr per rob §2.4. */ static void wwi_expr(FILE *of, Node *e); static void wwi_type(FILE *of, Node *t); @@ -447,7 +447,7 @@ wwi_decl(FILE *of, Node *d) } } -/* --- deterministic ordering (rob §3) -------------------------------- */ +/* deterministic ordering per rob §3. */ struct declent { Node *d; int idx; }; struct useent { const char *path; int idx; }; diff --git a/cmd/w6l/dyn.c b/cmd/w6l/dyn.c index ce33620a..c21baccd 100644 --- a/cmd/w6l/dyn.c +++ b/cmd/w6l/dyn.c @@ -1,11 +1,6 @@ /* - * dyn.c — load a shared object (ET_DYN) so the linker knows which - * symbols it exports and which DT_NEEDED entry to record. We do not - * pull bytes from the .so; the dynamic loader maps it at runtime. - * - * Each call appends one Lso to lnk->sos. `l_so_provides` answers - * "does this .so export the named symbol?" — l_resolve uses that to - * promote unresolved references to dynamic. + * We do not pull bytes from the .so; the dynamic loader maps it at + * runtime. */ #include "l.h" #include diff --git a/cmd/w6l/dynout.c b/cmd/w6l/dynout.c index 355889cb..f210214f 100644 --- a/cmd/w6l/dynout.c +++ b/cmd/w6l/dynout.c @@ -1,11 +1,7 @@ /* - * dynout.c — emit a dynamic-linked ELF executable. - * * The shape we produce is the simplest valid one: PT_INTERP + * PT_DYNAMIC + DT_BIND_NOW so the loader resolves every PLT slot at - * startup (no lazy binding, no PLT0 trampoline). Symbol versioning - * is omitted; modern glibc tolerates unversioned references by - * binding to each symbol's "default" version. SysV .hash, not + * startup (no lazy binding, no PLT0 trampoline). SysV .hash, not * .gnu.hash. Non-PIE, fixed base. * * File layout: @@ -31,7 +27,6 @@ #include #include -/* ELF constants */ #define ET_EXEC 2 #define EM_X86_64 62 #define EV_CURRENT 1 @@ -127,7 +122,6 @@ elf_hash(const char *name) return h; } -/* Patch a 4-byte little-endian field in `buf` at offset `off`. */ static void poke32(u8 *buf, u64 off, u32 v) { @@ -151,8 +145,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) const int N = l->dyn_n; - /* ---- Pass 1: collect dynamic symbol names + .dynstr layout ---- */ - /* dynstr layout: [0]='\0', then DT_NEEDED soname strings, then * one symbol name per dynamic Lsym. We index dyn syms by * plt_idx (assigned in l_resolve). Build an array sorted by @@ -189,7 +181,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) } } - /* Build .dynstr in a growable buffer. */ u8 *dynstr = NULL; u64 dynstr_cap = 0, dynstr_len = 0; #define DSTR_PUT(s) do { \ @@ -216,9 +207,7 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) DSTR_PUT(dynsyms[i]->name); } - /* ---- Versioning: group dyn syms by (lib, version) ---- - * - * For every sym whose dyn_version is non-NULL, there's a + /* For every sym whose dyn_version is non-NULL, there's a * Vernaux record under that lib's Verneed. The vna_other * value (assigned starting at 2; 1 is reserved for "global, * unversioned") becomes that sym's .gnu.version entry. @@ -263,13 +252,11 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) n_vlibs++; } - /* Assign vna_other indices starting at 2. */ u16 next_vna = 2; for (int i = 0; i < n_vlibs; i++) for (int k = 0; k < vlibs[i].n_versions; k++) vlibs[i].versions[k].vna_other = next_vna++; - /* Add version name strings to .dynstr. */ for (int i = 0; i < n_vlibs; i++) { for (int k = 0; k < vlibs[i].n_versions; k++) { vlibs[i].versions[k].dynstr_off = (u32)dynstr_len; @@ -301,8 +288,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) } } - /* ---- Pass 2: compute byte sizes of every section ---- */ - const u64 ehdr_sz = sizeof(Ehdr); const int n_phdrs = 4; const u64 phdr_sz = (u64)n_phdrs * sizeof(Phdr); @@ -343,8 +328,7 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) const u64 ndyn = (u64)nsos + 11 + (with_ver ? 3 : 0); const u64 dynamic_sz = ndyn * sizeof(Dyn64); - /* ---- Pass 3: assign file offsets and virtual addresses ---- - * Everything from the Ehdr through .text+.plt is in the R+X + /* Everything from the Ehdr through .text+.plt is in the R+X * load segment at base+0..text_end. .got.plt and .dynamic land * in the R+W segment at the next page boundary. */ @@ -409,9 +393,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) const u64 data_file_len = l->datalen - bsslen; const u64 file_data_end = data_off + data_file_len; - /* ---- Pass 4: build each section into a buffer ---- */ - - /* .dynsym */ Sym64 *dynsym = calloc((size_t)nsyms_total, sizeof *dynsym); for (int i = 0; i < N; i++) { Sym64 *e = &dynsym[1 + i]; @@ -438,7 +419,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) } /* elf_hash is also used by .gnu.version_r for vna_hash below. */ - /* .rela.plt */ Rela64 *relaplt = calloc((size_t)N, sizeof *relaplt); for (int i = 0; i < N; i++) { relaplt[i].r_offset = gotplt_va + (3 + (u64)i) * 8; @@ -522,7 +502,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) } /* [3..3+N-1] left zero; loader fills via R_X86_64_JUMP_SLOT. */ - /* .dynamic */ Dyn64 *dynamic = calloc((size_t)ndyn, sizeof *dynamic); { int k = 0; @@ -559,9 +538,9 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) } } - /* ---- Pass 5: patch .text relocations targeting dynamic syms --- - * The site is the existing PC32/PLT32 displacement field. Target - * is the address of the symbol's PLT stub. */ + /* Patch .text relocations targeting dynamic syms: the site is + * the existing PC32/PLT32 displacement field, the target the + * address of the symbol's PLT stub. */ for (Lrel *r = l->rels; r; r = r->next) { if (r->sym == NULL || !r->sym->is_dyn) continue; if (r->kind != R_X86_64_PC32 && r->kind != R_X86_64_PLT32) { @@ -579,8 +558,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) poke32(l->text, r->off, (u32)(i32)disp); } - /* ---- Pass 6: emit ---- */ - Ehdr eh = {0}; memcpy(eh.e_ident, "\x7f""ELF", 4); eh.e_ident[4] = ELFCLASS64; @@ -625,7 +602,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) ph[1].p_memsz = file_end - gotplt_off; ph[1].p_align = page; - /* PT_INTERP. */ ph[2].p_type = PT_INTERP; ph[2].p_flags = PF_R; ph[2].p_offset = interp_off; @@ -635,7 +611,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) ph[2].p_memsz = interp_sz; ph[2].p_align = 1; - /* PT_DYNAMIC. */ ph[3].p_type = PT_DYNAMIC; ph[3].p_flags = PF_R | PF_W; ph[3].p_offset = dynamic_off; @@ -648,7 +623,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry) fwrite(&eh, 1, sizeof eh, f); fwrite(ph, 1, sizeof ph, f); - /* helper: pad to absolute offset `to` */ #define PAD_TO(to) do { \ long _here = ftell(f); \ for (long _i = _here; _i < (long)(to); _i++) fputc(0, f); \ diff --git a/cmd/w6l/l.h b/cmd/w6l/l.h index 99e4dcbd..3c393e3e 100644 --- a/cmd/w6l/l.h +++ b/cmd/w6l/l.h @@ -1,9 +1,3 @@ -/* - * l.h — w6l-private header. Loads relocatable ELF64 .o files (the - * format produced by w6a) and links them into a static executable. - * - * No archives yet (phase 8). No dynamic linking ever. - */ #ifndef SIX_L_H #define SIX_L_H diff --git a/cmd/w6l/main.c b/cmd/w6l/main.c index c85bd293..ad964604 100644 --- a/cmd/w6l/main.c +++ b/cmd/w6l/main.c @@ -1,9 +1,4 @@ /* - * w6l — amd64 linker. Reads relocatable ELF .o files (from w6a) plus - * .a archives, resolves, relocates, writes a static ELF executable. - * Dynamic linking against .so files is the next increment; the -L/-l - * flag plumbing here is its first step. - * * w6l -o out [-L...] [-l...] file1.o file2.o ... * * The first symbol named "_start" defined among the inputs becomes @@ -134,7 +129,6 @@ main(int argc, char **argv) int rc = l_emit_elf(&l, f, base, base + 0x1000 + entry->val); fclose(f); if (rc == 0) { - /* chmod +x */ char cmd[1024]; snprintf(cmd, sizeof cmd, "chmod +x %s", out); (void)system(cmd); diff --git a/cmd/w6l/obj.c b/cmd/w6l/obj.c index 389a068a..f56484d1 100644 --- a/cmd/w6l/obj.c +++ b/cmd/w6l/obj.c @@ -1,9 +1,3 @@ -/* - * obj.c — load an ELF64 relocatable object emitted by w6a, append its - * .text bytes to the combined image, and pull its symbols and - * relocations into the global tables (with offsets adjusted to the - * combined section). - */ #include "l.h" #include #include @@ -97,11 +91,8 @@ emit_data(Lnk *l, const u8 *src, u64 n) l->datalen += n; } -/* Internal: load a single ELF .o image already in memory. The caller - * gives us the bytes (we own them) and a path tag for diagnostics. - * If the bytes look like an archive (magic "!\n") we recurse - * over each member instead. - */ +/* The caller gives us the bytes (we own them) and a path tag for + * diagnostics. */ static int load_image(Lnk *l, const char *path, u8 *buf, u64 len); static u64 @@ -119,9 +110,8 @@ ar_field(const u8 *p, int n) /* Read an ELF .o image's globally-defined symbol names without * actually appending it to the link. Returns a heap-allocated - * NULL-terminated array; caller frees the array (not the strings, - * which point into the .o image and must remain alive). - */ + * NULL-terminated array; caller frees the array, not the strings, + * which point into the .o image and need it kept alive. */ static char ** elf_globals(const u8 *buf, u64 len) { @@ -299,8 +289,6 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len) if (eh->e_shstrndx >= eh->e_shnum) { free(buf); return -1; } const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset); - /* find .text, .data (optional), .symtab, .strtab, .rela.text, - * .rela.data (optional) */ int idx_text = -1, idx_data = -1, idx_symtab = -1, idx_strtab = -1; int idx_rela = -1, idx_relad = -1; for (u16 i = 0; i < eh->e_shnum; i++) { @@ -334,12 +322,10 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len) ob->next = l->objs; l->objs = ob; - /* append .text and (if present) .data */ emit_text(l, buf + sh[idx_text].sh_offset, sh[idx_text].sh_size); if (idx_data >= 0 && sh[idx_data].sh_size > 0) emit_data(l, buf + sh[idx_data].sh_offset, sh[idx_data].sh_size); - /* per-object: load symbols */ Sym64 *symtab = (Sym64 *)(buf + sh[idx_symtab].sh_offset); u64 nsyms = sh[idx_symtab].sh_size / sizeof(Sym64); const char *str = (const char *)(buf + sh[idx_strtab].sh_offset); @@ -373,7 +359,6 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len) map[i] = gs; } - /* per-object: collect relocations */ if (idx_rela >= 0) { Rela64 *rt = (Rela64 *)(buf + sh[idx_rela].sh_offset); u64 nrel = sh[idx_rela].sh_size / sizeof(Rela64); @@ -389,8 +374,7 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len) l->rels = r; } } - /* per-object: collect data relocations from .rela.data. The - * .data section in the .o starts at a per-object 0; we shift + /* The .data section in the .o starts at a per-object 0; we shift * by ob->data_off so r->off indexes the combined .data buffer. */ if (idx_relad >= 0) { Rela64 *rt = (Rela64 *)(buf + sh[idx_relad].sh_offset); diff --git a/cmd/w6l/out.c b/cmd/w6l/out.c index 80dcab6f..fa4c17ff 100644 --- a/cmd/w6l/out.c +++ b/cmd/w6l/out.c @@ -1,6 +1,4 @@ /* - * out.c — emit a static ELF64 executable. - * * Layout (file order) without .data: * [0..64) ELF header * [64..120) one program header (PT_LOAD R+X) @@ -13,9 +11,6 @@ * [176..0x1000) zero pad * [0x1000..) .text bytes * [data_off..) .data bytes (file offset and vaddr page-aligned) - * - * No interpreter, no dynamic, no .bss yet. Entry point is the address - * of the symbol named "_start" (or whatever main supplies via -e). */ #include "l.h" #include @@ -102,9 +97,6 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry) eh.e_phentsize = sizeof(Phdr); eh.e_phnum = has_data ? 2 : 1; - /* R+X load covering [0, rx_end). When .data is present we still - * round up to a page in memsz so the loader doesn't try to give - * the same page both R+X and R+W permissions. */ Phdr phx = {0}; phx.p_type = PT_LOAD; phx.p_flags = PF_R | PF_X; @@ -131,14 +123,12 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry) fwrite(&phx, 1, sizeof phx, f); if (has_data) fwrite(&phw, 1, sizeof phw, f); - /* pad to text_off */ long here = ftell(f); for (long i = here; i < (long)text_off; i++) fputc(0, f); if (l->textlen) fwrite(l->text, 1, l->textlen, f); if (has_data && data_file_len > 0) { - /* pad to data_off */ here = ftell(f); for (long i = here; i < (long)data_off; i++) fputc(0, f); fwrite(l->data, 1, data_file_len, f); diff --git a/cmd/w6l/pass.c b/cmd/w6l/pass.c index 02dcc093..7adf3923 100644 --- a/cmd/w6l/pass.c +++ b/cmd/w6l/pass.c @@ -1,14 +1,3 @@ -/* - * pass.c — resolution + relocation. After all objects are loaded: - * - * l_resolve : check that every symbol referenced by a relocation - * is defined somewhere. Errors get logged. - * l_relocate: with the final virtual base address known, walk the - * relocation list and patch the .text bytes in place. - * - * Supported relocation kinds: PC32 (2), PLT32 (4). Both are PC-relative - * 32-bit displacements; for static linking PLT32 collapses to PC32. - */ #include "l.h" #include #include @@ -34,7 +23,7 @@ l_resolve(Lnk *l) * across runs (rels are pushed onto the head as objects load). */ for (Lrel *r = l->rels; r; r = r->next) { if (r->sym == NULL || r->sym->defined) continue; - if (r->sym->is_dyn) continue; /* already promoted */ + if (r->sym->is_dyn) continue; for (Lso *so = l->sos; so; so = so->next) { const char *ver = NULL; if (l_so_provides_v(so, r->sym->name, &ver)) { @@ -47,7 +36,6 @@ l_resolve(Lnk *l) } } - /* What remains undefined truly is undefined. */ for (Lrel *r = l->rels; r; r = r->next) { if (r->sym == NULL) continue; if (!r->sym->defined && !r->sym->is_dyn) { @@ -90,7 +78,8 @@ l_relocate(Lnk *l, u64 text_va, u64 data_va) switch (r->kind) { case R_X86_64_PC32: case R_X86_64_PLT32: { - /* PC-relative 32-bit displacement; lands in .text. */ + /* PC-relative 32-bit displacement; lands in .text. + * For static linking PLT32 collapses to PC32. */ u64 site = text_va + r->off; i64 rel = (i64)sym_va - (i64)site + r->addend; patch_u32(l->text + r->off, (u32)(i32)rel); diff --git a/cmd/w6l/sym.c b/cmd/w6l/sym.c index 5f467348..a1e06beb 100644 --- a/cmd/w6l/sym.c +++ b/cmd/w6l/sym.c @@ -1,6 +1,6 @@ /* - * sym.c — global symbol table for the linker. Plain singly-linked - * list; usually a few hundred entries, hashing isn't worth it yet. + * Plain singly-linked list; usually a few hundred entries, hashing + * isn't worth it yet. */ #include "l.h" #include diff --git a/cmd/wcc/ast.c b/cmd/wcc/ast.c index 6358cec0..d54aca5d 100644 --- a/cmd/wcc/ast.c +++ b/cmd/wcc/ast.c @@ -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 diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 0a97c9c0..547a7c24 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -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) { diff --git a/cmd/wcc/err.c b/cmd/wcc/err.c index 97ffb735..6cba0028 100644 --- a/cmd/wcc/err.c +++ b/cmd/wcc/err.c @@ -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" diff --git a/cmd/wcc/lex.c b/cmd/wcc/lex.c index 5b645767..440e9df3 100644 --- a/cmd/wcc/lex.c +++ b/cmd/wcc/lex.c @@ -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); diff --git a/cmd/wcc/mem.c b/cmd/wcc/mem.c index cd3a5caa..f1ccbfd5 100644 --- a/cmd/wcc/mem.c +++ b/cmd/wcc/mem.c @@ -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 @@ -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"); diff --git a/cmd/wcc/parse.c b/cmd/wcc/parse.c index 8c17b3ee..719ded7a 100644 --- a/cmd/wcc/parse.c +++ b/cmd/wcc/parse.c @@ -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 diff --git a/cmd/wcc/sym.c b/cmd/wcc/sym.c index 2b9501a8..de96f9ae 100644 --- a/cmd/wcc/sym.c +++ b/cmd/wcc/sym.c @@ -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 @@ -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. * diff --git a/cmd/wcc/tok.c b/cmd/wcc/tok.c index 15fc4d49..d4bbfd6b 100644 --- a/cmd/wcc/tok.c +++ b/cmd/wcc/tok.c @@ -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 @@ -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 }, diff --git a/cmd/wcc/type.c b/cmd/wcc/type.c index 7d171b1d..5bd8554a 100644 --- a/cmd/wcc/type.c +++ b/cmd/wcc/type.c @@ -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 @@ -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; diff --git a/cmd/wcc/ww.h b/cmd/wcc/ww.h index 932fdcad..d9acfa19 100644 --- a/cmd/wcc/ww.h +++ b/cmd/wcc/ww.h @@ -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; diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 4beaad92..37baccc6 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -1,11 +1,6 @@ /* * ww — the user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue. * - * Pipeline: - * ww build foo.ww → w6c foo.ww > foo.s ; w6a foo.s > foo.o ; - * w6l -o foo foo.o - * ww run foo.ww → build then exec ./foo - * * Tool paths default to siblings of $0 (so a fresh build runs out of * out/bin/), and can be overridden with WW_W6C / WW_W6A / WW_W6L. */ @@ -35,7 +30,7 @@ static const char *usage = " lib/... every package under lib, recursively (test only)\n" " . build the cwd's .ww\n"; -static char *self_dir; /* directory containing this binary */ +static char *self_dir; static const char * toolpath(const char *envvar, const char *name) @@ -125,8 +120,7 @@ exec_package_tests(int argc, char **argv, const char *target, return 1; } -/* Set of imported module paths, kept on the heap. Used to break - * cycles in `use` resolution. Linear because typical imports are +/* Breaks cycles in `use` resolution. Linear because typical imports are * a handful per build. */ struct ImportSet { char **paths; @@ -151,8 +145,7 @@ import_add(struct ImportSet *s, const char *path) s->paths[s->n++] = strdup(path); } -/* Translate dots in an `import` name to slashes for path lookup. - * `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s +/* `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s * use-path → fs-path mapping (ref/hare/hare/module/srcs.ha:78 * builds the same shape via path::push per ident part). */ static void @@ -164,12 +157,11 @@ import_path_form(const char *name, char *out, size_t outsz) out[i] = '\0'; } -/* try // as a directory (want_dir), else /.ww as - * a file. Sets *is_dir on hit. Symmetric with wwstage locatein for - * byte-id driver output (rule 10). The legacy //.ww - * form was dropped in task #22 — directory-as-module enumeration - * replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no - * fallback matching `foo/foo.ha`; a module IS the directory). */ +/* Symmetric with wwstage locatein for byte-id driver output (rule 10). + * The legacy //.ww form was dropped in task #22 — + * directory-as-module enumeration replaces it, mirroring + * ref/hare/hare/module/srcs.ha (Hare has no fallback matching + * `foo/foo.ha`; a module IS the directory). */ static int locate_import_in(const char *dir, const char *path_form, char *out, size_t outsz, int *is_dir, int want_dir) @@ -191,10 +183,7 @@ locate_import_in(const char *dir, const char *path_form, char *out, return 0; } -/* Walk a colon-separated dirlist trying to resolve `path_form`. Returns - * 1 on the first hit and writes the concrete path + dir/file marker. - * - * #98: "a module IS the directory" — a directory-package on ANY entry +/* #98: "a module IS the directory" — a directory-package on ANY entry * wins over a same-named sibling FILE on an EARLIER entry. The driver * builds the searchpath srcd-first; a co-located `lib//_test.ww` * entry makes srcd = lib/, so a self-named `import ` would @@ -231,10 +220,10 @@ locate_import(const char *dirs, const char *path_form, char *out, return 0; } -/* memcmp-based string compare for qsort. Byte-wise total order is - * locale-independent; rule-10 byte-id requires the two stages sort - * the same way. (strcmp would work today but Hare-fidelity points - * at memcmp via ref/hare/sort/cmp/cmp.ha:9.) */ +/* Byte-wise total order is locale-independent; rule-10 byte-id requires + * the two stages sort the same way. strcmp diverges from Hare's memcmp + * (ref/hare/sort/cmp/cmp.ha:9); the order is identical for NUL-free + * filenames. */ static int strs_cmp(const void *a, const void *b) { @@ -266,11 +255,9 @@ file_has_line_test(const char *path) return found; } -/* enumerate_dir_ww — collect production *.ww paths in `dirpath`, excluding - * *_test.ww test sources, then sort byte-wise. A line-leading @test in - * any other source is diagnosed here and returns -2. This is the sole - * directory-membership discovery path; the owning seppkg retains the - * returned list. */ +/* A line-leading @test in a production source is diagnosed here and + * returns -2. This is the sole directory-membership discovery path; the + * owning seppkg retains the returned list. */ static int enumerate_dir_ww(const char *dirpath, char ***out_files) { @@ -310,9 +297,8 @@ enumerate_dir_ww(const char *dirpath, char ***out_files) return n; } -/* ==================================================================== - * ww build — separate-compilation driver (task #46/c3). - * ==================================================================== +/* ww build — separate-compilation driver (task #46/c3). + * * This is the SOLE build path (E3-C1 flip, task #87): the legacy * single-file amalgamator is gone. Each imported * package's `.wwi` interface is materialized and every package is @@ -357,7 +343,6 @@ struct sepgraph { int n; }; -/* Find a package by dotted path, or add it. Returns its index, -1 full. */ static int sep_find_or_add(struct sepgraph *g, const char *path, const char *entry, int is_dir) @@ -395,8 +380,7 @@ sep_graph_free(struct sepgraph *g) free(g); } -/* Sanitize a package's dotted path into a scratch-file basename. Dots - * stay (legal in filenames); the root's empty path becomes "__root". */ +/* Dots stay (legal in filenames); the root's empty path becomes "__root". */ static void sep_fname(const struct sepgraph *g, int pi, const char *scratch, const char *suffix, char *out, size_t outsz) @@ -451,9 +435,8 @@ sep_ident_continue(int c) return sep_ident_start(c) || (c >= '0' && c <= '9'); } -/* Skip the whitespace and comments accepted before and within the leading - * package clause. This is deliberately only the loader's small header - * grammar, not a second compiler lexer. */ +/* Deliberately only the loader's small header grammar for the leading + * package clause, not a second compiler lexer. */ static int sep_skip_space(const char *src, size_t n, size_t *off) { @@ -481,7 +464,6 @@ sep_skip_space(const char *src, size_t n, size_t *off) return 0; } -/* Parse exactly the leading loader grammar `package ident;`. */ static int sep_package_clause(const char *src, size_t n, char *name, size_t namesz) { @@ -509,9 +491,7 @@ sep_package_clause(const char *src, size_t n, char *name, size_t namesz) return 0; } -/* Scan one source file's already-selected bytes for its leading package - * clause (when it is an owned directory source) and top-level imports. A - * DIRECTORY import is a package boundary: add it as a direct dep of pkg +/* A DIRECTORY import is a package boundary: add it as a direct dep of pkg * `pi`. A FILE import is an intra-package split — fold its imports into * `pi` (its bytes join pi's body at emit time). Collects package PATHS * rather than concatenating bytes the way the legacy amalgamator did @@ -715,7 +695,6 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no, return 0; } -/* Mark pi's transitive deps (excluding pi) in inset[]. */ static void sep_mark_deps(struct sepgraph *g, int pi, char *inset) { @@ -881,9 +860,9 @@ archive_o(const char *objpath, const char *apath) return 0; } -/* ---- -w workdir freshness --------------------------------------------- - * A `-w DIR` workdir is a caller-owned persistent package-artifact tree - * that replaces the fresh `.sepwork` scratch. Staleness is pure content +/* -w workdir freshness: a `-w DIR` workdir is a caller-owned persistent + * package-artifact tree that replaces the fresh `.sepwork` scratch. + * Staleness is pure content * identity, never mtime: a package is reused only when its freshly * composed unit byte-equals the committed unit AND the tool copies * recorded in the dir byte-equal the live tools — every decision is @@ -997,7 +976,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out, else if (access("lib", 0) == 0) srcdir = "lib"; else srcdir = libdir; } - /* search path: source-dir, then -I dirs, then srcdir. */ char srcd[1024]; if (entry_is_dir) { snprintf(srcd, sizeof srcd, "%s", src); @@ -1110,7 +1088,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out, } free(stack); - /* producer loop — dep-first, one `w6c -c -I` pass per package. */ for (int oi = 0; oi < norder; oi++) { int pi = order[oi]; char unitf[1024], wwi[1024], asmf[1024], obj[1024], apath[1024]; @@ -1250,7 +1227,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out, char objs[8192] = {0}; for (int oi = norder - 1; oi >= 0; oi--) { char path[1024]; - /* root: positional `.o` (force-load); deps: `.a` (selective). */ sep_fname(g, order[oi], scratch, order[oi] == root ? ".o" : ".a", path, sizeof path); size_t n = strlen(objs); @@ -1345,7 +1321,6 @@ search_path(const char *extra, char *buf, size_t bufsz) return buf; } -/* basename_no_ext: last path segment with any trailing ".ww" stripped. */ static void basename_no_ext(const char *path, char *out, size_t outsz) { @@ -1356,13 +1331,6 @@ basename_no_ext(const char *path, char *out, size_t outsz) if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; } -/* resolve_module: turn a name into a concrete entry path. - * foo.ww → use as-is if it exists - * → returns the dir path (caller dir-enumerates) - * . → cwd as a directory - * foo (bare) → walk cwd:incs:WW_LIB; first hit is dir or file. - * Sets *is_dir on hit. Dir resolution drives directory-as-module - * enumeration in build_one_sep. */ static int resolve_module(const char *name, const char *incs, char *out, size_t outsz, int *is_dir) @@ -1387,12 +1355,11 @@ resolve_module(const char *name, const char *incs, char *out, size_t outsz, return locate_import(sp, path_form, out, outsz, is_dir); } -/* Parse the standard -I/-L/-l/-o flags into incs/libdirs/libs/outpath. The - * first non-flag positional becomes *src_out. Returns the index past the last - * arg consumed for positionals (so callers can pick up trailing args), or -1 - * if a flag is missing its argument (diagnostic already emitted). `cmd` names - * the subcommand for the diagnostic, byte-identical to the wwstage twin's - * per-subcommand wording (selfhost/cmd/ww/main.ww dobuild/dorun). */ +/* Returns the index past the last arg consumed for positionals (so callers + * can pick up trailing args), or -1 if a flag is missing its argument + * (diagnostic already emitted). `cmd` names the subcommand for the + * diagnostic, byte-identical to the wwstage twin's per-subcommand wording + * (selfhost/cmd/ww/main.ww dobuild/dorun). */ static int parse_build_flags(const char *cmd, int argc, char **argv, char *incs, size_t incsz, @@ -1504,7 +1471,7 @@ do_build(int argc, char **argv) outflag, sizeof outflag, workdir, sizeof workdir, &src, &emit_asm) < 0) return 2; - if (src == NULL) src = "."; /* default: build cwd */ + if (src == NULL) src = "."; char resolved[1024]; int is_dir = 0; if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) { @@ -1568,7 +1535,6 @@ do_run(int argc, char **argv) fputs("ww: cannot remove temporary directory\n", stderr); return 1; } - /* exec the built binary with any trailing argv as its argv. */ pid_t pid = fork(); if (pid < 0) { perror("ww: fork"); @@ -1823,7 +1789,6 @@ do_test(int argc, char **argv) "ww test: package options need a directory\n"); return 2; } - /* single .ww file — build, then run unless -c (compile-only). */ char tmpdir[1024] = {0}, tmp[1024]; const char *outp; int owntmp = !outstem[0] && !workdir[0]; diff --git a/cmd/wwdump/main.c b/cmd/wwdump/main.c index 7320042f..5fe3e32a 100644 --- a/cmd/wwdump/main.c +++ b/cmd/wwdump/main.c @@ -1,10 +1,7 @@ /* - * wwdump — deterministic dump tool for ww source. - * - * Reads a .ww file and writes either a token stream or an AST - * s-expression to stdout, byte-for-byte stable across runs. It is the - * diff anchor for self-host: the C-side libwcc and the future ww-side - * frontend must produce the same dump for the same input. + * Diff anchor for self-host: the C-side libwcc and the future ww-side + * frontend must produce the same dump for the same input, byte-for-byte + * stable across runs. * * wwdump -t file.ww tokens, one per line: ":: [val]" * wwdump -a file.ww AST as s-expr, one node per line @@ -70,7 +67,7 @@ dump_ast(const char *src, char *buf, u64 len, FILE *out) int main(int argc, char **argv) { - int mode = 't'; /* tokens by default */ + int mode = 't'; const char *src = NULL; const char *out = NULL; for (int i = 1; i < argc; i++) { diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index 5f4b3118..01c80ce0 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -400,7 +400,6 @@ fn pkgdedup(ss: []str) []str = { return out; }; -// Decimal value, or -1 on empty, non-digit, or overflow past max. fn pkgparsedec(s: str, max: i64) i64 = { if (s.len == 0) { return -1i64; }; let i: i32 = 0; diff --git a/selfhost/cmd/w6a/asm.ww b/selfhost/cmd/w6a/asm.ww index 55721a8c..6f2560b3 100644 --- a/selfhost/cmd/w6a/asm.ww +++ b/selfhost/cmd/w6a/asm.ww @@ -1,13 +1,5 @@ -// selfhost/cmd/w6a/asm.ww — port of cmd/w6a/asm.c. -// -// Encode the parsed aprog list into amd64 machine bytes, appending to -// asm_.text. Relocations for CALL/branch targets that resolve to -// externals are queued in asm_.relocs. -// -// Encoding subset matches what w6c emits — see cmd/w6a/asm.c for the -// authoritative list. Helpers (rcode/rhi/modrm/emitrex etc.) are -// fully ported; encode itself is still a stub pending the full -// switch over A_*. +// Port of cmd/w6a/asm.c. Encoding subset matches what w6c emits — see +// cmd/w6a/asm.c for the authoritative list. package w6a; @@ -16,8 +8,6 @@ import rt; import opcodes; import strings; -// ---- text buffer growth ------------------------------------------------ - export fn emitbyte(a: *asm_, b: u8) void = { if (a.textlen + 1u64 > a.textcap) { let nc: u64 = a.textcap; @@ -45,16 +35,14 @@ export fn addreloc(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { a.relocs = r; }; -// Record a relocation that lives in the .data section. Used by -// DATAR to patch a 64-bit slot with a symbol's runtime VA. obj.ww -// separates these into .rela.data when emitting the .o. +// DATAR patches a 64-bit .data slot with a symbol's runtime VA; +// obj.ww separates section=1 relocs into .rela.data when emitting +// the .o. export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { let r: *areloc = alloc(areloc { off = off, section = 1, kind = kind, asy = s, addend = add, rnext = a.relocs })!; a.relocs = r; }; -// Append one byte to the writable .data buffer. Mirrors emitbyte -// but targets a.data instead of a.text. export fn emitdatabyte(a: *asm_, b: u8) void = { if (a.datalen + 1u64 > a.datacap) { let nc: u64 = a.datacap; @@ -70,8 +58,6 @@ export fn emitdatabyte(a: *asm_, b: u8) void = { a.datalen += 1u64; }; -// ---- register codes ---------------------------------------------------- - // Low 3 bits of register encoding. fn rcode(r: i32) i32 = { if (r == D_AX) { return 0; }; if (r == D_CX) { return 1; }; @@ -105,7 +91,6 @@ fn isxmm(r: i32) bool = { return false; }; -// ModR/M byte builder. fn modrmbyte(mod: i32, reg: i32, rm: i32) u8 = { return (((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)): u8; }; @@ -210,8 +195,6 @@ fn sserrw(a: *asm_, prefix: u8, op2: u8, regop: i32, rmop: i32) void = { emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop))); }; -// ---- label resolution / fixups ---------------------------------------- - fn resolvelabel(a: *asm_, name: str) u64 = { let s: *asym = a.syms; for (s != nil) { @@ -230,8 +213,6 @@ fn labeldefined(a: *asm_, name: str) bool = { return false; }; -// ---- fixup helper ----------------------------------------------------- - fn addfixup(a: *asm_, off: u64, label: str) void = { let f: *afixup = alloc(afixup { off = off, label = label, fnext = a.fixups })!; a.fixups = f; @@ -242,15 +223,9 @@ fn isgpr(t: i32) bool = { return false; }; -// `intern` lives in parse.ww — flat-scope concat lets us call it -// directly without an @symbol declaration here. - -// ---- encode ---------------------------------------------------------- - export fn encode(a: *asm_) i32 = { let p: *aprog = a.head; for (p != nil) { - // Define any pending label at the current PC. if (p.label.len > 0) { let s: *asym = intern(a, p.label); s.defined = 1; @@ -281,10 +256,9 @@ export fn encode(a: *asm_) i32 = { p = p.link; continue; }; if (op == A_DATAW) { - // Writable variant: bytes go into .data instead of - // .text. obj.ww emits the extra section conditionally - // on datalen > 0 so .o output stays byte-identical - // for inputs that don't use DATAW. + // obj.ww emits the .data section conditionally on + // datalen > 0 so .o output stays byte-identical for + // inputs that don't use DATAW. let s: *asym = intern(a, p.to.asym); s.defined = 1; s.isdata = 1; diff --git a/selfhost/cmd/w6a/lex.ww b/selfhost/cmd/w6a/lex.ww index c10190d8..2cfc9c4e 100644 --- a/selfhost/cmd/w6a/lex.ww +++ b/selfhost/cmd/w6a/lex.ww @@ -1,8 +1,4 @@ -// selfhost/cmd/w6a/lex.ww — port of cmd/w6a/lex.c. -// -// Character-level helpers for w6a's line-oriented parser. The parser -// itself is in parse.ww; here we keep tokenisers for identifiers and -// numbers so parse.ww stays focused on syntax. +// Port of cmd/w6a/lex.c. package w6a; @@ -20,10 +16,6 @@ export fn isidcont(c: i32) bool = { return false; }; -// parsenum — read a leading [+-]?[0x|0X|0]?digits from p[0..n-1]. -// Returns (value, consumed). Stops at first non-digit. -// Plain Plan 9-style: $123 / $0x1f / $-7. Decimal default; 0x prefix -// for hex; 0 prefix for octal when followed by a digit (else just 0). export fn parsenum(p: *u8, n: u64) (i64, u64) = { // strtoll(s, end, 0) semantics, matching the C twin cmd/w6a/lex.c:30: // skip leading whitespace, optional sign, base-0 prefix detection diff --git a/selfhost/cmd/w6a/main.ww b/selfhost/cmd/w6a/main.ww index b9c9a2ef..2b879661 100644 --- a/selfhost/cmd/w6a/main.ww +++ b/selfhost/cmd/w6a/main.ww @@ -1,6 +1,4 @@ -// selfhost/cmd/w6a/main.ww — port of cmd/w6a/main.c. -// -// w6a = amd64 assembler. Read .s, parse, encode, emit ELF .o. +// Port of cmd/w6a/main.c. // // w6a_ww -o file.o file.s @@ -33,8 +31,7 @@ fn cstrlen(p: *u8) u64 = { return n; }; -// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style -// callers to lib/os entrypoints (str post-task-#23). +// Bridges argv-style callers to lib/os entrypoints (str post-task-#23). fn pathstr(p: *u8) str = { let r: str; r.ptr = p; @@ -42,7 +39,6 @@ fn pathstr(p: *u8) str = { return r; }; -// Slurp the whole file into a fresh buffer. fn slurp(path: *u8) (*u8, u64) = { let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); if (fd < 0) { return nil, 0u64; }; @@ -122,7 +118,6 @@ export fn main(argc: i32, argv: **u8) i32 = { if (parse(&s) != 0) { return 1; }; if (encode(&s) != 0) { return 1; }; - // Open output for write. let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 if (fd < 0) { os.write(2, "w6a: cannot open output\n".ptr, 23u64); diff --git a/selfhost/cmd/w6a/obj.ww b/selfhost/cmd/w6a/obj.ww index 61afd2a0..d0d5d041 100644 --- a/selfhost/cmd/w6a/obj.ww +++ b/selfhost/cmd/w6a/obj.ww @@ -1,4 +1,4 @@ -// selfhost/cmd/w6a/obj.ww — port of cmd/w6a/obj.c. +// Port of cmd/w6a/obj.c. // // Emit a tiny ELF64 relocatable object. Layout (in file order): // [0] ELF header @@ -36,7 +36,6 @@ fn wrdrop(fd: i32, p: *u8, n: u64) void = { }; }; -// ---- ELF constants ---------------------------------------------------- def ELFCLASS64: u8 = 2u8; def ELFDATA2LSB: u8 = 1u8; def EV_CURRENT_W: u32 = 1u32; @@ -59,14 +58,11 @@ def STT_NOTYPE: u8 = 0u8; def STT_OBJECT: u8 = 1u8; def STT_FUNC: u8 = 2u8; -// Sizes of fixed structures. def EHDR_SZ: u64 = 64u64; def SHDR_SZ: u64 = 64u64; def SYM_SZ: u64 = 24u64; def RELA_SZ: u64 = 24u64; -// ---- LE byte writers (own the bytes — write into a *u8 + offset) ---- - fn wru8(p: *u8, off: u64, v: u8) void = { p[off] = v; }; fn wru16(p: *u8, off: u64, v: u16) void = { p[off] = (v & 255u16): u8; @@ -83,8 +79,6 @@ fn wru64(p: *u8, off: u64, v: u64) void = { wru32(p, off + 4u64, ((v >> 32u64) & 4294967295u64): u32); }; -// ---- growable byte buffer --------------------------------------------- - type buf = struct { p: *u8, n: u64, @@ -128,8 +122,6 @@ fn bufputcstr(b: *buf, s: str) u32 = { return off; }; -// ---- emitelf --------------------------------------------------------- - export fn emitelf(a: *asm_, fd: i32) i32 = { let shstr: buf; bufinit(&shstr); let str_: buf; bufinit(&str_); @@ -174,9 +166,9 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { let SH_STRTAB: u16 = SH_SYMTAB + 1u16; let SH_SHSTR: u16 = SH_STRTAB + 1u16; - // Section name offsets. Append .data / .rela.data only when - // used so the .shstrtab buffer stays byte-identical for the - // no-DATAW case (test 991 byte-diff invariant). + // Append .data / .rela.data names only when used so the + // .shstrtab buffer stays byte-identical for the no-DATAW case + // (test 991 byte-diff invariant). let shntext: u32 = bufputcstr(&shstr, ".text"); let shnrela: u32 = bufputcstr(&shstr, ".rela.text"); let shndata: u32 = 0u32; @@ -195,7 +187,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { for (zi < 24) { zsym[zi] = 0u8; zi += 1; }; bufputb(&sym, zsym.ptr, 24u64); - // Build symbols. let idx: i32 = 1; let s: *asym = a.syms; for (s != nil) { @@ -223,7 +214,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { s = s.snext; }; - // Build relocations — split into text vs data buffers. let r: *areloc = a.relocs; for (r != nil) { let entry: [24]u8; @@ -239,7 +229,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { r = r.rnext; }; - // File offsets. let off: u64 = EHDR_SZ; let offtext: u64 = off; off = off + a.textlen; let offrela: u64 = off; off = off + rela.n; @@ -258,7 +247,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { else { NSECT = 7u16; }; }; - // ---- Ehdr ---- let eh: [64]u8; let i: i32 = 0; for (i < 64) { eh[i] = 0u8; i += 1; }; @@ -321,13 +309,11 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { written += 1u64; }; - // Section header table — 6 headers of 64 bytes each = 384 bytes. let shbuf: [64]u8; // SHT_NULL let sn: i32 = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; wrdrop(fd, shbuf.ptr, 64u64); - // .text sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; wru32(shbuf.ptr, 0u64, shntext); @@ -337,7 +323,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru64(shbuf.ptr, 32u64, a.textlen); wru64(shbuf.ptr, 48u64, 1u64); // sh_addralign wrdrop(fd, shbuf.ptr, 64u64); - // .rela.text sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; wru32(shbuf.ptr, 0u64, shnrela); @@ -351,7 +336,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru64(shbuf.ptr, 56u64, RELA_SZ); wrdrop(fd, shbuf.ptr, 64u64); if (hasdata) { - // .data sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; wru32(shbuf.ptr, 0u64, shndata); @@ -362,7 +346,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru64(shbuf.ptr, 48u64, 8u64); // sh_addralign wrdrop(fd, shbuf.ptr, 64u64); if (hasdatarelocs) { - // .rela.data sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; wru32(shbuf.ptr, 0u64, shnrelad); @@ -377,7 +360,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wrdrop(fd, shbuf.ptr, 64u64); }; }; - // .symtab sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; wru32(shbuf.ptr, 0u64, shnsymtab); @@ -389,7 +371,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru64(shbuf.ptr, 48u64, 8u64); wru64(shbuf.ptr, 56u64, SYM_SZ); wrdrop(fd, shbuf.ptr, 64u64); - // .strtab sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; wru32(shbuf.ptr, 0u64, shnstrtab); @@ -398,7 +379,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { wru64(shbuf.ptr, 32u64, str_.n); wru64(shbuf.ptr, 48u64, 1u64); wrdrop(fd, shbuf.ptr, 64u64); - // .shstrtab sn = 0; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; wru32(shbuf.ptr, 0u64, shnshstrtab); diff --git a/selfhost/cmd/w6a/opcodes.ww b/selfhost/cmd/w6a/opcodes.ww index 615b6887..9d5455d9 100644 --- a/selfhost/cmd/w6a/opcodes.ww +++ b/selfhost/cmd/w6a/opcodes.ww @@ -1,12 +1,10 @@ -// selfhost/cmd/w6a/opcodes.ww — types + constants shared across the -// w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h. +// Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h. package w6a; -// ---- registers + operand kinds (from 6.out.h) ------------------------- -// These must stay numerically aligned with the C enum so that ww-cgen -// output (which reads them via `D_AX(SB)` etc.) lands on the same -// integers when read by ww-w6a. +// Registers + operand kinds must stay numerically aligned with the +// 6.out.h C enum so that ww-cgen output (which reads them via +// `D_AX(SB)` etc.) lands on the same integers when read by ww-w6a. def D_NONE: i32 = 0; def D_AX: i32 = 1; @@ -52,7 +50,6 @@ def D_BRANCH: i32 = 37; def D_EXTERN: i32 = 38; def D_INDIR: i32 = 39; -// ---- opcodes ---------------------------------------------------------- def A_NOP: i32 = 0; def A_TEXT: i32 = 1; def A_DATA: i32 = 2; @@ -145,8 +142,6 @@ def A_DATAR: i32 = 61; // with DIVQ. def A_CQO: i32 = 66; -// ---- structs (mirror cmd/w6a/a.h) -------------------------------------- - type aoperand = struct { atype: i32, // D_NONE / D_AX..D_R15 / D_CONST / D_INDIR / D_EXTERN / D_BRANCH reg: i32, diff --git a/selfhost/cmd/w6a/parse.ww b/selfhost/cmd/w6a/parse.ww index 5fab678a..1aff73df 100644 --- a/selfhost/cmd/w6a/parse.ww +++ b/selfhost/cmd/w6a/parse.ww @@ -1,4 +1,4 @@ -// selfhost/cmd/w6a/parse.ww — port of cmd/w6a/parse.c. +// Port of cmd/w6a/parse.c. // // Line-oriented parser for the asm subset emitted by w6c. // Grammar: @@ -28,8 +28,7 @@ fn streqlit(p: *u8, n: u64, lit: str) bool = { return true; }; -// opcodelookup — name (length-bounded *u8) → A_*. Returns 0 (A_NOP) -// if not found. +// Returns 0 (A_NOP) if not found. fn opcodelookup(p: *u8, n: u64) i32 = { if (streqlit(p, n, "MOVQ")) { return A_MOVQ; }; if (streqlit(p, n, "MOVL")) { return A_MOVL; }; @@ -100,7 +99,7 @@ fn opcodelookup(p: *u8, n: u64) i32 = { return A_NOP; }; -// reglookup — name → D_*. Returns D_NONE if not found. +// Returns D_NONE if not found. fn reglookup(p: *u8, n: u64) i32 = { if (streqlit(p, n, "AX")) { return D_AX; }; if (streqlit(p, n, "BX")) { return D_BX; }; @@ -180,7 +179,6 @@ fn perr(a: *asm_, msg: str) void = { a.errs += 1; }; -// dupstr — copy n bytes from p into a fresh heap str. fn dupstr(p: *u8, n: u64) str = { let view: str; view.ptr = p; @@ -188,8 +186,6 @@ fn dupstr(p: *u8, n: u64) str = { return strings.dup(view); }; -// ---- line iteration & whitespace -------------------------------------- - // Read next line into a fresh heap buffer; returns (ptr, len) or (nil,0) // at EOF. Advances a.pos past the newline. fn nextline(a: *asm_) (*u8, u64) = { @@ -204,7 +200,7 @@ fn nextline(a: *asm_) (*u8, u64) = { let i: u64 = 0u64; for (i < n) { buf[i] = a.src[start + i]; i += 1u64; }; buf[n] = 0u8; - a.pos += 1u64; // skip newline + a.pos += 1u64; return buf.ptr, n; }; // EOF without trailing newline @@ -226,7 +222,6 @@ fn skipws(p: *u8, off: u64, n: u64) u64 = { return i; }; -// parseoperand — parse one operand from p[off..n), populate out. // Returns new offset (clamped to n on error). fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { let off: u64 = skipws(p, offin, n); @@ -239,7 +234,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { if (off >= n) { return off; }; let c0: u8 = p[off]; - // $NUM if (c0 == '$') { off += 1u64; let v: i64; @@ -250,7 +244,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { return off + used; }; - // (REG) if (c0 == '(') { off += 1u64; let rstart: u64 = off; @@ -377,7 +370,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { return n; }; -// Append a fresh aprog to the list with given opcode and label. fn addprog(a: *asm_, opc: i32, lbl: str) *aprog = { let pr: *aprog = alloc(aprog { as_ = opc, line = a.line, label = lbl })!; pr.from = alloc(aoperand { })!; @@ -398,9 +390,7 @@ export fn parse(a: *asm_) i32 = { line, n = nextline(a); if (line == nil) { return a.errs; }; - // skip leading ws let i: u64 = skipws(line, 0u64, n); - // blank or //-comment if (i >= n) { a.line += 1; continue; }; if (i + 1u64 < n) { if (line[i] == '/') { if (line[i + 1u64] == '/') { @@ -435,7 +425,6 @@ export fn parse(a: *asm_) i32 = { }; }; - // MNEMONIC at the start of the rest. Scan to first ws/EOL. let mstart: u64 = i; let m: u64 = mstart; let scan: bool = true; @@ -458,11 +447,9 @@ export fn parse(a: *asm_) i32 = { let pr: *aprog = addprog(a, opc, pending); pending.ptr = nil; pending.len = 0; - // Skip ws after mnemonic let r0: u64 = skipws(line, m, n); if (opc == A_TEXT) { - // TEXT name,$framesize — find first ',' as the end of name. let q: u64 = r0; let commapos: u64 = n; let scant: bool = true; @@ -502,7 +489,6 @@ export fn parse(a: *asm_) i32 = { let toop: *aoperand = pr.to; toop.atype = D_EXTERN; toop.asym = dupstr(line + r0, lparen - r0); - // Skip past `(SB)` to land just after ')'. let p2: u64 = lparen; let scand2: bool = true; for (scand2) { @@ -510,7 +496,6 @@ export fn parse(a: *asm_) i32 = { else { if (line[p2] == ')') { p2 += 1u64; scand2 = false; } else { p2 += 1u64; }; }; }; - // Skip ws / ',' / tab between `)` and the `"`. let scand3: bool = true; for (scand3) { if (p2 >= n) { scand3 = false; } @@ -521,8 +506,7 @@ export fn parse(a: *asm_) i32 = { }; if (p2 >= n) { perr(a, "DATA missing payload"); a.line += 1; continue; }; if (line[p2] != '"') { perr(a, "DATA expects \"...\""); a.line += 1; continue; }; - p2 += 1u64; // past opening " - // Parse escape sequence into a fresh growable buffer. + p2 += 1u64; let cap: u64 = 32u64; let blen: u64 = 0u64; let dbuf: []u8 = alloc([], cap)!; @@ -575,8 +559,6 @@ export fn parse(a: *asm_) i32 = { a.line += 1; continue; }; - // Generic instruction: 0/1/2 operands separated by ','. - // Find top-level comma. let comma: i64 = -1i64; let q: u64 = r0; for (q < n) { diff --git a/selfhost/cmd/w6c/main.ww b/selfhost/cmd/w6c/main.ww index 94a0f774..d55caf0f 100644 --- a/selfhost/cmd/w6c/main.ww +++ b/selfhost/cmd/w6c/main.ww @@ -1,14 +1,4 @@ -// selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c. -// -// w6c = amd64 compiler. Read .ww, parse, codegen, emit Plan 9 amd64 -// asm to stdout (or the file given by -o). -// -// w6c_ww -o file.s file.ww -// -// The cgen routines in selfhost/cmd/wcc/cgen.ww write directly to -// fd 1 via os.write(1, ...). For -o, we open the output file and -// dup2 it onto fd 1 before invoking cgfile. This is the same trick -// the bootstrap uses with shell redirection, just in-process. +// Port of cmd/w6c/main.c. package main; diff --git a/selfhost/cmd/w6l/dyn.ww b/selfhost/cmd/w6l/dyn.ww index 5bc2cb7b..50aa4b90 100644 --- a/selfhost/cmd/w6l/dyn.ww +++ b/selfhost/cmd/w6l/dyn.ww @@ -1,12 +1,8 @@ -// selfhost/cmd/w6l/dyn.ww — port of cmd/w6l/dyn.c. +// Port of cmd/w6l/dyn.c. // // Load a shared object (ET_DYN) so the linker knows which symbols it // exports and which DT_NEEDED entry to record. We do not pull bytes // from the .so; the dynamic loader maps it at runtime. -// -// Each call appends one lso to lnk->sos. l_so_provides_v answers -// "does this .so export the named symbol, and at which version?" — -// l_resolve uses that to promote unresolved references to dynamic. package w6l; @@ -72,8 +68,6 @@ def VD_NEXT: u64 = 16u64; def VA_NAME: u64 = 0u64; def VA_NEXT: u64 = 4u64; -// ---- little-endian byte readers --------------------------------------- - fn du16(p: *u8, off: u64) u16 = { let b0: u16 = p[off]: u16; let b1: u16 = p[off + 1u64]: u16; @@ -98,8 +92,6 @@ fn di64(p: *u8, off: u64) i64 = { return du64(p, off): i64; }; -// ---- C-string helpers -------------------------------------------------- - fn dcstrlen(p: *u8) u64 = { let n: u64 = 0u64; for (p[n] != 0u8) { n += 1u64; }; @@ -114,7 +106,6 @@ fn dcstrtostr(p: *u8) str = { return strings.dup(view); }; -// basename: scan for last '/' and return pointer past it. fn dbasename(p: *u8) *u8 = { let n: u64 = dcstrlen(p); let i: u64 = n; @@ -127,8 +118,6 @@ fn dbasename(p: *u8) *u8 = { return p; }; -// ---- file slurp -------------------------------------------------------- - fn slurpso(path: *u8) (*u8, u64) = { let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); if (fd < 0) { return nil, 0u64; }; @@ -151,8 +140,6 @@ fn slurpso(path: *u8) (*u8, u64) = { return buf.ptr, n: u64; }; -// ---- verdef helpers ---------------------------------------------------- - // vdnameat — walk verdef records and return the name (as *u8 into // the .so's verstr buffer) for the entry whose vd_ndx == ndx. The name // is the first Verdaux's vda_name (subsequent auxes are predecessor @@ -176,8 +163,6 @@ fn vdnameat(buf: *u8, verdefoff: u64, verdefsize: u64, return nil; }; -// ---- entry points ------------------------------------------------------ - export fn loadso(l: *lnk, path: *u8) i32 = { let buf: *u8; let blen: u64; @@ -207,7 +192,6 @@ export fn loadso(l: *lnk, path: *u8) i32 = { if (shoff == 0u64) { return soerr("stripped .so unsupported"); }; if (shnum == 0u32) { return soerr("stripped .so unsupported"); }; - // Locate the four sections we care about. let idxdynsym: i32 = -1; let idxdynamic: i32 = -1; let idxversym: i32 = -1; @@ -292,8 +276,8 @@ export fn loadso(l: *lnk, path: *u8) i32 = { verstr = buf + vstroff; }; - // Build the lso. Exports are appended in dynsym order so - // soprovides_v's first-match semantics match the C version. + // Exports are appended in dynsym order so soprovides_v's + // first-match semantics match the C version. let so: *lso = alloc(lso { path = dcstrtostr(path), soname = dcstrtostr(sonamecs) })!; let tail: *lexport = nil; @@ -371,7 +355,6 @@ fn soerr(msg: str) i32 = { return -1; }; -// soprovides — 1 if so exports name, 0 otherwise. export fn soprovides(so: *lso, name: str) i32 = { if (so == nil) { return 0; }; let e: *lexport = so.exports; diff --git a/selfhost/cmd/w6l/dynout.ww b/selfhost/cmd/w6l/dynout.ww index 61e7d12a..6b867c35 100644 --- a/selfhost/cmd/w6l/dynout.ww +++ b/selfhost/cmd/w6l/dynout.ww @@ -1,4 +1,4 @@ -// selfhost/cmd/w6l/dynout.ww — port of cmd/w6l/dynout.c. +// Port of cmd/w6l/dynout.c. // // Emit a dynamic-linked ELF executable. The shape is the simplest // valid one: PT_INTERP + PT_DYNAMIC + DT_BIND_NOW so the loader @@ -29,7 +29,6 @@ import rt; import strings; import sym; -// ELF constants def ET_EXEC_D: u16 = 2u16; def EM_X86_64_D: u16 = 62u16; def EV_CURRENT_D: u32 = 1u32; @@ -75,8 +74,6 @@ def PAGE: u64 = 4096u64; def INTERP: str = "/lib64/ld-linux-x86-64.so.2"; -// ---- byte writers ------------------------------------------------------ - fn dwr8(buf: *u8, off: u64, v: u8) void = { buf[off] = v; }; @@ -106,8 +103,6 @@ fn dwri32(buf: *u8, off: u64, v: i32) void = { dwr32(buf, off, v: u32); }; -// ---- byte readers ------------------------------------------------------ - fn drdu16(p: *u8, off: u64) u16 = { let b0: u16 = p[off]: u16; let b1: u16 = p[off + 1u64]: u16; @@ -153,8 +148,6 @@ fn alignup(off: u64, a: u64) u64 = { return (off + a - 1u64) & ~(a - 1u64); }; -// ---- main entry -------------------------------------------------------- - export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { // .data shares the R+W PT_LOAD with .got.plt and .dynamic. // Placed after .dynamic so the segment is one contiguous run; @@ -163,7 +156,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { let n: i32 = l.dynn; let nu: u64 = n: u64; - // ---- collect dyn syms into a plt_idx-indexed array ---- let dynsyms: []*lsym = alloc([], nu)!; let s: *lsym = l.syms; for (s != nil) { @@ -185,7 +177,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { i += 1; }; - // ---- collect used .so's (in l.sos order) ---- + // Used .so's are collected in l.sos order. let maxsos: i32 = 0; let so: *lso = l.sos; for (so != nil) { maxsos += 1; so = so.sonext; }; @@ -208,7 +200,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { so = so.sonext; }; - // ---- build flat version table grouped by vlib ---- + // Flat version table grouped by vlib: // vlib_sos_idx[k] = sos_used index for vlib k. // vlib_first[k] = ver index of first version under vlib k. // vlib_count[k] = number of versions under vlib k. @@ -301,7 +293,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { vi += 1; }; - // ---- compute dynstr size ---- let dynstrsz: u64 = 1u64; // leading NUL let pi: i32 = 0; for (pi < nsos) { @@ -325,7 +316,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { pi += 1; }; - // ---- fill dynstr ---- let dynstr: []u8 = alloc([], dynstrsz)!; let dynstrpos: u64 = 1u64; // past leading NUL @@ -365,7 +355,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { pi += 1; }; - // ---- per-dyn-sym versym index ---- let versymfor: []u8 = alloc([], nu * 2u64)!; pi = 0; for (pi < n) { @@ -400,7 +389,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { pi += 1; }; - // ---- compute byte sizes ---- let ehdrsz: u64 = 64u64; let nphdrs: u64 = 4u64; let phdrsz: u64 = nphdrs * 56u64; @@ -433,7 +421,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { let ndyn: u64 = (nsos: u64) + 11u64 + extra; let dynamicsz: u64 = ndyn * 16u64; - // ---- compute file offsets ---- let off: u64 = ehdrsz + phdrsz; let interpoff: u64 = off; off += interpsz; off = alignup(off, 8u64); @@ -486,7 +473,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { let datafilelen: u64 = l.datalen - bsslen; let filedataend: u64 = dataoff + datafilelen; - // ---- build .dynsym ---- let dynsymbuf: []u8 = alloc([], dynsymsz)!; pi = 0; for (pi < n) { @@ -500,7 +486,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { pi += 1; }; - // ---- build .hash (SysV, 1 bucket) ---- + // .hash is SysV with a single bucket. let hashbuf: []u8 = alloc([], hashsz)!; dwr32(hashbuf.ptr, 0u64, nbuckets); dwr32(hashbuf.ptr, 4u64, nchain); @@ -515,7 +501,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { ci += 1u64; }; - // ---- build .rela.plt ---- let relapltbuf: []u8 = alloc([], relapltsz)!; pi = 0; for (pi < n) { @@ -527,7 +512,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { pi += 1; }; - // ---- build .gnu.version (u16 per dynsym entry) ---- + // .gnu.version is one u16 per dynsym entry. let versymbuf: []u8 = alloc([], versymsz)!; dwr16(versymbuf.ptr, 0u64, VER_NDX_LOCAL_D); pi = 0; @@ -536,7 +521,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { pi += 1; }; - // ---- build .gnu.version_r ---- let verneedbuf: []u8 = alloc([], verneedsz)!; if (verneedsz > 0u64) { let vnoff: u64 = 0u64; @@ -575,7 +559,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; }; - // ---- build .plt ---- let pltbuf: []u8 = alloc([], pltsz)!; pi = 0; for (pi < n) { @@ -590,11 +573,9 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { pi += 1; }; - // ---- build .got.plt ---- let gotpltbuf: []u8 = alloc([], gotpltsz)!; dwr64(gotpltbuf.ptr, 0u64, dynamicva); - // ---- build .dynamic ---- let dynamicbuf: []u8 = alloc([], dynamicsz)!; let dk: u64 = 0u64; pi = 0; @@ -625,7 +606,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { return 1; }; - // ---- patch .text relocs targeting dynamic syms ---- let r: *lrel = l.rels; for (r != nil) { if (r.sym != nil) { @@ -646,7 +626,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { r = r.rnext; }; - // ---- assemble file buffer ---- let filebuf: []u8 = alloc([], fileend)!; filebuf.len = fileend: i32; @@ -721,7 +700,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { dwr64(filebuf.ptr, p3 + 40u64, dynamicsz); dwr64(filebuf.ptr, p3 + 48u64, 8u64); - // Sections. dbcopy(filebuf.ptr, interpoff, INTERP.ptr, INTERP.len: u64); dwr8(filebuf.ptr, interpoff + (INTERP.len: u64), 0u8); dbcopy(filebuf.ptr, dynstroff, dynstr.ptr, dynstrsz); diff --git a/selfhost/cmd/w6l/main.ww b/selfhost/cmd/w6l/main.ww index 37c8f65d..11906d49 100644 --- a/selfhost/cmd/w6l/main.ww +++ b/selfhost/cmd/w6l/main.ww @@ -1,8 +1,4 @@ -// selfhost/cmd/w6l/main.ww — port of cmd/w6l/main.c. -// -// w6l = amd64 linker. Reads relocatable ELF .o files, SysV `ar` -// archives, and shared objects (ET_DYN). Resolves symbols, applies -// relocations, writes a static or dynamic-linked ELF executable. +// Port of cmd/w6l/main.c. // // w6l_ww -o out [-L...] [-l...] file1.o file2.o ... @@ -25,10 +21,6 @@ fn mklnk() *lnk = { return l; }; -// `cstreq` lives in obj.ww — same bundle, single definition. - -// `cstrlen` lives in obj.ww — same bundle, single definition. - // Build "/lib." into dst (NUL-terminated). Returns total // length excluding NUL. dst must be large enough. fn buildpath(dst: *u8, dir: *u8, name: *u8, ext: str) u64 = { @@ -274,8 +266,7 @@ export fn main(argc: i32, argv: **u8) i32 = { k += 1; }; - // Then resolve -l flags and load each. Archives append; shared - // objects register their exports. + // Archives append; shared objects register their exports. let lf: i32 = 0; for (lf < nlflags) { let p: *u8 = resolvelib(lflags[lf], libdirs.ptr, nlibdirs); diff --git a/selfhost/cmd/w6l/obj.ww b/selfhost/cmd/w6l/obj.ww index 9b5740c3..0f4642c9 100644 --- a/selfhost/cmd/w6l/obj.ww +++ b/selfhost/cmd/w6l/obj.ww @@ -1,12 +1,4 @@ -// selfhost/cmd/w6l/obj.ww — port of cmd/w6l/obj.c. -// -// Loads relocatable ELF64 .o files emitted by w6a, appends .text to -// the combined image, and pulls in symbols + relocations with -// offsets adjusted to the combined section. -// -// Also handles SysV `ar` archives (libwwrt.a). The two-pass loader -// indexes members on the first pass and iteratively pulls members -// that define currently-undefined symbols on subsequent passes. +// Port of cmd/w6l/obj.c. package w6l; @@ -22,7 +14,6 @@ def SHT_SYMTAB: i32 = 2; def SHT_STRTAB: i32 = 3; def SHT_RELA: i32 = 4; -// ---- little-endian byte readers ---------------------------------------- // w6a/w6l use straight LE on amd64. Reading via byte offsets keeps us off // the cgen's u16 field-load story for now (MOVZBQ exists; MOVZWQ doesn't). @@ -46,7 +37,6 @@ fn rdu64(p: *u8, off: u64) u64 = { return lo | (hi << 32u64); }; -// ---- ELF64 section header offsets (40 bytes total) -------------------- def SHDR_SIZE: u64 = 64u64; // sizeof(Shdr) per ELF64 spec def SHDR_NAME: u64 = 0u64; def SHDR_TYPE: u64 = 4u64; @@ -76,8 +66,6 @@ def RELA_OFFSET: u64 = 0u64; def RELA_INFO: u64 = 8u64; def RELA_ADDEND: u64 = 16u64; -// ---- file slurp -------------------------------------------------------- - fn slurp(path: *u8) (*u8, u64) = { let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); if (fd < 0) { return nil, 0u64; }; @@ -100,8 +88,6 @@ fn slurp(path: *u8) (*u8, u64) = { return buf.ptr, n: u64; }; -// ---- text buffer growth ------------------------------------------------ - fn emittext(l: *lnk, src: *u8, n: u64) void = { if (l.textlen + n > l.textcap) { let nc: u64 = l.textcap; @@ -149,17 +135,13 @@ fn emitdata(l: *lnk, src: *u8, n: u64) void = { l.datalen += n; }; -// ---- C-string helpers -------------------------------------------------- - fn cstrlen(p: *u8) u64 = { let n: u64 = 0u64; for (p[n] != 0u8) { n += 1u64; }; return n; }; -// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style -// callers to lib/os entrypoints (str post-task-#23). Shared with -// main.ww and dyn.ww via the w6l bundle. +// Bridges argv-style callers to lib/os entrypoints (str post-task-#23). fn pathstr(p: *u8) str = { let r: str; r.ptr = p; @@ -179,7 +161,6 @@ fn cstreq(p: *u8, lit: str) bool = { return true; }; -// Build a ww str from a NUL-terminated *u8 (for passing to intern). fn cstrtostr(p: *u8) str = { let n: u64 = cstrlen(p); let view: str; @@ -188,8 +169,6 @@ fn cstrtostr(p: *u8) str = { return strings.dup(view); }; -// ---- archive (SysV ar) types and helpers ------------------------------- -// // Each archive member starts with a 60-byte ar_hdr. The fields we care // about are the first byte (member type) and the size at offset 48 (a // 10-byte, space-padded decimal). Member bodies are 2-byte aligned. @@ -231,9 +210,8 @@ fn arfield(p: *u8, n: u64) u64 = { return v; }; -// elfglobals — return a linked list of names of globally-defined -// (STB_GLOBAL) symbols whose section is `.text`. Names are owned -// heap copies, so the source ELF buffer can be freed afterward. +// Names are owned heap copies, so the source ELF buffer can be freed +// afterward. fn elfglobals(buf: *u8, len: u64) *defent = { if (len < EHDR_SIZE) { return nil; }; if (buf[0u64] != 127u8) { return nil; }; @@ -310,9 +288,8 @@ fn elfglobals(buf: *u8, len: u64) *defent = { return head; }; -// memberdefinesundef — true if any of m's defined globals matches a -// currently-undefined symbol in the linker's symbol table. Names not -// already interned are uninteresting (the link doesn't need them yet). +// Names not already interned are uninteresting (the link doesn't need +// them yet). fn memberdefinesundef(l: *lnk, m: *armember) bool = { let de: *defent = m.defs; for (de != nil) { @@ -406,8 +383,6 @@ fn loadarchive(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { return 0; }; -// ---- main loader ------------------------------------------------------- - export fn load(l: *lnk, path: *u8) i32 = { let bufp: *u8; let buflen: u64; @@ -441,7 +416,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { let shstrshoff: u64 = rdu64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET); let shstr: *u8 = buf + shstrshoff; - // find .text, .data, .symtab, .rela.text, .rela.data let idxtext: i32 = -1; let idxdata: i32 = -1; let idxsymtab: i32 = -1; @@ -497,7 +471,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { datasize = rdu64(buf, datash + SHDR_SIZE_F); }; - // Track this object. let ob: *lobj = alloc(lobj { path = cstrtostr(path), buf = buf, @@ -510,18 +483,16 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { })!; l.objs = ob; - // Append .text bytes to the combined image. emittext(l, buf + textoff, textsize); - // Append .data bytes (if present) to the combined .data buffer. if (idxdata >= 0) { if (datasize > 0u64) { emitdata(l, buf + dataoff, datasize); }; }; - // Walk symbols. We don't keep a per-object map[] of *lsym. Instead - // the reloc loop re-walks symtab and re-interns by name. Simpler - // than dancing around the cgen's u64-shift gaps. + // We don't keep a per-object map[] of *lsym. Instead the reloc + // loop re-walks symtab and re-interns by name. Simpler than + // dancing around the cgen's u64-shift gaps. let si: u64 = 1u64; // skip index 0 (always undef sentinel) for (si < nsyms) { let symp: u64 = symoff + si * SYM_SIZE; @@ -569,7 +540,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { si += 1u64; }; - // Per-object relocation collection. if (idxrela >= 0) { let relash: u64 = shoff + (idxrela: u64) * SHDR_SIZE; let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET); @@ -590,7 +560,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { addend = raddend: i64, rnext = l.rels, })!; - // Look up the referenced sym by name (re-walk symtab). if ((rsymidx: u64) < nsyms) { let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE; let sname: u32 = rdu32(buf, sp + SYM_NAME); @@ -605,8 +574,8 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { }; }; - // Data-reloc collection. Offsets land in .data, shifted by - // this object's data_off so they index the combined buffer. + // Offsets land in .data, shifted by this object's dataoff so + // they index the combined buffer. if (idxrelad >= 0) { let relash: u64 = shoff + (idxrelad: u64) * SHDR_SIZE; let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET); diff --git a/selfhost/cmd/w6l/out.ww b/selfhost/cmd/w6l/out.ww index 8cf8b3da..8752210a 100644 --- a/selfhost/cmd/w6l/out.ww +++ b/selfhost/cmd/w6l/out.ww @@ -1,11 +1,4 @@ -// selfhost/cmd/w6l/out.ww — port of cmd/w6l/out.c. -// -// Emit a static ELF64 executable. File layout (per the C original): -// [0..64) Ehdr -// [64..120) Phdr (one PT_LOAD) -// [120..0x1000) zero pad -// [0x1000..) .text bytes -// Single PT_LOAD covers the whole file, R+X. No interpreter, no .bss. +// Port of cmd/w6l/out.c. package w6l; @@ -27,8 +20,6 @@ def PF_R: u32 = 4u32; def TEXT_OFF: u64 = 4096u64; // 0x1000 def PAGE_SZ: u64 = 4096u64; -// ---- little-endian byte writers ---------------------------------------- - fn wru16(buf: *u8, off: u64, v: u16) void = { buf[off] = (v & 255u16): u8; buf[off + 1u64] = ((v >> 8u16) & 255u16): u8; @@ -46,8 +37,6 @@ fn wru64(buf: *u8, off: u64, v: u64) void = { wru32(buf, off + 4u64, ((v >> 32u64) & 4294967295u64): u32); }; -// ---- emit --------------------------------------------------------------- - export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { // Dispatch: any loaded shared object plus any dynamic ref means // we owe the loader a real PT_INTERP/PT_DYNAMIC binary. @@ -92,7 +81,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { let hdr: []u8 = alloc([], TEXT_OFF)!; hdr.len = TEXT_OFF: i32; - // --- Ehdr (64 bytes) --- + // Ehdr (64 bytes). hdr[0u64] = 127u8; // 0x7f hdr[1u64] = 'E'; hdr[2u64] = 'L'; @@ -115,7 +104,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { wru16(hdr.ptr, 60u64, 0u16); // e_shnum wru16(hdr.ptr, 62u64, 0u16); // e_shstrndx - // --- Phdr #1 (R+X) at offset 64 --- + // Phdr #1 (R+X) at offset 64. wru32(hdr.ptr, 64u64, PT_LOAD); wru32(hdr.ptr, 68u64, PF_R | PF_X); wru64(hdr.ptr, 72u64, 0u64); // p_offset @@ -126,7 +115,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { wru64(hdr.ptr, 112u64, TEXT_OFF); // p_align if (hasdata) { - // --- Phdr #2 (R+W) at offset 64+56=120 --- + // Phdr #2 (R+W) at offset 64+56=120. wru32(hdr.ptr, 120u64, PT_LOAD); wru32(hdr.ptr, 124u64, PF_R | PF_W); wru64(hdr.ptr, 128u64, dataoff); // p_offset @@ -137,7 +126,6 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { wru64(hdr.ptr, 168u64, PAGE_SZ); // p_align }; - // Write [0..0x1000) then .text. let r1: (i64 | os.oserror) = os.writeall(fd, hdr.ptr, TEXT_OFF); let n1: i64 = 0i64; match (r1) { diff --git a/selfhost/cmd/w6l/pass.ww b/selfhost/cmd/w6l/pass.ww index dae017c0..e67b4872 100644 --- a/selfhost/cmd/w6l/pass.ww +++ b/selfhost/cmd/w6l/pass.ww @@ -1,11 +1,4 @@ -// selfhost/cmd/w6l/pass.ww — port of cmd/w6l/pass.c. -// -// Resolution + relocation. l_resolve flags every undefined symbol -// referenced by a relocation, and promotes those provided by some -// loaded .so to "dynamic" with a freshly-assigned PLT slot. -// l_relocate walks the rel list and patches the .text bytes in place -// once the final virtual base is known. Dynamic refs are deferred: -// their site is patched later in dynout, once the PLT vaddr is known. +// Port of cmd/w6l/pass.c. // // Supported relocation kinds: PC32 (=2), PLT32 (=4); both are 32-bit // PC-relative displacements (PLT32 == PC32 for static). diff --git a/selfhost/cmd/w6l/sym.ww b/selfhost/cmd/w6l/sym.ww index 7e0fba74..36d4fcad 100644 --- a/selfhost/cmd/w6l/sym.ww +++ b/selfhost/cmd/w6l/sym.ww @@ -1,7 +1,7 @@ -// selfhost/cmd/w6l/sym.ww — port of cmd/w6l/sym.c. +// Port of cmd/w6l/sym.c. // -// Linker symbol table. Singly-linked list, usually a few hundred -// entries; hashing isn't worth it yet. +// Singly-linked list, usually a few hundred entries; hashing isn't +// worth it yet. package w6l; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 5da2b6d4..d1e3a254 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -1,26 +1,4 @@ -// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c. -// -// Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c` -// producing byte-identical output to C-side `w6c` for the same source, -// then by assembling + linking + running the result. -// -// Current coverage: -// - decls: nkind.N_FILE, nkind.N_FNDECL (params, frame for locals, prologue -// + dual-epilogue suppression; FFI body-less fn skipped) -// - stmts: nkind.N_BLOCK, nkind.N_RETURN, nkind.N_EXPRSTMT, nkind.N_LET (no init), -// nkind.N_LET (int-literal / ident / call / nkind.N_BIN init), -// nkind.N_IF (with optional else), nkind.N_FOR (cond-only and full -// init/cond/post), nkind.N_BREAK, nkind.N_CONTINUE -// - exprs: nkind.N_INTLIT, nkind.N_IDENT (local/param), nkind.N_BIN with full op -// coverage (+/-/*/// %, &/|/^, <>, comparisons with -// signed-vs-unsigned dispatch, &&/||), nkind.N_UN (- ! ~ & *), -// nkind.N_CALL (recursive R-to-L push, pop into argregs L-to-R), -// nkind.N_ASSIGN to local idents (plain and compound +=/-=) -// -// Type info is shallow — frame slots are 8 bytes per local, all loads -// /stores are MOVQ. Programs that mix i8/i32/i64 locals work but spill -// 8 bytes per local. Float, str, slice, struct, match, defer, alloc, -// tagged-union return — none of those are wired yet. +// Port of cmd/w6c/cgen.c. package wcc; @@ -38,12 +16,8 @@ import cgenexpr; import cgenstmt; import cgendecl; -// ---- typedef alias registry ----------------------------------------- -// -// `type error = str;` makes `error` a struct-shape alias. We track -// alias→target so isstrtype / isslicetype / structlookup can -// resolve through the chain. Only direct nkind.N_TNAME aliases are mapped; -// `type p = struct {...}` is handled by collectstructs. +// Only direct nkind.N_TNAME aliases are mapped; `type p = struct {...}` +// is handled by collectstructs. type aliasent = struct { aname: str, @@ -156,12 +130,9 @@ fn aliassamemod(c: *cgen, name: str) *syntax.node = { return nil; }; -// ---- enum registry -------------------------------------------------- -// -// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk -// every `type Foo = enum [storage] { ... }`, pre-compute each -// member's u64 value (supporting auto-increment and sibling refs), -// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX. +// Enum member values are pre-computed at collect time (auto-increment +// + sibling refs) so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX. +// Mirrors cmd/wcc/check.c's enum resolution. // foldintliteral — fold the literal subset usable for top-level // constant slots: int/rune literal, true/false/nil, and a unary @@ -376,13 +347,6 @@ fn resolvetype(c: *cgen, t: *syntax.node) *syntax.node = { return cur; }; -// ---- struct registry ------------------------------------------------ -// -// Per-file map from struct name → list of fields with computed offsets -// and sizes. Built when cgfile walks nkind.N_TYPEDECL with nkind.N_TSTRUCT lhs. -// nkind.N_DOT and nkind.N_ASSIGN consult this to resolve `s.field` for struct or -// *struct bases. - type fieldinfo = struct { fname: str, foff: i32, @@ -399,8 +363,6 @@ type structinfo = struct { sinext: *structinfo, }; -// ---- locals / frame -------------------------------------------------- - type local = struct { name: str, off: i32, @@ -764,8 +726,6 @@ fn localfind(c: *cgen, name: str) i32 = { return 0; }; -// ---- emit helpers --------------------------------------------------- - // Cgfn defers its prologue (TEXT / SUBQ) until after the body so the // frame size reflects every emit-time localadd — the scanlocals pre- // pass that previously pre-computed it was dropped per #15/#26c. The @@ -942,10 +902,6 @@ fn mkscratchname(c: *cgen, prefix: str) str = { return r; }; -// ---- string interning ------------------------------------------------ -// -// streq is provided by sym.ww and reused here. - // internstrlit — return a stable label for `bytes`. Dedups by content // so identical literals share storage. fn internstrlit(c: *cgen, bytes: str) str = { @@ -1008,8 +964,6 @@ fn letscalarprim(nm: str) bool = { return false; }; -// letfloatprim — float type-name keywords. f32 → 4B slot, f64 → 8B. -// Returns the slot size or 0 if not a float type. fn letfloatprim(nm: str) i32 = { if (syntax.streq(nm, "f32")) { return 4; }; if (syntax.streq(nm, "f64")) { return 8; }; @@ -1019,11 +973,6 @@ fn letfloatprim(nm: str) i32 = { // letemitsize — slot size in bytes for a top-level `let`, or 0 if // the type isn't yet supported as a writable global. Walks type // aliases so byte output matches C cgen, which resolves Type kinds. -// 4 → f32 (literal init supported) -// 8 → scalar or f64 (literal init supported) -// 16 → str (only zero-init / nil / "" supported) -// 24 → slice (only zero-init supported) -// varies → struct (zero-init only; field reads/scalar-field writes) fn letemitsize(c: *cgen, d: *syntax.node) i32 = { if (d == nil) { return 0; }; let t: *syntax.node = d.lhs; @@ -2024,7 +1973,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node, }; if (eu != nil && eu.kind == syntax.tykind.TY_STRUCT) { - // Validate: every element must be N_STRUCTLIT (after N_CAST). let idx: i32 = 0; let last_ev: *syntax.node = nil; let e: *syntax.node = rhs.list; @@ -2113,7 +2061,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node, if (syntax.typeisfloat(au.sub)) { let isf32: bool = syntax.typeisf32(au.sub); - // Validate. let idx: i32 = 0; let e: *syntax.node = rhs.list; for (e != nil && idx < alen) { @@ -2211,7 +2158,6 @@ fn emitarraylitbytes(c: *cgen, arrt: *syntax.tinfo, rhs: *syntax.node, let e: *syntax.node = rhs.list; let last: u64 = 0u64; let repeat: bool = false; - // Validate first. for (e != nil && idx < alen) { if (e.kind == syntax.nkind.N_FIELD) { if (syntax.streq(e.str, "...")) { repeat = true; break; }; @@ -2551,7 +2497,6 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *syntax.tinfo, os.write(2, m.ptr, m.len: u64); os.exit(1); }; - // Writable backing data. emitline("DATAW "); emitfnname(c, name, module); emitline(".d(SB),\""); @@ -3494,12 +3439,9 @@ fn emitdatasection(c: *cgen) void = { }; }; -// ---- fn return-type map --------------------------------------------- -// -// Per-file: ident → ret-type-node. Used to decide whether to shuffle -// (AX, DX) → (AX, BX) after a CALL — needed for str-returning fns so -// the value flows through cgen as the canonical (AX, BX) str pair. - +// fnret decides whether to shuffle (AX, DX) → (AX, BX) after a CALL — +// needed for str-returning fns so the value flows through cgen as the +// canonical (AX, BX) str pair. type fnret = struct { fname: str, fmod: str, @@ -3670,12 +3612,8 @@ fn fnparamslookupmod(c: *cgen, name: str, mod: str) *syntax.node = { return fnparamslookup(c, name); }; -// ---- def-constant registry ------------------------------------------ -// // `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an -// ident reference loads it via `MOVQ NAME(SB), AX`. We collect them at -// file load and consult on nkind.N_IDENT lookup. - +// ident reference loads it via `MOVQ NAME(SB), AX`. type defent = struct { dname: str, dmod: str, // originating module (`// MODULE: foo`), or empty @@ -3819,8 +3757,6 @@ fn defisaddressable(c: *cgen, opnd: *syntax.node) bool = { return false; }; -// ---- module-private symbol map -------------------------------------- -// // Every non-FFI top-level fn decl lives in its module's namespace — // cgen mangles the leaf to `.` at the def site (TEXT) // and at every call/load site, so cross-module same-leaf fns (lib/os @@ -4046,8 +3982,6 @@ fn emitfnname(c: *cgen, ident: str, hint: str) void = { emitbytes( ident.ptr, ident.len: u64); }; -// ---- FFI map --------------------------------------------------------- - fn fficollect(c: *cgen, file: *syntax.node) void = { c.ffis = nil; if (file == nil) { return; }; @@ -4085,8 +4019,6 @@ fn ffiresolve(c: *cgen, ident: str) str = { return ident; }; -// ---- ABI argreg helpers --------------------------------------------- - fn argregname(i: i32) str = { if (i == 0) { return "DI"; }; if (i == 1) { return "SI"; }; diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index 9bb66099..885da180 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -1,24 +1,9 @@ -// selfhost/cmd/wcc/cgendecl.ww — split out of cgen.ww. -// -// Houses the top-level emission glue: -// - cgfnparams: parameter spilling per SysV -// - cgfn: fn body emit (TEXT/SUBQ patched after body), prologue -// deferred via cgen.ww's cgoutstate so the frame size -// reflects every emit-time localadd (#15/#26c) -// - cgfile: file-level entry (the exported driver) -// -// Bundler pulls this in transitively via cgen.ww; consumers don't -// need to `use cgendecl;` directly. - package wcc; import os; import syntax; import strconv; - -// ---- function-level cgen --------------------------------------------- - fn cgfnparams(c: *cgen, params: *syntax.node) void = { let p: *syntax.node = params; // sret (#23): RDI is consumed by the hidden dest pointer @@ -580,8 +565,6 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = { }; if (c.lastwasreturn == 0) { - // Run any registered defers in LIFO order before the - // implicit return. rundefers(c); // Zero AX before the fall-through return — matches cstage, // which always emits this so void-returning fns don't leak @@ -629,8 +612,6 @@ fn cgfn(c: *cgen, fn_: *syntax.node) void = { cgout_flush(); }; -// ---- file-level entry ------------------------------------------------ - export fn cgfile(c: *cgen, file: *syntax.node) void = { if (file == nil) { return; }; c.strlits = nil; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 2e26faec..b014db1f 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1,10 +1,5 @@ // selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww. // -// cgexpr is a thin dispatcher over n.kind; each non-trivial branch -// lives in a per-kind helper (cgstrlit, cgident, cgindex, cgmatch, -// cgdot, cgun, cgbin, cgcall, cgassign). Trivial literal loads -// (nkind.N_INTLIT, nkind.N_RUNELIT, nkind.N_TRUE/FALSE/NIL, nkind.N_CAST) stay inline. -// // The remainder of cgen lives in cgen.ww (foundation: types, emit // primitives, the collect* tables, FFI/module maps) and cgenstmt.ww // (cgstmt). @@ -683,9 +678,6 @@ fn cgtryunw(c: *cgen, n: *syntax.node) void = { }; fn cgtypetest(c: *cgen, n: *syntax.node) void = { - // `e is T` — load the lhs's tag, compare against T's variant - // index, set AX = (tag == idx). Result type is bool. - // // Slot resolution is inlined (rather than factored into a helper // with output parameters): wwstage cgen has a trap with i32 // stored via *i32 in this context — direct assignment of the @@ -1077,9 +1069,6 @@ fn cgcast(c: *cgen, n: *syntax.node) void = { else { if (lk == syntax.nkind.N_TENUM) { leaf_tn = leaf_tn.lhs; } else { if (lk == syntax.nkind.N_TNAME) { let lnm: str = leaf_tn.str; - // This is an alias chase loop - // — primsize is the leaf-primitive break test the loop - // wraps (aliaslookup advances the cursor on a miss). if (primsize(lnm) > 0) { break; }; let lal: *syntax.node = aliaslookup(c, lnm); if (lal == nil) { leaf_tn = nil; } @@ -2062,9 +2051,6 @@ fn cgplaceaddr(c: *cgen, n: *syntax.node, dstreg: str) bool = { }; fn cgindex(c: *cgen, n: *syntax.node) void = { - // Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL, - // str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast - // path when the base is a bare ident (mem.ww shape). let base: *syntax.node = n.lhs; let idx: *syntax.node = n.rhs; // Direct non-ident index bases that match none of the typed arms @@ -2512,7 +2498,6 @@ fn cgindex(c: *cgen, n: *syntax.node) void = { emitline("\t(BX), AX\n"); return; }; - // Generic fallback when base isn't a plain ident. // #135: N_DOT base on `[N]T` field needs the field's ADDRESS, // not its value. cgexpr would auto-deref + load the 8-byte value // as if it were a pointer. dotbaseaddr emits the address inline. @@ -2866,7 +2851,6 @@ fn cgslice(c: *cgen, n: *syntax.node) void = { let es60: *syntax.tinfo = tichase(bu60.sub); if (es60 != nil) { esz = es60.size: i32; }; }; - // base address if (baselocal != nil) { let tn: *syntax.node = baselocal.tnode; let isarray: bool = false; @@ -2936,11 +2920,9 @@ fn cgslice(c: *cgen, n: *syntax.node) void = { }; };};};}; emitline("\tPUSHQ\tAX\n"); - // lo (default 0) if (lo != nil) { cgexpr(c, lo); } else { emitline("\tMOVQ\t$0, AX\n"); }; emitline("\tPUSHQ\tAX\n"); - // hi (default base length) if (hi != nil) { cgexpr(c, hi); } else { if (baselocal != nil) { @@ -3140,8 +3122,6 @@ fn matcharmwant(c: *cgen, scrutt: *syntax.node, pat: *syntax.node) i32 = { }; fn cgmatch(c: *cgen, n: *syntax.node) void = { - // match (e) { case let v: T => stmt; ... } - // // Read the tagged-union slot and dispatch by tag. Slot // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings // (`case let v: T =>`) get a fresh local slot loaded from @@ -3415,7 +3395,6 @@ fn cgmatch(c: *cgen, n: *syntax.node) void = { }; }; }; - // Bind `let v: T` from the slot, if requested. let bn: str = cs.str; if (bn.len > 0) { if (pat != nil) { @@ -3468,7 +3447,6 @@ fn cgmatch(c: *cgen, n: *syntax.node) void = { }; }; }; - // Body. Match arms are statements; we cgstmt them. if (cs.body != nil) { cgstmt(c, cs.body); }; // Restore the locals head — pop everything the arm pushed // so post-match code resolves names to their original (outer) @@ -5299,10 +5277,6 @@ fn cgdot(c: *cgen, n: *syntax.node) void = { }; fn cgun(c: *cgen, n: *syntax.node) void = { - // Match C cgen ordering: evaluate operand first (load into AX), - // then apply the unary op. AMP / STAR override AX with the - // address / deref. The wasted load before AMP keeps our asm - // byte-identical to the C version. let fk: i32 = 0; if (n.lhs != nil) { let lt: *syntax.tinfo = n.lhs.type_: *syntax.tinfo; diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index a28d8aac..71665a7b 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1,21 +1,9 @@ -// selfhost/cmd/wcc/cgenstmt.ww — split out of cgen.ww. -// -// cgstmt is a thin dispatcher over n.kind; each branch defers to a -// per-kind helper: cgblock, cgreturn, cgexprstmt, cglet, cgif, cgfor, -// cgmassign, cgbreak, cgcontinue. -// -// The expression generator (cgexpr) lives in cgenexpr.ww; the -// foundation (types, emit primitives, collect* tables, FFI/module -// maps) lives in cgen.ww. - package wcc; import os; import syntax; import strconv; -// ---- statement cgen -------------------------------------------------- - fn cgstmt(c: *cgen, n: *syntax.node) void = { if (n == nil) { return; }; let k: syntax.nkind = n.kind; @@ -64,9 +52,8 @@ fn cgstmt(c: *cgen, n: *syntax.node) void = { }; fn cgyield(c: *cgen, n: *syntax.node) void = { - // Evaluate the value into AX (and BX for str), then JMP to the - // enclosing match's end label. Falls through silently if there - // is no active match — should be a checker error eventually. + // Falls through silently if there is no active match — should be + // a checker error eventually. if (n.lhs != nil) { cgexpr(c, n.lhs); }; if (c.yieldtop > 0) { let tgt: str = c.yieldbuf[c.yieldtop - 1]; @@ -100,8 +87,6 @@ fn cgblock(c: *cgen, n: *syntax.node) void = { return; }; -// rundefers — emit cgexpr for every queued defer in LIFO order. -// Called from cgreturn and the cgfn implicit-return path. fn rundefers(c: *cgen) void = { let i: i32 = c.defertop - 1; for (i >= 0) { @@ -3178,11 +3163,6 @@ fn cgfor(c: *cgen, n: *syntax.node) void = { return; }; -// Tuple-destructure assign: `a, b = call();`. The call's tuple -// return lands in (AX, DX); push DX to free it, store AX into -// the first lvalue, then pop DX into the second. Mirrors -// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same -// as C — no fixture uses >2 today). fn cgmassign(c: *cgen, n: *syntax.node) void = { // #83: positional per-element destructure REASSIGN. Same cursor as // cgmlet (and cgreturn; harec create_unpack_bindings, @@ -3394,17 +3374,6 @@ fn cgmassign(c: *cgen, n: *syntax.node) void = { return; }; -// Multi-let from a tuple-returning call: `let n, s = call();` or -// `let (n, s) = call();`. wwstage has no checker, so each binding's -// type is taken from its explicit annotation (l.lhs) when present -// or inferred from the called fn's return-type tuple element. -// -// Per the AX:DX:CX:R8 return convention (mirrors C cgen nkind.N_MLET): -// (scalar, scalar) — AX → l0, DX → l1. -// (scalar, str) — AX → scalar slot, (DX, CX, R8) → str slot -// as (.ptr, .len, .cap). Position-agnostic — the -// regs are routed by element type, not by AX/DX. -// str IS []u8 (24B): cap rides R8 (#1/Phase 3, task #5). fn cgmlet(c: *cgen, n: *syntax.node) void = { let rhs: *syntax.node = n.rhs; if (rhs == nil) { return; }; @@ -3958,7 +3927,6 @@ fn cgforrange(c: *cgen, n: *syntax.node) void = { nbinds = 1; }; - // init: ioff(BP) = 0 emitline("\tMOVQ\t$0, "); emitoff(ioff: i64); emitline("(BP)\n"); @@ -4290,5 +4258,3 @@ fn cgcontinue(c: *cgen, n: *syntax.node) void = { c.lastwasreturn = 0; return; }; - - diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 7f6d25f2..9b930c8f 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1,14 +1,3 @@ -// selfhost/cmd/wcc/cgenutil.ww — split out of cgen.ww. -// -// General helpers used across cgenexpr / cgenstmt / cgendecl: -// - pushargsrev: per-call arg pushing -// - type predicates: isstr*/isslice*/istagged*/nodeis* families -// - field ops: fieldloadop, fieldstoreop -// - index helpers: elemsizeof, elemsizeofc -// - slot sizing: structlookup, primsize, slotsize, fieldsize, -// registerstruct, collectstructs -// - rhs helpers: taggedvariantindex -// // Bundler pulls this in transitively via cgen.ww; consumers don't // need to `use cgenutil;` directly. @@ -18,8 +7,6 @@ import os; import syntax; import strconv; -// ---- variadic-call helpers (Hare-style `T...` param) ----------------- - // slicewrap — synthesise an N_TSLICE node wrapping the given element // type AST. Used by the Hare-style variadic path so the local entry // for the param (callee side) and the call-site slice descriptor @@ -263,8 +250,6 @@ fn calleecvariadic(c: *cgen, callee: *syntax.node, nfixed_out: *i32) bool = { return false; }; -// ---- expression cgen ------------------------------------------------- - // pushargsrev — recursively walks the arg list, evaluates rightmost // first, and pushes. str args take two slots (ptr in AX, len in BX); // the order on the stack so a left-to-right pop into argregs lands @@ -787,7 +772,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool, let es60: *syntax.tinfo = tichase(bu60.sub); if (es60 != nil) { esz = es60.size: i32; }; }; - // base address → push if (baselocal != nil) { let tn: *syntax.node = baselocal.tnode; let isarr60: bool = false; @@ -828,7 +812,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool, cgexpr(c, base); };};}; emitline("\tPUSHQ\tAX\n"); - // hi (default base length) → push if (hi != nil) { cgexpr(c, hi); } else { if (baselocal != nil) { @@ -945,7 +928,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool, emitline("\tMOVQ\t$0, AX\n"); };};};};}; emitline("\tPUSHQ\tAX\n"); - // lo (default 0) → AX if (lo != nil) { cgexpr(c, lo); } else { emitline("\tMOVQ\t$0, AX\n"); }; emitline("\tPOPQ\tBX\n"); // hi @@ -2098,14 +2080,9 @@ fn elemsizeofc(c: *cgen, t: *syntax.node) i32 = { return slotsize(c, elem); }; -// nodeisunsigned — best-effort cgen-time inference from the AST. We -// walk surface nodes (N_DOT now reads n.type_ — #55 A.6.3g): -// nkind.N_INTLIT — never marked unsigned (no tsuffix plumbing yet) -// nkind.N_IDENT — look up the local's declared type -// nkind.N_DOT — read the checker-stamped n.type_ (#55 A.6.3g) -// nkind.N_BIN / nkind.N_UN — recurse: unsigned if either operand is unsigned -// nkind.N_CAST — use the cast target type -// +// nodeisunsigned — best-effort cgen-time inference from the AST +// (N_DOT reads the checker-stamped n.type_ — #55 A.6.3g). +// nkind.N_INTLIT is never marked unsigned (no tsuffix plumbing yet). // Conservative: if we can't tell, return false (signed). The cost of // being wrong here is byte-different asm vs C, not bad runtime. fn nodeisunsigned(c: *cgen, n: *syntax.node) bool = { @@ -2191,8 +2168,6 @@ fn nodeprimwidth(c: *cgen, n: *syntax.node) i32 = { return 0; }; -// ---- type-driven slot sizing ---------------------------------------- - // structnaturalsize — type-natural size of `si`, i.e. max(foff + // fsz) across declared fields, UNROUNDED. This is the memory-copy // extent: cstage copies exactly these bytes for the >24B sret @@ -2594,11 +2569,8 @@ fn structsamemod(c: *cgen, name: str) *structinfo = { return nil; }; -// primsize — size in bytes of a primitive type name (or 0 if not -// recognised as a primitive — the caller falls back to other paths). -// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an -// index, or -1 if not all-digits. Used by cgdot to dispatch -// `t.0` / `t.1` against an nkind.N_TTUPLE local without pulling in strconv. +// fldnumidx — used by cgdot to dispatch `t.0` / `t.1` against an +// nkind.N_TTUPLE local without pulling in strconv. fn fldnumidx(s: str) i32 = { if (s.len == 0) { return -1; }; let r: i32 = 0; @@ -5141,7 +5113,6 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *syntax.tinfo, src: *syntax.node, slot_of }; }; }; }; - // Struct payload (literal or ident). let sname: str = rhsstructpayload(c, src); if (sname.len > 0) { let si: *structinfo = structlookup(c, sname); diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index ff19db04..0a4c746f 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -1,21 +1,4 @@ -// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c. -// -// Status: name-resolution + primitive-type seeding only. Full type -// inference, conversion rules, tagged-union dispatch typing, return- -// type checking, etc. all live in cmd/wcc/check.c (937 lines) and -// will land here in subsequent commits. -// -// What this version does: -// 1. Creates a top scope and seeds it with primitive type names so -// `i32`, `str`, `*u8` etc. resolve. -// 2. Walks the file's top-level decls (use/def/type/fn/let) and -// installs Sym entries for each. -// 3. Recursively walks fn bodies; for every nkind.N_IDENT used as an -// expression or as a type name, looks it up and counts the -// resolved vs. unresolved. -// 4. Returns a summary the caller (wwdump -r) prints; the test -// asserts unresolved == 0 on every selfhost fixture, which is -// the floor signal that the frontend can name-resolve real ww. +// Port of cmd/wcc/check.c. package wcc; @@ -91,8 +74,6 @@ fn circularnamed(c: *checker, t: *syntax.tinfo, n: *syntax.node) bool = { os.exit(1); }; -// seedprimitives — install the built-in type names so `i32`, `str`, -// etc. can be looked up like ordinary symbols. fn seedprimitives(c: *checker) void = { syntax.scopedefine(c.top, "void", syntax.skind.SK_TYPE, c.tc.tyvoid, nil); syntax.scopedefine(c.top, "bool", syntax.skind.SK_TYPE, c.tc.tybool, nil); @@ -822,8 +803,7 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = { // free identifier. if (n.lhs != nil) { resolvewalk(c, n.lhs); }; // A.6.0: branch returns early; stamp here so the post-walk - // dispatch below sees N_DOT covered. exprtype N_DOT arm is - // added in A.6.1; for now this is a no-op nil return. + // dispatch below sees N_DOT covered. let _t: *syntax.node = exprtype(c, n, nil); return; }; @@ -957,15 +937,12 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = { }; }; -// ---- type-level helpers (AST-level, no resolved tinfo) -------------- -// // The selfhost check operates on AST type expressions rather than // resolved Type structs. These helpers mirror what cmd/wcc/check.c // does with tinfo, but only on the subset of cases this checker // needs to enforce: tagged-union exhaustiveness, ? subset // propagation, and !-flag semantics. -// unwrapbang — strip an nkind.N_TBANG wrapper; leaves other nodes alone. fn unwrapbang(n: *syntax.node) *syntax.node = { if (n == nil) { return nil; }; if (n.kind == syntax.nkind.N_TBANG) { return n.lhs; }; @@ -1050,10 +1027,6 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = { return s; }; -// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return -// the typedecl's body (possibly recursively). Pass-through for any -// other node. The chain stops once we hit a non-nkind.N_TNAME node or a -// name we can't resolve. fn resolvealias(c: *checker, n: *syntax.node) *syntax.node = { let cur: *syntax.node = n; for (cur != nil) { @@ -1274,9 +1247,6 @@ fn scruttype(c: *checker, e: *syntax.node) *syntax.node = { return nil; }; -// mktname — fabricate an nkind.N_TNAME node with str = `nm`. Used by -// exprtype to return primitive type nodes for literal -// expressions. The arena keeps them around as long as the checker. fn mktname(c: *checker, nm: str) *syntax.node = { let n: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, "", 0, 0); n.str = nm; @@ -4692,9 +4662,6 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = { return nil; }; -// isuntypedint / is_str_like / is_bool_like — helpers used -// by the assignability check below to allow common AST shapes -// through without needing real type inference. fn isuntypedint(t: *syntax.node) bool = { if (t == nil) { return false; }; if (t.kind != syntax.nkind.N_TNAME) { return false; }; @@ -5386,14 +5353,10 @@ fn isassignable(c: *checker, dst: *syntax.node, src: *syntax.node, confident: *b return true; }; -// ---- match exhaustiveness -------------------------------------------- -// // For every match arm, verify that every variant of the scrutinee's // tagged-union type is handled by some case (or a default arm // exists). Multi-pattern `case A | B =>` covers all alts. -// qualleaf — rightmost dotted segment of a (possibly module-qualified) -// type name; the whole name when unqualified. fn qualleaf(nm: str) str = { let dotidx: i32 = -1; let i: i32 = 0; @@ -5408,8 +5371,6 @@ fn qualleaf(nm: str) str = { return leaf; }; -// qualmod — module qualifier of a type name (segment before the -// rightmost '.'), or `defmod` when unqualified. fn qualmod(nm: str, defmod: str) str = { let dotidx: i32 = -1; let i: i32 = 0; @@ -5646,8 +5607,6 @@ fn checkvariantcovered(c: *checker, n: *syntax.node, v: *syntax.node, unionmod: if (!covered) { errmatchvariant(c, n, v); }; }; -// ---- let init / return assignability -------------------------------- -// // AST-level approximation: when we can infer src's type and dst is // explicitly declared, verify isassignable. We only emit an error // when isassignable says "false with confidence." If we can't tell @@ -6581,8 +6540,6 @@ fn checkretassign(c: *checker, n: *syntax.node) void = { }; }; -// ---- is / as validity ------------------------------------------------ -// // `e is T` and `e as T` require that e's declared type be a tagged // union and that T name one of its variants. Operates on AST type // expressions; falls back silently when we can't determine e's @@ -6670,8 +6627,6 @@ fn checkisas(c: *checker, n: *syntax.node) void = { c.errs += 1; }; -// ---- ? subset propagation -------------------------------------------- -// // For `expr?`, the operand's error subset must be a subset of the // enclosing fn's return-type variants. Mirrors C check.c. Operand // is nkind.N_TRYPROP or nkind.N_TRYUNW (the F8 cardinality gate covers @@ -6836,9 +6791,6 @@ fn hascvariadic(params: *syntax.node) bool = { return false; }; -// install_param — when entering a fn body, define its params in a -// fresh local scope. -// // TODO(#11): cstage check.c (post-#32) errors `param '%s' redeclared` // when two params share a name. The fn body's scope IS fresh here // (resolvefnbody opens it before calling us), so guarding scopedefine's diff --git a/selfhost/cmd/wcc/err.ww b/selfhost/cmd/wcc/err.ww index 47117124..43933a0c 100644 --- a/selfhost/cmd/wcc/err.ww +++ b/selfhost/cmd/wcc/err.ww @@ -1,7 +1,6 @@ -// selfhost/cmd/wcc/err.ww — port of cmd/wcc/err.c. -// -// Diagnostics. Plan 9 style: short, no levels beyond fatal/error/warn. -// Output goes through os.write so we don't pull in libc stdio. +// Port of cmd/wcc/err.c. Plan 9 style: short, no levels beyond +// fatal/error/warn. Output goes through os.write so we don't pull in +// libc stdio. package wcc; diff --git a/selfhost/cmd/wcc/wwi.ww b/selfhost/cmd/wcc/wwi.ww index 0826c3fb..f8341c7c 100644 --- a/selfhost/cmd/wcc/wwi.ww +++ b/selfhost/cmd/wcc/wwi.ww @@ -16,17 +16,12 @@ // - A `.wwi` is ONE package's interface; the emit filters to PRIMARY // decls (imported==0). -// Imports mirror check.ww (os/tok/strconv only): node/nkind/sym/skind/ -// scopelookuptype/streq/checker/tkind resolve bare in the selfhost's flat -// bundled scope, exactly as check.ww references them. package wcc; import os; import syntax; import strconv; -// --- byte writers ------------------------------------------------------ - fn wputs(fd: i32, s: str) void = { os.write(fd, s.ptr, s.len: u64); }; @@ -71,13 +66,11 @@ fn wquote(fd: i32, s: str) void = { wputb(fd, '"'); }; -// --- check_exported_type (drew) --------------------------------------- -// -// Resolve an N_TNAME to its type sym via the public sym helpers (mirror -// of cstage wwi_typesym / scope_lookup_type) WITHOUT the side effects of -// the checker's aliassym (no on-demand resolve, no double error). A -// primitive/keyword resolves to no SK_TYPE → leaf. By producer time -// checkfile has finished and c.cur == c.top. +// check_exported_type (drew): resolve an N_TNAME to its type sym via the +// public sym helpers (mirror of cstage wwi_typesym / scope_lookup_type) +// WITHOUT the side effects of the checker's aliassym (no on-demand +// resolve, no double error). A primitive/keyword resolves to no SK_TYPE +// → leaf. By producer time checkfile has finished and c.cur == c.top. fn wwitypesym(c: *checker, nm: str) *syntax.sym = { let empty: str; @@ -188,7 +181,7 @@ fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = { bad = bad | wwichecktype(c, d, p.lhs); p = p.next; }; - bad = bad | wwichecktype(c, d, d.lhs); // ret + bad = bad | wwichecktype(c, d, d.lhs); } else { if (d.kind == syntax.nkind.N_TYPEDECL) { bad = bad | wwichecktype(c, d, d.lhs); } else { if (d.kind == syntax.nkind.N_DEF) { @@ -200,10 +193,8 @@ fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = { return bad; }; -// --- type-expr + const-expr unparser (rob §2.2/§2.4) ------------------ - -// wwihexdigits — emit the low `n` hex digits of `v`, most-significant -// first, lowercase. Byte-identical to cstage's fprintf("%0Nx"). +// Type-expr + const-expr unparser (rob §2.2/§2.4). wwihexdigits is +// byte-identical to cstage's fprintf("%0Nx"). fn wwihexdigits(fd: i32, v: u64, n: i32) void = { let i: i32 = n - 1; for (i >= 0) { @@ -515,8 +506,6 @@ fn wwidecl(fd: i32, d: *syntax.node) void = { };};};}; }; -// --- deterministic ordering (rob §3) ---------------------------------- - fn wwiprimary(n: *syntax.node) bool = { // imported==1 marks a decl reached through a `//ww:module ` // boundary (an imported module's concatenated section). @@ -529,8 +518,9 @@ fn wwiisdecl(d: *syntax.node) bool = { d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_LET; }; -// strcmp — byte lexicographic, mirror C strcmp sign (<0/0/>0). Both -// stages key the sort identically, so the `.wwi` order is deterministic. +// Deterministic ordering (rob §3): byte-lexicographic, mirror C strcmp +// sign (<0/0/>0). Both stages key the sort identically, so the `.wwi` +// order is deterministic. fn wwistrcmp(a: str, b: str) i32 = { let i: i32 = 0; for (i < a.len && i < b.len) { @@ -542,8 +532,7 @@ fn wwistrcmp(a: str, b: str) i32 = { return a.len - b.len; }; -// Selection sort over parallel (key, node) arrays. Total order keyed on -// the symbol name; ties broken by original index — so the result is +// Total order keyed on the symbol name; ties broken by original index — // stable regardless of any same-name collision, matching cstage's qsort // + idx tiebreak. fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = { diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index fca54947..bac5f7db 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -24,9 +24,8 @@ import strings; // the cgen #127 mod-mangle attribution bug consumer per rule-7. def CMD_MAX: u64 = 8192u64; -// cerr — bare stderr fragment writer for the driver's piecewise -// diagnostics. Tool-local (NOT a lib wrapper): messages are built from -// many fragments and we route through os.write to avoid libc stdio. +// Tool-local (NOT a lib wrapper): messages are built from many +// fragments and we route through os.write to avoid libc stdio. // .len replaces the error-prone hand-counted byte literals these sites // carried. Lives here (the selfhost ww-driver build is a single main.ww; // err.c's bare-message path is not ported into this tree). @@ -34,17 +33,14 @@ fn cerr(m: str) void = { os.write(2, m.ptr, m.len: u64); }; -// ---- C-string helpers -------------------------------------------------- - fn cstrlen(p: *u8) u64 = { let n: u64 = 0u64; for (p[n] != 0u8) { n += 1u64; }; return n; }; -// pathstr — view a NUL-terminated *u8 as a str. Bridges the -// driver's argv-style *u8 paths to lib/os entrypoints (str -// post-task-#23). +// Bridges the driver's argv-style *u8 paths to lib/os entrypoints +// (str post-task-#23). fn pathstr(p: *u8) str = { let r: str; r.ptr = p; @@ -56,12 +52,10 @@ fn cstreq(a: *u8, b: *u8) bool = { return strings.compare(pathstr(a), pathstr(b)) == 0; }; -// cstreqlit — compare a NUL-terminated *u8 to a ww string literal. fn cstreqlit(a: *u8, lit: str) bool = { return strings.compare(pathstr(a), lit) == 0; }; -// memcpy fn bytecpy(dst: *u8, src: *u8, n: u64) void = { let i: u64 = 0u64; for (i < n) { @@ -70,8 +64,6 @@ fn bytecpy(dst: *u8, src: *u8, n: u64) void = { }; }; -// Copy a NUL-terminated *u8 into dst starting at off; return the new -// offset (without writing a NUL). fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = { let i: u64 = 0u64; for (src[i] != 0u8) { @@ -81,7 +73,6 @@ fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = { return off + i; }; -// Same, but for a ww `str` (no NUL on the source side; we copy len bytes). fn strinto(dst: *u8, off: u64, src: str) u64 = { let n: i32 = src.len; let i: i32 = 0; @@ -94,22 +85,15 @@ fn strinto(dst: *u8, off: u64, src: str) u64 = { return off + nu; }; -// Write a single byte, return new offset. fn byteinto(dst: *u8, off: u64, c: u8) u64 = { dst[off] = c; return off + 1u64; }; -// NUL-terminate at off and return the same off (handy when passing the -// buffer to a syscall that expects a C-string). fn cstrseal(dst: *u8, off: u64) void = { dst[off] = 0u8; }; -// ---- Tool-path resolution --------------------------------------------- - -// dirname-equivalent: copy argv[0] up to (but not including) the last -// '/' into dst, NUL-terminated. If no slash, write ".". fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = { let n: u64 = cstrlen(argv0); let cut: u64 = 0u64; @@ -128,7 +112,6 @@ fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = { dst[cut] = 0u8; }; -// Build "$dir/$name" (NUL-terminated) into a fresh page-sized buffer. fn joinpath(dir: *u8, name: *u8) *u8 = { let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; buf.len = os.PATH_MAX; @@ -139,7 +122,6 @@ fn joinpath(dir: *u8, name: *u8) *u8 = { return buf.ptr; }; -// Same, but the second component is a ww `str` literal. fn joinpathlit(dir: *u8, name: str) *u8 = { let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; buf.len = os.PATH_MAX; @@ -208,8 +190,6 @@ fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32, return 1; }; -// ---- import resolution + visited-set ----------------------------------- -// // The separate-compilation producer scans each unit's top-of-file // `import IDENT;` lines and resolves them via the colon-separated `dirs` // search path. A per-scan visited set (linear; typical builds visit a @@ -409,10 +389,9 @@ fn dirfileclass(dirpath: *u8, name: *u8, nlen: u64) i32 = { return 1; }; -// Byte-wise memcmp returning < 0, 0, > 0. Rule-10 byte-id requires -// cstage and wwstage sort the same way; memcmp is the -// locale-independent total order (mirrors ref/hare/sort/cmp/cmp.ha -// strs). +// Rule-10 byte-id requires cstage and wwstage sort the same way; +// memcmp is the locale-independent total order (mirrors +// ref/hare/sort/cmp/cmp.ha strs). fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = { let n: u64 = alen; if (blen < n) { n = blen; }; @@ -526,8 +505,6 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = { return exact.ptr, n; }; -// ---- file slurp ------------------------------------------------------- - fn slurp(pathcs: *u8) (*u8, u64) = { let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32); if (fd < 0) { return nil, 0u64; }; @@ -566,7 +543,6 @@ fn isidentbyte(c: u8) bool = { // caller passes a slice of the source: src points at the line start. fn scanuse(src: *u8, len: u64) (*u8, u64) = { let i: u64 = 0u64; - // skip leading whitespace for (i < len) { if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; }; i += 1u64; @@ -595,11 +571,6 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = { return src + idstart, idlen; }; -// ---- Build pipeline --------------------------------------------------- - -// Strip the trailing ".ww" off `src` (a NUL-terminated path) into -// `stem`, NUL-terminated. If there's no .ww, the stem is the whole -// path. fn makestem(stem: *u8, src: *u8) void = { let n: u64 = cstrlen(src); let stop: u64 = n; @@ -617,7 +588,6 @@ fn makestem(stem: *u8, src: *u8) void = { stem[stop] = 0u8; }; -// Append a literal suffix to `stem` (which already lives in a buffer). fn appendlit(stem: *u8, suffix: str) *u8 = { let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; buf.len = os.PATH_MAX; @@ -638,8 +608,6 @@ type lflags = struct { nlibs: i32, }; -// ---- separate-compilation driver ------------------------------------- -// // Port of cmd/ww/main.c build_one_sep (task #46/c3). The build path // materializes each imported package's `.wwi` interface and compiles // every package on its own (`w6c -c`), then flat-links the `.o` set. @@ -677,7 +645,6 @@ type sepgraph = struct { n: i32, }; -// Find a package by dotted path, or add it. Returns index, -1 if full. fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = { let i: i32 = 0; for (i < g.n) { @@ -1060,8 +1027,6 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = { return 0; }; -// Print one cycle-chain node: a package path, or "(root)" for the -// empty root path. fn sepcyclenode(p: *u8) void = { if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); }; }; @@ -1103,7 +1068,6 @@ fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32, return 0; }; -// Mark pi's transitive deps (excluding pi) in inset[]. fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = { let k: i32 = 0; for (k < g.pkg[pi].ndeps) { @@ -1306,7 +1270,7 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = { // reverse-topo `w6l` of the root `.o` + dependency `.a` set + libwwrt.a. // Side files land in a cold `.sepwork` scratch dir. Twin of cstage // build_one_sep. -// ---- -w workdir freshness ---------------------------------------------- + // A `-w DIR` workdir is a caller-owned persistent package-artifact tree // that replaces the fresh `.sepwork` scratch. Staleness is pure content // identity, never mtime: a package is reused only when its freshly @@ -1478,7 +1442,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, let a6: *u8 = joinpathlit(selfdir, "w6a_ww"); let l6: *u8 = joinpathlit(selfdir, "w6l_ww"); - // Default lib search path: /../../lib let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!; dotdotlib.len = os.PATH_MAX; { @@ -1487,7 +1450,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, cstrseal(dotdotlib.ptr, off); }; - // Source directory. let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!; srcd.len = os.PATH_MAX; if (entryisdir != 0) { @@ -1532,7 +1494,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, cstrseal(searchpath.ptr, off); }; - // Stem for the scratch dir. let stem: []u8 = alloc([], (os.PATH_MAX: u64))!; stem.len = os.PATH_MAX; if (entryisdir != 0) { @@ -1606,7 +1567,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, }; }; - // libwwrt.a path: /../lib/libwwrt.a let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!; libwwrt.len = os.PATH_MAX; { @@ -1615,7 +1575,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, cstrseal(libwwrt.ptr, off); }; - // Discover. let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!; pkgslot.len = SEP_MAXPKG; let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!; @@ -1649,7 +1608,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, }; if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; }; - // Reset colors, reverse-topo. let ci: i32 = 0; for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; }; let order: []i32 = alloc([], g.n: u64)!; @@ -1659,7 +1617,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, let norder: i32 = 0; if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; }; - // Producer loop — dep-first, one `w6c -c -I` per package. let oi: i32 = 0; for (oi < norder) { let pi: i32 = order[oi]; @@ -1961,22 +1918,10 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, return r; }; -// ---- Module-by-name resolution ---------------------------------------- -// -// Mirrors cmd/ww/main.c:resolvemodule. Maps a name like "foo", "lib/foo", -// "foo.ww", or "." to a concrete .ww file path: -// 1. literal .ww that exists → use as-is -// 2. "." → /.ww → that, if it exists -// 3. /.ww → that, if it exists -// 4. walk search path (cwd:incs:/../../lib): -// /.ww or //.ww - fn cstrendswithlit(p: *u8, lit: str) bool = { return strings.hassuffix(pathstr(p), lit); }; -// basenameoff — return the offset of the last path segment within `p` -// (i.e. one past the final '/'). Returns 0 if there's no slash. fn basenameoff(p: *u8, plen: u64) u64 = { let start: u64 = 0u64; let i: u64 = 0u64; @@ -1987,8 +1932,6 @@ fn basenameoff(p: *u8, plen: u64) u64 = { return start; }; -// arenadupcstr — copy `plen` bytes from `src` into a fresh NUL-sealed -// heap buffer. fn arenadupcstr(src: *u8, plen: u64) *u8 = { let buf: []u8 = alloc([], plen + 1u64)!; let i: u64 = 0u64; @@ -1997,8 +1940,7 @@ fn arenadupcstr(src: *u8, plen: u64) *u8 = { return buf.ptr; }; -// buildsearchpath — compose the colon-separated lookup path used by -// resolvemodule's case (4). Order: "." : : /../../lib +// resolvemodule search-path order: "." : : /../../lib fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = { let buf: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!; let off: u64 = 0u64; @@ -2016,13 +1958,11 @@ fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = { return buf.ptr; }; -// resolvemodule — map a name like "foo", "lib/foo", "foo.ww", or -// "." to a concrete entry path. Sets *isdir when the entry is a +// Mirrors cmd/ww/main.c:resolvemodule. Sets *isdir when the entry is a // module directory (caller will dir-enumerate). fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = { let nlen: u64 = cstrlen(name); - // (1) Literal file that exists → use as-is. if (cstrendswithlit(name, ".ww")) { if (os.access(pathstr(name), 0i32) == 0) { *isdir = 0; @@ -2030,7 +1970,6 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = { }; }; - // (2) Existing path → use as-is, dir vs file via stat. let fi: os.filestat; let sr: (void | os.oserror) = os.stat(&fi, pathstr(name)); let found: bool = false; @@ -2048,13 +1987,10 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = { return arenadupcstr(name, nlen); }; - // (3) Search-path lookup with dot-to-slash path translation. let search: *u8 = buildsearchpath(selfdir, incs); return locateimport(search, name, nlen, isdir); }; -// ---- Subcommand handlers ---------------------------------------------- - fn writeusage(fd: i32) void = { let s: str = "usage: ww [-V] [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n lib/... every package under lib, recursively (test only)\n . build the cwd's .ww\n"; os.write(fd, s.ptr, s.len: u64); @@ -2065,8 +2001,6 @@ fn doversion() i32 = { return 0; }; -// Compute the basename of src (without trailing ".ww") into a fresh -// buffer. Used as the default output path for `ww build`. fn defaultoutpath(src: *u8) *u8 = { let n: u64 = cstrlen(src); let start: u64 = 0u64; @@ -2084,7 +2018,6 @@ fn defaultoutpath(src: *u8) *u8 = { off += 1u64; j += 1u64; }; - // Strip ".ww" if present. if (off >= 3u64) { if (out[off - 3u64] == 46u8) { if (out[off - 2u64] == 119u8) { @@ -2209,7 +2142,6 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { }; if (src == nil) { - // default to cwd module let dot: [2]u8 = ['.': u8, 0u8]; src = &dot[0]; }; @@ -2264,7 +2196,6 @@ fn makedrivertmp(buf: *u8, prefix: str) void = { pk += 1; }; let pid: i32 = os.getpid(); - // itoa for non-negative pid let dig: [16]u8; let n: i32 = 0; if (pid <= 0) { @@ -2383,7 +2314,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { src = p; i += 1; } else { - passstart = i; // remaining args go to the program + passstart = i; }; }; }; @@ -2426,8 +2357,8 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { return 1; }; - // Execute with [tmp, argv[passstart..argc)). The standard process - // facility inherits stdio and waits only for this user program. + // The standard process facility inherits stdio and waits only for + // this user program. let nextra: i32 = 0; if (passstart >= 0) { nextra = argc - passstart; }; let execargv: []str = alloc([], (nextra + 1): u64)!; @@ -2464,8 +2395,6 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { return rc; }; -// ---- ww test ---------------------------------------------------------- -// // Mirrors cmd/ww/main.c:dotest. Explicit regular files retain the bootstrap // compatibility route; directory/default requests delegate to wwtest. @@ -2762,15 +2691,12 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { replacement, targetindex < 0); }; -// ---- Entry ------------------------------------------------------------- - export fn main(argc: i32, argv: **u8) i32 = { if (argc < 1) { writeusage(2); return 2; }; - // selfdir = dirname(argv[0]) let selfdir: []u8 = alloc([], (os.PATH_MAX: u64))!; selfdir.len = os.PATH_MAX; selfdirinto(selfdir.ptr, (os.PATH_MAX: u64), argv[0]); diff --git a/selfhost/cmd/wwdump/main.ww b/selfhost/cmd/wwdump/main.ww index e1050ccc..41c8a494 100644 --- a/selfhost/cmd/wwdump/main.ww +++ b/selfhost/cmd/wwdump/main.ww @@ -1,13 +1,6 @@ -// selfhost/cmd/wwdump/main.ww — ww-side port of cmd/wwdump/main.c. -// -// Reads a .ww file, runs the ww-side lexer, prints tokens through -// the ww-side tokprint. The 990_selfhost test diffs this output -// byte-for-byte against the C-side wwdump on the same file. Any +// Port of cmd/wwdump/main.c. The 990_selfhost test diffs token output +// byte-for-byte against the C-side wwdump on the same file; any // divergence is a port bug in lex.ww or tok.ww. -// -// Modes: -// wwdump -t file.ww tokens (default) -// wwdump -a file.ww AST (not yet implemented; reserved) package main; @@ -17,10 +10,8 @@ import check; import cgen; import strconv; -// ---- argv helpers ----------------------------------------------------- - -// argstrlen — strlen on a NUL-terminated *u8. argv strings are always -// NUL-terminated (kernel-supplied) so this is safe. +// argv strings are always NUL-terminated (kernel-supplied), so the +// unbounded scan is safe. fn argstrlen(s: *u8) i32 = { let n: i32 = 0; for (s[n] != 0u8) { n += 1; }; @@ -34,7 +25,6 @@ fn argstr(p: *u8) str = { return s; }; -// streqlit — compare a NUL-terminated argv entry to a string literal. fn streqlit(p: *u8, lit: str) bool = { let i: i32 = 0; for (i < lit.len) { @@ -44,8 +34,6 @@ fn streqlit(p: *u8, lit: str) bool = { return p[i] == 0u8; }; -// ---- main ------------------------------------------------------------- - export fn main(argc: i32, argv: **u8) i32 = { let mode: i32 = 116; // 't' let path: *u8 = nil; @@ -142,7 +130,6 @@ export fn main(argc: i32, argv: **u8) i32 = { // Quiet by default; flip to 1 when debugging missing names. ck.verbose = 0; checkfile(&ck, f); - // (close out the if-else chain — we'll close all braces below) // ": / resolved" os.write(1, argstr(path).ptr, argstrlen(path): u64); os.write(1, ": ".ptr, 2u64);