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:
13
cmd/w6a/a.h
13
cmd/w6a/a.h
@@ -1,10 +1,7 @@
|
||||
/*
|
||||
* a.h — w6a-private header. Modelled on Plan 9 cmd/6a/a.h, trimmed
|
||||
* to the instruction subset that w6c emits.
|
||||
*
|
||||
* w6a is line-oriented and has no preprocessor: each non-blank, non-
|
||||
* label line is one instruction. We read the whole file into a list
|
||||
* of `Aprog`s, then encode and emit ELF64.
|
||||
* Modelled on Plan 9 cmd/6a/a.h, trimmed to the instruction subset
|
||||
* that w6c emits. Line-oriented, no preprocessor: each non-blank,
|
||||
* non-label line is one instruction.
|
||||
*/
|
||||
#ifndef SIX_A_H
|
||||
#define SIX_A_H
|
||||
@@ -69,17 +66,14 @@ struct Areloc {
|
||||
};
|
||||
|
||||
struct Asm {
|
||||
/* parser state */
|
||||
const char *file;
|
||||
const char *src;
|
||||
u64 srclen;
|
||||
u64 pos;
|
||||
int line;
|
||||
|
||||
/* program list */
|
||||
Aprog *head, *tail;
|
||||
|
||||
/* output text section */
|
||||
u8 *text;
|
||||
u64 textcap, textlen;
|
||||
|
||||
@@ -89,7 +83,6 @@ struct Asm {
|
||||
u8 *data;
|
||||
u64 datacap, datalen;
|
||||
|
||||
/* symbols */
|
||||
Asym *syms;
|
||||
Areloc *relocs;
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
/*
|
||||
* asm.c — encode the parsed Aprog list into amd64 machine bytes,
|
||||
* appending to Asm.text. Relocations for CALL/branch targets that
|
||||
* resolve to externals are queued in Asm.relocs.
|
||||
*
|
||||
* Encoding subset: the instructions cgen emits today. Operand shapes
|
||||
* we accept:
|
||||
* MOVQ $imm, reg — C7 /0 imm32 (REX.W) [imm fits in i32]
|
||||
@@ -95,8 +91,6 @@ a_addreloc_data(Asm *a, u64 off, int kind, Asym *s, i64 add)
|
||||
a->relocs = r;
|
||||
}
|
||||
|
||||
/* ------ register codes ------------------------------------------- */
|
||||
|
||||
/* low 3 bits of register encoding */
|
||||
static int
|
||||
rcode(int r)
|
||||
@@ -137,14 +131,12 @@ is_xmm(int r)
|
||||
return r >= D_X0 && r <= D_X15;
|
||||
}
|
||||
|
||||
/* ModR/M byte */
|
||||
static u8
|
||||
modrm(int mod, int reg, int rm)
|
||||
{
|
||||
return (u8)(((mod & 3) << 6) | ((reg & 7) << 3) | (rm & 7));
|
||||
}
|
||||
|
||||
/* emit REX with W=1 plus optional R/B for high regs */
|
||||
static void
|
||||
emit_rex(Asm *a, int regbit, int rmbit, int w)
|
||||
{
|
||||
@@ -155,9 +147,7 @@ emit_rex(Asm *a, int regbit, int rmbit, int w)
|
||||
if (b != 0x40 || w) a_emit_byte(a, b);
|
||||
}
|
||||
|
||||
/* encode mod/disp for [base+disp]; returns 0 on ok.
|
||||
* Special-cases SP (needs SIB) and BP (forces disp).
|
||||
*/
|
||||
/* Special-cases SP (needs SIB) and BP (forces disp). */
|
||||
static void
|
||||
emit_modrm_mem(Asm *a, int reg_field, int base, i64 disp)
|
||||
{
|
||||
@@ -191,7 +181,6 @@ encode_rr(Asm *a, u8 opcode, int src, int dst)
|
||||
a_emit_byte(a, modrm(3, rcode(src), rcode(dst)));
|
||||
}
|
||||
|
||||
/* MOVQ src reg → mem(base, disp). opcode = 0x89 */
|
||||
static void
|
||||
encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp)
|
||||
{
|
||||
@@ -200,7 +189,6 @@ encode_rm(Asm *a, u8 opcode, int src_reg, int base, i64 disp)
|
||||
emit_modrm_mem(a, rcode(src_reg), base, disp);
|
||||
}
|
||||
|
||||
/* MOVQ mem(base, disp) → reg. opcode = 0x8B */
|
||||
static void
|
||||
encode_mr(Asm *a, u8 opcode, int dst_reg, int base, i64 disp)
|
||||
{
|
||||
@@ -250,7 +238,7 @@ sse_mr_load(Asm *a, u8 prefix, u8 op2, int reg_op, int base, i64 disp)
|
||||
emit_modrm_mem(a, rcode(reg_op), base, disp);
|
||||
}
|
||||
|
||||
/* like sse_mr_load but encoded with REX.W (used by CVTTSD2SI / CVTSI2SD
|
||||
/* like sse_rr but encoded with REX.W (used by CVTTSD2SI / CVTSI2SD
|
||||
* which target/source 64-bit integer regs) */
|
||||
static void
|
||||
sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op)
|
||||
@@ -262,8 +250,6 @@ sse_rr_w(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op)
|
||||
a_emit_byte(a, modrm(3, rcode(reg_op), rcode(rm_op)));
|
||||
}
|
||||
|
||||
/* ------ second-pass helper: resolve labels to addresses ---------- */
|
||||
|
||||
static u64
|
||||
resolve_label(Asm *a, const char *name)
|
||||
{
|
||||
@@ -281,8 +267,6 @@ label_defined(Asm *a, const char *name)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------ first pass: encode ---------------------------------------- */
|
||||
|
||||
/* For local labels, we record a "fixup" — an offset in .text that
|
||||
* needs to be patched once the label is resolved at end of pass. */
|
||||
typedef struct Fixup Fixup;
|
||||
@@ -307,10 +291,9 @@ int
|
||||
a_encode(Asm *a)
|
||||
{
|
||||
fixups = NULL;
|
||||
const char *cur_text = NULL; /* current TEXT name */
|
||||
const char *cur_text = NULL;
|
||||
(void)cur_text;
|
||||
for (Aprog *p = a->head; p; p = p->link) {
|
||||
/* Define any pending label at the current PC */
|
||||
if (p->label) {
|
||||
Asym *s = a_intern(a, p->label);
|
||||
s->defined = 1;
|
||||
@@ -800,7 +783,6 @@ a_encode(Asm *a)
|
||||
/* R_X86_64_PLT32 (4); addend -4 */
|
||||
a_addreloc(a, reloff, 4, s, -4);
|
||||
} else if (p->to.type == D_BRANCH) {
|
||||
/* local call to a label */
|
||||
a_emit_byte(a, 0xE8);
|
||||
add_fixup(a->textlen, p->to.sym);
|
||||
a_emit_u32(a, 0);
|
||||
@@ -846,7 +828,6 @@ a_encode(Asm *a)
|
||||
}
|
||||
}
|
||||
|
||||
/* second pass: patch fixups */
|
||||
for (Fixup *f = fixups; f; f = f->next) {
|
||||
if (!label_defined(a, f->label)) {
|
||||
fprintf(stderr, "w6a: undefined label '%s'\n", f->label);
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/*
|
||||
* lex.c — character-level helpers for w6a's line-oriented parser.
|
||||
* The parser itself lives in parse.c; here we keep the tokenisers
|
||||
* for identifiers and numbers so parse.c stays focused on syntax.
|
||||
*/
|
||||
#include "a.h"
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/*
|
||||
* w6a — amd64 assembler driver. Read .s, parse, encode, emit ELF .o.
|
||||
*/
|
||||
#include "a.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/*
|
||||
* obj.c — emit a tiny ELF64 relocatable object.
|
||||
*
|
||||
* Layout (in file order):
|
||||
* [0] ELF header
|
||||
* [1] Section .text (program bytes)
|
||||
@@ -24,7 +22,6 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ELF constants */
|
||||
#define ELFMAG "\x7f""ELF"
|
||||
#define ELFCLASS64 2
|
||||
#define ELFDATA2LSB 1
|
||||
@@ -54,7 +51,6 @@
|
||||
#define R_X86_64_PLT32 4
|
||||
#define ELF64_R_INFO(s,t) (((u64)(s) << 32) | ((u64)(t) & 0xffffffff))
|
||||
|
||||
/* growable byte buffer */
|
||||
typedef struct Buf Buf;
|
||||
struct Buf { u8 *p; size_t n, cap; };
|
||||
|
||||
@@ -157,10 +153,9 @@ a_emit_elf(Asm *a, FILE *f)
|
||||
bput(&sym, &z, sizeof z);
|
||||
}
|
||||
|
||||
/* Build symbols (defined = global; undefined = global UND). Data
|
||||
* symbols carry STT_OBJECT and st_shndx=SH_DATA; everything else
|
||||
* keeps the legacy STT_FUNC/SH_TEXT shape so non-DATAW outputs
|
||||
* stay byte-identical. */
|
||||
/* Data symbols carry STT_OBJECT and st_shndx=SH_DATA; everything
|
||||
* else keeps the legacy STT_FUNC/SH_TEXT shape so non-DATAW
|
||||
* outputs stay byte-identical. */
|
||||
int idx = 1;
|
||||
for (Asym *s = a->syms; s; s = s->next) {
|
||||
Sym64 e = {0};
|
||||
@@ -193,7 +188,6 @@ a_emit_elf(Asm *a, FILE *f)
|
||||
bput(r->section == 1 ? &relad : &rela, &re, sizeof re);
|
||||
}
|
||||
|
||||
/* Layout offsets in the file */
|
||||
u64 off = sizeof(Ehdr);
|
||||
u64 off_text = off; off += a->textlen;
|
||||
u64 off_rela = off; off += rela.n;
|
||||
@@ -202,7 +196,6 @@ a_emit_elf(Asm *a, FILE *f)
|
||||
u64 off_sym = off; off += sym.n;
|
||||
u64 off_str = off; off += str.n;
|
||||
u64 off_shstr= off; off += shstr.n;
|
||||
/* align to 8 */
|
||||
while (off % 8) off++;
|
||||
u64 off_shdr = off;
|
||||
const int NSECT = has_data
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/*
|
||||
* parse.c — line-oriented parser for the asm subset emitted by w6c.
|
||||
*
|
||||
* Grammar:
|
||||
* line := blank | comment | label | text | instr
|
||||
* blank := /^\s*$/
|
||||
@@ -52,7 +50,6 @@ a_intern(Asm *a, const char *name)
|
||||
return s;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* line iterator: returns the next line as a NUL-terminated buffer in
|
||||
* line/llen pointers, advances pos. Returns 0 on EOF.
|
||||
*/
|
||||
@@ -70,7 +67,6 @@ nextline(Asm *a, char **line, size_t *llen, char *buf, size_t bufsz)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* skip leading whitespace */
|
||||
static const char *
|
||||
skipws(const char *p)
|
||||
{
|
||||
@@ -168,7 +164,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* (REG) form */
|
||||
if (*s == '(') {
|
||||
s++;
|
||||
char rbuf[8] = {0};
|
||||
@@ -183,7 +178,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* number(REG) form, or label form, or REG */
|
||||
const char *p = s;
|
||||
int sign = 1;
|
||||
if (*p == '-') { sign = -1; p++; }
|
||||
@@ -208,7 +202,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* IDENT — register or symbol-or-label */
|
||||
if (a_isidstart((unsigned char)*s)) {
|
||||
char buf[256] = {0};
|
||||
int n = 0;
|
||||
@@ -226,7 +219,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
||||
s = end;
|
||||
}
|
||||
|
||||
/* ID(SB) means external symbol */
|
||||
if (*s == '(') {
|
||||
char rbuf[8] = {0};
|
||||
int rn = 0;
|
||||
@@ -253,7 +245,6 @@ parse_operand(Asm *a, const char *s, Aoperand *out)
|
||||
out->type = r;
|
||||
return 0;
|
||||
}
|
||||
/* otherwise it's a branch target */
|
||||
out->type = D_BRANCH;
|
||||
out->sym = strdup(buf);
|
||||
return 0;
|
||||
@@ -281,7 +272,6 @@ a_parse(Asm *a)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* label? */
|
||||
if (a_isidstart((unsigned char)*p) && line[0] != '\t') {
|
||||
const char *q = p;
|
||||
while (a_isidcont((unsigned char)*q)) q++;
|
||||
@@ -308,7 +298,6 @@ a_parse(Asm *a)
|
||||
}
|
||||
}
|
||||
|
||||
/* TEXT or instruction */
|
||||
const char *m = p;
|
||||
char mnem[16] = {0};
|
||||
int n = 0;
|
||||
@@ -332,7 +321,6 @@ a_parse(Asm *a)
|
||||
const char *rest = m;
|
||||
|
||||
if (op == A_TEXT) {
|
||||
/* TEXT name,$framesize */
|
||||
char nbuf[256] = {0};
|
||||
int nn = 0;
|
||||
while (*m && *m != ',' && nn < 255) nbuf[nn++] = *m++;
|
||||
@@ -363,7 +351,6 @@ a_parse(Asm *a)
|
||||
prg->nbytes = 0;
|
||||
} else {
|
||||
m++;
|
||||
/* parse escapes into a fresh buffer */
|
||||
size_t cap = 32, len = 0;
|
||||
u8 *buf = malloc(cap);
|
||||
while (*m && *m != '"') {
|
||||
@@ -398,7 +385,6 @@ a_parse(Asm *a)
|
||||
prg->nbytes = len;
|
||||
}
|
||||
} else {
|
||||
/* split rest at top-level comma */
|
||||
const char *comma = NULL;
|
||||
for (const char *q = rest; *q; q++)
|
||||
if (*q == ',' && comma == NULL) comma = q;
|
||||
|
||||
@@ -114,7 +114,7 @@ enum {
|
||||
A_LAST
|
||||
};
|
||||
|
||||
const char *anames(int); /* opcode -> mnemonic */
|
||||
const char *rnames(int); /* register -> name */
|
||||
const char *anames(int);
|
||||
const char *rnames(int);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1366,9 +1366,9 @@ let_emit_size(Type *t)
|
||||
}
|
||||
}
|
||||
|
||||
/* Is the unwrapped type a str? Used by the load/store paths so the
|
||||
* (AX, BX) pair convention is preserved for str globals, mirroring
|
||||
* what we already do for str locals. */
|
||||
/* Used by the load/store paths so the (AX, BX) pair convention is
|
||||
* preserved for str globals, mirroring what we already do for str
|
||||
* locals. */
|
||||
static int
|
||||
let_isstr(Type *t)
|
||||
{
|
||||
@@ -1377,8 +1377,8 @@ let_isstr(Type *t)
|
||||
return u && u->kind == TY_STR;
|
||||
}
|
||||
|
||||
/* Is the unwrapped type a slice? Slice globals flow as the (AX, BX,
|
||||
* CX) triple — same as the local ABI. */
|
||||
/* Slice globals flow as the (AX, BX, CX) triple — same as the local
|
||||
* ABI. */
|
||||
static int
|
||||
let_isslice(Type *t)
|
||||
{
|
||||
@@ -1387,9 +1387,9 @@ let_isslice(Type *t)
|
||||
return u && u->kind == TY_SLICE;
|
||||
}
|
||||
|
||||
/* Is the unwrapped type a struct? Struct globals only support field
|
||||
* access (read + plain `=` write for scalar fields). Whole-struct
|
||||
* by-value flow through expressions isn't wired. */
|
||||
/* Struct globals only support field access (read + plain `=` write
|
||||
* for scalar fields). Whole-struct by-value flow through expressions
|
||||
* isn't wired. */
|
||||
static int
|
||||
let_isstruct(Type *t)
|
||||
{
|
||||
@@ -1398,9 +1398,8 @@ let_isstruct(Type *t)
|
||||
return u && u->kind == TY_STRUCT;
|
||||
}
|
||||
|
||||
/* Is the unwrapped type a fixed-length array? Array globals are
|
||||
* zero-init DATAW slots; cgindex addresses them as LEAQ name(SB)
|
||||
* and lets the element load/store run as usual. */
|
||||
/* Array globals are zero-init DATAW slots; cgindex addresses them as
|
||||
* LEAQ name(SB) and lets the element load/store run as usual. */
|
||||
static int
|
||||
let_isarray(Type *t)
|
||||
{
|
||||
@@ -1409,9 +1408,9 @@ let_isarray(Type *t)
|
||||
return u && u->kind == TY_ARRAY;
|
||||
}
|
||||
|
||||
/* Is the unwrapped type a float (f32 or f64)? Float globals flow
|
||||
* through X0 — load/store goes LEAQ name(SB),CX → MOVSS/MOVSD via the
|
||||
* indirect, since the asm has no D_EXTERN form for SSE moves yet. */
|
||||
/* Float globals flow through X0 — load/store goes LEAQ name(SB),CX →
|
||||
* MOVSS/MOVSD via the indirect, since the asm has no D_EXTERN form
|
||||
* for SSE moves yet. */
|
||||
static int
|
||||
let_isfloat(Type *t)
|
||||
{
|
||||
@@ -1722,7 +1721,6 @@ let_var_type(const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Glue `<module>.<ident>` into a fresh arena buffer. */
|
||||
static const char *
|
||||
mod_join(Cg *c, const char *mod, const char *ident)
|
||||
{
|
||||
@@ -1944,9 +1942,6 @@ cgslicehdr(Cg *c, int base)
|
||||
else if (base == D_AX) ins2(c, A_MOVQ, amem(base, 0), areg(D_AX));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* per-fn local table: name → stack offset (positive = below FP) */
|
||||
|
||||
typedef struct Local Local;
|
||||
struct Local {
|
||||
const char *name;
|
||||
@@ -2074,8 +2069,7 @@ cg_base_cap(Cg *c, Node *base, Type *bu, Local *locals, int dst)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* expressions: result lands in AX. Returns 1 on success. */
|
||||
/* expressions: result lands in AX. */
|
||||
|
||||
static void cgexpr(Cg*, Node*, Local*);
|
||||
static void cgstmt(Cg*, Node*, Local**, int*);
|
||||
@@ -4678,7 +4672,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
}
|
||||
if (amped) break;
|
||||
/* Fall through to silent-drop fallback below. */
|
||||
}
|
||||
if (opnd && opnd->kind == N_INDEX) {
|
||||
/* &base[i] = base + i*esz, no dereference.
|
||||
@@ -5436,7 +5429,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
after_dot_assign:
|
||||
if (u && u->kind == TY_STRUCT) {
|
||||
/* find field metadata */
|
||||
Tfield *f = NULL;
|
||||
for (Tfield *fl = u->fields; fl; fl = fl->next)
|
||||
if (strcmp(fl->name, n->lhs->str) == 0)
|
||||
@@ -5725,7 +5717,6 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* now store AX into target */
|
||||
if (via_ptr) {
|
||||
if (boff == 0 && let_islet(base->str)) {
|
||||
/* #47 (inverse): a GLOBAL *struct
|
||||
@@ -15147,7 +15138,6 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
baseoff = localoff(c, locals, bname, 8, frame);
|
||||
}
|
||||
|
||||
/* allocate per-name slots */
|
||||
struct { int off, sz, foff; Type *ftype; } binds[8] = {0};
|
||||
int nbinds = 0;
|
||||
if (destruct) {
|
||||
@@ -15733,7 +15723,6 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
{
|
||||
if (fn->body == NULL) return; /* extern decl, no body */
|
||||
|
||||
/* fresh per-fn state */
|
||||
c->head = c->tail = NULL;
|
||||
c->fnname = fn->str;
|
||||
c->cur_mod = (fn->module && fn->module[0]) ? fn->module : NULL;
|
||||
@@ -15788,7 +15777,6 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
text->from.offset = 0; /* framesize patched below */
|
||||
emit(c, text);
|
||||
|
||||
/* prologue */
|
||||
ins1(c, A_PUSHQ, areg(D_BP));
|
||||
ins2(c, A_MOVQ, areg(D_SP), areg(D_BP));
|
||||
Prog *subsp = newprog(c, A_SUBQ);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
/*
|
||||
* gc.h — w6c-private header: Prog/Adr structs, scratch register set,
|
||||
* stack-frame state. Plan 9 cmd/6c/gc.h shape, trimmed.
|
||||
*/
|
||||
/* Plan 9 cmd/6c/gc.h shape, trimmed. */
|
||||
#ifndef SIX_GC_H
|
||||
#define SIX_GC_H
|
||||
|
||||
@@ -73,9 +70,7 @@ void emit(Cg*, Prog*);
|
||||
/* txt.c */
|
||||
void txt_emit(FILE*, Prog *head);
|
||||
|
||||
/* wwi.c — `.wwi` export-data producer (w6c -I). M2 dead-code: writes a
|
||||
* re-parseable ww-prototype rendering of the package's exported surface.
|
||||
* Returns non-zero if check_exported_type rejects a dangling export. */
|
||||
/* wwi.c — non-zero return: check_exported_type rejected a dangling export. */
|
||||
int wwi_emit(Checker *c, FILE *of, Node *file);
|
||||
|
||||
/* swt.c, peep.c, reg.c — placeholders for now */
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* w6c — amd64 compiler driver. Reads a .ww source file, runs the
|
||||
* libwcc frontend (lex → parse → check), then walks the typed AST
|
||||
* via cgen.c and writes Plan 9-flavoured amd64 asm to stdout (or
|
||||
* the file given by -o).
|
||||
*/
|
||||
#include "gc.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* peep.c — peephole pass. Currently a no-op; reserved for the kind of
|
||||
* cleanup Plan 9 6c does (folding adjacent moves, removing redundant
|
||||
* compares). Wire in `peephole(c)` from cgen.c after the main walk.
|
||||
* Currently a no-op; reserved for the kind of cleanup Plan 9 6c does
|
||||
* (folding adjacent moves, removing redundant compares). Wire in
|
||||
* `peephole(c)` from cgen.c after the main walk.
|
||||
*/
|
||||
#include "gc.h"
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* reg.c — register allocator. The current cgen pins everything to AX
|
||||
* with BX as a scratch top-of-stack — no real allocation. This file
|
||||
* is the seam where a linear-scan or graph-colouring pass would land
|
||||
* later; today it's empty.
|
||||
* The current cgen pins everything to AX with BX as a scratch
|
||||
* top-of-stack — no real allocation. This file is the seam where a
|
||||
* linear-scan or graph-colouring pass would land later; today it's
|
||||
* empty.
|
||||
*/
|
||||
#include "gc.h"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* swt.c — switch-statement lowering. Stub for now: cgen falls
|
||||
* through to a no-op for N_SWITCH. When we add a real lowering, it
|
||||
* will live here, mirroring Plan 9 6c's pswt.c.
|
||||
* Stub for now: cgen falls through to a no-op for N_SWITCH. When we
|
||||
* add a real lowering, it will live here, mirroring Plan 9 6c's
|
||||
* pswt.c.
|
||||
*/
|
||||
#include "gc.h"
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/*
|
||||
* txt.c — print a Prog list as Plan 9-flavoured amd64 asm text.
|
||||
*
|
||||
* Format we emit (and that w6a expects):
|
||||
* TEXT name<framesize>
|
||||
* MOVQ $1, AX
|
||||
|
||||
@@ -35,11 +35,11 @@ wwi_primary(Node *n)
|
||||
return n && n->imported == 0;
|
||||
}
|
||||
|
||||
/* --- check_exported_type (drew) ------------------------------------- *
|
||||
* Resolve an N_TNAME to its type sym WITHOUT the side effects of
|
||||
* resolve_typename (no on-demand resolve, no double "unknown type"
|
||||
* error). A primitive/keyword resolves to no SK_TYPE → leaf. By the
|
||||
* time the producer runs, check_file has finished and c->cur == c->top.
|
||||
/* check_exported_type (drew): resolve an N_TNAME to its type sym WITHOUT
|
||||
* the side effects of resolve_typename (no on-demand resolve, no double
|
||||
* "unknown type" error). A primitive/keyword resolves to no SK_TYPE →
|
||||
* leaf. By the time the producer runs, check_file has finished and
|
||||
* c->cur == c->top.
|
||||
*/
|
||||
static Sym *
|
||||
wwi_typesym(Checker *c, const char *nm)
|
||||
@@ -141,7 +141,7 @@ wwi_check_decl(Checker *c, Node *d)
|
||||
return bad;
|
||||
}
|
||||
|
||||
/* --- type-expr + const-expr unparser (rob §2.2/§2.4) ---------------- */
|
||||
/* type-expr unparse per rob §2.2; const-expr per rob §2.4. */
|
||||
|
||||
static void wwi_expr(FILE *of, Node *e);
|
||||
static void wwi_type(FILE *of, Node *t);
|
||||
@@ -447,7 +447,7 @@ wwi_decl(FILE *of, Node *d)
|
||||
}
|
||||
}
|
||||
|
||||
/* --- deterministic ordering (rob §3) -------------------------------- */
|
||||
/* deterministic ordering per rob §3. */
|
||||
|
||||
struct declent { Node *d; int idx; };
|
||||
struct useent { const char *path; int idx; };
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
/*
|
||||
* dyn.c — load a shared object (ET_DYN) so the linker knows which
|
||||
* symbols it exports and which DT_NEEDED entry to record. We do not
|
||||
* pull bytes from the .so; the dynamic loader maps it at runtime.
|
||||
*
|
||||
* Each call appends one Lso to lnk->sos. `l_so_provides` answers
|
||||
* "does this .so export the named symbol?" — l_resolve uses that to
|
||||
* promote unresolved references to dynamic.
|
||||
* We do not pull bytes from the .so; the dynamic loader maps it at
|
||||
* runtime.
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
/*
|
||||
* dynout.c — emit a dynamic-linked ELF executable.
|
||||
*
|
||||
* The shape we produce is the simplest valid one: PT_INTERP +
|
||||
* PT_DYNAMIC + DT_BIND_NOW so the loader resolves every PLT slot at
|
||||
* startup (no lazy binding, no PLT0 trampoline). Symbol versioning
|
||||
* is omitted; modern glibc tolerates unversioned references by
|
||||
* binding to each symbol's "default" version. SysV .hash, not
|
||||
* startup (no lazy binding, no PLT0 trampoline). SysV .hash, not
|
||||
* .gnu.hash. Non-PIE, fixed base.
|
||||
*
|
||||
* File layout:
|
||||
@@ -31,7 +27,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ELF constants */
|
||||
#define ET_EXEC 2
|
||||
#define EM_X86_64 62
|
||||
#define EV_CURRENT 1
|
||||
@@ -127,7 +122,6 @@ elf_hash(const char *name)
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Patch a 4-byte little-endian field in `buf` at offset `off`. */
|
||||
static void
|
||||
poke32(u8 *buf, u64 off, u32 v)
|
||||
{
|
||||
@@ -151,8 +145,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
|
||||
const int N = l->dyn_n;
|
||||
|
||||
/* ---- Pass 1: collect dynamic symbol names + .dynstr layout ---- */
|
||||
|
||||
/* dynstr layout: [0]='\0', then DT_NEEDED soname strings, then
|
||||
* one symbol name per dynamic Lsym. We index dyn syms by
|
||||
* plt_idx (assigned in l_resolve). Build an array sorted by
|
||||
@@ -189,7 +181,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
}
|
||||
}
|
||||
|
||||
/* Build .dynstr in a growable buffer. */
|
||||
u8 *dynstr = NULL;
|
||||
u64 dynstr_cap = 0, dynstr_len = 0;
|
||||
#define DSTR_PUT(s) do { \
|
||||
@@ -216,9 +207,7 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
DSTR_PUT(dynsyms[i]->name);
|
||||
}
|
||||
|
||||
/* ---- Versioning: group dyn syms by (lib, version) ----
|
||||
*
|
||||
* For every sym whose dyn_version is non-NULL, there's a
|
||||
/* For every sym whose dyn_version is non-NULL, there's a
|
||||
* Vernaux record under that lib's Verneed. The vna_other
|
||||
* value (assigned starting at 2; 1 is reserved for "global,
|
||||
* unversioned") becomes that sym's .gnu.version entry.
|
||||
@@ -263,13 +252,11 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
n_vlibs++;
|
||||
}
|
||||
|
||||
/* Assign vna_other indices starting at 2. */
|
||||
u16 next_vna = 2;
|
||||
for (int i = 0; i < n_vlibs; i++)
|
||||
for (int k = 0; k < vlibs[i].n_versions; k++)
|
||||
vlibs[i].versions[k].vna_other = next_vna++;
|
||||
|
||||
/* Add version name strings to .dynstr. */
|
||||
for (int i = 0; i < n_vlibs; i++) {
|
||||
for (int k = 0; k < vlibs[i].n_versions; k++) {
|
||||
vlibs[i].versions[k].dynstr_off = (u32)dynstr_len;
|
||||
@@ -301,8 +288,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Pass 2: compute byte sizes of every section ---- */
|
||||
|
||||
const u64 ehdr_sz = sizeof(Ehdr);
|
||||
const int n_phdrs = 4;
|
||||
const u64 phdr_sz = (u64)n_phdrs * sizeof(Phdr);
|
||||
@@ -343,8 +328,7 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
const u64 ndyn = (u64)nsos + 11 + (with_ver ? 3 : 0);
|
||||
const u64 dynamic_sz = ndyn * sizeof(Dyn64);
|
||||
|
||||
/* ---- Pass 3: assign file offsets and virtual addresses ----
|
||||
* Everything from the Ehdr through .text+.plt is in the R+X
|
||||
/* Everything from the Ehdr through .text+.plt is in the R+X
|
||||
* load segment at base+0..text_end. .got.plt and .dynamic land
|
||||
* in the R+W segment at the next page boundary. */
|
||||
|
||||
@@ -409,9 +393,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
const u64 data_file_len = l->datalen - bsslen;
|
||||
const u64 file_data_end = data_off + data_file_len;
|
||||
|
||||
/* ---- Pass 4: build each section into a buffer ---- */
|
||||
|
||||
/* .dynsym */
|
||||
Sym64 *dynsym = calloc((size_t)nsyms_total, sizeof *dynsym);
|
||||
for (int i = 0; i < N; i++) {
|
||||
Sym64 *e = &dynsym[1 + i];
|
||||
@@ -438,7 +419,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
}
|
||||
/* elf_hash is also used by .gnu.version_r for vna_hash below. */
|
||||
|
||||
/* .rela.plt */
|
||||
Rela64 *relaplt = calloc((size_t)N, sizeof *relaplt);
|
||||
for (int i = 0; i < N; i++) {
|
||||
relaplt[i].r_offset = gotplt_va + (3 + (u64)i) * 8;
|
||||
@@ -522,7 +502,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
}
|
||||
/* [3..3+N-1] left zero; loader fills via R_X86_64_JUMP_SLOT. */
|
||||
|
||||
/* .dynamic */
|
||||
Dyn64 *dynamic = calloc((size_t)ndyn, sizeof *dynamic);
|
||||
{
|
||||
int k = 0;
|
||||
@@ -559,9 +538,9 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Pass 5: patch .text relocations targeting dynamic syms ---
|
||||
* The site is the existing PC32/PLT32 displacement field. Target
|
||||
* is the address of the symbol's PLT stub. */
|
||||
/* Patch .text relocations targeting dynamic syms: the site is
|
||||
* the existing PC32/PLT32 displacement field, the target the
|
||||
* address of the symbol's PLT stub. */
|
||||
for (Lrel *r = l->rels; r; r = r->next) {
|
||||
if (r->sym == NULL || !r->sym->is_dyn) continue;
|
||||
if (r->kind != R_X86_64_PC32 && r->kind != R_X86_64_PLT32) {
|
||||
@@ -579,8 +558,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
poke32(l->text, r->off, (u32)(i32)disp);
|
||||
}
|
||||
|
||||
/* ---- Pass 6: emit ---- */
|
||||
|
||||
Ehdr eh = {0};
|
||||
memcpy(eh.e_ident, "\x7f""ELF", 4);
|
||||
eh.e_ident[4] = ELFCLASS64;
|
||||
@@ -625,7 +602,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
ph[1].p_memsz = file_end - gotplt_off;
|
||||
ph[1].p_align = page;
|
||||
|
||||
/* PT_INTERP. */
|
||||
ph[2].p_type = PT_INTERP;
|
||||
ph[2].p_flags = PF_R;
|
||||
ph[2].p_offset = interp_off;
|
||||
@@ -635,7 +611,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
ph[2].p_memsz = interp_sz;
|
||||
ph[2].p_align = 1;
|
||||
|
||||
/* PT_DYNAMIC. */
|
||||
ph[3].p_type = PT_DYNAMIC;
|
||||
ph[3].p_flags = PF_R | PF_W;
|
||||
ph[3].p_offset = dynamic_off;
|
||||
@@ -648,7 +623,6 @@ l_emit_dyn_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
fwrite(&eh, 1, sizeof eh, f);
|
||||
fwrite(ph, 1, sizeof ph, f);
|
||||
|
||||
/* helper: pad to absolute offset `to` */
|
||||
#define PAD_TO(to) do { \
|
||||
long _here = ftell(f); \
|
||||
for (long _i = _here; _i < (long)(to); _i++) fputc(0, f); \
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* l.h — w6l-private header. Loads relocatable ELF64 .o files (the
|
||||
* format produced by w6a) and links them into a static executable.
|
||||
*
|
||||
* No archives yet (phase 8). No dynamic linking ever.
|
||||
*/
|
||||
#ifndef SIX_L_H
|
||||
#define SIX_L_H
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
/*
|
||||
* w6l — amd64 linker. Reads relocatable ELF .o files (from w6a) plus
|
||||
* .a archives, resolves, relocates, writes a static ELF executable.
|
||||
* Dynamic linking against .so files is the next increment; the -L/-l
|
||||
* flag plumbing here is its first step.
|
||||
*
|
||||
* w6l -o out [-L<dir>...] [-l<name>...] file1.o file2.o ...
|
||||
*
|
||||
* The first symbol named "_start" defined among the inputs becomes
|
||||
@@ -134,7 +129,6 @@ main(int argc, char **argv)
|
||||
int rc = l_emit_elf(&l, f, base, base + 0x1000 + entry->val);
|
||||
fclose(f);
|
||||
if (rc == 0) {
|
||||
/* chmod +x */
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "chmod +x %s", out);
|
||||
(void)system(cmd);
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* obj.c — load an ELF64 relocatable object emitted by w6a, append its
|
||||
* .text bytes to the combined image, and pull its symbols and
|
||||
* relocations into the global tables (with offsets adjusted to the
|
||||
* combined section).
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -97,11 +91,8 @@ emit_data(Lnk *l, const u8 *src, u64 n)
|
||||
l->datalen += n;
|
||||
}
|
||||
|
||||
/* Internal: load a single ELF .o image already in memory. The caller
|
||||
* gives us the bytes (we own them) and a path tag for diagnostics.
|
||||
* If the bytes look like an archive (magic "!<arch>\n") we recurse
|
||||
* over each member instead.
|
||||
*/
|
||||
/* The caller gives us the bytes (we own them) and a path tag for
|
||||
* diagnostics. */
|
||||
static int load_image(Lnk *l, const char *path, u8 *buf, u64 len);
|
||||
|
||||
static u64
|
||||
@@ -119,9 +110,8 @@ ar_field(const u8 *p, int n)
|
||||
|
||||
/* Read an ELF .o image's globally-defined symbol names without
|
||||
* actually appending it to the link. Returns a heap-allocated
|
||||
* NULL-terminated array; caller frees the array (not the strings,
|
||||
* which point into the .o image and must remain alive).
|
||||
*/
|
||||
* NULL-terminated array; caller frees the array, not the strings,
|
||||
* which point into the .o image and need it kept alive. */
|
||||
static char **
|
||||
elf_globals(const u8 *buf, u64 len)
|
||||
{
|
||||
@@ -299,8 +289,6 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
||||
if (eh->e_shstrndx >= eh->e_shnum) { free(buf); return -1; }
|
||||
const char *shstr = (const char *)(buf + sh[eh->e_shstrndx].sh_offset);
|
||||
|
||||
/* find .text, .data (optional), .symtab, .strtab, .rela.text,
|
||||
* .rela.data (optional) */
|
||||
int idx_text = -1, idx_data = -1, idx_symtab = -1, idx_strtab = -1;
|
||||
int idx_rela = -1, idx_relad = -1;
|
||||
for (u16 i = 0; i < eh->e_shnum; i++) {
|
||||
@@ -334,12 +322,10 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
||||
ob->next = l->objs;
|
||||
l->objs = ob;
|
||||
|
||||
/* append .text and (if present) .data */
|
||||
emit_text(l, buf + sh[idx_text].sh_offset, sh[idx_text].sh_size);
|
||||
if (idx_data >= 0 && sh[idx_data].sh_size > 0)
|
||||
emit_data(l, buf + sh[idx_data].sh_offset, sh[idx_data].sh_size);
|
||||
|
||||
/* per-object: load symbols */
|
||||
Sym64 *symtab = (Sym64 *)(buf + sh[idx_symtab].sh_offset);
|
||||
u64 nsyms = sh[idx_symtab].sh_size / sizeof(Sym64);
|
||||
const char *str = (const char *)(buf + sh[idx_strtab].sh_offset);
|
||||
@@ -373,7 +359,6 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
||||
map[i] = gs;
|
||||
}
|
||||
|
||||
/* per-object: collect relocations */
|
||||
if (idx_rela >= 0) {
|
||||
Rela64 *rt = (Rela64 *)(buf + sh[idx_rela].sh_offset);
|
||||
u64 nrel = sh[idx_rela].sh_size / sizeof(Rela64);
|
||||
@@ -389,8 +374,7 @@ load_image(Lnk *l, const char *path, u8 *buf, u64 len)
|
||||
l->rels = r;
|
||||
}
|
||||
}
|
||||
/* per-object: collect data relocations from .rela.data. The
|
||||
* .data section in the .o starts at a per-object 0; we shift
|
||||
/* The .data section in the .o starts at a per-object 0; we shift
|
||||
* by ob->data_off so r->off indexes the combined .data buffer. */
|
||||
if (idx_relad >= 0) {
|
||||
Rela64 *rt = (Rela64 *)(buf + sh[idx_relad].sh_offset);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/*
|
||||
* out.c — emit a static ELF64 executable.
|
||||
*
|
||||
* Layout (file order) without .data:
|
||||
* [0..64) ELF header
|
||||
* [64..120) one program header (PT_LOAD R+X)
|
||||
@@ -13,9 +11,6 @@
|
||||
* [176..0x1000) zero pad
|
||||
* [0x1000..) .text bytes
|
||||
* [data_off..) .data bytes (file offset and vaddr page-aligned)
|
||||
*
|
||||
* No interpreter, no dynamic, no .bss yet. Entry point is the address
|
||||
* of the symbol named "_start" (or whatever main supplies via -e).
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdio.h>
|
||||
@@ -102,9 +97,6 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
eh.e_phentsize = sizeof(Phdr);
|
||||
eh.e_phnum = has_data ? 2 : 1;
|
||||
|
||||
/* R+X load covering [0, rx_end). When .data is present we still
|
||||
* round up to a page in memsz so the loader doesn't try to give
|
||||
* the same page both R+X and R+W permissions. */
|
||||
Phdr phx = {0};
|
||||
phx.p_type = PT_LOAD;
|
||||
phx.p_flags = PF_R | PF_X;
|
||||
@@ -131,14 +123,12 @@ l_emit_elf(Lnk *l, FILE *f, u64 base, u64 entry)
|
||||
fwrite(&phx, 1, sizeof phx, f);
|
||||
if (has_data) fwrite(&phw, 1, sizeof phw, f);
|
||||
|
||||
/* pad to text_off */
|
||||
long here = ftell(f);
|
||||
for (long i = here; i < (long)text_off; i++) fputc(0, f);
|
||||
|
||||
if (l->textlen) fwrite(l->text, 1, l->textlen, f);
|
||||
|
||||
if (has_data && data_file_len > 0) {
|
||||
/* pad to data_off */
|
||||
here = ftell(f);
|
||||
for (long i = here; i < (long)data_off; i++) fputc(0, f);
|
||||
fwrite(l->data, 1, data_file_len, f);
|
||||
|
||||
@@ -1,14 +1,3 @@
|
||||
/*
|
||||
* pass.c — resolution + relocation. After all objects are loaded:
|
||||
*
|
||||
* l_resolve : check that every symbol referenced by a relocation
|
||||
* is defined somewhere. Errors get logged.
|
||||
* l_relocate: with the final virtual base address known, walk the
|
||||
* relocation list and patch the .text bytes in place.
|
||||
*
|
||||
* Supported relocation kinds: PC32 (2), PLT32 (4). Both are PC-relative
|
||||
* 32-bit displacements; for static linking PLT32 collapses to PC32.
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -34,7 +23,7 @@ l_resolve(Lnk *l)
|
||||
* across runs (rels are pushed onto the head as objects load). */
|
||||
for (Lrel *r = l->rels; r; r = r->next) {
|
||||
if (r->sym == NULL || r->sym->defined) continue;
|
||||
if (r->sym->is_dyn) continue; /* already promoted */
|
||||
if (r->sym->is_dyn) continue;
|
||||
for (Lso *so = l->sos; so; so = so->next) {
|
||||
const char *ver = NULL;
|
||||
if (l_so_provides_v(so, r->sym->name, &ver)) {
|
||||
@@ -47,7 +36,6 @@ l_resolve(Lnk *l)
|
||||
}
|
||||
}
|
||||
|
||||
/* What remains undefined truly is undefined. */
|
||||
for (Lrel *r = l->rels; r; r = r->next) {
|
||||
if (r->sym == NULL) continue;
|
||||
if (!r->sym->defined && !r->sym->is_dyn) {
|
||||
@@ -90,7 +78,8 @@ l_relocate(Lnk *l, u64 text_va, u64 data_va)
|
||||
switch (r->kind) {
|
||||
case R_X86_64_PC32:
|
||||
case R_X86_64_PLT32: {
|
||||
/* PC-relative 32-bit displacement; lands in .text. */
|
||||
/* PC-relative 32-bit displacement; lands in .text.
|
||||
* For static linking PLT32 collapses to PC32. */
|
||||
u64 site = text_va + r->off;
|
||||
i64 rel = (i64)sym_va - (i64)site + r->addend;
|
||||
patch_u32(l->text + r->off, (u32)(i32)rel);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* sym.c — global symbol table for the linker. Plain singly-linked
|
||||
* list; usually a few hundred entries, hashing isn't worth it yet.
|
||||
* Plain singly-linked list; usually a few hundred entries, hashing
|
||||
* isn't worth it yet.
|
||||
*/
|
||||
#include "l.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
/*
|
||||
* ast.c — Node constructor + s-expression printer.
|
||||
*
|
||||
* Constructor zeroes everything past kind/pos. Printer is rigid and
|
||||
* deterministic so golden tests can diff. One node per logical line,
|
||||
* children indented by 2 spaces.
|
||||
* Printer output is rigid and deterministic so golden tests can diff.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/*
|
||||
* check.c — name resolution + type checking pass.
|
||||
*
|
||||
* Two-stage:
|
||||
* 1) collect: walk top-level decls and install Syms with stub types.
|
||||
* 2) resolve: expand types, check fn bodies and def initialisers.
|
||||
@@ -1046,8 +1044,6 @@ resolve_type(Checker *c, Node *n)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- expressions -------------------------------------------------- */
|
||||
|
||||
static Type *
|
||||
unify_arith(Checker *c, Pos p, Type *a, Type *b)
|
||||
{
|
||||
@@ -2007,7 +2003,6 @@ cexpr(Checker *c, Node *n)
|
||||
(void)cexpr(c, n->rhs);
|
||||
return n->type = ty_void;
|
||||
}
|
||||
/* Reject assignment to a const-bound name. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
||||
n->lhs->str);
|
||||
@@ -2387,8 +2382,6 @@ cexpr(Checker *c, Node *n)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- statements --------------------------------------------------- */
|
||||
|
||||
static void
|
||||
clet(Checker *c, Node *n)
|
||||
{
|
||||
@@ -2731,8 +2724,6 @@ cstmt(Checker *c, Node *n)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- top-level ---------------------------------------------------- */
|
||||
|
||||
static Type *
|
||||
build_fn_type(Checker *c, Node *fn)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
/*
|
||||
* err.c — diagnostics.
|
||||
*
|
||||
* fatal prints, sets exit(1).
|
||||
* errorf prints with source location, increments nerrors.
|
||||
* warnf prints with source location, increments nwarnings.
|
||||
*
|
||||
* Plan 9 style: short, no levels beyond fatal/error/warn, no colour.
|
||||
*/
|
||||
#include "ww.h"
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
/*
|
||||
* lex.c — hand-rolled DFA. UTF-8 source, ASCII operators.
|
||||
*
|
||||
* Comments: //... and (slash-star ... star-slash). Both stripped.
|
||||
* Whitespace: space, tab, CR, NL.
|
||||
* Identifiers: [A-Za-z_][A-Za-z0-9_]* — also matches keywords; we
|
||||
* look up the kw table after lexing the run.
|
||||
* Integer: 0x[0-9a-fA-F_]+, 0o[0-7_]+, 0b[01_]+, [0-9][0-9_]*
|
||||
* Float: [0-9]+'.'[0-9]+([eE][+-]?[0-9]+)?
|
||||
* Rune: 'x' with C-like escapes
|
||||
* String: "..." with C-like escapes
|
||||
* Operators: longest match.
|
||||
*
|
||||
* No automatic semicolon insertion (Hare rule). The lexer only emits
|
||||
* what is in the source; the parser is responsible for non-empty rules.
|
||||
*/
|
||||
@@ -81,7 +69,6 @@ ishex(int c)
|
||||
(c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
/* skip whitespace and comments. returns 0 on EOF, else 1. */
|
||||
static int
|
||||
skipws(Lex *l)
|
||||
{
|
||||
@@ -94,7 +81,7 @@ skipws(Lex *l)
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && lpeek(l, 1) == '/') {
|
||||
lget(l); lget(l); /* consume '//' */
|
||||
lget(l); lget(l);
|
||||
/* #16 option-B: the driver emits `//ww:module-reset`
|
||||
* before a package-less file's bytes; recognize the
|
||||
* whole-line directive (without consuming differently)
|
||||
@@ -379,10 +366,7 @@ lexnum(Lex *l, Pos start)
|
||||
}
|
||||
}
|
||||
|
||||
/* Typed suffix: i8/i16/i32/i64, u8/u16/u32/u64, f32/f64.
|
||||
* Must be glued (no whitespace) to the digits. We grab the
|
||||
* adjacent identifier-like run and accept it only if it's one
|
||||
* of the recognised type names. */
|
||||
/* A typed suffix must be glued (no whitespace) to the digits. */
|
||||
if (isidstart(lpeek(l, 0))) {
|
||||
u64 sb = l->pos;
|
||||
while (isidcont(lpeek(l, 0))) lget(l);
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
/*
|
||||
* mem.c — arena allocator. No free per allocation; freearena releases
|
||||
* the whole chain. Aligned to 16 so structs with 8-byte fields and
|
||||
* doubles are happy.
|
||||
*
|
||||
* Hot allocations in the compiler land in arenas: tokens, AST nodes,
|
||||
* symbols, types. The chunk size doubles up to a cap so we don't
|
||||
* fragment on huge inputs.
|
||||
* No free per allocation; freearena releases the whole chain.
|
||||
* Aligned to 16 so structs with 8-byte fields and doubles are happy.
|
||||
* The chunk size doubles up to a cap so we don't fragment on huge
|
||||
* inputs.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <stdlib.h>
|
||||
@@ -43,7 +40,6 @@ grow(Arena *a, u64 need)
|
||||
if (ncap < need)
|
||||
ncap = roundup(need, ALIGN);
|
||||
|
||||
/* push current chunk onto chain, allocate fresh head */
|
||||
Arena *old = malloc(sizeof *old);
|
||||
if (old == NULL)
|
||||
fatal("arena: oom");
|
||||
|
||||
@@ -100,8 +100,6 @@ static Node *parsetype(Parser *p);
|
||||
static Node *parseblock(Parser *p);
|
||||
static Node *parsestmt(Parser *p);
|
||||
|
||||
/* ------- type expressions ------------------------------------------ */
|
||||
|
||||
static Node *
|
||||
parseparams(Parser *p)
|
||||
{
|
||||
@@ -376,8 +374,6 @@ parsetype(Parser *p)
|
||||
}
|
||||
}
|
||||
|
||||
/* ------- expressions (Pratt) ---------------------------------------- */
|
||||
|
||||
/* binary precedence; 0 = not a binary op */
|
||||
static int
|
||||
bprec(Tkind k)
|
||||
@@ -663,10 +659,9 @@ parseprimary(Parser *p)
|
||||
n->str = t.text;
|
||||
n->strlen = t.tlen;
|
||||
advance(p);
|
||||
/* dotted ident chain folded into one IDENT for type-ish refs */
|
||||
while (p->cur.kind == TK_DOT && peek(p).kind == TK_IDENT) {
|
||||
advance(p);
|
||||
n = (Node*)n; /* keep stable */
|
||||
n = (Node*)n;
|
||||
Node *mr = newnode(p->a, N_DOT, pp);
|
||||
mr->lhs = n;
|
||||
mr->str = p->cur.text;
|
||||
@@ -674,7 +669,6 @@ parseprimary(Parser *p)
|
||||
advance(p);
|
||||
n = mr;
|
||||
}
|
||||
/* struct literal: ident '{' ... '}' (only if ident-shaped) */
|
||||
if (p->cur.kind == TK_LBRACE) {
|
||||
/* #76: bare `Foo{}` keeps the N_IDENT fast-path; a
|
||||
* qualified `pkg.Type{}` (N_DOT chain) flattens first. */
|
||||
@@ -902,8 +896,6 @@ parseexpr_top(Parser *p)
|
||||
return parseexpr(p);
|
||||
}
|
||||
|
||||
/* ------- statements ------------------------------------------------- */
|
||||
|
||||
static Node *
|
||||
parselet(Parser *p, int top)
|
||||
{
|
||||
@@ -944,7 +936,6 @@ parselet(Parser *p, int top)
|
||||
return m;
|
||||
}
|
||||
|
||||
/* parse first binding */
|
||||
Pos lp = p->cur.pos;
|
||||
Node *first = newnode(p->a, N_LET, lp);
|
||||
first->str = expectbindname(p);
|
||||
@@ -952,7 +943,6 @@ parselet(Parser *p, int top)
|
||||
first->lhs = parsetype(p);
|
||||
|
||||
if (p->cur.kind == TK_COMMA) {
|
||||
/* multi-let: collect (name, type) pairs, then '=' rhs */
|
||||
Node *m = newnode(p->a, N_MLET, pp);
|
||||
Node *head = first, *tail = first;
|
||||
while (accept(p, TK_COMMA)) {
|
||||
@@ -1038,7 +1028,7 @@ parsefor(Parser *p)
|
||||
* Tuple destructure: `let (a, b) .. expr`. */
|
||||
Tok save_cur = p->cur;
|
||||
(void)save_cur;
|
||||
advance(p); /* consume LET */
|
||||
advance(p);
|
||||
if (p->cur.kind == TK_LPAREN) {
|
||||
advance(p);
|
||||
Node *names = NULL, *tail = NULL;
|
||||
@@ -1067,8 +1057,8 @@ parsefor(Parser *p)
|
||||
int isunder = p->cur.kind == TK_UNDER;
|
||||
Tok la = peek(p);
|
||||
if (la.kind == TK_DOTDOT) {
|
||||
advance(p); /* consume IDENT/UNDER */
|
||||
advance(p); /* consume DOTDOT */
|
||||
advance(p);
|
||||
advance(p);
|
||||
Node *rng = newnode(p->a, N_FORRANGE, pp);
|
||||
rng->str = isunder ? "" : nm;
|
||||
rng->lhs = parseexpr(p);
|
||||
@@ -1283,8 +1273,6 @@ parseblock(Parser *p)
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ------- top-level decls ------------------------------------------- */
|
||||
|
||||
/* `import encoding.utf8;` — the driver resolves the dotted path to a
|
||||
* directory; the checker only needs the leaf (`utf8`) as the module
|
||||
* bareword for n_use→decl disambiguation, mirroring Hare's
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* sym.c — symbol table. Plan 9-flavoured: a per-scope hashtable
|
||||
* chained to the parent scope. Lookup walks up. Duplicate definitions
|
||||
* within the same scope are flagged by the caller (we just refuse the
|
||||
* insert and return the first one).
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
|
||||
@@ -54,13 +48,6 @@ scope_lookup(Scope *s, const char *name)
|
||||
}
|
||||
|
||||
/*
|
||||
* scope_lookup_in_module — module-filtered chain walk.
|
||||
*
|
||||
* Same FNV bucket + hashnext chain + parent walk as scope_lookup,
|
||||
* plus a (b->mod != NULL && strcmp(b->mod, mod) == 0) filter. When
|
||||
* `mod` is NULL we fall back to unfiltered scope_lookup semantics,
|
||||
* so callers that don't care about disambiguation get the default.
|
||||
*
|
||||
* Used by resolve_typename and the cexpr N_DOT branch to pick the
|
||||
* right same-leaf-name type when two imports each export it
|
||||
* (`bufio.stream` vs `io.stream`).
|
||||
@@ -80,19 +67,13 @@ scope_lookup_in_module(Scope *s, const char *mod, const char *name)
|
||||
}
|
||||
|
||||
/*
|
||||
* scope_lookup_prefer — bare-leaf lookup with same-module preference.
|
||||
*
|
||||
* Walks the same FNV bucket + hashnext chain + parent walk scope_lookup
|
||||
* uses. Within each scope's bucket: Pass 1 prefers entries whose
|
||||
* `sym.mod` matches the caller's `mod`; Pass 2 falls back to the first
|
||||
* match regardless of mod (the existing scope_lookup semantics). We
|
||||
* only descend to the parent scope when the current scope has no
|
||||
* matching entry at all — so a local binding in a closer scope still
|
||||
* shadows a same-name fn from a parent scope, even when the parent
|
||||
* entry mod-matches.
|
||||
*
|
||||
* When `mod` is NULL we just call scope_lookup — there's no module
|
||||
* identity to prefer.
|
||||
* Within each scope's bucket: Pass 1 prefers entries whose `sym.mod`
|
||||
* matches the caller's `mod`; Pass 2 falls back to the first match
|
||||
* regardless of mod (the existing scope_lookup semantics). We only
|
||||
* descend to the parent scope when the current scope has no matching
|
||||
* entry at all — so a local binding in a closer scope still shadows a
|
||||
* same-name fn from a parent scope, even when the parent entry
|
||||
* mod-matches.
|
||||
*
|
||||
* Used at bare-leaf lookup sites inside a known current module so that
|
||||
* a bare `read` inside lib/os resolves to os.read rather than the
|
||||
@@ -117,15 +98,12 @@ scope_lookup_prefer(Scope *s, const char *mod, const char *name)
|
||||
}
|
||||
|
||||
/*
|
||||
* scope_lookup_type — kind-filtered bare-leaf lookup for type position.
|
||||
*
|
||||
* Same FNV bucket + hashnext chain + parent walk and same-module
|
||||
* preference as scope_lookup_prefer, but skips every Sym whose kind
|
||||
* isn't SK_TYPE and KEEPS scanning — so it returns the innermost
|
||||
* SK_TYPE of `name`, looking past a same-named value binding (SK_VAR/
|
||||
* SK_PARAM/SK_FN) that shadows it in a closer scope. ww keeps type and
|
||||
* value namespaces separate (wwstage already does; #225 conformance
|
||||
* gap): a param `off` must not hide the global `type off`.
|
||||
* Skips every Sym whose kind isn't SK_TYPE and KEEPS scanning — so it
|
||||
* returns the innermost SK_TYPE of `name`, looking past a same-named
|
||||
* value binding (SK_VAR/SK_PARAM/SK_FN) that shadows it in a closer
|
||||
* scope. ww keeps type and value namespaces separate (wwstage already
|
||||
* does; #225 conformance gap): a param `off` must not hide the global
|
||||
* `type off`.
|
||||
*/
|
||||
Sym *
|
||||
scope_lookup_type(Scope *s, const char *mod, const char *name)
|
||||
@@ -151,10 +129,8 @@ scope_define(Scope *s, const char *name, Skind k, Type *t, Node *decl)
|
||||
}
|
||||
|
||||
/*
|
||||
* scope_define_in_module — bucket insert with per-mod dedup.
|
||||
*
|
||||
* Same insertion as scope_define, but the duplicate-rejection key is
|
||||
* (name, mod) rather than name alone. This lets two imports each
|
||||
* The duplicate-rejection key is (name, mod) rather than name alone.
|
||||
* This lets two imports each
|
||||
* register their own `stream` SK_TYPE in the flat scope, and lets the
|
||||
* primary register `stream` (mod=NULL) alongside imported `stream`s.
|
||||
*
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* tok.c — token names, keyword lookup, debug printer.
|
||||
*
|
||||
* One table-of-records keyed by kind. The keyword subset is also
|
||||
* scanned linearly during lexing — fewer than 25 entries, a hash
|
||||
* isn't worth it.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
|
||||
@@ -13,7 +6,8 @@ struct kwent {
|
||||
Tkind kind;
|
||||
};
|
||||
|
||||
/* keep alphabetised, so kwlookup is easy to read. */
|
||||
/* keep alphabetised, so kwlookup is easy to read. Scanned linearly:
|
||||
* fewer than 25 entries, a hash isn't worth it. */
|
||||
static const struct kwent kwtab[] = {
|
||||
{ "as", TK_AS },
|
||||
{ "break", TK_BREAK },
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/*
|
||||
* type.c — Type values and structural equality.
|
||||
*
|
||||
* Built-in types are constructed once and exposed as globals so the
|
||||
* rest of the compiler can `==`-compare them. Compound types (ptr,
|
||||
* slice, array, fn, struct, chan) are constructed on demand and
|
||||
* de-duplicated when equality is cheap (only ptr/slice for now).
|
||||
* rest of the compiler can `==`-compare them.
|
||||
*/
|
||||
#include "ww.h"
|
||||
#include <string.h>
|
||||
@@ -147,8 +143,7 @@ type_named(Arena *a, const char *name, Type *under)
|
||||
return t;
|
||||
}
|
||||
|
||||
/* type_chase_named — walk the TY_NAMED.under chain to the deepest non-
|
||||
* named type. Chain-of-aliases (#22): `type b = a; type a = struct;`
|
||||
/* Chain-of-aliases (#22): `type b = a; type a = struct;`
|
||||
* stacks two TY_NAMED layers — a single peel leaves `t` pointing at
|
||||
* the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT,
|
||||
* TY_SLICE, TY_TAGGED, TY_PTR) miss and the consumer silently falls
|
||||
@@ -372,7 +367,6 @@ type_assignable(Type *dst, Type *src)
|
||||
}
|
||||
}
|
||||
|
||||
/* Untyped → typed: only if the typed kind can hold the value. */
|
||||
if (type_isuntyped(src)) {
|
||||
Type *du = type_chase_named(dst);
|
||||
if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1;
|
||||
|
||||
14
cmd/wcc/ww.h
14
cmd/wcc/ww.h
@@ -1,6 +1,4 @@
|
||||
/*
|
||||
* ww.h — central header for libwcc.a (the ww frontend library).
|
||||
*
|
||||
* Plan 9 in spirit. This file mirrors cc/cc.h's role: one shared
|
||||
* header that declares everything every translation unit in the
|
||||
* frontend cares about.
|
||||
@@ -19,7 +17,6 @@
|
||||
/* version banner — printed by `ww -V` */
|
||||
#define WW_VERSION "0.0"
|
||||
|
||||
/* short integer aliases, Plan 9 / Hare-flavoured */
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
@@ -29,7 +26,6 @@ typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
/* forward decls — concrete shapes appear in their phases. */
|
||||
typedef struct Tok Tok;
|
||||
typedef struct Lex Lex;
|
||||
typedef struct Node Node;
|
||||
@@ -54,8 +50,6 @@ char *astrndup(Arena*, const char*, u64);
|
||||
char *aprintf(Arena*, const char*, ...);
|
||||
void freearena(Arena*);
|
||||
|
||||
/* err.c — diagnostics. Phase 0 has only fatal/warn; later phases add
|
||||
* source-location-bearing variants. */
|
||||
typedef struct Pos Pos;
|
||||
struct Pos {
|
||||
const char *file;
|
||||
@@ -72,15 +66,12 @@ void fatal(const char*, ...) __attribute__((noreturn, format(printf, 1, 2)));
|
||||
void errorf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
|
||||
void warnf(Pos, const char*, ...) __attribute__((format(printf, 2, 3)));
|
||||
|
||||
/* tiny helpers */
|
||||
#define nelem(a) ((sizeof(a) / sizeof((a)[0])))
|
||||
|
||||
/* ---- lexer (lex.c, tok.c) ----------------------------------------- */
|
||||
typedef enum {
|
||||
/* zero is "no token" so memset-zero structs read sane */
|
||||
TK_NONE = 0,
|
||||
|
||||
/* trivial */
|
||||
TK_EOF,
|
||||
TK_ERR,
|
||||
TK_IDENT,
|
||||
@@ -117,7 +108,6 @@ typedef enum {
|
||||
TK_CONST, /* const binding */
|
||||
TK_UNDER, /* bare '_' discard */
|
||||
|
||||
/* punct + operators */
|
||||
TK_LPAREN, /* ( */
|
||||
TK_RPAREN, /* ) */
|
||||
TK_LBRACE, /* { */
|
||||
@@ -233,7 +223,6 @@ const char *tokname(Tkind); /* canonical spelling, e.g. "fn", "+=" */
|
||||
void tokprint(FILE*, Tok); /* one line, "%s:%d:%d: %s %q" */
|
||||
Tkind kwlookup(const char *s, u64 n); /* TK_NONE if not a keyword */
|
||||
|
||||
/* ---- AST (ast.c, parse.c) ----------------------------------------- */
|
||||
typedef enum {
|
||||
N_NONE = 0,
|
||||
|
||||
@@ -399,7 +388,6 @@ void parserinit(Parser*, Arena*, Lex*);
|
||||
Node *parsefile(Parser*);
|
||||
Node *parseexpr_top(Parser*); /* for testing: parse one expression */
|
||||
|
||||
/* ---- types (type.c) ----------------------------------------------- */
|
||||
typedef enum {
|
||||
TY_NONE = 0,
|
||||
TY_VOID,
|
||||
@@ -525,7 +513,6 @@ int type_isuntyped(Type *t);
|
||||
int type_assignable(Type *dst, Type *src);
|
||||
Type *type_default(Type *t); /* untyped → default concrete */
|
||||
|
||||
/* ---- symbols (sym.c) ---------------------------------------------- */
|
||||
typedef enum {
|
||||
SK_NONE = 0,
|
||||
SK_VAR,
|
||||
@@ -580,7 +567,6 @@ Sym *scope_lookup_in_module(Scope*, const char *mod, const char *name);
|
||||
Sym *scope_lookup_prefer(Scope*, const char *mod, const char *name);
|
||||
Sym *scope_lookup_type(Scope*, const char *mod, const char *name);
|
||||
|
||||
/* ---- checker (check.c) -------------------------------------------- */
|
||||
typedef struct Checker Checker;
|
||||
struct Checker {
|
||||
Arena *a;
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
/*
|
||||
* ww — the user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue.
|
||||
*
|
||||
* Pipeline:
|
||||
* ww build foo.ww → w6c foo.ww > foo.s ; w6a foo.s > foo.o ;
|
||||
* w6l -o foo foo.o <runtime.o>
|
||||
* ww run foo.ww → build then exec ./foo
|
||||
*
|
||||
* Tool paths default to siblings of $0 (so a fresh build runs out of
|
||||
* out/bin/), and can be overridden with WW_W6C / WW_W6A / WW_W6L.
|
||||
*/
|
||||
@@ -35,7 +30,7 @@ static const char *usage =
|
||||
" lib/... every package under lib, recursively (test only)\n"
|
||||
" . build the cwd's <basename>.ww\n";
|
||||
|
||||
static char *self_dir; /* directory containing this binary */
|
||||
static char *self_dir;
|
||||
|
||||
static const char *
|
||||
toolpath(const char *envvar, const char *name)
|
||||
@@ -125,8 +120,7 @@ exec_package_tests(int argc, char **argv, const char *target,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Set of imported module paths, kept on the heap. Used to break
|
||||
* cycles in `use` resolution. Linear because typical imports are
|
||||
/* Breaks cycles in `use` resolution. Linear because typical imports are
|
||||
* a handful per build. */
|
||||
struct ImportSet {
|
||||
char **paths;
|
||||
@@ -151,8 +145,7 @@ import_add(struct ImportSet *s, const char *path)
|
||||
s->paths[s->n++] = strdup(path);
|
||||
}
|
||||
|
||||
/* Translate dots in an `import` name to slashes for path lookup.
|
||||
* `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s
|
||||
/* `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s
|
||||
* use-path → fs-path mapping (ref/hare/hare/module/srcs.ha:78
|
||||
* builds the same shape via path::push per ident part). */
|
||||
static void
|
||||
@@ -164,12 +157,11 @@ import_path_form(const char *name, char *out, size_t outsz)
|
||||
out[i] = '\0';
|
||||
}
|
||||
|
||||
/* try <dir>/<path>/ as a directory (want_dir), else <dir>/<path>.ww as
|
||||
* a file. Sets *is_dir on hit. Symmetric with wwstage locatein for
|
||||
* byte-id driver output (rule 10). The legacy <dir>/<name>/<name>.ww
|
||||
* form was dropped in task #22 — directory-as-module enumeration
|
||||
* replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no
|
||||
* fallback matching `foo/foo.ha`; a module IS the directory). */
|
||||
/* Symmetric with wwstage locatein for byte-id driver output (rule 10).
|
||||
* The legacy <dir>/<name>/<name>.ww form was dropped in task #22 —
|
||||
* directory-as-module enumeration replaces it, mirroring
|
||||
* ref/hare/hare/module/srcs.ha (Hare has no fallback matching
|
||||
* `foo/foo.ha`; a module IS the directory). */
|
||||
static int
|
||||
locate_import_in(const char *dir, const char *path_form, char *out,
|
||||
size_t outsz, int *is_dir, int want_dir)
|
||||
@@ -191,10 +183,7 @@ locate_import_in(const char *dir, const char *path_form, char *out,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Walk a colon-separated dirlist trying to resolve `path_form`. Returns
|
||||
* 1 on the first hit and writes the concrete path + dir/file marker.
|
||||
*
|
||||
* #98: "a module IS the directory" — a directory-package on ANY entry
|
||||
/* #98: "a module IS the directory" — a directory-package on ANY entry
|
||||
* wins over a same-named sibling FILE on an EARLIER entry. The driver
|
||||
* builds the searchpath srcd-first; a co-located `lib/<mod>/<x>_test.ww`
|
||||
* entry makes srcd = lib/<mod>, so a self-named `import <mod>` would
|
||||
@@ -231,10 +220,10 @@ locate_import(const char *dirs, const char *path_form, char *out,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* memcmp-based string compare for qsort. Byte-wise total order is
|
||||
* locale-independent; rule-10 byte-id requires the two stages sort
|
||||
* the same way. (strcmp would work today but Hare-fidelity points
|
||||
* at memcmp via ref/hare/sort/cmp/cmp.ha:9.) */
|
||||
/* Byte-wise total order is locale-independent; rule-10 byte-id requires
|
||||
* the two stages sort the same way. strcmp diverges from Hare's memcmp
|
||||
* (ref/hare/sort/cmp/cmp.ha:9); the order is identical for NUL-free
|
||||
* filenames. */
|
||||
static int
|
||||
strs_cmp(const void *a, const void *b)
|
||||
{
|
||||
@@ -266,11 +255,9 @@ file_has_line_test(const char *path)
|
||||
return found;
|
||||
}
|
||||
|
||||
/* enumerate_dir_ww — collect production *.ww paths in `dirpath`, excluding
|
||||
* *_test.ww test sources, then sort byte-wise. A line-leading @test in
|
||||
* any other source is diagnosed here and returns -2. This is the sole
|
||||
* directory-membership discovery path; the owning seppkg retains the
|
||||
* returned list. */
|
||||
/* A line-leading @test in a production source is diagnosed here and
|
||||
* returns -2. This is the sole directory-membership discovery path; the
|
||||
* owning seppkg retains the returned list. */
|
||||
static int
|
||||
enumerate_dir_ww(const char *dirpath, char ***out_files)
|
||||
{
|
||||
@@ -310,9 +297,8 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
|
||||
return n;
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* ww build — separate-compilation driver (task #46/c3).
|
||||
* ====================================================================
|
||||
/* ww build — separate-compilation driver (task #46/c3).
|
||||
*
|
||||
* This is the SOLE build path (E3-C1 flip, task #87): the legacy
|
||||
* single-file amalgamator is gone. Each imported
|
||||
* package's `.wwi` interface is materialized and every package is
|
||||
@@ -357,7 +343,6 @@ struct sepgraph {
|
||||
int n;
|
||||
};
|
||||
|
||||
/* Find a package by dotted path, or add it. Returns its index, -1 full. */
|
||||
static int
|
||||
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
||||
int is_dir)
|
||||
@@ -395,8 +380,7 @@ sep_graph_free(struct sepgraph *g)
|
||||
free(g);
|
||||
}
|
||||
|
||||
/* Sanitize a package's dotted path into a scratch-file basename. Dots
|
||||
* stay (legal in filenames); the root's empty path becomes "__root". */
|
||||
/* Dots stay (legal in filenames); the root's empty path becomes "__root". */
|
||||
static void
|
||||
sep_fname(const struct sepgraph *g, int pi, const char *scratch,
|
||||
const char *suffix, char *out, size_t outsz)
|
||||
@@ -451,9 +435,8 @@ sep_ident_continue(int c)
|
||||
return sep_ident_start(c) || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
/* Skip the whitespace and comments accepted before and within the leading
|
||||
* package clause. This is deliberately only the loader's small header
|
||||
* grammar, not a second compiler lexer. */
|
||||
/* Deliberately only the loader's small header grammar for the leading
|
||||
* package clause, not a second compiler lexer. */
|
||||
static int
|
||||
sep_skip_space(const char *src, size_t n, size_t *off)
|
||||
{
|
||||
@@ -481,7 +464,6 @@ sep_skip_space(const char *src, size_t n, size_t *off)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Parse exactly the leading loader grammar `package ident;`. */
|
||||
static int
|
||||
sep_package_clause(const char *src, size_t n, char *name, size_t namesz)
|
||||
{
|
||||
@@ -509,9 +491,7 @@ sep_package_clause(const char *src, size_t n, char *name, size_t namesz)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Scan one source file's already-selected bytes for its leading package
|
||||
* clause (when it is an owned directory source) and top-level imports. A
|
||||
* DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
||||
/* A DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
||||
* `pi`. A FILE import is an intra-package split — fold its imports into
|
||||
* `pi` (its bytes join pi's body at emit time). Collects package PATHS
|
||||
* rather than concatenating bytes the way the legacy amalgamator did
|
||||
@@ -715,7 +695,6 @@ sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mark pi's transitive deps (excluding pi) in inset[]. */
|
||||
static void
|
||||
sep_mark_deps(struct sepgraph *g, int pi, char *inset)
|
||||
{
|
||||
@@ -881,9 +860,9 @@ archive_o(const char *objpath, const char *apath)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- -w workdir freshness ---------------------------------------------
|
||||
* A `-w DIR` workdir is a caller-owned persistent package-artifact tree
|
||||
* that replaces the fresh `.sepwork` scratch. Staleness is pure content
|
||||
/* -w workdir freshness: a `-w DIR` workdir is a caller-owned persistent
|
||||
* package-artifact tree that replaces the fresh `.sepwork` scratch.
|
||||
* Staleness is pure content
|
||||
* identity, never mtime: a package is reused only when its freshly
|
||||
* composed unit byte-equals the committed unit AND the tool copies
|
||||
* recorded in the dir byte-equal the live tools — every decision is
|
||||
@@ -997,7 +976,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
||||
else if (access("lib", 0) == 0) srcdir = "lib";
|
||||
else srcdir = libdir;
|
||||
}
|
||||
/* search path: source-dir, then -I dirs, then srcdir. */
|
||||
char srcd[1024];
|
||||
if (entry_is_dir) {
|
||||
snprintf(srcd, sizeof srcd, "%s", src);
|
||||
@@ -1110,7 +1088,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
||||
}
|
||||
free(stack);
|
||||
|
||||
/* producer loop — dep-first, one `w6c -c -I` pass per package. */
|
||||
for (int oi = 0; oi < norder; oi++) {
|
||||
int pi = order[oi];
|
||||
char unitf[1024], wwi[1024], asmf[1024], obj[1024], apath[1024];
|
||||
@@ -1250,7 +1227,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *out,
|
||||
char objs[8192] = {0};
|
||||
for (int oi = norder - 1; oi >= 0; oi--) {
|
||||
char path[1024];
|
||||
/* root: positional `.o` (force-load); deps: `.a` (selective). */
|
||||
sep_fname(g, order[oi], scratch,
|
||||
order[oi] == root ? ".o" : ".a", path, sizeof path);
|
||||
size_t n = strlen(objs);
|
||||
@@ -1345,7 +1321,6 @@ search_path(const char *extra, char *buf, size_t bufsz)
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* basename_no_ext: last path segment with any trailing ".ww" stripped. */
|
||||
static void
|
||||
basename_no_ext(const char *path, char *out, size_t outsz)
|
||||
{
|
||||
@@ -1356,13 +1331,6 @@ basename_no_ext(const char *path, char *out, size_t outsz)
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
|
||||
/* resolve_module: turn a name into a concrete entry path.
|
||||
* foo.ww → use as-is if it exists
|
||||
* <existing dir> → returns the dir path (caller dir-enumerates)
|
||||
* . → cwd as a directory
|
||||
* foo (bare) → walk cwd:incs:WW_LIB; first hit is dir or file.
|
||||
* Sets *is_dir on hit. Dir resolution drives directory-as-module
|
||||
* enumeration in build_one_sep. */
|
||||
static int
|
||||
resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
||||
int *is_dir)
|
||||
@@ -1387,12 +1355,11 @@ resolve_module(const char *name, const char *incs, char *out, size_t outsz,
|
||||
return locate_import(sp, path_form, out, outsz, is_dir);
|
||||
}
|
||||
|
||||
/* Parse the standard -I/-L/-l/-o flags into incs/libdirs/libs/outpath. The
|
||||
* first non-flag positional becomes *src_out. Returns the index past the last
|
||||
* arg consumed for positionals (so callers can pick up trailing args), or -1
|
||||
* if a flag is missing its argument (diagnostic already emitted). `cmd` names
|
||||
* the subcommand for the diagnostic, byte-identical to the wwstage twin's
|
||||
* per-subcommand wording (selfhost/cmd/ww/main.ww dobuild/dorun). */
|
||||
/* Returns the index past the last arg consumed for positionals (so callers
|
||||
* can pick up trailing args), or -1 if a flag is missing its argument
|
||||
* (diagnostic already emitted). `cmd` names the subcommand for the
|
||||
* diagnostic, byte-identical to the wwstage twin's per-subcommand wording
|
||||
* (selfhost/cmd/ww/main.ww dobuild/dorun). */
|
||||
static int
|
||||
parse_build_flags(const char *cmd, int argc, char **argv,
|
||||
char *incs, size_t incsz,
|
||||
@@ -1504,7 +1471,7 @@ do_build(int argc, char **argv)
|
||||
outflag, sizeof outflag, workdir, sizeof workdir,
|
||||
&src, &emit_asm) < 0)
|
||||
return 2;
|
||||
if (src == NULL) src = "."; /* default: build cwd */
|
||||
if (src == NULL) src = ".";
|
||||
char resolved[1024];
|
||||
int is_dir = 0;
|
||||
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
|
||||
@@ -1568,7 +1535,6 @@ do_run(int argc, char **argv)
|
||||
fputs("ww: cannot remove temporary directory\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
/* exec the built binary with any trailing argv as its argv. */
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
perror("ww: fork");
|
||||
@@ -1823,7 +1789,6 @@ do_test(int argc, char **argv)
|
||||
"ww test: package options need a directory\n");
|
||||
return 2;
|
||||
}
|
||||
/* single .ww file — build, then run unless -c (compile-only). */
|
||||
char tmpdir[1024] = {0}, tmp[1024];
|
||||
const char *outp;
|
||||
int owntmp = !outstem[0] && !workdir[0];
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
/*
|
||||
* wwdump — deterministic dump tool for ww source.
|
||||
*
|
||||
* Reads a .ww file and writes either a token stream or an AST
|
||||
* s-expression to stdout, byte-for-byte stable across runs. It is the
|
||||
* diff anchor for self-host: the C-side libwcc and the future ww-side
|
||||
* frontend must produce the same dump for the same input.
|
||||
* Diff anchor for self-host: the C-side libwcc and the future ww-side
|
||||
* frontend must produce the same dump for the same input, byte-for-byte
|
||||
* stable across runs.
|
||||
*
|
||||
* wwdump -t file.ww tokens, one per line: "<file>:<l>:<c> <kind> [val]"
|
||||
* wwdump -a file.ww AST as s-expr, one node per line
|
||||
@@ -70,7 +67,7 @@ dump_ast(const char *src, char *buf, u64 len, FILE *out)
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int mode = 't'; /* tokens by default */
|
||||
int mode = 't';
|
||||
const char *src = NULL;
|
||||
const char *out = NULL;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
|
||||
Reference in New Issue
Block a user