ww: rename toolchain to w-prefix + hare-style build/run/test driver

Plan 9-style w-prefix on the per-arch tools, disambiguating from the
real Plan 9 6c/6a/6l in ref/plan9front/:

    cmd/wwc/      → cmd/wcc/        libwwc.a → libwcc.a
    cmd/6{c,a,l}  → cmd/w6{c,a,l}   binary names too
    test/wwc/     → test/wcc/       6 test files w/ w6 prefix
    selfhost/cmd  mirror in lockstep
    bootstrap/amd64/{w6c,w6a,w6l}   snapshot binaries (gitignored)
    WW_6{C,A,L}   → WW_W6{C,A,L}    env-var overrides

Plan 9 source-tree refs ("Plan 9 6c shape", ref/plan9front/, etc.)
preserved. Hare-style driver, both C and ww sides:

    ww test [path]   discover *_test.ww in a directory module, run
                     each; single-file mode for `ww test foo.ww`
    Module-by-name   `ww build foo` resolves to foo.ww or foo/foo.ww
                     via search path (cwd : -I dirs : $WW_LIB)
    Default-to-cwd   `ww build` / `ww test` build the cwd module
    Run pass-through `ww run path arg1 arg2` reaches the program

lib/os: getcwd (79) and getdents64 (217) syscalls power `.` resolution
and directory enumeration on the ww side.

Makefile: wwstage tool deps now include lib/os/os.ww (+ lib/strconv
for wwdump_ww) so lib/* edits force their rebuild instead of leaving
stale binaries — surfaced when test 995 first failed against a stale
w6c_ww built before the lib/os additions.

Test 993 byte-identical parity gate (C-side ww vs ww-side ww_ww on a
build corpus) stays green; all 19 tests pass.
This commit is contained in:
2026-05-11 13:49:27 +09:00
parent e217cd32d1
commit 2c33228b7e
96 changed files with 2129 additions and 973 deletions

107
cmd/w6c/6.out.h Normal file
View File

@@ -0,0 +1,107 @@
/*
* 6.out.h — amd64 instruction enum + register names. Mirrors the
* Plan 9 6c shape (cmd/6c/6.out.h) but trimmed to the subset that
* w6c emits and w6a consumes in this bootstrap. Each new opcode added
* here must also gain encoding support in cmd/w6a/asm.c.
*/
#ifndef SIX_OUT_H
#define SIX_OUT_H
/* registers — Plan 9 names; lowercase = 8-bit, etc. We use 64-bit. */
enum {
D_NONE = 0,
/* general purpose 64-bit */
D_AX, D_CX, D_DX, D_BX,
D_SP, D_BP, D_SI, D_DI,
D_R8, D_R9, D_R10, D_R11,
D_R12, D_R13, D_R14, D_R15,
/* SSE/XMM 64-bit float regs */
D_X0, D_X1, D_X2, D_X3,
D_X4, D_X5, D_X6, D_X7,
D_X8, D_X9, D_X10, D_X11,
D_X12, D_X13, D_X14, D_X15,
/* pseudo regs (Plan 9) */
D_PSP, /* SP pseudo (frame-relative) */
D_PFP, /* FP pseudo (incoming args) */
D_PSB, /* SB pseudo (static base) */
/* operand kinds; not registers but share the slot */
D_CONST, /* $N immediate */
D_BRANCH, /* label reference */
D_EXTERN, /* external symbol */
D_INDIR /* offset(reg) memory */
};
/* opcodes — the small set we currently emit & encode */
enum {
A_NOP = 0,
A_TEXT,
A_DATA,
A_GLOBL,
A_END,
A_MOVQ,
A_MOVL,
A_MOVB,
A_MOVZBQ, /* movzx r64, r/m8 — load byte zero-extended */
A_MOVSXD, /* movsxd r64, r/m32 — load i32 sign-extended */
/* SSE2 scalar double-precision float */
A_MOVSD, /* xmm/m → xmm and xmm → m */
A_ADDSD,
A_SUBSD,
A_MULSD,
A_DIVSD,
A_UCOMISD,
A_CVTTSD2SI, /* truncate f64 → i64 */
A_CVTSI2SD, /* convert i64 → f64 */
/* SSE scalar single-precision float (f32). Same xmm regs. */
A_MOVSS,
A_ADDSS,
A_SUBSS,
A_MULSS,
A_DIVSS,
A_UCOMISS,
A_CVTTSS2SI,
A_CVTSI2SS,
A_CVTSD2SS, /* f64 → f32 truncate */
A_CVTSS2SD, /* f32 → f64 widen */
A_ADDQ,
A_SUBQ,
A_IMULQ,
A_IDIVQ,
A_DIVQ, /* unsigned 64-bit divide; sibling of IDIVQ */
A_NEGQ,
A_NOTQ,
A_ANDQ,
A_ORQ,
A_XORQ,
A_SHLQ,
A_SHRQ,
A_CMPQ,
A_PUSHQ,
A_POPQ,
A_LEAQ,
A_CALL,
A_RET,
A_JMP,
A_JE, A_JNE,
A_JL, A_JLE, A_JG, A_JGE,
A_JB, A_JBE, A_JA, A_JAE,
A_JZ, A_JNZ,
A_SYSCALL,
A_LAST
};
const char *anames(int); /* opcode -> mnemonic */
const char *rnames(int); /* register -> name */
#endif

