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:
405
cmd/w6a/parse.c
Normal file
405
cmd/w6a/parse.c
Normal file
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
* parse.c — line-oriented parser for the asm subset emitted by w6c.
|
||||
*
|
||||
* Grammar:
|
||||
* line := blank | comment | label | text | instr
|
||||
* blank := /^\s*$/
|
||||
* comment := /^\s*\/\/.*$/
|
||||
* label := /^IDENT:$/
|
||||
* text := TEXT name,$framesize
|
||||
* instr := \tMNEM\t[OP1[, OP2]]
|
||||
* OP := $NUM | REG | NUM(REG) | (REG) | name(SB) | label
|
||||
*
|
||||
* Identifiers may include '.' and '_'. Whitespace inside operands
|
||||
* (between '$' and a number, etc.) is rejected for sanity.
|
||||
*/
|
||||
#include "a.h"
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
extern int a_isidstart(int);
|
||||
extern int a_isidcont(int);
|
||||
extern i64 a_parsenum(const char *, char **);
|
||||
|
||||
void
|
||||
a_init(Asm *a, const char *file, const char *src, u64 len)
|
||||
{
|
||||
memset(a, 0, sizeof *a);
|
||||
a->file = file;
|
||||
a->src = src;
|
||||
a->srclen = len;
|
||||
a->line = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
err(Asm *a, const char *msg)
|
||||
{
|
||||
fprintf(stderr, "w6a: %s:%d: %s\n", a->file, a->line, msg);
|
||||
a->errs++;
|
||||
}
|
||||
|
||||
Asym *
|
||||
a_intern(Asm *a, const char *name)
|
||||
{
|
||||
for (Asym *s = a->syms; s; s = s->next)
|
||||
if (strcmp(s->name, name) == 0) return s;
|
||||
Asym *s = calloc(1, sizeof *s);
|
||||
s->name = strdup(name);
|
||||
s->next = a->syms;
|
||||
a->syms = s;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* line iterator: returns the next line as a NUL-terminated buffer in
|
||||
* line/llen pointers, advances pos. Returns 0 on EOF.
|
||||
*/
|
||||
static int
|
||||
nextline(Asm *a, char **line, size_t *llen, char *buf, size_t bufsz)
|
||||
{
|
||||
if (a->pos >= a->srclen) return 0;
|
||||
size_t n = 0;
|
||||
while (a->pos < a->srclen && a->src[a->pos] != '\n' && n + 1 < bufsz)
|
||||
buf[n++] = a->src[a->pos++];
|
||||
buf[n] = '\0';
|
||||
if (a->pos < a->srclen && a->src[a->pos] == '\n') a->pos++;
|
||||
*line = buf;
|
||||
*llen = n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* skip leading whitespace */
|
||||
static const char *
|
||||
skipws(const char *p)
|
||||
{
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
static int
|
||||
opcode_lookup(const char *m)
|
||||
{
|
||||
struct { const char *m; int op; } tab[] = {
|
||||
{ "MOVQ", A_MOVQ }, { "MOVL", A_MOVL },
|
||||
{ "MOVB", A_MOVB }, { "MOVZBQ", A_MOVZBQ },
|
||||
{ "MOVSXD", A_MOVSXD },
|
||||
{ "MOVSD", A_MOVSD },
|
||||
{ "ADDSD", A_ADDSD },{ "SUBSD", A_SUBSD },
|
||||
{ "MULSD", A_MULSD },{ "DIVSD", A_DIVSD },
|
||||
{ "UCOMISD", A_UCOMISD },
|
||||
{ "CVTTSD2SI", A_CVTTSD2SI },
|
||||
{ "CVTSI2SD", A_CVTSI2SD },
|
||||
{ "MOVSS", A_MOVSS },
|
||||
{ "ADDSS", A_ADDSS },{ "SUBSS", A_SUBSS },
|
||||
{ "MULSS", A_MULSS },{ "DIVSS", A_DIVSS },
|
||||
{ "UCOMISS", A_UCOMISS },
|
||||
{ "CVTTSS2SI", A_CVTTSS2SI },
|
||||
{ "CVTSI2SS", A_CVTSI2SS },
|
||||
{ "CVTSD2SS", A_CVTSD2SS },
|
||||
{ "CVTSS2SD", A_CVTSS2SD },
|
||||
{ "ADDQ", A_ADDQ }, { "SUBQ", A_SUBQ },
|
||||
{ "IMULQ",A_IMULQ},{ "IDIVQ",A_IDIVQ},
|
||||
{ "DIVQ", A_DIVQ },
|
||||
{ "NEGQ", A_NEGQ },{ "NOTQ", A_NOTQ },
|
||||
{ "ANDQ", A_ANDQ },{ "ORQ", A_ORQ },
|
||||
{ "XORQ", A_XORQ },
|
||||
{ "SHLQ", A_SHLQ },{ "SHRQ", A_SHRQ },
|
||||
{ "CMPQ", A_CMPQ },
|
||||
{ "PUSHQ",A_PUSHQ},{ "POPQ", A_POPQ },
|
||||
{ "LEAQ", A_LEAQ },
|
||||
{ "CALL", A_CALL },{ "RET", A_RET },
|
||||
{ "JMP", A_JMP },
|
||||
{ "JE", A_JE },{ "JNE", A_JNE },
|
||||
{ "JL", A_JL },{ "JLE", A_JLE },
|
||||
{ "JG", A_JG },{ "JGE", A_JGE },
|
||||
{ "JB", A_JB },{ "JBE", A_JBE },
|
||||
{ "JA", A_JA },{ "JAE", A_JAE },
|
||||
{ "JZ", A_JZ },{ "JNZ", A_JNZ },
|
||||
{ "SYSCALL", A_SYSCALL },
|
||||
{ "TEXT", A_TEXT },
|
||||
{ "DATA", A_DATA },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
for (int i = 0; tab[i].m; i++)
|
||||
if (strcmp(tab[i].m, m) == 0) return tab[i].op;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
reg_lookup(const char *r)
|
||||
{
|
||||
struct { const char *m; int reg; } tab[] = {
|
||||
{ "AX", D_AX }, { "BX", D_BX }, { "CX", D_CX }, { "DX", D_DX },
|
||||
{ "SP", D_SP }, { "BP", D_BP }, { "SI", D_SI }, { "DI", D_DI },
|
||||
{ "R8", D_R8 }, { "R9", D_R9 }, { "R10", D_R10 },
|
||||
{ "R11", D_R11 }, { "R12", D_R12 }, { "R13", D_R13 },
|
||||
{ "R14", D_R14 }, { "R15", D_R15 },
|
||||
{ "X0", D_X0 }, { "X1", D_X1 }, { "X2", D_X2 }, { "X3", D_X3 },
|
||||
{ "X4", D_X4 }, { "X5", D_X5 }, { "X6", D_X6 }, { "X7", D_X7 },
|
||||
{ "X8", D_X8 }, { "X9", D_X9 }, { "X10", D_X10 },
|
||||
{ "X11", D_X11 }, { "X12", D_X12 }, { "X13", D_X13 },
|
||||
{ "X14", D_X14 }, { "X15", D_X15 },
|
||||
{ "SB", D_PSB }, { "FP", D_PFP },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
for (int i = 0; tab[i].m; i++)
|
||||
if (strcmp(tab[i].m, r) == 0) return tab[i].reg;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_operand(Asm *a, const char *s, Aoperand *out)
|
||||
{
|
||||
while (*s == ' ' || *s == '\t') s++;
|
||||
if (*s == '\0') { out->type = D_NONE; return 0; }
|
||||
|
||||
if (*s == '$') {
|
||||
s++;
|
||||
char *end;
|
||||
out->type = D_CONST;
|
||||
out->offset = a_parsenum(s, &end);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* (REG) form */
|
||||
if (*s == '(') {
|
||||
s++;
|
||||
char rbuf[8] = {0};
|
||||
int n = 0;
|
||||
while (*s && *s != ')' && n < 7) rbuf[n++] = *s++;
|
||||
if (*s != ')') { err(a, "missing ')' in indirect"); return -1; }
|
||||
int r = reg_lookup(rbuf);
|
||||
if (r == 0) { err(a, "bad register in indirect"); return -1; }
|
||||
out->type = D_INDIR;
|
||||
out->reg = r;
|
||||
out->offset = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* number(REG) form, or label form, or REG */
|
||||
const char *p = s;
|
||||
int sign = 1;
|
||||
if (*p == '-') { sign = -1; p++; }
|
||||
if (isdigit((unsigned char)*p)) {
|
||||
char *end;
|
||||
i64 off = a_parsenum(s, &end);
|
||||
if (*end == '(') {
|
||||
char rbuf[8] = {0};
|
||||
int n = 0;
|
||||
end++;
|
||||
while (*end && *end != ')' && n < 7) rbuf[n++] = *end++;
|
||||
if (*end != ')') { err(a, "missing ')'"); return -1; }
|
||||
int r = reg_lookup(rbuf);
|
||||
if (r == 0) { err(a, "bad register"); return -1; }
|
||||
out->type = D_INDIR;
|
||||
out->reg = r;
|
||||
out->offset = off;
|
||||
return 0;
|
||||
}
|
||||
out->type = D_CONST;
|
||||
out->offset = off * sign;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* IDENT — register or symbol-or-label */
|
||||
if (a_isidstart((unsigned char)*s)) {
|
||||
char buf[64] = {0};
|
||||
int n = 0;
|
||||
while (a_isidcont((unsigned char)*s) && n < 63) buf[n++] = *s++;
|
||||
buf[n] = 0;
|
||||
|
||||
/* ID(SB) means external symbol */
|
||||
if (*s == '(') {
|
||||
char rbuf[8] = {0};
|
||||
int rn = 0;
|
||||
s++;
|
||||
while (*s && *s != ')' && rn < 7) rbuf[rn++] = *s++;
|
||||
if (*s != ')') { err(a, "missing ')'"); return -1; }
|
||||
s++;
|
||||
int r = reg_lookup(rbuf);
|
||||
if (r == D_PSB) {
|
||||
out->type = D_EXTERN;
|
||||
out->sym = strdup(buf);
|
||||
return 0;
|
||||
}
|
||||
out->type = D_INDIR;
|
||||
out->reg = r;
|
||||
out->offset = 0;
|
||||
/* unusual case: name(REG) with named offset; not used */
|
||||
return 0;
|
||||
}
|
||||
|
||||
int r = reg_lookup(buf);
|
||||
if (r != 0) {
|
||||
out->type = r;
|
||||
return 0;
|
||||
}
|
||||
/* otherwise it's a branch target */
|
||||
out->type = D_BRANCH;
|
||||
out->sym = strdup(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
err(a, "unrecognised operand");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
a_parse(Asm *a)
|
||||
{
|
||||
char buf[1024];
|
||||
char *line;
|
||||
size_t len;
|
||||
const char *pending_label = NULL;
|
||||
|
||||
while (nextline(a, &line, &len, buf, sizeof buf)) {
|
||||
const char *p = skipws(line);
|
||||
if (*p == '\0' || (*p == '/' && p[1] == '/')) {
|
||||
a->line++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* label? */
|
||||
if (a_isidstart((unsigned char)*p) && line[0] != '\t') {
|
||||
const char *q = p;
|
||||
while (a_isidcont((unsigned char)*q)) q++;
|
||||
if (*q == ':') {
|
||||
size_t nl = q - p;
|
||||
char *name = malloc(nl + 1);
|
||||
memcpy(name, p, nl);
|
||||
name[nl] = '\0';
|
||||
/* If a label is already pending we'd lose it
|
||||
* by overwriting; flush it onto a NOP prog so
|
||||
* each label still pins to a real address. */
|
||||
if (pending_label) {
|
||||
Aprog *prg = calloc(1, sizeof *prg);
|
||||
prg->as = A_NOP;
|
||||
prg->label = pending_label;
|
||||
prg->line = a->line;
|
||||
if (a->head == NULL) a->head = prg;
|
||||
else a->tail->link = prg;
|
||||
a->tail = prg;
|
||||
}
|
||||
pending_label = name;
|
||||
a->line++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* TEXT or instruction */
|
||||
const char *m = p;
|
||||
char mnem[16] = {0};
|
||||
int n = 0;
|
||||
while (*m && *m != ' ' && *m != '\t' && n < 15) mnem[n++] = *m++;
|
||||
mnem[n] = '\0';
|
||||
|
||||
int op = opcode_lookup(mnem);
|
||||
if (op == 0) {
|
||||
err(a, "unknown opcode");
|
||||
a->line++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Aprog *prg = calloc(1, sizeof *prg);
|
||||
prg->as = op;
|
||||
prg->line = a->line;
|
||||
prg->label = pending_label;
|
||||
pending_label = NULL;
|
||||
|
||||
while (*m == ' ' || *m == '\t') m++;
|
||||
const char *rest = m;
|
||||
|
||||
if (op == A_TEXT) {
|
||||
/* TEXT name,$framesize */
|
||||
char nbuf[64] = {0};
|
||||
int nn = 0;
|
||||
while (*m && *m != ',' && nn < 63) nbuf[nn++] = *m++;
|
||||
prg->to.type = D_EXTERN;
|
||||
prg->to.sym = strdup(nbuf);
|
||||
if (*m == ',') {
|
||||
m++;
|
||||
while (*m == ' ' || *m == '$') m++;
|
||||
prg->from.type = D_CONST;
|
||||
prg->from.offset = a_parsenum(m, NULL);
|
||||
}
|
||||
} else if (op == A_DATA) {
|
||||
/* DATA name(SB),"escaped bytes" */
|
||||
char nbuf[128] = {0};
|
||||
int nn = 0;
|
||||
while (*m && *m != '(' && nn < 127) nbuf[nn++] = *m++;
|
||||
prg->to.type = D_EXTERN;
|
||||
prg->to.sym = strdup(nbuf);
|
||||
if (*m == '(') {
|
||||
while (*m && *m != ')') m++;
|
||||
if (*m == ')') m++;
|
||||
}
|
||||
while (*m == ' ' || *m == ',' || *m == '\t') m++;
|
||||
if (*m != '"') {
|
||||
err(a, "DATA expects \"...\"");
|
||||
prg->bytes = NULL;
|
||||
prg->nbytes = 0;
|
||||
} else {
|
||||
m++;
|
||||
/* parse escapes into a fresh buffer */
|
||||
size_t cap = 32, len = 0;
|
||||
u8 *buf = malloc(cap);
|
||||
while (*m && *m != '"') {
|
||||
int c = (unsigned char)*m++;
|
||||
if (c == '\\' && *m) {
|
||||
int e = (unsigned char)*m++;
|
||||
switch (e) {
|
||||
case 'n': c = '\n'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case '\\': c = '\\'; break;
|
||||
case '"': c = '"'; break;
|
||||
case '0': c = 0; break;
|
||||
case 'x': {
|
||||
int hi = (unsigned char)*m++;
|
||||
int lo = (unsigned char)*m++;
|
||||
int h = (hi<='9'?hi-'0':(hi|0x20)-'a'+10);
|
||||
int l = (lo<='9'?lo-'0':(lo|0x20)-'a'+10);
|
||||
c = (h << 4) | l;
|
||||
break;
|
||||
}
|
||||
default: c = e; break;
|
||||
}
|
||||
}
|
||||
if (len + 1 > cap) {
|
||||
cap *= 2;
|
||||
buf = realloc(buf, cap);
|
||||
}
|
||||
buf[len++] = (u8)c;
|
||||
}
|
||||
prg->bytes = buf;
|
||||
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;
|
||||
if (comma) {
|
||||
char op1[256], op2[256];
|
||||
size_t l1 = comma - rest;
|
||||
if (l1 >= sizeof op1) l1 = sizeof op1 - 1;
|
||||
memcpy(op1, rest, l1); op1[l1] = '\0';
|
||||
size_t l2 = strlen(comma + 1);
|
||||
if (l2 >= sizeof op2) l2 = sizeof op2 - 1;
|
||||
memcpy(op2, comma + 1, l2); op2[l2] = '\0';
|
||||
parse_operand(a, op1, &prg->from);
|
||||
parse_operand(a, op2, &prg->to);
|
||||
} else if (*rest) {
|
||||
parse_operand(a, rest, &prg->to);
|
||||
}
|
||||
}
|
||||
|
||||
if (a->head == NULL) a->head = prg;
|
||||
else a->tail->link = prg;
|
||||
a->tail = prg;
|
||||
a->line++;
|
||||
}
|
||||
return a->errs;
|
||||
}
|
||||
Reference in New Issue
Block a user