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

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

View File

@@ -1,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;

View File

@@ -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);

View File

@@ -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 <ctype.h>
#include <stdlib.h>

View File

@@ -1,6 +1,3 @@
/*
* w6a — amd64 assembler driver. Read .s, parse, encode, emit ELF .o.
*/
#include "a.h"
#include <stdio.h>
#include <stdlib.h>

View File

@@ -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 <string.h>
#include <stdio.h>
/* 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

View File

@@ -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;

View File

@@ -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

View File

@@ -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 `<module>.<ident>` 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);

View File

@@ -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 */

View File

@@ -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 <stdio.h>
#include <stdlib.h>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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<framesize>
* MOVQ $1, AX

View File

@@ -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; };

View File

@@ -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 <stdio.h>

View File

@@ -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 <stdlib.h>
#include <string.h>
/* 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); \

View File

@@ -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

View File

@@ -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<dir>...] [-l<name>...] 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);

View File

@@ -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 <stdlib.h>
#include <string.h>
@@ -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 "!<arch>\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);

View File

@@ -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 <stdio.h>
@@ -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);

View File

@@ -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 <stdio.h>
#include <string.h>
@@ -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);

View File

@@ -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 <stdlib.h>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <runtime.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 <basename>.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 <dir>/<path>/ as a directory (want_dir), else <dir>/<path>.ww as
* a file. Sets *is_dir on hit. Symmetric with wwstage locatein for
* byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.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 <dir>/<name>/<name>.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/<mod>/<x>_test.ww`
* entry makes srcd = lib/<mod>, so a self-named `import <mod>` 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
* <existing dir> → 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];

View File

@@ -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: "<file>:<l>:<c> <kind> [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++) {