2687
cmd/w6c/cgen.c Normal file

File diff suppressed because it is too large Load Diff

56
cmd/w6c/gc.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* 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
#define SIX_GC_H
#include "ww.h"
#include "6.out.h"
typedef struct Prog Prog;
typedef struct Adr Adr;
/* one operand: register, immediate, indirect, or symbolic. */
struct Adr {
int type; /* D_AX, D_CONST, D_INDIR, ... */
int reg; /* base register for D_INDIR */
long long offset; /* immediate value or memory displacement */
const char *sym; /* symbol name for D_EXTERN/D_BRANCH */
};
struct Prog {
int as; /* opcode (A_MOVQ, ...) */
Adr from; /* source operand */
Adr to; /* destination operand (Plan 9 order) */
int line;
const char *label; /* if non-NULL, this prog is preceded by label: */
Prog *link;
};
/* per-fn codegen state */
typedef struct Cg Cg;
struct Cg {
Arena *a;
Prog *head, *tail;
const char *fnname;
int framesize; /* bytes of locals; 16-byte aligned */
int curoff; /* current top of locals */
Scope *locals; /* (name → offset) tracked via Sym */
int labelseq;
};
/* cgen.c */
void cg_init(Cg*, Arena*);
void cg_file(Cg*, FILE *out, Node *file);
Prog *newprog(Cg*, int op);
void emit(Cg*, Prog*);
/* txt.c */
void txt_emit(FILE*, Prog *head);
/* swt.c, peep.c, reg.c — placeholders for now */
void peephole(Cg*);
void regalloc_init(Cg*);
#endif

93
cmd/w6c/main.c Normal file
View File

