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 * Modelled on Plan 9 cmd/6a/a.h, trimmed to the instruction subset
* to the instruction subset that w6c emits. * that w6c emits. Line-oriented, no preprocessor: each non-blank,
* * non-label line is one instruction.
* 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.
*/ */
#ifndef SIX_A_H #ifndef SIX_A_H
#define SIX_A_H #define SIX_A_H
@@ -69,17 +66,14 @@ struct Areloc {
}; };
struct Asm { struct Asm {
/* parser state */
const char *file; const char *file;
const char *src; const char *src;
u64 srclen; u64 srclen;
u64 pos; u64 pos;
int line; int line;
/* program list */
Aprog *head, *tail; Aprog *head, *tail;
/* output text section */
u8 *text; u8 *text;
u64 textcap, textlen; u64 textcap, textlen;
@@ -89,7 +83,6 @@ struct Asm {
u8 *data; u8 *data;
u64 datacap, datalen; u64 datacap, datalen;
/* symbols */
Asym *syms; Asym *syms;
Areloc *relocs; 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 * Encoding subset: the instructions cgen emits today. Operand shapes
* we accept: * we accept:
* MOVQ $imm, reg — C7 /0 imm32 (REX.W) [imm fits in i32] * 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; a->relocs = r;
} }
/* ------ register codes ------------------------------------------- */
/* low 3 bits of register encoding */ /* low 3 bits of register encoding */
static int static int
rcode(int r) rcode(int r)
@@ -137,14 +131,12 @@ is_xmm(int r)
return r >= D_X0 && r <= D_X15; return r >= D_X0 && r <= D_X15;
} }
/* ModR/M byte */
static u8 static u8
modrm(int mod, int reg, int rm) modrm(int mod, int reg, int rm)
{ {
return (u8)(((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)); return (u8)(((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7));
} }
/* emit REX with W=1 plus optional R/B for high regs */
static void static void
emit_rex(Asm *a, int regbit, int rmbit, int w) 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); 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 static void
emit_modrm_mem(Asm *a, int reg_field, int base, i64 disp) 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))); a_emit_byte(a, modrm(3, rcode(src), rcode(dst)));
} }
/* MOVQ src reg → mem(base, disp). opcode = 0x89 */
static void static void
encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp) 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); emit_modrm_mem(a, rcode(src_reg), base, disp);
} }
/* MOVQ mem(base, disp) → reg. opcode = 0x8B */
static void static void
encode_mr(Asm *a, u8 opcode, int dst_reg, int base, i64 disp) 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); 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) */ * which target/source 64-bit integer regs) */
static void static void
sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op) 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))); a_emit_byte(a, modrm(3, rcode(reg_op), rcode(rm_op)));
} }
/* ------ second-pass helper: resolve labels to addresses ---------- */
static u64 static u64
resolve_label(Asm *a, const char *name) resolve_label(Asm *a, const char *name)
{ {
@@ -281,8 +267,6 @@ label_defined(Asm *a, const char *name)
return 0; return 0;
} }
/* ------ first pass: encode ---------------------------------------- */
/* For local labels, we record a "fixup" — an offset in .text that /* 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. */ * needs to be patched once the label is resolved at end of pass. */
typedef struct Fixup Fixup; typedef struct Fixup Fixup;
@@ -307,10 +291,9 @@ int
a_encode(Asm *a) a_encode(Asm *a)
{ {
fixups = NULL; fixups = NULL;
const char *cur_text = NULL; /* current TEXT name */ const char *cur_text = NULL;
(void)cur_text; (void)cur_text;
for (Aprog *p = a->head; p; p = p->link) { for (Aprog *p = a->head; p; p = p->link) {
/* Define any pending label at the current PC */
if (p->label) { if (p->label) {
Asym *s = a_intern(a, p->label); Asym *s = a_intern(a, p->label);
s->defined = 1; s->defined = 1;
@@ -800,7 +783,6 @@ a_encode(Asm *a)
/* R_X86_64_PLT32 (4); addend -4 */ /* R_X86_64_PLT32 (4); addend -4 */
a_addreloc(a, reloff, 4, s, -4); a_addreloc(a, reloff, 4, s, -4);
} else if (p->to.type == D_BRANCH) { } else if (p->to.type == D_BRANCH) {
/* local call to a label */
a_emit_byte(a, 0xE8); a_emit_byte(a, 0xE8);
add_fixup(a->textlen, p->to.sym); add_fixup(a->textlen, p->to.sym);
a_emit_u32(a, 0); 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) { for (Fixup *f = fixups; f; f = f->next) {
if (!label_defined(a, f->label)) { if (!label_defined(a, f->label)) {
fprintf(stderr, "w6a: undefined label '%s'\n", 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 "a.h"
#include <ctype.h> #include <ctype.h>
#include <stdlib.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 "a.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>

View File

@@ -1,6 +1,4 @@
/* /*
* obj.c — emit a tiny ELF64 relocatable object.
*
* Layout (in file order): * Layout (in file order):
* [0] ELF header * [0] ELF header
* [1] Section .text (program bytes) * [1] Section .text (program bytes)
@@ -24,7 +22,6 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
/* ELF constants */
#define ELFMAG "\x7f""ELF" #define ELFMAG "\x7f""ELF"
#define ELFCLASS64 2 #define ELFCLASS64 2
#define ELFDATA2LSB 1 #define ELFDATA2LSB 1
@@ -54,7 +51,6 @@
#define R_X86_64_PLT32 4 #define R_X86_64_PLT32 4
#define ELF64_R_INFO(s,t) (((u64)(s) << 32) | ((u64)(t) & 0xffffffff)) #define ELF64_R_INFO(s,t) (((u64)(s) << 32) | ((u64)(t) & 0xffffffff))
/* growable byte buffer */
typedef struct Buf Buf; typedef struct Buf Buf;
struct Buf { u8 *p; size_t n, cap; }; struct Buf { u8 *p; size_t n, cap; };
@@ -157,10 +153,9 @@ a_emit_elf(Asm *a, FILE *f)
bput(&sym, &z, sizeof z); bput(&sym, &z, sizeof z);
} }
/* Build symbols (defined = global; undefined = global UND). Data /* Data symbols carry STT_OBJECT and st_shndx=SH_DATA; everything
* symbols carry STT_OBJECT and st_shndx=SH_DATA; everything else * else keeps the legacy STT_FUNC/SH_TEXT shape so non-DATAW
* keeps the legacy STT_FUNC/SH_TEXT shape so non-DATAW outputs * outputs stay byte-identical. */
* stay byte-identical. */
int idx = 1; int idx = 1;
for (Asym *s = a->syms; s; s = s->next) { for (Asym *s = a->syms; s; s = s->next) {
Sym64 e = {0}; Sym64 e = {0};
@@ -193,7 +188,6 @@ a_emit_elf(Asm *a, FILE *f)
bput(r->section == 1 ? &relad : &rela, &re, sizeof re); bput(r->section == 1 ? &relad : &rela, &re, sizeof re);
} }
/* Layout offsets in the file */
u64 off = sizeof(Ehdr); u64 off = sizeof(Ehdr);
u64 off_text = off; off += a->textlen; u64 off_text = off; off += a->textlen;
u64 off_rela = off; off += rela.n; 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_sym = off; off += sym.n;
u64 off_str = off; off += str.n; u64 off_str = off; off += str.n;
u64 off_shstr= off; off += shstr.n; u64 off_shstr= off; off += shstr.n;
/* align to 8 */
while (off % 8) off++; while (off % 8) off++;
u64 off_shdr = off; u64 off_shdr = off;
const int NSECT = has_data const int NSECT = has_data

View File

@@ -1,6 +1,4 @@
/* /*
* parse.c — line-oriented parser for the asm subset emitted by w6c.
*
* Grammar: * Grammar:
* line := blank | comment | label | text | instr * line := blank | comment | label | text | instr
* blank := /^\s*$/ * blank := /^\s*$/
@@ -52,7 +50,6 @@ a_intern(Asm *a, const char *name)
return s; return s;
} }
/* ------------------------------------------------------------------ */
/* line iterator: returns the next line as a NUL-terminated buffer in /* line iterator: returns the next line as a NUL-terminated buffer in
* line/llen pointers, advances pos. Returns 0 on EOF. * 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; return 1;
} }
/* skip leading whitespace */
static const char * static const char *
skipws(const char *p) skipws(const char *p)
{ {
@@ -168,7 +164,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
return 0; return 0;
} }
/* (REG) form */
if (*s == '(') { if (*s == '(') {
s++; s++;
char rbuf[8] = {0}; char rbuf[8] = {0};
@@ -183,7 +178,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
return 0; return 0;
} }
/* number(REG) form, or label form, or REG */
const char *p = s; const char *p = s;
int sign = 1; int sign = 1;
if (*p == '-') { sign = -1; p++; } if (*p == '-') { sign = -1; p++; }
@@ -208,7 +202,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
return 0; return 0;
} }
/* IDENT — register or symbol-or-label */
if (a_isidstart((unsigned char)*s)) { if (a_isidstart((unsigned char)*s)) {
char buf[256] = {0}; char buf[256] = {0};
int n = 0; int n = 0;
@@ -226,7 +219,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
s = end; s = end;
} }
/* ID(SB) means external symbol */
if (*s == '(') { if (*s == '(') {
char rbuf[8] = {0}; char rbuf[8] = {0};
int rn = 0; int rn = 0;
@@ -253,7 +245,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
out->type = r; out->type = r;
return 0; return 0;
} }
/* otherwise it's a branch target */
out->type = D_BRANCH; out->type = D_BRANCH;
out->sym = strdup(buf); out->sym = strdup(buf);
return 0; return 0;
@@ -281,7 +272,6 @@ a_parse(Asm *a)
continue; continue;
} }
/* label? */
if (a_isidstart((unsigned char)*p) && line[0] != '\t') { if (a_isidstart((unsigned char)*p) && line[0] != '\t') {
const char *q = p; const char *q = p;
while (a_isidcont((unsigned char)*q)) q++; while (a_isidcont((unsigned char)*q)) q++;
@@ -308,7 +298,6 @@ a_parse(Asm *a)
} }
} }
/* TEXT or instruction */
const char *m = p; const char *m = p;
char mnem[16] = {0}; char mnem[16] = {0};
int n = 0; int n = 0;
@@ -332,7 +321,6 @@ a_parse(Asm *a)
const char *rest = m; const char *rest = m;
if (op == A_TEXT) { if (op == A_TEXT) {
/* TEXT name,$framesize */
char nbuf[256] = {0}; char nbuf[256] = {0};
int nn = 0; int nn = 0;
while (*m && *m != ',' && nn < 255) nbuf[nn++] = *m++; while (*m && *m != ',' && nn < 255) nbuf[nn++] = *m++;
@@ -363,7 +351,6 @@ a_parse(Asm *a)
prg->nbytes = 0; prg->nbytes = 0;
} else { } else {
m++; m++;
/* parse escapes into a fresh buffer */
size_t cap = 32, len = 0; size_t cap = 32, len = 0;
u8 *buf = malloc(cap); u8 *buf = malloc(cap);
while (*m && *m != '"') { while (*m && *m != '"') {
@@ -398,7 +385,6 @@ a_parse(Asm *a)
prg->nbytes = len; prg->nbytes = len;
} }
} else { } else {
/* split rest at top-level comma */
const char *comma = NULL; const char *comma = NULL;
for (const char *q = rest; *q; q++) for (const char *q = rest; *q; q++)
if (*q == ',' && comma == NULL) comma = q; if (*q == ',' && comma == NULL) comma = q;

View File

@@ -114,7 +114,7 @@ enum {
A_LAST A_LAST
}; };
const char *anames(int); /* opcode -> mnemonic */ const char *anames(int);
const char *rnames(int); /* register -> name */ const char *rnames(int);
#endif #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 /* Used by the load/store paths so the (AX, BX) pair convention is
* (AX, BX) pair convention is preserved for str globals, mirroring * preserved for str globals, mirroring what we already do for str
* what we already do for str locals. */ * locals. */
static int static int
let_isstr(Type *t) let_isstr(Type *t)
{ {
@@ -1377,8 +1377,8 @@ let_isstr(Type *t)
return u && u->kind == TY_STR; return u && u->kind == TY_STR;
} }
/* Is the unwrapped type a slice? Slice globals flow as the (AX, BX, /* Slice globals flow as the (AX, BX, CX) triple — same as the local
* CX) triple — same as the local ABI. */ * ABI. */
static int static int
let_isslice(Type *t) let_isslice(Type *t)
{ {
@@ -1387,9 +1387,9 @@ let_isslice(Type *t)
return u && u->kind == TY_SLICE; return u && u->kind == TY_SLICE;
} }
/* Is the unwrapped type a struct? Struct globals only support field /* Struct globals only support field access (read + plain `=` write
* access (read + plain `=` write for scalar fields). Whole-struct * for scalar fields). Whole-struct by-value flow through expressions
* by-value flow through expressions isn't wired. */ * isn't wired. */
static int static int
let_isstruct(Type *t) let_isstruct(Type *t)
{ {
@@ -1398,9 +1398,8 @@ let_isstruct(Type *t)
return u && u->kind == TY_STRUCT; return u && u->kind == TY_STRUCT;
} }
/* Is the unwrapped type a fixed-length array? Array globals are /* Array globals are zero-init DATAW slots; cgindex addresses them as
* zero-init DATAW slots; cgindex addresses them as LEAQ name(SB) * LEAQ name(SB) and lets the element load/store run as usual. */
* and lets the element load/store run as usual. */
static int static int
let_isarray(Type *t) let_isarray(Type *t)
{ {
@@ -1409,9 +1408,9 @@ let_isarray(Type *t)
return u && u->kind == TY_ARRAY; return u && u->kind == TY_ARRAY;
} }
/* Is the unwrapped type a float (f32 or f64)? Float globals flow /* Float globals flow through X0 — load/store goes LEAQ name(SB),CX →
* through X0 — load/store goes LEAQ name(SB),CX → MOVSS/MOVSD via the * MOVSS/MOVSD via the indirect, since the asm has no D_EXTERN form
* indirect, since the asm has no D_EXTERN form for SSE moves yet. */ * for SSE moves yet. */
static int static int
let_isfloat(Type *t) let_isfloat(Type *t)
{ {
@@ -1722,7 +1721,6 @@ let_var_type(const char *name)
return NULL; return NULL;
} }
/* Glue `<module>.<ident>` into a fresh arena buffer. */
static const char * static const char *
mod_join(Cg *c, const char *mod, const char *ident) 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)); 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; typedef struct Local Local;
struct Local { struct Local {
const char *name; const char *name;
@@ -2074,8 +2069,7 @@ cg_base_cap(Cg *c, Node *base, Type *bu, Local *locals, int dst)
return 0; return 0;
} }
/* ------------------------------------------------------------------ */ /* expressions: result lands in AX. */
/* expressions: result lands in AX. Returns 1 on success. */
static void cgexpr(Cg*, Node*, Local*); static void cgexpr(Cg*, Node*, Local*);
static void cgstmt(Cg*, Node*, Local**, int*); static void cgstmt(Cg*, Node*, Local**, int*);
@@ -4678,7 +4672,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
} }
} }
if (amped) break; if (amped) break;
/* Fall through to silent-drop fallback below. */
} }
if (opnd && opnd->kind == N_INDEX) { if (opnd && opnd->kind == N_INDEX) {
/* &base[i] = base + i*esz, no dereference. /* &base[i] = base + i*esz, no dereference.
@@ -5436,7 +5429,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
} }
after_dot_assign: after_dot_assign:
if (u && u->kind == TY_STRUCT) { if (u && u->kind == TY_STRUCT) {
/* find field metadata */
Tfield *f = NULL; Tfield *f = NULL;
for (Tfield *fl = u->fields; fl; fl = fl->next) for (Tfield *fl = u->fields; fl; fl = fl->next)
if (strcmp(fl->name, n->lhs->str) == 0) if (strcmp(fl->name, n->lhs->str) == 0)
@@ -5725,7 +5717,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
} }
break; break;
} }
/* now store AX into target */
if (via_ptr) { if (via_ptr) {
if (boff == 0 && let_islet(base->str)) { if (boff == 0 && let_islet(base->str)) {
/* #47 (inverse): a GLOBAL *struct /* #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); baseoff = localoff(c, locals, bname, 8, frame);
} }
/* allocate per-name slots */
struct { int off, sz, foff; Type *ftype; } binds[8] = {0}; struct { int off, sz, foff; Type *ftype; } binds[8] = {0};
int nbinds = 0; int nbinds = 0;
if (destruct) { if (destruct) {
@@ -15733,7 +15723,6 @@ cgfn(Cg *c, FILE *out, Node *fn)
{ {
if (fn->body == NULL) return; /* extern decl, no body */ if (fn->body == NULL) return; /* extern decl, no body */
/* fresh per-fn state */
c->head = c->tail = NULL; c->head = c->tail = NULL;
c->fnname = fn->str; c->fnname = fn->str;
c->cur_mod = (fn->module && fn->module[0]) ? fn->module : NULL; 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 */ text->from.offset = 0; /* framesize patched below */
emit(c, text); emit(c, text);
/* prologue */
ins1(c, A_PUSHQ, areg(D_BP)); ins1(c, A_PUSHQ, areg(D_BP));
ins2(c, A_MOVQ, areg(D_SP), areg(D_BP)); ins2(c, A_MOVQ, areg(D_SP), areg(D_BP));
Prog *subsp = newprog(c, A_SUBQ); Prog *subsp = newprog(c, A_SUBQ);

View File

@@ -1,7 +1,4 @@
/* /* Plan 9 cmd/6c/gc.h shape, trimmed. */
* gc.h — w6c-private header: Prog/Adr structs, scratch register set,
* stack-frame state. Plan 9 cmd/6c/gc.h shape, trimmed.
*/
#ifndef SIX_GC_H #ifndef SIX_GC_H
#define SIX_GC_H #define SIX_GC_H
@@ -73,9 +70,7 @@ void emit(Cg*, Prog*);
/* txt.c */ /* txt.c */
void txt_emit(FILE*, Prog *head); void txt_emit(FILE*, Prog *head);
/* wwi.c — `.wwi` export-data producer (w6c -I). M2 dead-code: writes a /* wwi.c — non-zero return: check_exported_type rejected a dangling export. */
* re-parseable ww-prototype rendering of the package's exported surface.
* Returns non-zero if check_exported_type rejects a dangling export. */
int wwi_emit(Checker *c, FILE *of, Node *file); int wwi_emit(Checker *c, FILE *of, Node *file);
/* swt.c, peep.c, reg.c — placeholders for now */ /* 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 "gc.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>

View File

@@ -1,7 +1,7 @@
/* /*
* peep.c — peephole pass. Currently a no-op; reserved for the kind of * Currently a no-op; reserved for the kind of cleanup Plan 9 6c does
* cleanup Plan 9 6c does (folding adjacent moves, removing redundant * (folding adjacent moves, removing redundant compares). Wire in
* compares). Wire in `peephole(c)` from cgen.c after the main walk. * `peephole(c)` from cgen.c after the main walk.
*/ */
#include "gc.h" #include "gc.h"

View File

@@ -1,8 +1,8 @@
/* /*
* reg.c — register allocator. The current cgen pins everything to AX * The current cgen pins everything to AX with BX as a scratch
* with BX as a scratch top-of-stack — no real allocation. This file * top-of-stack — no real allocation. This file is the seam where a
* is the seam where a linear-scan or graph-colouring pass would land * linear-scan or graph-colouring pass would land later; today it's
* later; today it's empty. * empty.
*/ */
#include "gc.h" #include "gc.h"

View File

@@ -1,7 +1,7 @@
/* /*
* swt.c — switch-statement lowering. Stub for now: cgen falls * Stub for now: cgen falls through to a no-op for N_SWITCH. When we
* through to a no-op for N_SWITCH. When we add a real lowering, it * add a real lowering, it will live here, mirroring Plan 9 6c's
* will live here, mirroring Plan 9 6c's pswt.c. * pswt.c.
*/ */
#include "gc.h" #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): * Format we emit (and that w6a expects):
* TEXT name<framesize> * TEXT name<framesize>
* MOVQ $1, AX * MOVQ $1, AX

View File

@@ -35,11 +35,11 @@ wwi_primary(Node *n)
return n && n->imported == 0; return n && n->imported == 0;
} }
/* --- check_exported_type (drew) ------------------------------------- * /* check_exported_type (drew): resolve an N_TNAME to its type sym WITHOUT
* Resolve an N_TNAME to its type sym WITHOUT the side effects of * the side effects of resolve_typename (no on-demand resolve, no double
* resolve_typename (no on-demand resolve, no double "unknown type" * "unknown type" error). A primitive/keyword resolves to no SK_TYPE →
* error). A primitive/keyword resolves to no SK_TYPE → leaf. By the * leaf. By the time the producer runs, check_file has finished and
* time the producer runs, check_file has finished and c->cur == c->top. * c->cur == c->top.
*/ */
static Sym * static Sym *
wwi_typesym(Checker *c, const char *nm) wwi_typesym(Checker *c, const char *nm)
@@ -141,7 +141,7 @@ wwi_check_decl(Checker *c, Node *d)
return bad; 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_expr(FILE *of, Node *e);
static void wwi_type(FILE *of, Node *t); 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 declent { Node *d; int idx; };
struct useent { const char *path; 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 * We do not pull bytes from the .so; the dynamic loader maps it at
* symbols it exports and which DT_NEEDED entry to record. We do not * runtime.
* 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.
*/ */
#include "l.h" #include "l.h"
#include <stdio.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 + * The shape we produce is the simplest valid one: PT_INTERP +
* PT_DYNAMIC + DT_BIND_NOW so the loader resolves every PLT slot at * PT_DYNAMIC + DT_BIND_NOW so the loader resolves every PLT slot at
* startup (no lazy binding, no PLT0 trampoline). Symbol versioning * startup (no lazy binding, no PLT0 trampoline). SysV .hash, not
* is omitted; modern glibc tolerates unversioned references by
* binding to each symbol's "default" version. SysV .hash, not
* .gnu.hash. Non-PIE, fixed base. * .gnu.hash. Non-PIE, fixed base.
* *
* File layout: * File layout:
@@ -31,7 +27,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
/* ELF constants */
#define ET_EXEC 2 #define ET_EXEC 2
#define EM_X86_64 62 #define EM_X86_64 62
#define EV_CURRENT 1 #define EV_CURRENT 1
@@ -127,7 +122,6 @@ elf_hash(const char *name)
return h; return h;
} }
/* Patch a 4-byte little-endian field in `buf` at offset `off`. */
static void static void
poke32(u8 *buf, u64 off, u32 v) 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; const int N = l->dyn_n;
/* ---- Pass 1: collect dynamic symbol names + .dynstr layout ---- */
/* dynstr layout: [0]='\0', then DT_NEEDED soname strings, then /* dynstr layout: [0]='\0', then DT_NEEDED soname strings, then
* one symbol name per dynamic Lsym. We index dyn syms by * one symbol name per dynamic Lsym. We index dyn syms by
* plt_idx (assigned in l_resolve). Build an array sorted 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; u8 *dynstr = NULL;
u64 dynstr_cap = 0, dynstr_len = 0; u64 dynstr_cap = 0, dynstr_len = 0;
#define DSTR_PUT(s) do { \ #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); 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 * Vernaux record under that lib's Verneed. The vna_other
* value (assigned starting at 2; 1 is reserved for "global, * value (assigned starting at 2; 1 is reserved for "global,
* unversioned") becomes that sym's .gnu.version entry. * 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++; n_vlibs++;
} }
/* Assign vna_other indices starting at 2. */
u16 next_vna = 2; u16 next_vna = 2;
for (int i = 0; i < n_vlibs; i++) for (int i = 0; i < n_vlibs; i++)
for (int k = 0; k < vlibs[i].n_versions; k++) for (int k = 0; k < vlibs[i].n_versions; k++)
vlibs[i].versions[k].vna_other = next_vna++; vlibs[i].versions[k].vna_other = next_vna++;
/* Add version name strings to .dynstr. */
for (int i = 0; i < n_vlibs; i++) { for (int i = 0; i < n_vlibs; i++) {
for (int k = 0; k < vlibs[i].n_versions; k++) { for (int k = 0; k < vlibs[i].n_versions; k++) {
vlibs[i].versions[k].dynstr_off = (u32)dynstr_len; 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 u64 ehdr_sz = sizeof(Ehdr);
const int n_phdrs = 4; const int n_phdrs = 4;
const u64 phdr_sz = (u64)n_phdrs * sizeof(Phdr); 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 ndyn = (u64)nsos + 11 + (with_ver ? 3 : 0);
const u64 dynamic_sz = ndyn * sizeof(Dyn64); 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 * load segment at base+0..text_end. .got.plt and .dynamic land
* in the R+W segment at the next page boundary. */ * 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 data_file_len = l->datalen - bsslen;
const u64 file_data_end = data_off + data_file_len; 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); Sym64 *dynsym = calloc((size_t)nsyms_total, sizeof *dynsym);
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
Sym64 *e = &dynsym[1 + 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. */ /* elf_hash is also used by .gnu.version_r for vna_hash below. */
/* .rela.plt */
Rela64 *relaplt = calloc((size_t)N, sizeof *relaplt); Rela64 *relaplt = calloc((size_t)N, sizeof *relaplt);
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
relaplt[i].r_offset = gotplt_va + (3 + (u64)i) * 8; 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. */ /* [3..3+N-1] left zero; loader fills via R_X86_64_JUMP_SLOT. */
/* .dynamic */
Dyn64 *dynamic = calloc((size_t)ndyn, sizeof *dynamic); Dyn64 *dynamic = calloc((size_t)ndyn, sizeof *dynamic);
{ {
int k = 0; 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 --- /* Patch .text relocations targeting dynamic syms: the site is
* The site is the existing PC32/PLT32 displacement field. Target * the existing PC32/PLT32 displacement field, the target the
* is the address of the symbol's PLT stub. */ * address of the symbol's PLT stub. */
for (Lrel *r = l->rels; r; r = r->next) { for (Lrel *r = l->rels; r; r = r->next) {
if (r->sym == NULL || !r->sym->is_dyn) continue; if (r->sym == NULL || !r->sym->is_dyn) continue;
if (r->kind != R_X86_64_PC32 && r->kind != R_X86_64_PLT32) { 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); poke32(l->text, r->off, (u32)(i32)disp);
} }
/* ---- Pass 6: emit ---- */
Ehdr eh = {0}; Ehdr eh = {0};
memcpy(eh.e_ident, "\x7f""ELF", 4); memcpy(eh.e_ident, "\x7f""ELF", 4);
eh.e_ident[4] = ELFCLASS64; 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_memsz = file_end - gotplt_off;
ph[1].p_align = page; ph[1].p_align = page;
/* PT_INTERP. */
ph[2].p_type = PT_INTERP; ph[2].p_type = PT_INTERP;
ph[2].p_flags = PF_R; ph[2].p_flags = PF_R;
ph[2].p_offset = interp_off; 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_memsz = interp_sz;
ph[2].p_align = 1; ph[2].p_align = 1;
/* PT_DYNAMIC. */
ph[3].p_type = PT_DYNAMIC; ph[3].p_type = PT_DYNAMIC;
ph[3].p_flags = PF_R | PF_W; ph[3].p_flags = PF_R | PF_W;
ph[3].p_offset = dynamic_off; 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(&eh, 1, sizeof eh, f);
fwrite(ph, 1, sizeof ph, f); fwrite(ph, 1, sizeof ph, f);
/* helper: pad to absolute offset `to` */
#define PAD_TO(to) do { \ #define PAD_TO(to) do { \
long _here = ftell(f); \ long _here = ftell(f); \
for (long _i = _here; _i < (long)(to); _i++) fputc(0, 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 #ifndef SIX_L_H
#define 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 ... * w6l -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
* *
* The first symbol named "_start" defined among the inputs becomes * 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); int rc = l_emit_elf(&l, f, base, base + 0x1000 + entry->val);
fclose(f); fclose(f);
if (rc == 0) { if (rc == 0) {
/* chmod +x */
char cmd[1024]; char cmd[1024];
snprintf(cmd, sizeof cmd, "chmod +x %s", out); snprintf(cmd, sizeof cmd, "chmod +x %s", out);
(void)system(cmd); (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 "l.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -97,11 +91,8 @@ emit_data(Lnk *l, const u8 *src, u64 n)
l->datalen += n; l->datalen += n;
} }
/* Internal: load a single ELF .o image already in memory. The caller /* The caller gives us the bytes (we own them) and a path tag for
* gives us the bytes (we own them) and a path tag for diagnostics. * diagnostics. */
* If the bytes look like an archive (magic "!<arch>\n") we recurse
* over each member instead.
*/
static int load_image(Lnk *l, const char *path, u8 *buf, u64 len); static int load_image(Lnk *l, const char *path, u8 *buf, u64 len);
static u64 static u64
@@ -119,9 +110,8 @@ ar_field(const u8 *p, int n)
/* Read an ELF .o image's globally-defined symbol names without /* Read an ELF .o image's globally-defined symbol names without
* actually appending it to the link. Returns a heap-allocated * actually appending it to the link. Returns a heap-allocated
* NULL-terminated array; caller frees the array (not the strings, * NULL-terminated array; caller frees the array, not the strings,
* which point into the .o image and must remain alive). * which point into the .o image and need it kept alive. */
*/
static char ** static char **
elf_globals(const u8 *buf, u64 len) 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; } if (eh->e_shstrndx >= eh->e_shnum) { free(buf); return -1; }
const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset); 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_text = -1, idx_data = -1, idx_symtab = -1, idx_strtab = -1;
int idx_rela = -1, idx_relad = -1; int idx_rela = -1, idx_relad = -1;
for (u16 i = 0; i < eh->e_shnum; i++) { 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; ob->next = l->objs;
l->objs = ob; l->objs = ob;
/* append .text and (if present) .data */
emit_text(l, buf + sh[idx_text].sh_offset, sh[idx_text].sh_size); emit_text(l, buf + sh[idx_text].sh_offset, sh[idx_text].sh_size);
if (idx_data >= 0 && sh[idx_data].sh_size > 0) if (idx_data >= 0 && sh[idx_data].sh_size > 0)
emit_data(l, buf + sh[idx_data].sh_offset, sh[idx_data].sh_size); 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); Sym64 *symtab = (Sym64 *)(buf + sh[idx_symtab].sh_offset);
u64 nsyms = sh[idx_symtab].sh_size / sizeof(Sym64); u64 nsyms = sh[idx_symtab].sh_size / sizeof(Sym64);
const char *str = (const char *)(buf + sh[idx_strtab].sh_offset); 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; map[i] = gs;
} }
/* per-object: collect relocations */
if (idx_rela >= 0) { if (idx_rela >= 0) {
Rela64 *rt = (Rela64 *)(buf + sh[idx_rela].sh_offset); Rela64 *rt = (Rela64 *)(buf + sh[idx_rela].sh_offset);
u64 nrel = sh[idx_rela].sh_size / sizeof(Rela64); 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; l->rels = r;
} }
} }
/* per-object: collect data relocations from .rela.data. The /* The .data section in the .o starts at a per-object 0; we shift
* .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. */ * by ob->data_off so r->off indexes the combined .data buffer. */
if (idx_relad >= 0) { if (idx_relad >= 0) {
Rela64 *rt = (Rela64 *)(buf + sh[idx_relad].sh_offset); 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: * Layout (file order) without .data:
* [0..64) ELF header * [0..64) ELF header
* [64..120) one program header (PT_LOAD R+X) * [64..120) one program header (PT_LOAD R+X)
@@ -13,9 +11,6 @@
* [176..0x1000) zero pad * [176..0x1000) zero pad
* [0x1000..) .text bytes * [0x1000..) .text bytes
* [data_off..) .data bytes (file offset and vaddr page-aligned) * [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 "l.h"
#include <stdio.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_phentsize = sizeof(Phdr);
eh.e_phnum = has_data ? 2 : 1; 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}; Phdr phx = {0};
phx.p_type = PT_LOAD; phx.p_type = PT_LOAD;
phx.p_flags = PF_R | PF_X; 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); fwrite(&phx, 1, sizeof phx, f);
if (has_data) fwrite(&phw, 1, sizeof phw, f); if (has_data) fwrite(&phw, 1, sizeof phw, f);
/* pad to text_off */
long here = ftell(f); long here = ftell(f);
for (long i = here; i < (long)text_off; i++) fputc(0, f); for (long i = here; i < (long)text_off; i++) fputc(0, f);
if (l->textlen) fwrite(l->text, 1, l->textlen, f); if (l->textlen) fwrite(l->text, 1, l->textlen, f);
if (has_data && data_file_len > 0) { if (has_data && data_file_len > 0) {
/* pad to data_off */
here = ftell(f); here = ftell(f);
for (long i = here; i < (long)data_off; i++) fputc(0, f); for (long i = here; i < (long)data_off; i++) fputc(0, f);
fwrite(l->data, 1, data_file_len, 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 "l.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -34,7 +23,7 @@ l_resolve(Lnk *l)
* across runs (rels are pushed onto the head as objects load). */ * across runs (rels are pushed onto the head as objects load). */
for (Lrel *r = l->rels; r; r = r->next) { for (Lrel *r = l->rels; r; r = r->next) {
if (r->sym == NULL || r->sym->defined) continue; 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) { for (Lso *so = l->sos; so; so = so->next) {
const char *ver = NULL; const char *ver = NULL;
if (l_so_provides_v(so, r->sym->name, &ver)) { 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) { for (Lrel *r = l->rels; r; r = r->next) {
if (r->sym == NULL) continue; if (r->sym == NULL) continue;
if (!r->sym->defined && !r->sym->is_dyn) { 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) { switch (r->kind) {
case R_X86_64_PC32: case R_X86_64_PC32:
case R_X86_64_PLT32: { 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; u64 site = text_va + r->off;
i64 rel = (i64)sym_va - (i64)site + r->addend; i64 rel = (i64)sym_va - (i64)site + r->addend;
patch_u32(l->text + r->off, (u32)(i32)rel); 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 * Plain singly-linked list; usually a few hundred entries, hashing
* list; usually a few hundred entries, hashing isn't worth it yet. * isn't worth it yet.
*/ */
#include "l.h" #include "l.h"
#include <stdlib.h> #include <stdlib.h>

View File

@@ -1,9 +1,5 @@
/* /*
* ast.c — Node constructor + s-expression printer. * Printer output is rigid and deterministic so golden tests can diff.
*
* 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.
*/ */
#include "ww.h" #include "ww.h"
#include <string.h> #include <string.h>

View File

@@ -1,6 +1,4 @@
/* /*
* check.c — name resolution + type checking pass.
*
* Two-stage: * Two-stage:
* 1) collect: walk top-level decls and install Syms with stub types. * 1) collect: walk top-level decls and install Syms with stub types.
* 2) resolve: expand types, check fn bodies and def initialisers. * 2) resolve: expand types, check fn bodies and def initialisers.
@@ -1046,8 +1044,6 @@ resolve_type(Checker *c, Node *n)
} }
} }
/* ---- expressions -------------------------------------------------- */
static Type * static Type *
unify_arith(Checker *c, Pos p, Type *a, Type *b) unify_arith(Checker *c, Pos p, Type *a, Type *b)
{ {
@@ -2007,7 +2003,6 @@ cexpr(Checker *c, Node *n)
(void)cexpr(c, n->rhs); (void)cexpr(c, n->rhs);
return n->type = ty_void; return n->type = ty_void;
} }
/* Reject assignment to a const-bound name. */
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) { if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
n->lhs->str); n->lhs->str);
@@ -2387,8 +2382,6 @@ cexpr(Checker *c, Node *n)
} }
} }
/* ---- statements --------------------------------------------------- */
static void static void
clet(Checker *c, Node *n) clet(Checker *c, Node *n)
{ {
@@ -2731,8 +2724,6 @@ cstmt(Checker *c, Node *n)
} }
} }
/* ---- top-level ---------------------------------------------------- */
static Type * static Type *
build_fn_type(Checker *c, Node *fn) 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. * Plan 9 style: short, no levels beyond fatal/error/warn, no colour.
*/ */
#include "ww.h" #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 * No automatic semicolon insertion (Hare rule). The lexer only emits
* what is in the source; the parser is responsible for non-empty rules. * what is in the source; the parser is responsible for non-empty rules.
*/ */
@@ -81,7 +69,6 @@ ishex(int c)
(c >= 'A' && c <= 'F'); (c >= 'A' && c <= 'F');
} }
/* skip whitespace and comments. returns 0 on EOF, else 1. */
static int static int
skipws(Lex *l) skipws(Lex *l)
{ {
@@ -94,7 +81,7 @@ skipws(Lex *l)
continue; continue;
} }
if (c == '/' && lpeek(l, 1) == '/') { if (c == '/' && lpeek(l, 1) == '/') {
lget(l); lget(l); /* consume '//' */ lget(l); lget(l);
/* #16 option-B: the driver emits `//ww:module-reset` /* #16 option-B: the driver emits `//ww:module-reset`
* before a package-less file's bytes; recognize the * before a package-less file's bytes; recognize the
* whole-line directive (without consuming differently) * 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. /* A typed suffix must be glued (no whitespace) to the digits. */
* 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. */
if (isidstart(lpeek(l, 0))) { if (isidstart(lpeek(l, 0))) {
u64 sb = l->pos; u64 sb = l->pos;
while (isidcont(lpeek(l, 0))) lget(l); while (isidcont(lpeek(l, 0))) lget(l);

View File

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

View File

@@ -100,8 +100,6 @@ static Node *parsetype(Parser *p);
static Node *parseblock(Parser *p); static Node *parseblock(Parser *p);
static Node *parsestmt(Parser *p); static Node *parsestmt(Parser *p);
/* ------- type expressions ------------------------------------------ */
static Node * static Node *
parseparams(Parser *p) parseparams(Parser *p)
{ {
@@ -376,8 +374,6 @@ parsetype(Parser *p)
} }
} }
/* ------- expressions (Pratt) ---------------------------------------- */
/* binary precedence; 0 = not a binary op */ /* binary precedence; 0 = not a binary op */
static int static int
bprec(Tkind k) bprec(Tkind k)
@@ -663,10 +659,9 @@ parseprimary(Parser *p)
n->str = t.text; n->str = t.text;
n->strlen = t.tlen; n->strlen = t.tlen;
advance(p); advance(p);
/* dotted ident chain folded into one IDENT for type-ish refs */
while (p->cur.kind == TK_DOT && peek(p).kind == TK_IDENT) { while (p->cur.kind == TK_DOT && peek(p).kind == TK_IDENT) {
advance(p); advance(p);
n = (Node*)n; /* keep stable */ n = (Node*)n;
Node *mr = newnode(p->a, N_DOT, pp); Node *mr = newnode(p->a, N_DOT, pp);
mr->lhs = n; mr->lhs = n;
mr->str = p->cur.text; mr->str = p->cur.text;
@@ -674,7 +669,6 @@ parseprimary(Parser *p)
advance(p); advance(p);
n = mr; n = mr;
} }
/* struct literal: ident '{' ... '}' (only if ident-shaped) */
if (p->cur.kind == TK_LBRACE) { if (p->cur.kind == TK_LBRACE) {
/* #76: bare `Foo{}` keeps the N_IDENT fast-path; a /* #76: bare `Foo{}` keeps the N_IDENT fast-path; a
* qualified `pkg.Type{}` (N_DOT chain) flattens first. */ * qualified `pkg.Type{}` (N_DOT chain) flattens first. */
@@ -902,8 +896,6 @@ parseexpr_top(Parser *p)
return parseexpr(p); return parseexpr(p);
} }
/* ------- statements ------------------------------------------------- */
static Node * static Node *
parselet(Parser *p, int top) parselet(Parser *p, int top)
{ {
@@ -944,7 +936,6 @@ parselet(Parser *p, int top)
return m; return m;
} }
/* parse first binding */
Pos lp = p->cur.pos; Pos lp = p->cur.pos;
Node *first = newnode(p->a, N_LET, lp); Node *first = newnode(p->a, N_LET, lp);
first->str = expectbindname(p); first->str = expectbindname(p);
@@ -952,7 +943,6 @@ parselet(Parser *p, int top)
first->lhs = parsetype(p); first->lhs = parsetype(p);
if (p->cur.kind == TK_COMMA) { if (p->cur.kind == TK_COMMA) {
/* multi-let: collect (name, type) pairs, then '=' rhs */
Node *m = newnode(p->a, N_MLET, pp); Node *m = newnode(p->a, N_MLET, pp);
Node *head = first, *tail = first; Node *head = first, *tail = first;
while (accept(p, TK_COMMA)) { while (accept(p, TK_COMMA)) {
@@ -1038,7 +1028,7 @@ parsefor(Parser *p)
* Tuple destructure: `let (a, b) .. expr`. */ * Tuple destructure: `let (a, b) .. expr`. */
Tok save_cur = p->cur; Tok save_cur = p->cur;
(void)save_cur; (void)save_cur;
advance(p); /* consume LET */ advance(p);
if (p->cur.kind == TK_LPAREN) { if (p->cur.kind == TK_LPAREN) {
advance(p); advance(p);
Node *names = NULL, *tail = NULL; Node *names = NULL, *tail = NULL;
@@ -1067,8 +1057,8 @@ parsefor(Parser *p)
int isunder = p->cur.kind == TK_UNDER; int isunder = p->cur.kind == TK_UNDER;
Tok la = peek(p); Tok la = peek(p);
if (la.kind == TK_DOTDOT) { if (la.kind == TK_DOTDOT) {
advance(p); /* consume IDENT/UNDER */ advance(p);
advance(p); /* consume DOTDOT */ advance(p);
Node *rng = newnode(p->a, N_FORRANGE, pp); Node *rng = newnode(p->a, N_FORRANGE, pp);
rng->str = isunder ? "" : nm; rng->str = isunder ? "" : nm;
rng->lhs = parseexpr(p); rng->lhs = parseexpr(p);
@@ -1283,8 +1273,6 @@ parseblock(Parser *p)
return n; return n;
} }
/* ------- top-level decls ------------------------------------------- */
/* `import encoding.utf8;` — the driver resolves the dotted path to a /* `import encoding.utf8;` — the driver resolves the dotted path to a
* directory; the checker only needs the leaf (`utf8`) as the module * directory; the checker only needs the leaf (`utf8`) as the module
* bareword for n_use→decl disambiguation, mirroring Hare's * 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 "ww.h"
#include <string.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 * Used by resolve_typename and the cexpr N_DOT branch to pick the
* right same-leaf-name type when two imports each export it * right same-leaf-name type when two imports each export it
* (`bufio.stream` vs `io.stream`). * (`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. * 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
* Walks the same FNV bucket + hashnext chain + parent walk scope_lookup * regardless of mod (the existing scope_lookup semantics). We only
* uses. Within each scope's bucket: Pass 1 prefers entries whose * descend to the parent scope when the current scope has no matching
* `sym.mod` matches the caller's `mod`; Pass 2 falls back to the first * entry at all — so a local binding in a closer scope still shadows a
* match regardless of mod (the existing scope_lookup semantics). We * same-name fn from a parent scope, even when the parent entry
* only descend to the parent scope when the current scope has no * mod-matches.
* 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.
* *
* Used at bare-leaf lookup sites inside a known current module so that * 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 * 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. * 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
* Same FNV bucket + hashnext chain + parent walk and same-module * value binding (SK_VAR/SK_PARAM/SK_FN) that shadows it in a closer
* preference as scope_lookup_prefer, but skips every Sym whose kind * scope. ww keeps type and value namespaces separate (wwstage already
* isn't SK_TYPE and KEEPS scanning — so it returns the innermost * does; #225 conformance gap): a param `off` must not hide the global
* SK_TYPE of `name`, looking past a same-named value binding (SK_VAR/ * `type off`.
* 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 * Sym *
scope_lookup_type(Scope *s, const char *mod, const char *name) 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. * The duplicate-rejection key is (name, mod) rather than name alone.
* * This lets two imports each
* Same insertion as scope_define, but 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 * register their own `stream` SK_TYPE in the flat scope, and lets the
* primary register `stream` (mod=NULL) alongside imported `stream`s. * 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 "ww.h"
#include <string.h> #include <string.h>
@@ -13,7 +6,8 @@ struct kwent {
Tkind kind; 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[] = { static const struct kwent kwtab[] = {
{ "as", TK_AS }, { "as", TK_AS },
{ "break", TK_BREAK }, { "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 * Built-in types are constructed once and exposed as globals so the
* rest of the compiler can `==`-compare them. Compound types (ptr, * rest of the compiler can `==`-compare them.
* slice, array, fn, struct, chan) are constructed on demand and
* de-duplicated when equality is cheap (only ptr/slice for now).
*/ */
#include "ww.h" #include "ww.h"
#include <string.h> #include <string.h>
@@ -147,8 +143,7 @@ type_named(Arena *a, const char *name, Type *under)
return t; return t;
} }
/* type_chase_named — walk the TY_NAMED.under chain to the deepest non- /* Chain-of-aliases (#22): `type b = a; type a = struct;`
* named type. Chain-of-aliases (#22): `type b = a; type a = struct;`
* stacks two TY_NAMED layers — a single peel leaves `t` pointing at * stacks two TY_NAMED layers — a single peel leaves `t` pointing at
* the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT, * the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT,
* TY_SLICE, TY_TAGGED, TY_PTR) miss and the consumer silently falls * 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)) { if (type_isuntyped(src)) {
Type *du = type_chase_named(dst); Type *du = type_chase_named(dst);
if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1; 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 * Plan 9 in spirit. This file mirrors cc/cc.h's role: one shared
* header that declares everything every translation unit in the * header that declares everything every translation unit in the
* frontend cares about. * frontend cares about.
@@ -19,7 +17,6 @@
/* version banner — printed by `ww -V` */ /* version banner — printed by `ww -V` */
#define WW_VERSION "0.0" #define WW_VERSION "0.0"
/* short integer aliases, Plan 9 / Hare-flavoured */
typedef int8_t i8; typedef int8_t i8;
typedef int16_t i16; typedef int16_t i16;
typedef int32_t i32; typedef int32_t i32;
@@ -29,7 +26,6 @@ typedef uint16_t u16;
typedef uint32_t u32; typedef uint32_t u32;
typedef uint64_t u64; typedef uint64_t u64;
/* forward decls — concrete shapes appear in their phases. */
typedef struct Tok Tok; typedef struct Tok Tok;
typedef struct Lex Lex; typedef struct Lex Lex;
typedef struct Node Node; typedef struct Node Node;
@@ -54,8 +50,6 @@ char *astrndup(Arena*, const char*, u64);
char *aprintf(Arena*, const char*, ...); char *aprintf(Arena*, const char*, ...);
void freearena(Arena*); void freearena(Arena*);
/* err.c — diagnostics. Phase 0 has only fatal/warn; later phases add
* source-location-bearing variants. */
typedef struct Pos Pos; typedef struct Pos Pos;
struct Pos { struct Pos {
const char *file; 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 errorf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
void warnf(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]))) #define nelem(a) ((sizeof(a) / sizeof((a)[0])))
/* ---- lexer (lex.c, tok.c) ----------------------------------------- */
typedef enum { typedef enum {
/* zero is "no token" so memset-zero structs read sane */ /* zero is "no token" so memset-zero structs read sane */
TK_NONE = 0, TK_NONE = 0,
/* trivial */
TK_EOF, TK_EOF,
TK_ERR, TK_ERR,
TK_IDENT, TK_IDENT,
@@ -117,7 +108,6 @@ typedef enum {
TK_CONST, /* const binding */ TK_CONST, /* const binding */
TK_UNDER, /* bare '_' discard */ TK_UNDER, /* bare '_' discard */
/* punct + operators */
TK_LPAREN, /* ( */ TK_LPAREN, /* ( */
TK_RPAREN, /* ) */ TK_RPAREN, /* ) */
TK_LBRACE, /* { */ 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" */ void tokprint(FILE*, Tok); /* one line, "%s:%d:%d: %s %q" */
Tkind kwlookup(const char *s, u64 n); /* TK_NONE if not a keyword */ Tkind kwlookup(const char *s, u64 n); /* TK_NONE if not a keyword */
/* ---- AST (ast.c, parse.c) ----------------------------------------- */
typedef enum { typedef enum {
N_NONE = 0, N_NONE = 0,
@@ -399,7 +388,6 @@ void parserinit(Parser*, Arena*, Lex*);
Node *parsefile(Parser*); Node *parsefile(Parser*);
Node *parseexpr_top(Parser*); /* for testing: parse one expression */ Node *parseexpr_top(Parser*); /* for testing: parse one expression */
/* ---- types (type.c) ----------------------------------------------- */
typedef enum { typedef enum {
TY_NONE = 0, TY_NONE = 0,
TY_VOID, TY_VOID,
@@ -525,7 +513,6 @@ int type_isuntyped(Type *t);
int type_assignable(Type *dst, Type *src); int type_assignable(Type *dst, Type *src);
Type *type_default(Type *t); /* untyped → default concrete */ Type *type_default(Type *t); /* untyped → default concrete */
/* ---- symbols (sym.c) ---------------------------------------------- */
typedef enum { typedef enum {
SK_NONE = 0, SK_NONE = 0,
SK_VAR, 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_prefer(Scope*, const char *mod, const char *name);
Sym *scope_lookup_type(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; typedef struct Checker Checker;
struct Checker { struct Checker {
Arena *a; Arena *a;

View File

@@ -1,11 +1,6 @@
/* /*
* ww — the user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue. * 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 * 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. * 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" " lib/... every package under lib, recursively (test only)\n"
" . build the cwd's <basename>.ww\n"; " . build the cwd's <basename>.ww\n";
static char *self_dir; /* directory containing this binary */ static char *self_dir;
static const char * static const char *
toolpath(const char *envvar, const char *name) toolpath(const char *envvar, const char *name)
@@ -125,8 +120,7 @@ exec_package_tests(int argc, char **argv, const char *target,
return 1; return 1;
} }
/* Set of imported module paths, kept on the heap. Used to break /* Breaks cycles in `use` resolution. Linear because typical imports are
* cycles in `use` resolution. Linear because typical imports are
* a handful per build. */ * a handful per build. */
struct ImportSet { struct ImportSet {
char **paths; char **paths;
@@ -151,8 +145,7 @@ import_add(struct ImportSet *s, const char *path)
s->paths[s->n++] = strdup(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 * use-path → fs-path mapping (ref/hare/hare/module/srcs.ha:78
* builds the same shape via path::push per ident part). */ * builds the same shape via path::push per ident part). */
static void static void
@@ -164,12 +157,11 @@ import_path_form(const char *name, char *out, size_t outsz)
out[i] = '\0'; out[i] = '\0';
} }
/* try <dir>/<path>/ as a directory (want_dir), else <dir>/<path>.ww as /* Symmetric with wwstage locatein for byte-id driver output (rule 10).
* a file. Sets *is_dir on hit. Symmetric with wwstage locatein for * The legacy <dir>/<name>/<name>.ww form was dropped in task #22 —
* byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww * directory-as-module enumeration replaces it, mirroring
* form was dropped in task #22 — directory-as-module enumeration * ref/hare/hare/module/srcs.ha (Hare has no fallback matching
* replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no * `foo/foo.ha`; a module IS the directory). */
* fallback matching `foo/foo.ha`; a module IS the directory). */
static int static int
locate_import_in(const char *dir, const char *path_form, char *out, locate_import_in(const char *dir, const char *path_form, char *out,
size_t outsz, int *is_dir, int want_dir) 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; return 0;
} }
/* Walk a colon-separated dirlist trying to resolve `path_form`. Returns /* #98: "a module IS the directory" — a directory-package on ANY entry
* 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
* wins over a same-named sibling FILE on an EARLIER entry. The driver * 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` * 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 * 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; return 0;
} }
/* memcmp-based string compare for qsort. Byte-wise total order is /* Byte-wise total order is locale-independent; rule-10 byte-id requires
* locale-independent; rule-10 byte-id requires the two stages sort * the two stages sort the same way. strcmp diverges from Hare's memcmp
* the same way. (strcmp would work today but Hare-fidelity points * (ref/hare/sort/cmp/cmp.ha:9); the order is identical for NUL-free
* at memcmp via ref/hare/sort/cmp/cmp.ha:9.) */ * filenames. */
static int static int
strs_cmp(const void *a, const void *b) strs_cmp(const void *a, const void *b)
{ {
@@ -266,11 +255,9 @@ file_has_line_test(const char *path)
return found; return found;
} }
/* enumerate_dir_ww — collect production *.ww paths in `dirpath`, excluding /* A line-leading @test in a production source is diagnosed here and
* *_test.ww test sources, then sort byte-wise. A line-leading @test in * returns -2. This is the sole directory-membership discovery path; the
* any other source is diagnosed here and returns -2. This is the sole * owning seppkg retains the returned list. */
* directory-membership discovery path; the owning seppkg retains the
* returned list. */
static int static int
enumerate_dir_ww(const char *dirpath, char ***out_files) enumerate_dir_ww(const char *dirpath, char ***out_files)
{ {
@@ -310,9 +297,8 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
return n; 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 * This is the SOLE build path (E3-C1 flip, task #87): the legacy
* single-file amalgamator is gone. Each imported * single-file amalgamator is gone. Each imported
* package's `.wwi` interface is materialized and every package is * package's `.wwi` interface is materialized and every package is
@@ -357,7 +343,6 @@ struct sepgraph {
int n; int n;
}; };
/* Find a package by dotted path, or add it. Returns its index, -1 full. */
static int static int
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry, sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
int is_dir) int is_dir)
@@ -395,8 +380,7 @@ sep_graph_free(struct sepgraph *g)
free(g); free(g);
} }
/* Sanitize a package's dotted path into a scratch-file basename. Dots /* Dots stay (legal in filenames); the root's empty path becomes "__root". */
* stay (legal in filenames); the root's empty path becomes "__root". */
static void static void
sep_fname(const struct sepgraph *g, int pi, const char *scratch, sep_fname(const struct sepgraph *g, int pi, const char *scratch,
const char *suffix, char *out, size_t outsz) 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'); return sep_ident_start(c) || (c >= '0' && c <= '9');
} }
/* Skip the whitespace and comments accepted before and within the leading /* Deliberately only the loader's small header grammar for the leading
* package clause. This is deliberately only the loader's small header * package clause, not a second compiler lexer. */
* grammar, not a second compiler lexer. */
static int static int
sep_skip_space(const char *src, size_t n, size_t *off) 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; return 0;
} }
/* Parse exactly the leading loader grammar `package ident;`. */
static int static int
sep_package_clause(const char *src, size_t n, char *name, size_t namesz) 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; return 0;
} }
/* Scan one source file's already-selected bytes for its leading package /* A DIRECTORY import is a package boundary: add it as a direct dep of pkg
* 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
* `pi`. A FILE import is an intra-package split — fold its imports into * `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 * `pi` (its bytes join pi's body at emit time). Collects package PATHS
* rather than concatenating bytes the way the legacy amalgamator did * 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; return 0;
} }
/* Mark pi's transitive deps (excluding pi) in inset[]. */
static void static void
sep_mark_deps(struct sepgraph *g, int pi, char *inset) sep_mark_deps(struct sepgraph *g, int pi, char *inset)
{ {
@@ -881,9 +860,9 @@ archive_o(const char *objpath, const char *apath)
return 0; return 0;
} }
/* ---- -w workdir freshness --------------------------------------------- /* -w workdir freshness: a `-w DIR` workdir is a caller-owned persistent
* A `-w DIR` workdir is a caller-owned persistent package-artifact tree * package-artifact tree that replaces the fresh `.sepwork` scratch.
* that replaces the fresh `.sepwork` scratch. Staleness is pure content * Staleness is pure content
* identity, never mtime: a package is reused only when its freshly * identity, never mtime: a package is reused only when its freshly
* composed unit byte-equals the committed unit AND the tool copies * composed unit byte-equals the committed unit AND the tool copies
* recorded in the dir byte-equal the live tools — every decision is * 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 if (access("lib", 0) == 0) srcdir = "lib";
else srcdir = libdir; else srcdir = libdir;
} }
/* search path: source-dir, then -I dirs, then srcdir. */
char srcd[1024]; char srcd[1024];
if (entry_is_dir) { if (entry_is_dir) {
snprintf(srcd, sizeof srcd, "%s", src); 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); free(stack);
/* producer loop — dep-first, one `w6c -c -I` pass per package. */
for (int oi = 0; oi < norder; oi++) { for (int oi = 0; oi < norder; oi++) {
int pi = order[oi]; int pi = order[oi];
char unitf[1024], wwi[1024], asmf[1024], obj[1024], apath[1024]; 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}; char objs[8192] = {0};
for (int oi = norder - 1; oi >= 0; oi--) { for (int oi = norder - 1; oi >= 0; oi--) {
char path[1024]; char path[1024];
/* root: positional `.o` (force-load); deps: `.a` (selective). */
sep_fname(g, order[oi], scratch, sep_fname(g, order[oi], scratch,
order[oi] == root ? ".o" : ".a", path, sizeof path); order[oi] == root ? ".o" : ".a", path, sizeof path);
size_t n = strlen(objs); size_t n = strlen(objs);
@@ -1345,7 +1321,6 @@ search_path(const char *extra, char *buf, size_t bufsz)
return buf; return buf;
} }
/* basename_no_ext: last path segment with any trailing ".ww" stripped. */
static void static void
basename_no_ext(const char *path, char *out, size_t outsz) 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'; 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 static int
resolve_module(const char *name, const char *incs, char *out, size_t outsz, resolve_module(const char *name, const char *incs, char *out, size_t outsz,
int *is_dir) 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); return locate_import(sp, path_form, out, outsz, is_dir);
} }
/* Parse the standard -I/-L/-l/-o flags into incs/libdirs/libs/outpath. The /* Returns the index past the last arg consumed for positionals (so callers
* first non-flag positional becomes *src_out. Returns the index past the last * can pick up trailing args), or -1 if a flag is missing its argument
* arg consumed for positionals (so callers can pick up trailing args), or -1 * (diagnostic already emitted). `cmd` names the subcommand for the
* if a flag is missing its argument (diagnostic already emitted). `cmd` names * diagnostic, byte-identical to the wwstage twin's per-subcommand wording
* the subcommand for the diagnostic, byte-identical to the wwstage twin's * (selfhost/cmd/ww/main.ww dobuild/dorun). */
* per-subcommand wording (selfhost/cmd/ww/main.ww dobuild/dorun). */
static int static int
parse_build_flags(const char *cmd, int argc, char **argv, parse_build_flags(const char *cmd, int argc, char **argv,
char *incs, size_t incsz, char *incs, size_t incsz,
@@ -1504,7 +1471,7 @@ do_build(int argc, char **argv)
outflag, sizeof outflag, workdir, sizeof workdir, outflag, sizeof outflag, workdir, sizeof workdir,
&src, &emit_asm) < 0) &src, &emit_asm) < 0)
return 2; return 2;
if (src == NULL) src = "."; /* default: build cwd */ if (src == NULL) src = ".";
char resolved[1024]; char resolved[1024];
int is_dir = 0; int is_dir = 0;
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) { 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); fputs("ww: cannot remove temporary directory\n", stderr);
return 1; return 1;
} }
/* exec the built binary with any trailing argv as its argv. */
pid_t pid = fork(); pid_t pid = fork();
if (pid < 0) { if (pid < 0) {
perror("ww: fork"); perror("ww: fork");
@@ -1823,7 +1789,6 @@ do_test(int argc, char **argv)
"ww test: package options need a directory\n"); "ww test: package options need a directory\n");
return 2; return 2;
} }
/* single .ww file — build, then run unless -c (compile-only). */
char tmpdir[1024] = {0}, tmp[1024]; char tmpdir[1024] = {0}, tmp[1024];
const char *outp; const char *outp;
int owntmp = !outstem[0] && !workdir[0]; int owntmp = !outstem[0] && !workdir[0];

View File

@@ -1,10 +1,7 @@
/* /*
* wwdump — deterministic dump tool for ww source. * 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
* Reads a .ww file and writes either a token stream or an AST * stable across runs.
* 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.
* *
* wwdump -t file.ww tokens, one per line: "<file>:<l>:<c> <kind> [val]" * 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 * 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 int
main(int argc, char **argv) main(int argc, char **argv)
{ {
int mode = 't'; /* tokens by default */ int mode = 't';
const char *src = NULL; const char *src = NULL;
const char *out = NULL; const char *out = NULL;
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {

View File

@@ -400,7 +400,6 @@ fn pkgdedup(ss: []str) []str = {
return out; return out;
}; };
// Decimal value, or -1 on empty, non-digit, or overflow past max.
fn pkgparsedec(s: str, max: i64) i64 = { fn pkgparsedec(s: str, max: i64) i64 = {
if (s.len == 0) { return -1i64; }; if (s.len == 0) { return -1i64; };
let i: i32 = 0; let i: i32 = 0;

View File

@@ -1,13 +1,5 @@
// selfhost/cmd/w6a/asm.ww — port of cmd/w6a/asm.c. // Port of cmd/w6a/asm.c. Encoding subset matches what w6c emits — see
// // cmd/w6a/asm.c for the authoritative list.
// 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_*.
package w6a; package w6a;
@@ -16,8 +8,6 @@ import rt;
import opcodes; import opcodes;
import strings; import strings;
// ---- text buffer growth ------------------------------------------------
export fn emitbyte(a: *asm_, b: u8) void = { export fn emitbyte(a: *asm_, b: u8) void = {
if (a.textlen + 1u64 > a.textcap) { if (a.textlen + 1u64 > a.textcap) {
let nc: u64 = 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; a.relocs = r;
}; };
// Record a relocation that lives in the .data section. Used by // DATAR patches a 64-bit .data slot with a symbol's runtime VA;
// DATAR to patch a 64-bit slot with a symbol's runtime VA. obj.ww // obj.ww separates section=1 relocs into .rela.data when emitting
// separates these into .rela.data when emitting the .o. // the .o.
export fn addrelocdata(a: *asm_, off: u64, kind: i32, s: *asym, add: i64) void = { 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 })!; let r: *areloc = alloc(areloc { off = off, section = 1, kind = kind, asy = s, addend = add, rnext = a.relocs })!;
a.relocs = r; 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 = { export fn emitdatabyte(a: *asm_, b: u8) void = {
if (a.datalen + 1u64 > a.datacap) { if (a.datalen + 1u64 > a.datacap) {
let nc: u64 = a.datacap; let nc: u64 = a.datacap;
@@ -70,8 +58,6 @@ export fn emitdatabyte(a: *asm_, b: u8) void = {
a.datalen += 1u64; a.datalen += 1u64;
}; };
// ---- register codes ----------------------------------------------------
// Low 3 bits of register encoding. // Low 3 bits of register encoding.
fn rcode(r: i32) i32 = { fn rcode(r: i32) i32 = {
if (r == D_AX) { return 0; }; if (r == D_CX) { return 1; }; if (r == D_AX) { return 0; }; if (r == D_CX) { return 1; };
@@ -105,7 +91,6 @@ fn isxmm(r: i32) bool = {
return false; return false;
}; };
// ModR/M byte builder.
fn modrmbyte(mod: i32, reg: i32, rm: i32) u8 = { fn modrmbyte(mod: i32, reg: i32, rm: i32) u8 = {
return (((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7)): 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))); emitbyte(a, modrmbyte(3, rcode(regop), rcode(rmop)));
}; };
// ---- label resolution / fixups ----------------------------------------
fn resolvelabel(a: *asm_, name: str) u64 = { fn resolvelabel(a: *asm_, name: str) u64 = {
let s: *asym = a.syms; let s: *asym = a.syms;
for (s != nil) { for (s != nil) {
@@ -230,8 +213,6 @@ fn labeldefined(a: *asm_, name: str) bool = {
return false; return false;
}; };
// ---- fixup helper -----------------------------------------------------
fn addfixup(a: *asm_, off: u64, label: str) void = { fn addfixup(a: *asm_, off: u64, label: str) void = {
let f: *afixup = alloc(afixup { off = off, label = label, fnext = a.fixups })!; let f: *afixup = alloc(afixup { off = off, label = label, fnext = a.fixups })!;
a.fixups = f; a.fixups = f;
@@ -242,15 +223,9 @@ fn isgpr(t: i32) bool = {
return false; 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 = { export fn encode(a: *asm_) i32 = {
let p: *aprog = a.head; let p: *aprog = a.head;
for (p != nil) { for (p != nil) {
// Define any pending label at the current PC.
if (p.label.len > 0) { if (p.label.len > 0) {
let s: *asym = intern(a, p.label); let s: *asym = intern(a, p.label);
s.defined = 1; s.defined = 1;
@@ -281,10 +256,9 @@ export fn encode(a: *asm_) i32 = {
p = p.link; continue; p = p.link; continue;
}; };
if (op == A_DATAW) { if (op == A_DATAW) {
// Writable variant: bytes go into .data instead of // obj.ww emits the .data section conditionally on
// .text. obj.ww emits the extra section conditionally // datalen > 0 so .o output stays byte-identical for
// on datalen > 0 so .o output stays byte-identical // inputs that don't use DATAW.
// for inputs that don't use DATAW.
let s: *asym = intern(a, p.to.asym); let s: *asym = intern(a, p.to.asym);
s.defined = 1; s.defined = 1;
s.isdata = 1; s.isdata = 1;

View File

@@ -1,8 +1,4 @@
// selfhost/cmd/w6a/lex.ww — port of cmd/w6a/lex.c. // 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.
package w6a; package w6a;
@@ -20,10 +16,6 @@ export fn isidcont(c: i32) bool = {
return false; 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) = { export fn parsenum(p: *u8, n: u64) (i64, u64) = {
// strtoll(s, end, 0) semantics, matching the C twin cmd/w6a/lex.c:30: // strtoll(s, end, 0) semantics, matching the C twin cmd/w6a/lex.c:30:
// skip leading whitespace, optional sign, base-0 prefix detection // skip leading whitespace, optional sign, base-0 prefix detection

View File

@@ -1,6 +1,4 @@
// selfhost/cmd/w6a/main.ww — port of cmd/w6a/main.c. // Port of cmd/w6a/main.c.
//
// w6a = amd64 assembler. Read .s, parse, encode, emit ELF .o.
// //
// w6a_ww -o file.o file.s // w6a_ww -o file.o file.s
@@ -33,8 +31,7 @@ fn cstrlen(p: *u8) u64 = {
return n; return n;
}; };
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style // Bridges argv-style callers to lib/os entrypoints (str post-task-#23).
// callers to lib/os entrypoints (str post-task-#23).
fn pathstr(p: *u8) str = { fn pathstr(p: *u8) str = {
let r: str; let r: str;
r.ptr = p; r.ptr = p;
@@ -42,7 +39,6 @@ fn pathstr(p: *u8) str = {
return r; return r;
}; };
// Slurp the whole file into a fresh buffer.
fn slurp(path: *u8) (*u8, u64) = { fn slurp(path: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; }; 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 (parse(&s) != 0) { return 1; };
if (encode(&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 let fd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
if (fd < 0) { if (fd < 0) {
os.write(2, "w6a: cannot open output\n".ptr, 23u64); os.write(2, "w6a: cannot open output\n".ptr, 23u64);

View File

@@ -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): // Emit a tiny ELF64 relocatable object. Layout (in file order):
// [0] ELF header // [0] ELF header
@@ -36,7 +36,6 @@ fn wrdrop(fd: i32, p: *u8, n: u64) void = {
}; };
}; };
// ---- ELF constants ----------------------------------------------------
def ELFCLASS64: u8 = 2u8; def ELFCLASS64: u8 = 2u8;
def ELFDATA2LSB: u8 = 1u8; def ELFDATA2LSB: u8 = 1u8;
def EV_CURRENT_W: u32 = 1u32; def EV_CURRENT_W: u32 = 1u32;
@@ -59,14 +58,11 @@ def STT_NOTYPE: u8 = 0u8;
def STT_OBJECT: u8 = 1u8; def STT_OBJECT: u8 = 1u8;
def STT_FUNC: u8 = 2u8; def STT_FUNC: u8 = 2u8;
// Sizes of fixed structures.
def EHDR_SZ: u64 = 64u64; def EHDR_SZ: u64 = 64u64;
def SHDR_SZ: u64 = 64u64; def SHDR_SZ: u64 = 64u64;
def SYM_SZ: u64 = 24u64; def SYM_SZ: u64 = 24u64;
def RELA_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 wru8(p: *u8, off: u64, v: u8) void = { p[off] = v; };
fn wru16(p: *u8, off: u64, v: u16) void = { fn wru16(p: *u8, off: u64, v: u16) void = {
p[off] = (v & 255u16): u8; 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); wru32(p, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
}; };
// ---- growable byte buffer ---------------------------------------------
type buf = struct { type buf = struct {
p: *u8, p: *u8,
n: u64, n: u64,
@@ -128,8 +122,6 @@ fn bufputcstr(b: *buf, s: str) u32 = {
return off; return off;
}; };
// ---- emitelf ---------------------------------------------------------
export fn emitelf(a: *asm_, fd: i32) i32 = { export fn emitelf(a: *asm_, fd: i32) i32 = {
let shstr: buf; bufinit(&shstr); let shstr: buf; bufinit(&shstr);
let str_: buf; bufinit(&str_); 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_STRTAB: u16 = SH_SYMTAB + 1u16;
let SH_SHSTR: u16 = SH_STRTAB + 1u16; let SH_SHSTR: u16 = SH_STRTAB + 1u16;
// Section name offsets. Append .data / .rela.data only when // Append .data / .rela.data names only when used so the
// used so the .shstrtab buffer stays byte-identical for the // .shstrtab buffer stays byte-identical for the no-DATAW case
// no-DATAW case (test 991 byte-diff invariant). // (test 991 byte-diff invariant).
let shntext: u32 = bufputcstr(&shstr, ".text"); let shntext: u32 = bufputcstr(&shstr, ".text");
let shnrela: u32 = bufputcstr(&shstr, ".rela.text"); let shnrela: u32 = bufputcstr(&shstr, ".rela.text");
let shndata: u32 = 0u32; let shndata: u32 = 0u32;
@@ -195,7 +187,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
for (zi < 24) { zsym[zi] = 0u8; zi += 1; }; for (zi < 24) { zsym[zi] = 0u8; zi += 1; };
bufputb(&sym, zsym.ptr, 24u64); bufputb(&sym, zsym.ptr, 24u64);
// Build symbols.
let idx: i32 = 1; let idx: i32 = 1;
let s: *asym = a.syms; let s: *asym = a.syms;
for (s != nil) { for (s != nil) {
@@ -223,7 +214,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
s = s.snext; s = s.snext;
}; };
// Build relocations — split into text vs data buffers.
let r: *areloc = a.relocs; let r: *areloc = a.relocs;
for (r != nil) { for (r != nil) {
let entry: [24]u8; let entry: [24]u8;
@@ -239,7 +229,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
r = r.rnext; r = r.rnext;
}; };
// File offsets.
let off: u64 = EHDR_SZ; let off: u64 = EHDR_SZ;
let offtext: u64 = off; off = off + a.textlen; let offtext: u64 = off; off = off + a.textlen;
let offrela: u64 = off; off = off + rela.n; let offrela: u64 = off; off = off + rela.n;
@@ -258,7 +247,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
else { NSECT = 7u16; }; else { NSECT = 7u16; };
}; };
// ---- Ehdr ----
let eh: [64]u8; let eh: [64]u8;
let i: i32 = 0; let i: i32 = 0;
for (i < 64) { eh[i] = 0u8; i += 1; }; for (i < 64) { eh[i] = 0u8; i += 1; };
@@ -321,13 +309,11 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
written += 1u64; written += 1u64;
}; };
// Section header table — 6 headers of 64 bytes each = 384 bytes.
let shbuf: [64]u8; let shbuf: [64]u8;
// SHT_NULL // SHT_NULL
let sn: i32 = 0; let sn: i32 = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wrdrop(fd, shbuf.ptr, 64u64); wrdrop(fd, shbuf.ptr, 64u64);
// .text
sn = 0; sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shntext); 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, 32u64, a.textlen);
wru64(shbuf.ptr, 48u64, 1u64); // sh_addralign wru64(shbuf.ptr, 48u64, 1u64); // sh_addralign
wrdrop(fd, shbuf.ptr, 64u64); wrdrop(fd, shbuf.ptr, 64u64);
// .rela.text
sn = 0; sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnrela); wru32(shbuf.ptr, 0u64, shnrela);
@@ -351,7 +336,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
wru64(shbuf.ptr, 56u64, RELA_SZ); wru64(shbuf.ptr, 56u64, RELA_SZ);
wrdrop(fd, shbuf.ptr, 64u64); wrdrop(fd, shbuf.ptr, 64u64);
if (hasdata) { if (hasdata) {
// .data
sn = 0; sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shndata); wru32(shbuf.ptr, 0u64, shndata);
@@ -362,7 +346,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
wru64(shbuf.ptr, 48u64, 8u64); // sh_addralign wru64(shbuf.ptr, 48u64, 8u64); // sh_addralign
wrdrop(fd, shbuf.ptr, 64u64); wrdrop(fd, shbuf.ptr, 64u64);
if (hasdatarelocs) { if (hasdatarelocs) {
// .rela.data
sn = 0; sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnrelad); wru32(shbuf.ptr, 0u64, shnrelad);
@@ -377,7 +360,6 @@ export fn emitelf(a: *asm_, fd: i32) i32 = {
wrdrop(fd, shbuf.ptr, 64u64); wrdrop(fd, shbuf.ptr, 64u64);
}; };
}; };
// .symtab
sn = 0; sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnsymtab); 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, 48u64, 8u64);
wru64(shbuf.ptr, 56u64, SYM_SZ); wru64(shbuf.ptr, 56u64, SYM_SZ);
wrdrop(fd, shbuf.ptr, 64u64); wrdrop(fd, shbuf.ptr, 64u64);
// .strtab
sn = 0; sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnstrtab); 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, 32u64, str_.n);
wru64(shbuf.ptr, 48u64, 1u64); wru64(shbuf.ptr, 48u64, 1u64);
wrdrop(fd, shbuf.ptr, 64u64); wrdrop(fd, shbuf.ptr, 64u64);
// .shstrtab
sn = 0; sn = 0;
for (sn < 64) { shbuf[sn] = 0u8; sn += 1; }; for (sn < 64) { shbuf[sn] = 0u8; sn += 1; };
wru32(shbuf.ptr, 0u64, shnshstrtab); wru32(shbuf.ptr, 0u64, shnshstrtab);

View File

@@ -1,12 +1,10 @@
// selfhost/cmd/w6a/opcodes.ww — types + constants shared across the // Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
// w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h.
package w6a; package w6a;
// ---- registers + operand kinds (from 6.out.h) ------------------------- // Registers + operand kinds must stay numerically aligned with the
// These must stay numerically aligned with the C enum so that ww-cgen // 6.out.h C enum so that ww-cgen output (which reads them via
// output (which reads them via `D_AX(SB)` etc.) lands on the same // `D_AX(SB)` etc.) lands on the same integers when read by ww-w6a.
// integers when read by ww-w6a.
def D_NONE: i32 = 0; def D_NONE: i32 = 0;
def D_AX: i32 = 1; def D_AX: i32 = 1;
@@ -52,7 +50,6 @@ def D_BRANCH: i32 = 37;
def D_EXTERN: i32 = 38; def D_EXTERN: i32 = 38;
def D_INDIR: i32 = 39; def D_INDIR: i32 = 39;
// ---- opcodes ----------------------------------------------------------
def A_NOP: i32 = 0; def A_NOP: i32 = 0;
def A_TEXT: i32 = 1; def A_TEXT: i32 = 1;
def A_DATA: i32 = 2; def A_DATA: i32 = 2;
@@ -145,8 +142,6 @@ def A_DATAR: i32 = 61;
// with DIVQ. // with DIVQ.
def A_CQO: i32 = 66; def A_CQO: i32 = 66;
// ---- structs (mirror cmd/w6a/a.h) --------------------------------------
type aoperand = struct { type aoperand = struct {
atype: i32, // D_NONE / D_AX..D_R15 / D_CONST / D_INDIR / D_EXTERN / D_BRANCH atype: i32, // D_NONE / D_AX..D_R15 / D_CONST / D_INDIR / D_EXTERN / D_BRANCH
reg: i32, reg: i32,

View File

@@ -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. // Line-oriented parser for the asm subset emitted by w6c.
// Grammar: // Grammar:
@@ -28,8 +28,7 @@ fn streqlit(p: *u8, n: u64, lit: str) bool = {
return true; return true;
}; };
// opcodelookup — name (length-bounded *u8) → A_*. Returns 0 (A_NOP) // Returns 0 (A_NOP) if not found.
// if not found.
fn opcodelookup(p: *u8, n: u64) i32 = { fn opcodelookup(p: *u8, n: u64) i32 = {
if (streqlit(p, n, "MOVQ")) { return A_MOVQ; }; if (streqlit(p, n, "MOVQ")) { return A_MOVQ; };
if (streqlit(p, n, "MOVL")) { return A_MOVL; }; if (streqlit(p, n, "MOVL")) { return A_MOVL; };
@@ -100,7 +99,7 @@ fn opcodelookup(p: *u8, n: u64) i32 = {
return A_NOP; 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 = { fn reglookup(p: *u8, n: u64) i32 = {
if (streqlit(p, n, "AX")) { return D_AX; }; if (streqlit(p, n, "AX")) { return D_AX; };
if (streqlit(p, n, "BX")) { return D_BX; }; if (streqlit(p, n, "BX")) { return D_BX; };
@@ -180,7 +179,6 @@ fn perr(a: *asm_, msg: str) void = {
a.errs += 1; a.errs += 1;
}; };
// dupstr — copy n bytes from p into a fresh heap str.
fn dupstr(p: *u8, n: u64) str = { fn dupstr(p: *u8, n: u64) str = {
let view: str; let view: str;
view.ptr = p; view.ptr = p;
@@ -188,8 +186,6 @@ fn dupstr(p: *u8, n: u64) str = {
return strings.dup(view); return strings.dup(view);
}; };
// ---- line iteration & whitespace --------------------------------------
// Read next line into a fresh heap buffer; returns (ptr, len) or (nil,0) // Read next line into a fresh heap buffer; returns (ptr, len) or (nil,0)
// at EOF. Advances a.pos past the newline. // at EOF. Advances a.pos past the newline.
fn nextline(a: *asm_) (*u8, u64) = { fn nextline(a: *asm_) (*u8, u64) = {
@@ -204,7 +200,7 @@ fn nextline(a: *asm_) (*u8, u64) = {
let i: u64 = 0u64; let i: u64 = 0u64;
for (i < n) { buf[i] = a.src[start + i]; i += 1u64; }; for (i < n) { buf[i] = a.src[start + i]; i += 1u64; };
buf[n] = 0u8; buf[n] = 0u8;
a.pos += 1u64; // skip newline a.pos += 1u64;
return buf.ptr, n; return buf.ptr, n;
}; };
// EOF without trailing newline // EOF without trailing newline
@@ -226,7 +222,6 @@ fn skipws(p: *u8, off: u64, n: u64) u64 = {
return i; return i;
}; };
// parseoperand — parse one operand from p[off..n), populate out.
// Returns new offset (clamped to n on error). // Returns new offset (clamped to n on error).
fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = { fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
let off: u64 = skipws(p, offin, n); 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; }; if (off >= n) { return off; };
let c0: u8 = p[off]; let c0: u8 = p[off];
// $NUM
if (c0 == '$') { if (c0 == '$') {
off += 1u64; off += 1u64;
let v: i64; let v: i64;
@@ -250,7 +244,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
return off + used; return off + used;
}; };
// (REG)
if (c0 == '(') { if (c0 == '(') {
off += 1u64; off += 1u64;
let rstart: u64 = off; let rstart: u64 = off;
@@ -377,7 +370,6 @@ fn parseoperand(a: *asm_, p: *u8, offin: u64, n: u64, out: *aoperand) u64 = {
return n; return n;
}; };
// Append a fresh aprog to the list with given opcode and label.
fn addprog(a: *asm_, opc: i32, lbl: str) *aprog = { fn addprog(a: *asm_, opc: i32, lbl: str) *aprog = {
let pr: *aprog = alloc(aprog { as_ = opc, line = a.line, label = lbl })!; let pr: *aprog = alloc(aprog { as_ = opc, line = a.line, label = lbl })!;
pr.from = alloc(aoperand { })!; pr.from = alloc(aoperand { })!;
@@ -398,9 +390,7 @@ export fn parse(a: *asm_) i32 = {
line, n = nextline(a); line, n = nextline(a);
if (line == nil) { return a.errs; }; if (line == nil) { return a.errs; };
// skip leading ws
let i: u64 = skipws(line, 0u64, n); let i: u64 = skipws(line, 0u64, n);
// blank or //-comment
if (i >= n) { a.line += 1; continue; }; if (i >= n) { a.line += 1; continue; };
if (i + 1u64 < n) { if (i + 1u64 < n) {
if (line[i] == '/') { if (line[i + 1u64] == '/') { 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 mstart: u64 = i;
let m: u64 = mstart; let m: u64 = mstart;
let scan: bool = true; let scan: bool = true;
@@ -458,11 +447,9 @@ export fn parse(a: *asm_) i32 = {
let pr: *aprog = addprog(a, opc, pending); let pr: *aprog = addprog(a, opc, pending);
pending.ptr = nil; pending.len = 0; pending.ptr = nil; pending.len = 0;
// Skip ws after mnemonic
let r0: u64 = skipws(line, m, n); let r0: u64 = skipws(line, m, n);
if (opc == A_TEXT) { if (opc == A_TEXT) {
// TEXT name,$framesize — find first ',' as the end of name.
let q: u64 = r0; let q: u64 = r0;
let commapos: u64 = n; let commapos: u64 = n;
let scant: bool = true; let scant: bool = true;
@@ -502,7 +489,6 @@ export fn parse(a: *asm_) i32 = {
let toop: *aoperand = pr.to; let toop: *aoperand = pr.to;
toop.atype = D_EXTERN; toop.atype = D_EXTERN;
toop.asym = dupstr(line + r0, lparen - r0); toop.asym = dupstr(line + r0, lparen - r0);
// Skip past `(SB)` to land just after ')'.
let p2: u64 = lparen; let p2: u64 = lparen;
let scand2: bool = true; let scand2: bool = true;
for (scand2) { for (scand2) {
@@ -510,7 +496,6 @@ export fn parse(a: *asm_) i32 = {
else { if (line[p2] == ')') { p2 += 1u64; scand2 = false; } else { if (line[p2] == ')') { p2 += 1u64; scand2 = false; }
else { p2 += 1u64; }; }; else { p2 += 1u64; }; };
}; };
// Skip ws / ',' / tab between `)` and the `"`.
let scand3: bool = true; let scand3: bool = true;
for (scand3) { for (scand3) {
if (p2 >= n) { scand3 = false; } 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 (p2 >= n) { perr(a, "DATA missing payload"); a.line += 1; continue; };
if (line[p2] != '"') { perr(a, "DATA expects \"...\""); a.line += 1; continue; }; if (line[p2] != '"') { perr(a, "DATA expects \"...\""); a.line += 1; continue; };
p2 += 1u64; // past opening " p2 += 1u64;
// Parse escape sequence into a fresh growable buffer.
let cap: u64 = 32u64; let cap: u64 = 32u64;
let blen: u64 = 0u64; let blen: u64 = 0u64;
let dbuf: []u8 = alloc([], cap)!; let dbuf: []u8 = alloc([], cap)!;
@@ -575,8 +559,6 @@ export fn parse(a: *asm_) i32 = {
a.line += 1; continue; a.line += 1; continue;
}; };
// Generic instruction: 0/1/2 operands separated by ','.
// Find top-level comma.
let comma: i64 = -1i64; let comma: i64 = -1i64;
let q: u64 = r0; let q: u64 = r0;
for (q < n) { for (q < n) {

View File

@@ -1,14 +1,4 @@
// selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c. // 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.
package main; package main;

View File

@@ -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 // 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 // exports and which DT_NEEDED entry to record. We do not pull bytes
// from the .so; the dynamic loader maps it at runtime. // 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; package w6l;
@@ -72,8 +68,6 @@ def VD_NEXT: u64 = 16u64;
def VA_NAME: u64 = 0u64; def VA_NAME: u64 = 0u64;
def VA_NEXT: u64 = 4u64; def VA_NEXT: u64 = 4u64;
// ---- little-endian byte readers ---------------------------------------
fn du16(p: *u8, off: u64) u16 = { fn du16(p: *u8, off: u64) u16 = {
let b0: u16 = p[off]: u16; let b0: u16 = p[off]: u16;
let b1: u16 = p[off + 1u64]: u16; let b1: u16 = p[off + 1u64]: u16;
@@ -98,8 +92,6 @@ fn di64(p: *u8, off: u64) i64 = {
return du64(p, off): i64; return du64(p, off): i64;
}; };
// ---- C-string helpers --------------------------------------------------
fn dcstrlen(p: *u8) u64 = { fn dcstrlen(p: *u8) u64 = {
let n: u64 = 0u64; let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; }; for (p[n] != 0u8) { n += 1u64; };
@@ -114,7 +106,6 @@ fn dcstrtostr(p: *u8) str = {
return strings.dup(view); return strings.dup(view);
}; };
// basename: scan for last '/' and return pointer past it.
fn dbasename(p: *u8) *u8 = { fn dbasename(p: *u8) *u8 = {
let n: u64 = dcstrlen(p); let n: u64 = dcstrlen(p);
let i: u64 = n; let i: u64 = n;
@@ -127,8 +118,6 @@ fn dbasename(p: *u8) *u8 = {
return p; return p;
}; };
// ---- file slurp --------------------------------------------------------
fn slurpso(path: *u8) (*u8, u64) = { fn slurpso(path: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; }; if (fd < 0) { return nil, 0u64; };
@@ -151,8 +140,6 @@ fn slurpso(path: *u8) (*u8, u64) = {
return buf.ptr, n: u64; return buf.ptr, n: u64;
}; };
// ---- verdef helpers ----------------------------------------------------
// vdnameat — walk verdef records and return the name (as *u8 into // 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 // 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 // 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; return nil;
}; };
// ---- entry points ------------------------------------------------------
export fn loadso(l: *lnk, path: *u8) i32 = { export fn loadso(l: *lnk, path: *u8) i32 = {
let buf: *u8; let buf: *u8;
let blen: u64; let blen: u64;
@@ -207,7 +192,6 @@ export fn loadso(l: *lnk, path: *u8) i32 = {
if (shoff == 0u64) { return soerr("stripped .so unsupported"); }; if (shoff == 0u64) { return soerr("stripped .so unsupported"); };
if (shnum == 0u32) { 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 idxdynsym: i32 = -1;
let idxdynamic: i32 = -1; let idxdynamic: i32 = -1;
let idxversym: i32 = -1; let idxversym: i32 = -1;
@@ -292,8 +276,8 @@ export fn loadso(l: *lnk, path: *u8) i32 = {
verstr = buf + vstroff; verstr = buf + vstroff;
}; };
// Build the lso. Exports are appended in dynsym order so // Exports are appended in dynsym order so soprovides_v's
// soprovides_v's first-match semantics match the C version. // first-match semantics match the C version.
let so: *lso = alloc(lso { path = dcstrtostr(path), soname = dcstrtostr(sonamecs) })!; let so: *lso = alloc(lso { path = dcstrtostr(path), soname = dcstrtostr(sonamecs) })!;
let tail: *lexport = nil; let tail: *lexport = nil;
@@ -371,7 +355,6 @@ fn soerr(msg: str) i32 = {
return -1; return -1;
}; };
// soprovides — 1 if so exports name, 0 otherwise.
export fn soprovides(so: *lso, name: str) i32 = { export fn soprovides(so: *lso, name: str) i32 = {
if (so == nil) { return 0; }; if (so == nil) { return 0; };
let e: *lexport = so.exports; let e: *lexport = so.exports;

View File

@@ -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 // Emit a dynamic-linked ELF executable. The shape is the simplest
// valid one: PT_INTERP + PT_DYNAMIC + DT_BIND_NOW so the loader // valid one: PT_INTERP + PT_DYNAMIC + DT_BIND_NOW so the loader
@@ -29,7 +29,6 @@ import rt;
import strings; import strings;
import sym; import sym;
// ELF constants
def ET_EXEC_D: u16 = 2u16; def ET_EXEC_D: u16 = 2u16;
def EM_X86_64_D: u16 = 62u16; def EM_X86_64_D: u16 = 62u16;
def EV_CURRENT_D: u32 = 1u32; def EV_CURRENT_D: u32 = 1u32;
@@ -75,8 +74,6 @@ def PAGE: u64 = 4096u64;
def INTERP: str = "/lib64/ld-linux-x86-64.so.2"; def INTERP: str = "/lib64/ld-linux-x86-64.so.2";
// ---- byte writers ------------------------------------------------------
fn dwr8(buf: *u8, off: u64, v: u8) void = { fn dwr8(buf: *u8, off: u64, v: u8) void = {
buf[off] = v; buf[off] = v;
}; };
@@ -106,8 +103,6 @@ fn dwri32(buf: *u8, off: u64, v: i32) void = {
dwr32(buf, off, v: u32); dwr32(buf, off, v: u32);
}; };
// ---- byte readers ------------------------------------------------------
fn drdu16(p: *u8, off: u64) u16 = { fn drdu16(p: *u8, off: u64) u16 = {
let b0: u16 = p[off]: u16; let b0: u16 = p[off]: u16;
let b1: u16 = p[off + 1u64]: 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); return (off + a - 1u64) & ~(a - 1u64);
}; };
// ---- main entry --------------------------------------------------------
export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
// .data shares the R+W PT_LOAD with .got.plt and .dynamic. // .data shares the R+W PT_LOAD with .got.plt and .dynamic.
// Placed after .dynamic so the segment is one contiguous run; // 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 n: i32 = l.dynn;
let nu: u64 = n: u64; let nu: u64 = n: u64;
// ---- collect dyn syms into a plt_idx-indexed array ----
let dynsyms: []*lsym = alloc([], nu)!; let dynsyms: []*lsym = alloc([], nu)!;
let s: *lsym = l.syms; let s: *lsym = l.syms;
for (s != nil) { for (s != nil) {
@@ -185,7 +177,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
i += 1; i += 1;
}; };
// ---- collect used .so's (in l.sos order) ---- // Used .so's are collected in l.sos order.
let maxsos: i32 = 0; let maxsos: i32 = 0;
let so: *lso = l.sos; let so: *lso = l.sos;
for (so != nil) { maxsos += 1; so = so.sonext; }; 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; 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_sos_idx[k] = sos_used index for vlib k.
// vlib_first[k] = ver index of first version under vlib k. // vlib_first[k] = ver index of first version under vlib k.
// vlib_count[k] = number of versions 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; vi += 1;
}; };
// ---- compute dynstr size ----
let dynstrsz: u64 = 1u64; // leading NUL let dynstrsz: u64 = 1u64; // leading NUL
let pi: i32 = 0; let pi: i32 = 0;
for (pi < nsos) { for (pi < nsos) {
@@ -325,7 +316,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1; pi += 1;
}; };
// ---- fill dynstr ----
let dynstr: []u8 = alloc([], dynstrsz)!; let dynstr: []u8 = alloc([], dynstrsz)!;
let dynstrpos: u64 = 1u64; // past leading NUL 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; pi += 1;
}; };
// ---- per-dyn-sym versym index ----
let versymfor: []u8 = alloc([], nu * 2u64)!; let versymfor: []u8 = alloc([], nu * 2u64)!;
pi = 0; pi = 0;
for (pi < n) { for (pi < n) {
@@ -400,7 +389,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1; pi += 1;
}; };
// ---- compute byte sizes ----
let ehdrsz: u64 = 64u64; let ehdrsz: u64 = 64u64;
let nphdrs: u64 = 4u64; let nphdrs: u64 = 4u64;
let phdrsz: u64 = nphdrs * 56u64; 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 ndyn: u64 = (nsos: u64) + 11u64 + extra;
let dynamicsz: u64 = ndyn * 16u64; let dynamicsz: u64 = ndyn * 16u64;
// ---- compute file offsets ----
let off: u64 = ehdrsz + phdrsz; let off: u64 = ehdrsz + phdrsz;
let interpoff: u64 = off; off += interpsz; let interpoff: u64 = off; off += interpsz;
off = alignup(off, 8u64); 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 datafilelen: u64 = l.datalen - bsslen;
let filedataend: u64 = dataoff + datafilelen; let filedataend: u64 = dataoff + datafilelen;
// ---- build .dynsym ----
let dynsymbuf: []u8 = alloc([], dynsymsz)!; let dynsymbuf: []u8 = alloc([], dynsymsz)!;
pi = 0; pi = 0;
for (pi < n) { for (pi < n) {
@@ -500,7 +486,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1; pi += 1;
}; };
// ---- build .hash (SysV, 1 bucket) ---- // .hash is SysV with a single bucket.
let hashbuf: []u8 = alloc([], hashsz)!; let hashbuf: []u8 = alloc([], hashsz)!;
dwr32(hashbuf.ptr, 0u64, nbuckets); dwr32(hashbuf.ptr, 0u64, nbuckets);
dwr32(hashbuf.ptr, 4u64, nchain); dwr32(hashbuf.ptr, 4u64, nchain);
@@ -515,7 +501,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
ci += 1u64; ci += 1u64;
}; };
// ---- build .rela.plt ----
let relapltbuf: []u8 = alloc([], relapltsz)!; let relapltbuf: []u8 = alloc([], relapltsz)!;
pi = 0; pi = 0;
for (pi < n) { for (pi < n) {
@@ -527,7 +512,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1; pi += 1;
}; };
// ---- build .gnu.version (u16 per dynsym entry) ---- // .gnu.version is one u16 per dynsym entry.
let versymbuf: []u8 = alloc([], versymsz)!; let versymbuf: []u8 = alloc([], versymsz)!;
dwr16(versymbuf.ptr, 0u64, VER_NDX_LOCAL_D); dwr16(versymbuf.ptr, 0u64, VER_NDX_LOCAL_D);
pi = 0; pi = 0;
@@ -536,7 +521,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1; pi += 1;
}; };
// ---- build .gnu.version_r ----
let verneedbuf: []u8 = alloc([], verneedsz)!; let verneedbuf: []u8 = alloc([], verneedsz)!;
if (verneedsz > 0u64) { if (verneedsz > 0u64) {
let vnoff: u64 = 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)!; let pltbuf: []u8 = alloc([], pltsz)!;
pi = 0; pi = 0;
for (pi < n) { for (pi < n) {
@@ -590,11 +573,9 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
pi += 1; pi += 1;
}; };
// ---- build .got.plt ----
let gotpltbuf: []u8 = alloc([], gotpltsz)!; let gotpltbuf: []u8 = alloc([], gotpltsz)!;
dwr64(gotpltbuf.ptr, 0u64, dynamicva); dwr64(gotpltbuf.ptr, 0u64, dynamicva);
// ---- build .dynamic ----
let dynamicbuf: []u8 = alloc([], dynamicsz)!; let dynamicbuf: []u8 = alloc([], dynamicsz)!;
let dk: u64 = 0u64; let dk: u64 = 0u64;
pi = 0; pi = 0;
@@ -625,7 +606,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
return 1; return 1;
}; };
// ---- patch .text relocs targeting dynamic syms ----
let r: *lrel = l.rels; let r: *lrel = l.rels;
for (r != nil) { for (r != nil) {
if (r.sym != nil) { if (r.sym != nil) {
@@ -646,7 +626,6 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
r = r.rnext; r = r.rnext;
}; };
// ---- assemble file buffer ----
let filebuf: []u8 = alloc([], fileend)!; let filebuf: []u8 = alloc([], fileend)!;
filebuf.len = fileend: i32; 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 + 40u64, dynamicsz);
dwr64(filebuf.ptr, p3 + 48u64, 8u64); dwr64(filebuf.ptr, p3 + 48u64, 8u64);
// Sections.
dbcopy(filebuf.ptr, interpoff, INTERP.ptr, INTERP.len: u64); dbcopy(filebuf.ptr, interpoff, INTERP.ptr, INTERP.len: u64);
dwr8(filebuf.ptr, interpoff + (INTERP.len: u64), 0u8); dwr8(filebuf.ptr, interpoff + (INTERP.len: u64), 0u8);
dbcopy(filebuf.ptr, dynstroff, dynstr.ptr, dynstrsz); dbcopy(filebuf.ptr, dynstroff, dynstr.ptr, dynstrsz);

View File

@@ -1,8 +1,4 @@
// selfhost/cmd/w6l/main.ww — port of cmd/w6l/main.c. // 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.
// //
// w6l_ww -o out [-L<dir>...] [-l<name>...] file1.o file2.o ... // w6l_ww -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
@@ -25,10 +21,6 @@ fn mklnk() *lnk = {
return l; return l;
}; };
// `cstreq` lives in obj.ww — same bundle, single definition.
// `cstrlen` lives in obj.ww — same bundle, single definition.
// Build "<dir>/lib<name>.<ext>" into dst (NUL-terminated). Returns total // Build "<dir>/lib<name>.<ext>" into dst (NUL-terminated). Returns total
// length excluding NUL. dst must be large enough. // length excluding NUL. dst must be large enough.
fn buildpath(dst: *u8, dir: *u8, name: *u8, ext: str) u64 = { 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; k += 1;
}; };
// Then resolve -l flags and load each. Archives append; shared // Archives append; shared objects register their exports.
// objects register their exports.
let lf: i32 = 0; let lf: i32 = 0;
for (lf < nlflags) { for (lf < nlflags) {
let p: *u8 = resolvelib(lflags[lf], libdirs.ptr, nlibdirs); let p: *u8 = resolvelib(lflags[lf], libdirs.ptr, nlibdirs);

View File

@@ -1,12 +1,4 @@
// selfhost/cmd/w6l/obj.ww — port of cmd/w6l/obj.c. // 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.
package w6l; package w6l;
@@ -22,7 +14,6 @@ def SHT_SYMTAB: i32 = 2;
def SHT_STRTAB: i32 = 3; def SHT_STRTAB: i32 = 3;
def SHT_RELA: i32 = 4; def SHT_RELA: i32 = 4;
// ---- little-endian byte readers ----------------------------------------
// w6a/w6l use straight LE on amd64. Reading via byte offsets keeps us off // 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). // 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); return lo | (hi << 32u64);
}; };
// ---- ELF64 section header offsets (40 bytes total) --------------------
def SHDR_SIZE: u64 = 64u64; // sizeof(Shdr) per ELF64 spec def SHDR_SIZE: u64 = 64u64; // sizeof(Shdr) per ELF64 spec
def SHDR_NAME: u64 = 0u64; def SHDR_NAME: u64 = 0u64;
def SHDR_TYPE: u64 = 4u64; def SHDR_TYPE: u64 = 4u64;
@@ -76,8 +66,6 @@ def RELA_OFFSET: u64 = 0u64;
def RELA_INFO: u64 = 8u64; def RELA_INFO: u64 = 8u64;
def RELA_ADDEND: u64 = 16u64; def RELA_ADDEND: u64 = 16u64;
// ---- file slurp --------------------------------------------------------
fn slurp(path: *u8) (*u8, u64) = { fn slurp(path: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; }; if (fd < 0) { return nil, 0u64; };
@@ -100,8 +88,6 @@ fn slurp(path: *u8) (*u8, u64) = {
return buf.ptr, n: u64; return buf.ptr, n: u64;
}; };
// ---- text buffer growth ------------------------------------------------
fn emittext(l: *lnk, src: *u8, n: u64) void = { fn emittext(l: *lnk, src: *u8, n: u64) void = {
if (l.textlen + n > l.textcap) { if (l.textlen + n > l.textcap) {
let nc: u64 = l.textcap; let nc: u64 = l.textcap;
@@ -149,17 +135,13 @@ fn emitdata(l: *lnk, src: *u8, n: u64) void = {
l.datalen += n; l.datalen += n;
}; };
// ---- C-string helpers --------------------------------------------------
fn cstrlen(p: *u8) u64 = { fn cstrlen(p: *u8) u64 = {
let n: u64 = 0u64; let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; }; for (p[n] != 0u8) { n += 1u64; };
return n; return n;
}; };
// pathstr — view a NUL-terminated *u8 as a str. Bridges argv-style // Bridges argv-style callers to lib/os entrypoints (str post-task-#23).
// callers to lib/os entrypoints (str post-task-#23). Shared with
// main.ww and dyn.ww via the w6l bundle.
fn pathstr(p: *u8) str = { fn pathstr(p: *u8) str = {
let r: str; let r: str;
r.ptr = p; r.ptr = p;
@@ -179,7 +161,6 @@ fn cstreq(p: *u8, lit: str) bool = {
return true; return true;
}; };
// Build a ww str from a NUL-terminated *u8 (for passing to intern).
fn cstrtostr(p: *u8) str = { fn cstrtostr(p: *u8) str = {
let n: u64 = cstrlen(p); let n: u64 = cstrlen(p);
let view: str; let view: str;
@@ -188,8 +169,6 @@ fn cstrtostr(p: *u8) str = {
return strings.dup(view); return strings.dup(view);
}; };
// ---- archive (SysV ar) types and helpers -------------------------------
//
// Each archive member starts with a 60-byte ar_hdr. The fields we care // 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 // 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. // 10-byte, space-padded decimal). Member bodies are 2-byte aligned.
@@ -231,9 +210,8 @@ fn arfield(p: *u8, n: u64) u64 = {
return v; return v;
}; };
// elfglobals — return a linked list of names of globally-defined // Names are owned heap copies, so the source ELF buffer can be freed
// (STB_GLOBAL) symbols whose section is `.text`. Names are owned // afterward.
// heap copies, so the source ELF buffer can be freed afterward.
fn elfglobals(buf: *u8, len: u64) *defent = { fn elfglobals(buf: *u8, len: u64) *defent = {
if (len < EHDR_SIZE) { return nil; }; if (len < EHDR_SIZE) { return nil; };
if (buf[0u64] != 127u8) { return nil; }; if (buf[0u64] != 127u8) { return nil; };
@@ -310,9 +288,8 @@ fn elfglobals(buf: *u8, len: u64) *defent = {
return head; return head;
}; };
// memberdefinesundef — true if any of m's defined globals matches a // Names not already interned are uninteresting (the link doesn't need
// currently-undefined symbol in the linker's symbol table. Names not // them yet).
// already interned are uninteresting (the link doesn't need them yet).
fn memberdefinesundef(l: *lnk, m: *armember) bool = { fn memberdefinesundef(l: *lnk, m: *armember) bool = {
let de: *defent = m.defs; let de: *defent = m.defs;
for (de != nil) { for (de != nil) {
@@ -406,8 +383,6 @@ fn loadarchive(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
return 0; return 0;
}; };
// ---- main loader -------------------------------------------------------
export fn load(l: *lnk, path: *u8) i32 = { export fn load(l: *lnk, path: *u8) i32 = {
let bufp: *u8; let bufp: *u8;
let buflen: u64; 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 shstrshoff: u64 = rdu64(buf, shoff + (shstrndx: u64) * SHDR_SIZE + SHDR_OFFSET);
let shstr: *u8 = buf + shstrshoff; let shstr: *u8 = buf + shstrshoff;
// find .text, .data, .symtab, .rela.text, .rela.data
let idxtext: i32 = -1; let idxtext: i32 = -1;
let idxdata: i32 = -1; let idxdata: i32 = -1;
let idxsymtab: 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); datasize = rdu64(buf, datash + SHDR_SIZE_F);
}; };
// Track this object.
let ob: *lobj = alloc(lobj { let ob: *lobj = alloc(lobj {
path = cstrtostr(path), path = cstrtostr(path),
buf = buf, buf = buf,
@@ -510,18 +483,16 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
})!; })!;
l.objs = ob; l.objs = ob;
// Append .text bytes to the combined image.
emittext(l, buf + textoff, textsize); emittext(l, buf + textoff, textsize);
// Append .data bytes (if present) to the combined .data buffer.
if (idxdata >= 0) { if (idxdata >= 0) {
if (datasize > 0u64) { if (datasize > 0u64) {
emitdata(l, buf + dataoff, datasize); emitdata(l, buf + dataoff, datasize);
}; };
}; };
// Walk symbols. We don't keep a per-object map[] of *lsym. Instead // We don't keep a per-object map[] of *lsym. Instead the reloc
// the reloc loop re-walks symtab and re-interns by name. Simpler // loop re-walks symtab and re-interns by name. Simpler than
// than dancing around the cgen's u64-shift gaps. // dancing around the cgen's u64-shift gaps.
let si: u64 = 1u64; // skip index 0 (always undef sentinel) let si: u64 = 1u64; // skip index 0 (always undef sentinel)
for (si < nsyms) { for (si < nsyms) {
let symp: u64 = symoff + si * SYM_SIZE; let symp: u64 = symoff + si * SYM_SIZE;
@@ -569,7 +540,6 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = {
si += 1u64; si += 1u64;
}; };
// Per-object relocation collection.
if (idxrela >= 0) { if (idxrela >= 0) {
let relash: u64 = shoff + (idxrela: u64) * SHDR_SIZE; let relash: u64 = shoff + (idxrela: u64) * SHDR_SIZE;
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET); 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, addend = raddend: i64,
rnext = l.rels, rnext = l.rels,
})!; })!;
// Look up the referenced sym by name (re-walk symtab).
if ((rsymidx: u64) < nsyms) { if ((rsymidx: u64) < nsyms) {
let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE; let sp: u64 = symoff + (rsymidx: u64) * SYM_SIZE;
let sname: u32 = rdu32(buf, sp + SYM_NAME); 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 // Offsets land in .data, shifted by this object's dataoff so
// this object's data_off so they index the combined buffer. // they index the combined buffer.
if (idxrelad >= 0) { if (idxrelad >= 0) {
let relash: u64 = shoff + (idxrelad: u64) * SHDR_SIZE; let relash: u64 = shoff + (idxrelad: u64) * SHDR_SIZE;
let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET); let relaoff: u64 = rdu64(buf, relash + SHDR_OFFSET);

View File

@@ -1,11 +1,4 @@
// selfhost/cmd/w6l/out.ww — port of cmd/w6l/out.c. // 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.
package w6l; package w6l;
@@ -27,8 +20,6 @@ def PF_R: u32 = 4u32;
def TEXT_OFF: u64 = 4096u64; // 0x1000 def TEXT_OFF: u64 = 4096u64; // 0x1000
def PAGE_SZ: u64 = 4096u64; def PAGE_SZ: u64 = 4096u64;
// ---- little-endian byte writers ----------------------------------------
fn wru16(buf: *u8, off: u64, v: u16) void = { fn wru16(buf: *u8, off: u64, v: u16) void = {
buf[off] = (v & 255u16): u8; buf[off] = (v & 255u16): u8;
buf[off + 1u64] = ((v >> 8u16) & 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); wru32(buf, off + 4u64, ((v >> 32u64) & 4294967295u64): u32);
}; };
// ---- emit ---------------------------------------------------------------
export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
// Dispatch: any loaded shared object plus any dynamic ref means // Dispatch: any loaded shared object plus any dynamic ref means
// we owe the loader a real PT_INTERP/PT_DYNAMIC binary. // 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)!; let hdr: []u8 = alloc([], TEXT_OFF)!;
hdr.len = TEXT_OFF: i32; hdr.len = TEXT_OFF: i32;
// --- Ehdr (64 bytes) --- // Ehdr (64 bytes).
hdr[0u64] = 127u8; // 0x7f hdr[0u64] = 127u8; // 0x7f
hdr[1u64] = 'E'; hdr[1u64] = 'E';
hdr[2u64] = 'L'; 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, 60u64, 0u16); // e_shnum
wru16(hdr.ptr, 62u64, 0u16); // e_shstrndx 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, 64u64, PT_LOAD);
wru32(hdr.ptr, 68u64, PF_R | PF_X); wru32(hdr.ptr, 68u64, PF_R | PF_X);
wru64(hdr.ptr, 72u64, 0u64); // p_offset 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 wru64(hdr.ptr, 112u64, TEXT_OFF); // p_align
if (hasdata) { 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, 120u64, PT_LOAD);
wru32(hdr.ptr, 124u64, PF_R | PF_W); wru32(hdr.ptr, 124u64, PF_R | PF_W);
wru64(hdr.ptr, 128u64, dataoff); // p_offset 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 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 r1: (i64 | os.oserror) = os.writeall(fd, hdr.ptr, TEXT_OFF);
let n1: i64 = 0i64; let n1: i64 = 0i64;
match (r1) { match (r1) {

View File

@@ -1,11 +1,4 @@
// selfhost/cmd/w6l/pass.ww — port of cmd/w6l/pass.c. // 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.
// //
// Supported relocation kinds: PC32 (=2), PLT32 (=4); both are 32-bit // Supported relocation kinds: PC32 (=2), PLT32 (=4); both are 32-bit
// PC-relative displacements (PLT32 == PC32 for static). // PC-relative displacements (PLT32 == PC32 for static).

View File

@@ -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 // Singly-linked list, usually a few hundred entries; hashing isn't
// entries; hashing isn't worth it yet. // worth it yet.
package w6l; package w6l;

View File

@@ -1,26 +1,4 @@
// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c. // 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 (- ! ~ &amp; *),
// 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.
package wcc; package wcc;
@@ -38,12 +16,8 @@ import cgenexpr;
import cgenstmt; import cgenstmt;
import cgendecl; import cgendecl;
// ---- typedef alias registry ----------------------------------------- // Only direct nkind.N_TNAME aliases are mapped; `type p = struct {...}`
// // is handled by collectstructs.
// `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.
type aliasent = struct { type aliasent = struct {
aname: str, aname: str,
@@ -156,12 +130,9 @@ fn aliassamemod(c: *cgen, name: str) *syntax.node = {
return nil; return nil;
}; };
// ---- enum registry -------------------------------------------------- // 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 at collect time: walk // Mirrors cmd/wcc/check.c's enum resolution.
// 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.
// foldintliteral — fold the literal subset usable for top-level // foldintliteral — fold the literal subset usable for top-level
// constant slots: int/rune literal, true/false/nil, and a unary // 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; 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 { type fieldinfo = struct {
fname: str, fname: str,
foff: i32, foff: i32,
@@ -399,8 +363,6 @@ type structinfo = struct {
sinext: *structinfo, sinext: *structinfo,
}; };
// ---- locals / frame --------------------------------------------------
type local = struct { type local = struct {
name: str, name: str,
off: i32, off: i32,
@@ -764,8 +726,6 @@ fn localfind(c: *cgen, name: str) i32 = {
return 0; return 0;
}; };
// ---- emit helpers ---------------------------------------------------
// Cgfn defers its prologue (TEXT / SUBQ) until after the body so the // Cgfn defers its prologue (TEXT / SUBQ) until after the body so the
// frame size reflects every emit-time localadd — the scanlocals pre- // frame size reflects every emit-time localadd — the scanlocals pre-
// pass that previously pre-computed it was dropped per #15/#26c. The // 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; return r;
}; };
// ---- string interning ------------------------------------------------
//
// streq is provided by sym.ww and reused here.
// internstrlit — return a stable label for `bytes`. Dedups by content // internstrlit — return a stable label for `bytes`. Dedups by content
// so identical literals share storage. // so identical literals share storage.
fn internstrlit(c: *cgen, bytes: str) str = { fn internstrlit(c: *cgen, bytes: str) str = {
@@ -1008,8 +964,6 @@ fn letscalarprim(nm: str) bool = {
return false; 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 = { fn letfloatprim(nm: str) i32 = {
if (syntax.streq(nm, "f32")) { return 4; }; if (syntax.streq(nm, "f32")) { return 4; };
if (syntax.streq(nm, "f64")) { return 8; }; 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 // 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 // the type isn't yet supported as a writable global. Walks type
// aliases so byte output matches C cgen, which resolves Type kinds. // 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 = { fn letemitsize(c: *cgen, d: *syntax.node) i32 = {
if (d == nil) { return 0; }; if (d == nil) { return 0; };
let t: *syntax.node = d.lhs; 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) { if (eu != nil && eu.kind == syntax.tykind.TY_STRUCT) {
// Validate: every element must be N_STRUCTLIT (after N_CAST).
let idx: i32 = 0; let idx: i32 = 0;
let last_ev: *syntax.node = nil; let last_ev: *syntax.node = nil;
let e: *syntax.node = rhs.list; 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)) { if (syntax.typeisfloat(au.sub)) {
let isf32: bool = syntax.typeisf32(au.sub); let isf32: bool = syntax.typeisf32(au.sub);
// Validate.
let idx: i32 = 0; let idx: i32 = 0;
let e: *syntax.node = rhs.list; let e: *syntax.node = rhs.list;
for (e != nil && idx < alen) { 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 e: *syntax.node = rhs.list;
let last: u64 = 0u64; let last: u64 = 0u64;
let repeat: bool = false; let repeat: bool = false;
// Validate first.
for (e != nil && idx < alen) { for (e != nil && idx < alen) {
if (e.kind == syntax.nkind.N_FIELD) { if (e.kind == syntax.nkind.N_FIELD) {
if (syntax.streq(e.str, "...")) { repeat = true; break; }; 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.write(2, m.ptr, m.len: u64);
os.exit(1); os.exit(1);
}; };
// Writable backing data.
emitline("DATAW "); emitline("DATAW ");
emitfnname(c, name, module); emitfnname(c, name, module);
emitline(".d(SB),\""); emitline(".d(SB),\"");
@@ -3494,12 +3439,9 @@ fn emitdatasection(c: *cgen) void = {
}; };
}; };
// ---- fn return-type map --------------------------------------------- // 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
// Per-file: ident → ret-type-node. Used to decide whether to shuffle // canonical (AX, BX) str pair.
// (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 { type fnret = struct {
fname: str, fname: str,
fmod: str, fmod: str,
@@ -3670,12 +3612,8 @@ fn fnparamslookupmod(c: *cgen, name: str, mod: str) *syntax.node = {
return fnparamslookup(c, name); return fnparamslookup(c, name);
}; };
// ---- def-constant registry ------------------------------------------
//
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an // `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 // ident reference loads it via `MOVQ NAME(SB), AX`.
// file load and consult on nkind.N_IDENT lookup.
type defent = struct { type defent = struct {
dname: str, dname: str,
dmod: str, // originating module (`// MODULE: foo`), or empty dmod: str, // originating module (`// MODULE: foo`), or empty
@@ -3819,8 +3757,6 @@ fn defisaddressable(c: *cgen, opnd: *syntax.node) bool = {
return false; return false;
}; };
// ---- module-private symbol map --------------------------------------
//
// Every non-FFI top-level fn decl lives in its module's namespace — // Every non-FFI top-level fn decl lives in its module's namespace —
// cgen mangles the leaf to `<module>.<name>` at the def site (TEXT) // cgen mangles the leaf to `<module>.<name>` at the def site (TEXT)
// and at every call/load site, so cross-module same-leaf fns (lib/os // 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); emitbytes( ident.ptr, ident.len: u64);
}; };
// ---- FFI map ---------------------------------------------------------
fn fficollect(c: *cgen, file: *syntax.node) void = { fn fficollect(c: *cgen, file: *syntax.node) void = {
c.ffis = nil; c.ffis = nil;
if (file == nil) { return; }; if (file == nil) { return; };
@@ -4085,8 +4019,6 @@ fn ffiresolve(c: *cgen, ident: str) str = {
return ident; return ident;
}; };
// ---- ABI argreg helpers ---------------------------------------------
fn argregname(i: i32) str = { fn argregname(i: i32) str = {
if (i == 0) { return "DI"; }; if (i == 0) { return "DI"; };
if (i == 1) { return "SI"; }; if (i == 1) { return "SI"; };

View File

@@ -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; package wcc;
import os; import os;
import syntax; import syntax;
import strconv; import strconv;
// ---- function-level cgen ---------------------------------------------
fn cgfnparams(c: *cgen, params: *syntax.node) void = { fn cgfnparams(c: *cgen, params: *syntax.node) void = {
let p: *syntax.node = params; let p: *syntax.node = params;
// sret (#23): RDI is consumed by the hidden dest pointer // 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) { if (c.lastwasreturn == 0) {
// Run any registered defers in LIFO order before the
// implicit return.
rundefers(c); rundefers(c);
// Zero AX before the fall-through return — matches cstage, // Zero AX before the fall-through return — matches cstage,
// which always emits this so void-returning fns don't leak // 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(); cgout_flush();
}; };
// ---- file-level entry ------------------------------------------------
export fn cgfile(c: *cgen, file: *syntax.node) void = { export fn cgfile(c: *cgen, file: *syntax.node) void = {
if (file == nil) { return; }; if (file == nil) { return; };
c.strlits = nil; c.strlits = nil;

View File

@@ -1,10 +1,5 @@
// selfhost/cmd/wcc/cgenexpr.ww — split out of cgen.ww. // 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 // The remainder of cgen lives in cgen.ww (foundation: types, emit
// primitives, the collect* tables, FFI/module maps) and cgenstmt.ww // primitives, the collect* tables, FFI/module maps) and cgenstmt.ww
// (cgstmt). // (cgstmt).
@@ -683,9 +678,6 @@ fn cgtryunw(c: *cgen, n: *syntax.node) void = {
}; };
fn cgtypetest(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 // Slot resolution is inlined (rather than factored into a helper
// with output parameters): wwstage cgen has a trap with i32 // with output parameters): wwstage cgen has a trap with i32
// stored via *i32 in this context — direct assignment of the // 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_TENUM) { leaf_tn = leaf_tn.lhs; }
else { if (lk == syntax.nkind.N_TNAME) { else { if (lk == syntax.nkind.N_TNAME) {
let lnm: str = leaf_tn.str; 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; }; if (primsize(lnm) > 0) { break; };
let lal: *syntax.node = aliaslookup(c, lnm); let lal: *syntax.node = aliaslookup(c, lnm);
if (lal == nil) { leaf_tn = nil; } 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 = { 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 base: *syntax.node = n.lhs;
let idx: *syntax.node = n.rhs; let idx: *syntax.node = n.rhs;
// Direct non-ident index bases that match none of the typed arms // 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"); emitline("\t(BX), AX\n");
return; return;
}; };
// Generic fallback when base isn't a plain ident.
// #135: N_DOT base on `[N]T` field needs the field's ADDRESS, // #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 // not its value. cgexpr would auto-deref + load the 8-byte value
// as if it were a pointer. dotbaseaddr emits the address inline. // 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); let es60: *syntax.tinfo = tichase(bu60.sub);
if (es60 != nil) { esz = es60.size: i32; }; if (es60 != nil) { esz = es60.size: i32; };
}; };
// base address
if (baselocal != nil) { if (baselocal != nil) {
let tn: *syntax.node = baselocal.tnode; let tn: *syntax.node = baselocal.tnode;
let isarray: bool = false; let isarray: bool = false;
@@ -2936,11 +2920,9 @@ fn cgslice(c: *cgen, n: *syntax.node) void = {
}; };
};};};}; };};};};
emitline("\tPUSHQ\tAX\n"); emitline("\tPUSHQ\tAX\n");
// lo (default 0)
if (lo != nil) { cgexpr(c, lo); } if (lo != nil) { cgexpr(c, lo); }
else { emitline("\tMOVQ\t$0, AX\n"); }; else { emitline("\tMOVQ\t$0, AX\n"); };
emitline("\tPUSHQ\tAX\n"); emitline("\tPUSHQ\tAX\n");
// hi (default base length)
if (hi != nil) { if (hi != nil) {
cgexpr(c, hi); cgexpr(c, hi);
} else { if (baselocal != nil) { } 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 = { 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 // Read the tagged-union slot and dispatch by tag. Slot
// layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings // layout: [+0]=tag, [+8]=value0, [+16]=value1. Bindings
// (`case let v: T =>`) get a fresh local slot loaded from // (`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; let bn: str = cs.str;
if (bn.len > 0) { if (bn.len > 0) {
if (pat != nil) { 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); }; if (cs.body != nil) { cgstmt(c, cs.body); };
// Restore the locals head — pop everything the arm pushed // Restore the locals head — pop everything the arm pushed
// so post-match code resolves names to their original (outer) // 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 = { 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; let fk: i32 = 0;
if (n.lhs != nil) { if (n.lhs != nil) {
let lt: *syntax.tinfo = n.lhs.type_: *syntax.tinfo; let lt: *syntax.tinfo = n.lhs.type_: *syntax.tinfo;

View File

@@ -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; package wcc;
import os; import os;
import syntax; import syntax;
import strconv; import strconv;
// ---- statement cgen --------------------------------------------------
fn cgstmt(c: *cgen, n: *syntax.node) void = { fn cgstmt(c: *cgen, n: *syntax.node) void = {
if (n == nil) { return; }; if (n == nil) { return; };
let k: syntax.nkind = n.kind; 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 = { fn cgyield(c: *cgen, n: *syntax.node) void = {
// Evaluate the value into AX (and BX for str), then JMP to the // Falls through silently if there is no active match — should be
// enclosing match's end label. Falls through silently if there // a checker error eventually.
// is no active match — should be a checker error eventually.
if (n.lhs != nil) { cgexpr(c, n.lhs); }; if (n.lhs != nil) { cgexpr(c, n.lhs); };
if (c.yieldtop > 0) { if (c.yieldtop > 0) {
let tgt: str = c.yieldbuf[c.yieldtop - 1]; let tgt: str = c.yieldbuf[c.yieldtop - 1];
@@ -100,8 +87,6 @@ fn cgblock(c: *cgen, n: *syntax.node) void = {
return; 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 = { fn rundefers(c: *cgen) void = {
let i: i32 = c.defertop - 1; let i: i32 = c.defertop - 1;
for (i >= 0) { for (i >= 0) {
@@ -3178,11 +3163,6 @@ fn cgfor(c: *cgen, n: *syntax.node) void = {
return; 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 = { fn cgmassign(c: *cgen, n: *syntax.node) void = {
// #83: positional per-element destructure REASSIGN. Same cursor as // #83: positional per-element destructure REASSIGN. Same cursor as
// cgmlet (and cgreturn; harec create_unpack_bindings, // cgmlet (and cgreturn; harec create_unpack_bindings,
@@ -3394,17 +3374,6 @@ fn cgmassign(c: *cgen, n: *syntax.node) void = {
return; 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 = { fn cgmlet(c: *cgen, n: *syntax.node) void = {
let rhs: *syntax.node = n.rhs; let rhs: *syntax.node = n.rhs;
if (rhs == nil) { return; }; if (rhs == nil) { return; };
@@ -3958,7 +3927,6 @@ fn cgforrange(c: *cgen, n: *syntax.node) void = {
nbinds = 1; nbinds = 1;
}; };
// init: ioff(BP) = 0
emitline("\tMOVQ\t$0, "); emitline("\tMOVQ\t$0, ");
emitoff(ioff: i64); emitoff(ioff: i64);
emitline("(BP)\n"); emitline("(BP)\n");
@@ -4290,5 +4258,3 @@ fn cgcontinue(c: *cgen, n: *syntax.node) void = {
c.lastwasreturn = 0; c.lastwasreturn = 0;
return; return;
}; };

View File

@@ -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 // Bundler pulls this in transitively via cgen.ww; consumers don't
// need to `use cgenutil;` directly. // need to `use cgenutil;` directly.
@@ -18,8 +7,6 @@ import os;
import syntax; import syntax;
import strconv; import strconv;
// ---- variadic-call helpers (Hare-style `T...` param) -----------------
// slicewrap — synthesise an N_TSLICE node wrapping the given element // slicewrap — synthesise an N_TSLICE node wrapping the given element
// type AST. Used by the Hare-style variadic path so the local entry // type AST. Used by the Hare-style variadic path so the local entry
// for the param (callee side) and the call-site slice descriptor // 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; return false;
}; };
// ---- expression cgen -------------------------------------------------
// pushargsrev — recursively walks the arg list, evaluates rightmost // pushargsrev — recursively walks the arg list, evaluates rightmost
// first, and pushes. str args take two slots (ptr in AX, len in BX); // 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 // 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); let es60: *syntax.tinfo = tichase(bu60.sub);
if (es60 != nil) { esz = es60.size: i32; }; if (es60 != nil) { esz = es60.size: i32; };
}; };
// base address → push
if (baselocal != nil) { if (baselocal != nil) {
let tn: *syntax.node = baselocal.tnode; let tn: *syntax.node = baselocal.tnode;
let isarr60: bool = false; let isarr60: bool = false;
@@ -828,7 +812,6 @@ fn pushargsrev(c: *cgen, arg: *syntax.node, param: *syntax.node, memphase: bool,
cgexpr(c, base); cgexpr(c, base);
};};}; };};};
emitline("\tPUSHQ\tAX\n"); emitline("\tPUSHQ\tAX\n");
// hi (default base length) → push
if (hi != nil) { if (hi != nil) {
cgexpr(c, hi); cgexpr(c, hi);
} else { if (baselocal != nil) { } 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("\tMOVQ\t$0, AX\n");
};};};};}; };};};};};
emitline("\tPUSHQ\tAX\n"); emitline("\tPUSHQ\tAX\n");
// lo (default 0) → AX
if (lo != nil) { cgexpr(c, lo); } if (lo != nil) { cgexpr(c, lo); }
else { emitline("\tMOVQ\t$0, AX\n"); }; else { emitline("\tMOVQ\t$0, AX\n"); };
emitline("\tPOPQ\tBX\n"); // hi emitline("\tPOPQ\tBX\n"); // hi
@@ -2098,14 +2080,9 @@ fn elemsizeofc(c: *cgen, t: *syntax.node) i32 = {
return slotsize(c, elem); return slotsize(c, elem);
}; };
// nodeisunsigned — best-effort cgen-time inference from the AST. We // nodeisunsigned — best-effort cgen-time inference from the AST
// walk surface nodes (N_DOT now reads n.type_ — #55 A.6.3g): // (N_DOT reads the checker-stamped n.type_ — #55 A.6.3g).
// nkind.N_INTLIT never marked unsigned (no tsuffix plumbing yet) // nkind.N_INTLIT is 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
//
// Conservative: if we can't tell, return false (signed). The cost of // Conservative: if we can't tell, return false (signed). The cost of
// being wrong here is byte-different asm vs C, not bad runtime. // being wrong here is byte-different asm vs C, not bad runtime.
fn nodeisunsigned(c: *cgen, n: *syntax.node) bool = { fn nodeisunsigned(c: *cgen, n: *syntax.node) bool = {
@@ -2191,8 +2168,6 @@ fn nodeprimwidth(c: *cgen, n: *syntax.node) i32 = {
return 0; return 0;
}; };
// ---- type-driven slot sizing ----------------------------------------
// structnaturalsize — type-natural size of `si`, i.e. max(foff + // structnaturalsize — type-natural size of `si`, i.e. max(foff +
// fsz) across declared fields, UNROUNDED. This is the memory-copy // fsz) across declared fields, UNROUNDED. This is the memory-copy
// extent: cstage copies exactly these bytes for the >24B sret // extent: cstage copies exactly these bytes for the >24B sret
@@ -2594,11 +2569,8 @@ fn structsamemod(c: *cgen, name: str) *structinfo = {
return nil; return nil;
}; };
// primsize — size in bytes of a primitive type name (or 0 if not // fldnumidx — used by cgdot to dispatch `t.0` / `t.1` against an
// recognised as a primitive — the caller falls back to other paths). // nkind.N_TTUPLE local without pulling in strconv.
// 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.
fn fldnumidx(s: str) i32 = { fn fldnumidx(s: str) i32 = {
if (s.len == 0) { return -1; }; if (s.len == 0) { return -1; };
let r: i32 = 0; 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); let sname: str = rhsstructpayload(c, src);
if (sname.len > 0) { if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname); let si: *structinfo = structlookup(c, sname);

View File

@@ -1,21 +1,4 @@
// selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c. // 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.
package wcc; package wcc;
@@ -91,8 +74,6 @@ fn circularnamed(c: *checker, t: *syntax.tinfo, n: *syntax.node) bool = {
os.exit(1); 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 = { fn seedprimitives(c: *checker) void = {
syntax.scopedefine(c.top, "void", syntax.skind.SK_TYPE, c.tc.tyvoid, nil); 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); 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. // free identifier.
if (n.lhs != nil) { resolvewalk(c, n.lhs); }; if (n.lhs != nil) { resolvewalk(c, n.lhs); };
// A.6.0: branch returns early; stamp here so the post-walk // A.6.0: branch returns early; stamp here so the post-walk
// dispatch below sees N_DOT covered. exprtype N_DOT arm is // dispatch below sees N_DOT covered.
// added in A.6.1; for now this is a no-op nil return.
let _t: *syntax.node = exprtype(c, n, nil); let _t: *syntax.node = exprtype(c, n, nil);
return; 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 // The selfhost check operates on AST type expressions rather than
// resolved Type structs. These helpers mirror what cmd/wcc/check.c // resolved Type structs. These helpers mirror what cmd/wcc/check.c
// does with tinfo, but only on the subset of cases this checker // does with tinfo, but only on the subset of cases this checker
// needs to enforce: tagged-union exhaustiveness, ? subset // needs to enforce: tagged-union exhaustiveness, ? subset
// propagation, and !-flag semantics. // propagation, and !-flag semantics.
// unwrapbang — strip an nkind.N_TBANG wrapper; leaves other nodes alone.
fn unwrapbang(n: *syntax.node) *syntax.node = { fn unwrapbang(n: *syntax.node) *syntax.node = {
if (n == nil) { return nil; }; if (n == nil) { return nil; };
if (n.kind == syntax.nkind.N_TBANG) { return n.lhs; }; 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; 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 = { fn resolvealias(c: *checker, n: *syntax.node) *syntax.node = {
let cur: *syntax.node = n; let cur: *syntax.node = n;
for (cur != nil) { for (cur != nil) {
@@ -1274,9 +1247,6 @@ fn scruttype(c: *checker, e: *syntax.node) *syntax.node = {
return nil; 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 = { fn mktname(c: *checker, nm: str) *syntax.node = {
let n: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, "", 0, 0); let n: *syntax.node = syntax.newnode(syntax.nkind.N_TNAME, "", 0, 0);
n.str = nm; n.str = nm;
@@ -4692,9 +4662,6 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
return nil; 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 = { fn isuntypedint(t: *syntax.node) bool = {
if (t == nil) { return false; }; if (t == nil) { return false; };
if (t.kind != syntax.nkind.N_TNAME) { 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; return true;
}; };
// ---- match exhaustiveness --------------------------------------------
//
// For every match arm, verify that every variant of the scrutinee's // For every match arm, verify that every variant of the scrutinee's
// tagged-union type is handled by some case (or a default arm // tagged-union type is handled by some case (or a default arm
// exists). Multi-pattern `case A | B =>` covers all alts. // 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 = { fn qualleaf(nm: str) str = {
let dotidx: i32 = -1; let dotidx: i32 = -1;
let i: i32 = 0; let i: i32 = 0;
@@ -5408,8 +5371,6 @@ fn qualleaf(nm: str) str = {
return leaf; return leaf;
}; };
// qualmod — module qualifier of a type name (segment before the
// rightmost '.'), or `defmod` when unqualified.
fn qualmod(nm: str, defmod: str) str = { fn qualmod(nm: str, defmod: str) str = {
let dotidx: i32 = -1; let dotidx: i32 = -1;
let i: i32 = 0; 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); }; if (!covered) { errmatchvariant(c, n, v); };
}; };
// ---- let init / return assignability --------------------------------
//
// AST-level approximation: when we can infer src's type and dst is // AST-level approximation: when we can infer src's type and dst is
// explicitly declared, verify isassignable. We only emit an error // explicitly declared, verify isassignable. We only emit an error
// when isassignable says "false with confidence." If we can't tell // 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 // `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 // union and that T name one of its variants. Operates on AST type
// expressions; falls back silently when we can't determine e's // 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; c.errs += 1;
}; };
// ---- ? subset propagation --------------------------------------------
//
// For `expr?`, the operand's error subset must be a subset of the // For `expr?`, the operand's error subset must be a subset of the
// enclosing fn's return-type variants. Mirrors C check.c. Operand // enclosing fn's return-type variants. Mirrors C check.c. Operand
// is nkind.N_TRYPROP or nkind.N_TRYUNW (the F8 cardinality gate covers // 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; 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` // 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 // when two params share a name. The fn body's scope IS fresh here
// (resolvefnbody opens it before calling us), so guarding scopedefine's // (resolvefnbody opens it before calling us), so guarding scopedefine's

View File

@@ -1,7 +1,6 @@
// selfhost/cmd/wcc/err.ww — port of cmd/wcc/err.c. // 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
// Diagnostics. Plan 9 style: short, no levels beyond fatal/error/warn. // libc stdio.
// Output goes through os.write so we don't pull in libc stdio.
package wcc; package wcc;

View File

@@ -16,17 +16,12 @@
// - A `.wwi` is ONE package's interface; the emit filters to PRIMARY // - A `.wwi` is ONE package's interface; the emit filters to PRIMARY
// decls (imported==0). // 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; package wcc;
import os; import os;
import syntax; import syntax;
import strconv; import strconv;
// --- byte writers ------------------------------------------------------
fn wputs(fd: i32, s: str) void = { fn wputs(fd: i32, s: str) void = {
os.write(fd, s.ptr, s.len: u64); os.write(fd, s.ptr, s.len: u64);
}; };
@@ -71,13 +66,11 @@ fn wquote(fd: i32, s: str) void = {
wputb(fd, '"'); wputb(fd, '"');
}; };
// --- check_exported_type (drew) --------------------------------------- // 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)
// Resolve an N_TNAME to its type sym via the public sym helpers (mirror // WITHOUT the side effects of the checker's aliassym (no on-demand
// of cstage wwi_typesym / scope_lookup_type) WITHOUT the side effects of // resolve, no double error). A primitive/keyword resolves to no SK_TYPE
// the checker's aliassym (no on-demand resolve, no double error). A // → leaf. By producer time checkfile has finished and c.cur == c.top.
// 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 = { fn wwitypesym(c: *checker, nm: str) *syntax.sym = {
let empty: str; let empty: str;
@@ -188,7 +181,7 @@ fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = {
bad = bad | wwichecktype(c, d, p.lhs); bad = bad | wwichecktype(c, d, p.lhs);
p = p.next; 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) { } else { if (d.kind == syntax.nkind.N_TYPEDECL) {
bad = bad | wwichecktype(c, d, d.lhs); bad = bad | wwichecktype(c, d, d.lhs);
} else { if (d.kind == syntax.nkind.N_DEF) { } else { if (d.kind == syntax.nkind.N_DEF) {
@@ -200,10 +193,8 @@ fn wwicheckdecl(c: *checker, d: *syntax.node) i32 = {
return bad; return bad;
}; };
// --- type-expr + const-expr unparser (rob §2.2/§2.4) ------------------ // Type-expr + const-expr unparser (rob §2.2/§2.4). wwihexdigits is
// byte-identical to cstage's fprintf("%0Nx").
// wwihexdigits — emit the low `n` hex digits of `v`, most-significant
// first, lowercase. Byte-identical to cstage's fprintf("%0Nx").
fn wwihexdigits(fd: i32, v: u64, n: i32) void = { fn wwihexdigits(fd: i32, v: u64, n: i32) void = {
let i: i32 = n - 1; let i: i32 = n - 1;
for (i >= 0) { 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 = { fn wwiprimary(n: *syntax.node) bool = {
// imported==1 marks a decl reached through a `//ww:module <path>` // imported==1 marks a decl reached through a `//ww:module <path>`
// boundary (an imported module's concatenated section). // 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; d.kind == syntax.nkind.N_DEF || d.kind == syntax.nkind.N_LET;
}; };
// strcmp — byte lexicographic, mirror C strcmp sign (<0/0/>0). Both // Deterministic ordering (rob §3): byte-lexicographic, mirror C strcmp
// stages key the sort identically, so the `.wwi` order is deterministic. // sign (<0/0/>0). Both stages key the sort identically, so the `.wwi`
// order is deterministic.
fn wwistrcmp(a: str, b: str) i32 = { fn wwistrcmp(a: str, b: str) i32 = {
let i: i32 = 0; let i: i32 = 0;
for (i < a.len && i < b.len) { for (i < a.len && i < b.len) {
@@ -542,8 +532,7 @@ fn wwistrcmp(a: str, b: str) i32 = {
return a.len - b.len; return a.len - b.len;
}; };
// Selection sort over parallel (key, node) arrays. Total order keyed on // Total order keyed on the symbol name; ties broken by original index —
// the symbol name; ties broken by original index — so the result is
// stable regardless of any same-name collision, matching cstage's qsort // stable regardless of any same-name collision, matching cstage's qsort
// + idx tiebreak. // + idx tiebreak.
fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = { fn wwisortdecls(keys: []str, nodes: []*syntax.node, n: i32) void = {

View File

@@ -24,9 +24,8 @@ import strings;
// the cgen #127 mod-mangle attribution bug consumer per rule-7. // the cgen #127 mod-mangle attribution bug consumer per rule-7.
def CMD_MAX: u64 = 8192u64; def CMD_MAX: u64 = 8192u64;
// cerr — bare stderr fragment writer for the driver's piecewise // Tool-local (NOT a lib wrapper): messages are built from many
// diagnostics. Tool-local (NOT a lib wrapper): messages are built from // fragments and we route through os.write to avoid libc stdio.
// many fragments and we route through os.write to avoid libc stdio.
// .len replaces the error-prone hand-counted byte literals these sites // .len replaces the error-prone hand-counted byte literals these sites
// carried. Lives here (the selfhost ww-driver build is a single main.ww; // 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). // 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); os.write(2, m.ptr, m.len: u64);
}; };
// ---- C-string helpers --------------------------------------------------
fn cstrlen(p: *u8) u64 = { fn cstrlen(p: *u8) u64 = {
let n: u64 = 0u64; let n: u64 = 0u64;
for (p[n] != 0u8) { n += 1u64; }; for (p[n] != 0u8) { n += 1u64; };
return n; return n;
}; };
// pathstr — view a NUL-terminated *u8 as a str. Bridges the // Bridges the driver's argv-style *u8 paths to lib/os entrypoints
// driver's argv-style *u8 paths to lib/os entrypoints (str // (str post-task-#23).
// post-task-#23).
fn pathstr(p: *u8) str = { fn pathstr(p: *u8) str = {
let r: str; let r: str;
r.ptr = p; r.ptr = p;
@@ -56,12 +52,10 @@ fn cstreq(a: *u8, b: *u8) bool = {
return strings.compare(pathstr(a), pathstr(b)) == 0; 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 = { fn cstreqlit(a: *u8, lit: str) bool = {
return strings.compare(pathstr(a), lit) == 0; return strings.compare(pathstr(a), lit) == 0;
}; };
// memcpy
fn bytecpy(dst: *u8, src: *u8, n: u64) void = { fn bytecpy(dst: *u8, src: *u8, n: u64) void = {
let i: u64 = 0u64; let i: u64 = 0u64;
for (i < n) { 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 = { fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = {
let i: u64 = 0u64; let i: u64 = 0u64;
for (src[i] != 0u8) { for (src[i] != 0u8) {
@@ -81,7 +73,6 @@ fn cstrinto(dst: *u8, off: u64, src: *u8) u64 = {
return off + i; 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 = { fn strinto(dst: *u8, off: u64, src: str) u64 = {
let n: i32 = src.len; let n: i32 = src.len;
let i: i32 = 0; let i: i32 = 0;
@@ -94,22 +85,15 @@ fn strinto(dst: *u8, off: u64, src: str) u64 = {
return off + nu; return off + nu;
}; };
// Write a single byte, return new offset.
fn byteinto(dst: *u8, off: u64, c: u8) u64 = { fn byteinto(dst: *u8, off: u64, c: u8) u64 = {
dst[off] = c; dst[off] = c;
return off + 1u64; 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 = { fn cstrseal(dst: *u8, off: u64) void = {
dst[off] = 0u8; 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 = { fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
let n: u64 = cstrlen(argv0); let n: u64 = cstrlen(argv0);
let cut: u64 = 0u64; let cut: u64 = 0u64;
@@ -128,7 +112,6 @@ fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = {
dst[cut] = 0u8; dst[cut] = 0u8;
}; };
// Build "$dir/$name" (NUL-terminated) into a fresh page-sized buffer.
fn joinpath(dir: *u8, name: *u8) *u8 = { fn joinpath(dir: *u8, name: *u8) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX; buf.len = os.PATH_MAX;
@@ -139,7 +122,6 @@ fn joinpath(dir: *u8, name: *u8) *u8 = {
return buf.ptr; return buf.ptr;
}; };
// Same, but the second component is a ww `str` literal.
fn joinpathlit(dir: *u8, name: str) *u8 = { fn joinpathlit(dir: *u8, name: str) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX; buf.len = os.PATH_MAX;
@@ -208,8 +190,6 @@ fn execpackagetests(selfdir: *u8, argv: **u8, argc: i32, start: i32,
return 1; return 1;
}; };
// ---- import resolution + visited-set -----------------------------------
//
// The separate-compilation producer scans each unit's top-of-file // The separate-compilation producer scans each unit's top-of-file
// `import IDENT;` lines and resolves them via the colon-separated `dirs` // `import IDENT;` lines and resolves them via the colon-separated `dirs`
// search path. A per-scan visited set (linear; typical builds visit a // 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; return 1;
}; };
// Byte-wise memcmp returning < 0, 0, > 0. Rule-10 byte-id requires // Rule-10 byte-id requires cstage and wwstage sort the same way;
// cstage and wwstage sort the same way; memcmp is the // memcmp is the locale-independent total order (mirrors
// locale-independent total order (mirrors ref/hare/sort/cmp/cmp.ha // ref/hare/sort/cmp/cmp.ha strs).
// strs).
fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = { fn bytecmp(a: *u8, alen: u64, b: *u8, blen: u64) i32 = {
let n: u64 = alen; let n: u64 = alen;
if (blen < n) { n = blen; }; if (blen < n) { n = blen; };
@@ -526,8 +505,6 @@ fn enumeratedir(dirpath: *u8) (**u8, i32) = {
return exact.ptr, n; return exact.ptr, n;
}; };
// ---- file slurp -------------------------------------------------------
fn slurp(pathcs: *u8) (*u8, u64) = { fn slurp(pathcs: *u8) (*u8, u64) = {
let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32); let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32);
if (fd < 0) { return nil, 0u64; }; 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. // caller passes a slice of the source: src points at the line start.
fn scanuse(src: *u8, len: u64) (*u8, u64) = { fn scanuse(src: *u8, len: u64) (*u8, u64) = {
let i: u64 = 0u64; let i: u64 = 0u64;
// skip leading whitespace
for (i < len) { for (i < len) {
if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; }; if (src[i] != 32u8) { if (src[i] != 9u8) { break; }; };
i += 1u64; i += 1u64;
@@ -595,11 +571,6 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = {
return src + idstart, idlen; 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 = { fn makestem(stem: *u8, src: *u8) void = {
let n: u64 = cstrlen(src); let n: u64 = cstrlen(src);
let stop: u64 = n; let stop: u64 = n;
@@ -617,7 +588,6 @@ fn makestem(stem: *u8, src: *u8) void = {
stem[stop] = 0u8; stem[stop] = 0u8;
}; };
// Append a literal suffix to `stem` (which already lives in a buffer).
fn appendlit(stem: *u8, suffix: str) *u8 = { fn appendlit(stem: *u8, suffix: str) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
buf.len = os.PATH_MAX; buf.len = os.PATH_MAX;
@@ -638,8 +608,6 @@ type lflags = struct {
nlibs: i32, nlibs: i32,
}; };
// ---- separate-compilation driver -------------------------------------
//
// Port of cmd/ww/main.c build_one_sep (task #46/c3). The build path // Port of cmd/ww/main.c build_one_sep (task #46/c3). The build path
// materializes each imported package's `.wwi` interface and compiles // materializes each imported package's `.wwi` interface and compiles
// every package on its own (`w6c -c`), then flat-links the `.o` set. // every package on its own (`w6c -c`), then flat-links the `.o` set.
@@ -677,7 +645,6 @@ type sepgraph = struct {
n: i32, 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 = { fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
let i: i32 = 0; let i: i32 = 0;
for (i < g.n) { for (i < g.n) {
@@ -1060,8 +1027,6 @@ fn seploadpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
return 0; return 0;
}; };
// Print one cycle-chain node: a package path, or "(root)" for the
// empty root path.
fn sepcyclenode(p: *u8) void = { fn sepcyclenode(p: *u8) void = {
if (p[0] == 0u8) { cerr("(root)"); } else { cerr(pathstr(p)); }; 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; return 0;
}; };
// Mark pi's transitive deps (excluding pi) in inset[].
fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = { fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
let k: i32 = 0; let k: i32 = 0;
for (k < g.pkg[pi].ndeps) { 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. // reverse-topo `w6l` of the root `.o` + dependency `.a` set + libwwrt.a.
// Side files land in a cold `<stem>.sepwork` scratch dir. Twin of cstage // Side files land in a cold `<stem>.sepwork` scratch dir. Twin of cstage
// build_one_sep. // build_one_sep.
// ---- -w workdir freshness ----------------------------------------------
// A `-w DIR` workdir is a caller-owned persistent package-artifact tree // A `-w DIR` workdir is a caller-owned persistent package-artifact tree
// that replaces the fresh `.sepwork` scratch. Staleness is pure content // that replaces the fresh `.sepwork` scratch. Staleness is pure content
// identity, never mtime: a package is reused only when its freshly // 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 a6: *u8 = joinpathlit(selfdir, "w6a_ww");
let l6: *u8 = joinpathlit(selfdir, "w6l_ww"); let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
// Default lib search path: <selfdir>/../../lib
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!; let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
dotdotlib.len = os.PATH_MAX; dotdotlib.len = os.PATH_MAX;
{ {
@@ -1487,7 +1450,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
cstrseal(dotdotlib.ptr, off); cstrseal(dotdotlib.ptr, off);
}; };
// Source directory.
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!; let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
srcd.len = os.PATH_MAX; srcd.len = os.PATH_MAX;
if (entryisdir != 0) { if (entryisdir != 0) {
@@ -1532,7 +1494,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
cstrseal(searchpath.ptr, off); cstrseal(searchpath.ptr, off);
}; };
// Stem for the scratch dir.
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!; let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
stem.len = os.PATH_MAX; stem.len = os.PATH_MAX;
if (entryisdir != 0) { if (entryisdir != 0) {
@@ -1606,7 +1567,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
}; };
}; };
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!; let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
libwwrt.len = os.PATH_MAX; libwwrt.len = os.PATH_MAX;
{ {
@@ -1615,7 +1575,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
cstrseal(libwwrt.ptr, off); cstrseal(libwwrt.ptr, off);
}; };
// Discover.
let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!; let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!;
pkgslot.len = SEP_MAXPKG; pkgslot.len = SEP_MAXPKG;
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!; 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; }; if (seploadpkg(g, root, searchpath.ptr) < 0) { return 1; };
// Reset colors, reverse-topo.
let ci: i32 = 0; let ci: i32 = 0;
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; }; for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
let order: []i32 = alloc([], g.n: u64)!; 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; let norder: i32 = 0;
if (septopovisit(g, root, order, &norder, stack, 0) < 0) { return 1; }; 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; let oi: i32 = 0;
for (oi < norder) { for (oi < norder) {
let pi: i32 = order[oi]; let pi: i32 = order[oi];
@@ -1961,22 +1918,10 @@ fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
return r; 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 <name>.ww that exists → use as-is
// 2. "." → <cwd>/<basename(cwd)>.ww → that, if it exists
// 3. <name>/<basename(name)>.ww → that, if it exists
// 4. walk search path (cwd:incs:<selfdir>/../../lib):
// <dir>/<name>.ww or <dir>/<name>/<name>.ww
fn cstrendswithlit(p: *u8, lit: str) bool = { fn cstrendswithlit(p: *u8, lit: str) bool = {
return strings.hassuffix(pathstr(p), lit); 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 = { fn basenameoff(p: *u8, plen: u64) u64 = {
let start: u64 = 0u64; let start: u64 = 0u64;
let i: u64 = 0u64; let i: u64 = 0u64;
@@ -1987,8 +1932,6 @@ fn basenameoff(p: *u8, plen: u64) u64 = {
return start; return start;
}; };
// arenadupcstr — copy `plen` bytes from `src` into a fresh NUL-sealed
// heap buffer.
fn arenadupcstr(src: *u8, plen: u64) *u8 = { fn arenadupcstr(src: *u8, plen: u64) *u8 = {
let buf: []u8 = alloc([], plen + 1u64)!; let buf: []u8 = alloc([], plen + 1u64)!;
let i: u64 = 0u64; let i: u64 = 0u64;
@@ -1997,8 +1940,7 @@ fn arenadupcstr(src: *u8, plen: u64) *u8 = {
return buf.ptr; return buf.ptr;
}; };
// buildsearchpath — compose the colon-separated lookup path used by // resolvemodule search-path order: "." : <incs> : <selfdir>/../../lib
// resolvemodule's case (4). Order: "." : <incs> : <selfdir>/../../lib
fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = { fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = {
let buf: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!; let buf: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
let off: u64 = 0u64; let off: u64 = 0u64;
@@ -2016,13 +1958,11 @@ fn buildsearchpath(selfdir: *u8, incs: *u8) *u8 = {
return buf.ptr; return buf.ptr;
}; };
// resolvemodule — map a name like "foo", "lib/foo", "foo.ww", or // Mirrors cmd/ww/main.c:resolvemodule. Sets *isdir when the entry is a
// "." to a concrete entry path. Sets *isdir when the entry is a
// module directory (caller will dir-enumerate). // module directory (caller will dir-enumerate).
fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = { fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
let nlen: u64 = cstrlen(name); let nlen: u64 = cstrlen(name);
// (1) Literal file that exists → use as-is.
if (cstrendswithlit(name, ".ww")) { if (cstrendswithlit(name, ".ww")) {
if (os.access(pathstr(name), 0i32) == 0) { if (os.access(pathstr(name), 0i32) == 0) {
*isdir = 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 fi: os.filestat;
let sr: (void | os.oserror) = os.stat(&fi, pathstr(name)); let sr: (void | os.oserror) = os.stat(&fi, pathstr(name));
let found: bool = false; let found: bool = false;
@@ -2048,13 +1987,10 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
return arenadupcstr(name, nlen); return arenadupcstr(name, nlen);
}; };
// (3) Search-path lookup with dot-to-slash path translation.
let search: *u8 = buildsearchpath(selfdir, incs); let search: *u8 = buildsearchpath(selfdir, incs);
return locateimport(search, name, nlen, isdir); return locateimport(search, name, nlen, isdir);
}; };
// ---- Subcommand handlers ----------------------------------------------
fn writeusage(fd: i32) void = { fn writeusage(fd: i32) void = {
let s: str = "usage: ww [-V] <subcommand> [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 <basename>.ww\n"; let s: str = "usage: ww [-V] <subcommand> [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 <basename>.ww\n";
os.write(fd, s.ptr, s.len: u64); os.write(fd, s.ptr, s.len: u64);
@@ -2065,8 +2001,6 @@ fn doversion() i32 = {
return 0; 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 = { fn defaultoutpath(src: *u8) *u8 = {
let n: u64 = cstrlen(src); let n: u64 = cstrlen(src);
let start: u64 = 0u64; let start: u64 = 0u64;
@@ -2084,7 +2018,6 @@ fn defaultoutpath(src: *u8) *u8 = {
off += 1u64; off += 1u64;
j += 1u64; j += 1u64;
}; };
// Strip ".ww" if present.
if (off >= 3u64) { if (off >= 3u64) {
if (out[off - 3u64] == 46u8) { if (out[off - 3u64] == 46u8) {
if (out[off - 2u64] == 119u8) { if (out[off - 2u64] == 119u8) {
@@ -2209,7 +2142,6 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
}; };
if (src == nil) { if (src == nil) {
// default to cwd module
let dot: [2]u8 = ['.': u8, 0u8]; let dot: [2]u8 = ['.': u8, 0u8];
src = &dot[0]; src = &dot[0];
}; };
@@ -2264,7 +2196,6 @@ fn makedrivertmp(buf: *u8, prefix: str) void = {
pk += 1; pk += 1;
}; };
let pid: i32 = os.getpid(); let pid: i32 = os.getpid();
// itoa for non-negative pid
let dig: [16]u8; let dig: [16]u8;
let n: i32 = 0; let n: i32 = 0;
if (pid <= 0) { if (pid <= 0) {
@@ -2383,7 +2314,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
src = p; src = p;
i += 1; i += 1;
} else { } 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; return 1;
}; };
// Execute with [tmp, argv[passstart..argc)). The standard process // The standard process facility inherits stdio and waits only for
// facility inherits stdio and waits only for this user program. // this user program.
let nextra: i32 = 0; let nextra: i32 = 0;
if (passstart >= 0) { nextra = argc - passstart; }; if (passstart >= 0) { nextra = argc - passstart; };
let execargv: []str = alloc([], (nextra + 1): u64)!; let execargv: []str = alloc([], (nextra + 1): u64)!;
@@ -2464,8 +2395,6 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
return rc; return rc;
}; };
// ---- ww test ----------------------------------------------------------
//
// Mirrors cmd/ww/main.c:dotest. Explicit regular files retain the bootstrap // Mirrors cmd/ww/main.c:dotest. Explicit regular files retain the bootstrap
// compatibility route; directory/default requests delegate to wwtest. // 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); replacement, targetindex < 0);
}; };
// ---- Entry -------------------------------------------------------------
export fn main(argc: i32, argv: **u8) i32 = { export fn main(argc: i32, argv: **u8) i32 = {
if (argc < 1) { if (argc < 1) {
writeusage(2); writeusage(2);
return 2; return 2;
}; };
// selfdir = dirname(argv[0])
let selfdir: []u8 = alloc([], (os.PATH_MAX: u64))!; let selfdir: []u8 = alloc([], (os.PATH_MAX: u64))!;
selfdir.len = os.PATH_MAX; selfdir.len = os.PATH_MAX;
selfdirinto(selfdir.ptr, (os.PATH_MAX: u64), argv[0]); selfdirinto(selfdir.ptr, (os.PATH_MAX: u64), argv[0]);

View File

@@ -1,13 +1,6 @@
// selfhost/cmd/wwdump/main.ww — ww-side port of cmd/wwdump/main.c. // 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
// 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
// divergence is a port bug in lex.ww or tok.ww. // 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; package main;
@@ -17,10 +10,8 @@ import check;
import cgen; import cgen;
import strconv; import strconv;
// ---- argv helpers ----------------------------------------------------- // argv strings are always NUL-terminated (kernel-supplied), so the
// unbounded scan is safe.
// argstrlen — strlen on a NUL-terminated *u8. argv strings are always
// NUL-terminated (kernel-supplied) so this is safe.
fn argstrlen(s: *u8) i32 = { fn argstrlen(s: *u8) i32 = {
let n: i32 = 0; let n: i32 = 0;
for (s[n] != 0u8) { n += 1; }; for (s[n] != 0u8) { n += 1; };
@@ -34,7 +25,6 @@ fn argstr(p: *u8) str = {
return s; return s;
}; };
// streqlit — compare a NUL-terminated argv entry to a string literal.
fn streqlit(p: *u8, lit: str) bool = { fn streqlit(p: *u8, lit: str) bool = {
let i: i32 = 0; let i: i32 = 0;
for (i < lit.len) { for (i < lit.len) {
@@ -44,8 +34,6 @@ fn streqlit(p: *u8, lit: str) bool = {
return p[i] == 0u8; return p[i] == 0u8;
}; };
// ---- main -------------------------------------------------------------
export fn main(argc: i32, argv: **u8) i32 = { export fn main(argc: i32, argv: **u8) i32 = {
let mode: i32 = 116; // 't' let mode: i32 = 116; // 't'
let path: *u8 = nil; 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. // Quiet by default; flip to 1 when debugging missing names.
ck.verbose = 0; ck.verbose = 0;
checkfile(&ck, f); checkfile(&ck, f);
// (close out the if-else chain — we'll close all braces below)
// "<file>: <resolved>/<resolved+unresolved> resolved" // "<file>: <resolved>/<resolved+unresolved> resolved"
os.write(1, argstr(path).ptr, argstrlen(path): u64); os.write(1, argstr(path).ptr, argstrlen(path): u64);
os.write(1, ": ".ptr, 2u64); os.write(1, ": ".ptr, 2u64);