@@ -0,0 +1,93 @@
/*
* 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>
#include <string.h>
static int
slurp(const char *path, char **outbuf, u64 *outlen)
{
FILE *f = fopen(path, "rb");
if (f == NULL) return -1;
fseek(f, 0, SEEK_END);
long n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n < 0) { fclose(f); return -1; }
char *b = malloc((size_t)n + 1);
if (b == NULL) { fclose(f); return -1; }
if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
b[n] = '\0';
fclose(f);
*outbuf = b;
*outlen = (u64)n;
return 0;
}
int
main(int argc, char **argv)
{
const char *src = NULL;
const char *out = NULL;
for (int i = 1; i < argc; i++) {
const char *a = argv[i];
if (strcmp(a, "-o") == 0 && i + 1 < argc) {
out = argv[++i];
} else if (a[0] == '-') {
fprintf(stderr, "w6c: unknown flag %s\n", a);
return 2;
} else if (src == NULL) {
src = a;
} else {
fprintf(stderr, "w6c: only one input supported\n");
return 2;
}
}
if (src == NULL) {
fputs("usage: w6c [-o out.s] file.ww\n", stderr);
return 2;
}
char *buf;
u64 len;
if (slurp(src, &buf, &len) < 0) {
fprintf(stderr, "w6c: %s: cannot read\n", src);
return 1;
}
Arena *a = newarena();
Lex l;
Parser p;
Checker c;
Cg cg;
lexinit(&l, a, src, buf, len);
parserinit(&p, a, &l);
Node *file = parsefile(&p);
if (l.errs || p.errs) return 1;
check_init(&c, a);
check_file(&c, file);
if (c.errs) return 1;
FILE *of = stdout;
if (out) {
of = fopen(out, "wb");
if (of == NULL) {
fprintf(stderr, "w6c: cannot open %s\n", out);
return 1;
}
}
cg_init(&cg, a);
cg_file(&cg, of, file);
if (of != stdout) fclose(of);
freearena(a);
free(buf);
return 0;
}

8
cmd/w6c/peep.c Normal file
View File

@@ -0,0 +1,8 @@
/*
* 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.
*/
#include "gc.h"
void peep_run(Cg *c) { (void)c; }

9
cmd/w6c/reg.c Normal file
View File

@@ -0,0 +1,9 @@
/*
* 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.
*/
#include "gc.h"
void reg_run(Cg *c) { (void)c; }

8
cmd/w6c/swt.c Normal file
View File

@@ -0,0 +1,8 @@
/*
* 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.
*/
#include "gc.h"
void swt_lower(Cg *c, Node *n) { (void)c; (void)n; }

189
cmd/w6c/txt.c Normal file
View File

@@ -0,0 +1,189 @@
/*
* 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
* MOVQ AX, name(SB) ; extern symbol
* MOVQ off(BP), AX ; local
* CMPQ AX, $0
* JE label
* RET
* label:
*
* Operand order is Plan 9-ish: source first, dest second. (Same as
* AT&T; the convention diverges from Plan 9 only on a few items we
* don't yet emit.)
*/
#include "gc.h"
#include <string.h>
const char *
anames(int op)
{
switch (op) {
case A_NOP: return "NOP";
case A_TEXT: return "TEXT";
case A_DATA: return "DATA";
case A_GLOBL: return "GLOBL";
case A_END: return "END";
case A_MOVQ: return "MOVQ";
case A_MOVL: return "MOVL";
case A_MOVB: return "MOVB";
case A_MOVZBQ: return "MOVZBQ";
case A_MOVSXD: return "MOVSXD";
case A_MOVSD: return "MOVSD";
case A_ADDSD: return "ADDSD";
case A_SUBSD: return "SUBSD";
case A_MULSD: return "MULSD";
case A_DIVSD: return "DIVSD";
case A_UCOMISD: return "UCOMISD";
case A_CVTTSD2SI: return "CVTTSD2SI";
case A_CVTSI2SD:return "CVTSI2SD";
case A_MOVSS: return "MOVSS";
case A_ADDSS: return "ADDSS";
case A_SUBSS: return "SUBSS";
case A_MULSS: return "MULSS";
case A_DIVSS: return "DIVSS";
case A_UCOMISS: return "UCOMISS";
case A_CVTTSS2SI: return "CVTTSS2SI";
case A_CVTSI2SS:return "CVTSI2SS";
case A_CVTSD2SS:return "CVTSD2SS";
case A_CVTSS2SD:return "CVTSS2SD";
case A_ADDQ: return "ADDQ";
case A_SUBQ: return "SUBQ";
case A_IMULQ: return "IMULQ";
case A_IDIVQ: return "IDIVQ";
case A_DIVQ: return "DIVQ";
case A_NEGQ: return "NEGQ";
case A_NOTQ: return "NOTQ";
case A_ANDQ: return "ANDQ";
case A_ORQ: return "ORQ";
case A_XORQ: return "XORQ";
case A_SHLQ: return "SHLQ";
case A_SHRQ: return "SHRQ";
case A_CMPQ: return "CMPQ";
case A_PUSHQ: return "PUSHQ";
case A_POPQ: return "POPQ";
case A_LEAQ: return "LEAQ";
case A_CALL: return "CALL";
case A_RET: return "RET";
case A_JMP: return "JMP";
case A_JE: return "JE";
case A_JNE: return "JNE";
case A_JL: return "JL";
case A_JLE: return "JLE";
case A_JG: return "JG";
case A_JGE: return "JGE";
case A_JB: return "JB";
case A_JBE: return "JBE";
case A_JA: return "JA";
case A_JAE: return "JAE";
case A_JZ: return "JZ";
case A_JNZ: return "JNZ";
case A_SYSCALL: return "SYSCALL";
}
return "??";
}
const char *
rnames(int r)
{
switch (r) {
case D_AX: return "AX"; case D_CX: return "CX";
case D_DX: return "DX"; case D_BX: return "BX";
case D_SP: return "SP"; case D_BP: return "BP";
case D_SI: return "SI"; case D_DI: return "DI";
case D_R8: return "R8"; case D_R9: return "R9";
case D_R10: return "R10"; case D_R11: return "R11";
case D_R12: return "R12"; case D_R13: return "R13";
case D_R14: return "R14"; case D_R15: return "R15";
case D_X0: return "X0"; case D_X1: return "X1";
case D_X2: return "X2"; case D_X3: return "X3";
case D_X4: return "X4"; case D_X5: return "X5";
case D_X6: return "X6"; case D_X7: return "X7";
case D_X8: return "X8"; case D_X9: return "X9";
case D_X10: return "X10"; case D_X11: return "X11";
case D_X12: return "X12"; case D_X13: return "X13";
case D_X14: return "X14"; case D_X15: return "X15";
case D_PSP: return "SP"; case D_PFP: return "FP"; case D_PSB: return "SB";
}
return "?";
}
static void
prAdr(FILE *f, Adr a)
{
switch (a.type) {
case D_NONE: fputs("?", f); break;
case D_CONST:
fprintf(f, "$%lld", a.offset);
break;
case D_INDIR:
if (a.offset)
fprintf(f, "%lld(%s)", a.offset, rnames(a.reg));
else
fprintf(f, "(%s)", rnames(a.reg));
break;
case D_BRANCH:
fputs(a.sym ? a.sym : "?", f);
break;
case D_EXTERN:
fprintf(f, "%s(SB)", a.sym ? a.sym : "?");
break;
default:
fputs(rnames(a.type), f);
}
}
void
txt_emit(FILE *f, Prog *head)
{
for (Prog *p = head; p; p = p->link) {
if (p->label) {
fprintf(f, "%s:\n", p->label);
if (p->as == A_NOP) continue;
}
switch (p->as) {
case A_NOP:
break;
case A_TEXT:
fprintf(f, "TEXT %s,$%lld\n",
p->to.sym ? p->to.sym : "?",
p->from.offset);
break;
case A_RET:
fputs("\tRET\n", f);
break;
case A_SYSCALL:
fputs("\tSYSCALL\n", f);
break;
case A_NEGQ:
case A_NOTQ:
case A_PUSHQ:
case A_POPQ:
case A_IDIVQ:
case A_DIVQ:
fprintf(f, "\t%s\t", anames(p->as));
prAdr(f, p->to);
fputc('\n', f);
break;
case A_CALL:
case A_JMP:
case A_JE: case A_JNE:
case A_JL: case A_JLE: case A_JG: case A_JGE:
case A_JB: case A_JBE: case A_JA: case A_JAE:
case A_JZ: case A_JNZ:
fprintf(f, "\t%s\t", anames(p->as));
prAdr(f, p->to);
fputc('\n', f);
break;
default:
fprintf(f, "\t%s\t", anames(p->as));
prAdr(f, p->from);
fputs(", ", f);
prAdr(f, p->to);
fputc('\n', f);
}
}
}