/* * 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 #include #include #include 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 }, { "MOVW", A_MOVW }, { "MOVB", A_MOVB }, { "MOVZBQ", A_MOVZBQ }, { "MOVZWQ", A_MOVZWQ }, { "MOVSXD", A_MOVSXD }, { "MOVSWQ", A_MOVSWQ }, { "MOVSBQ", A_MOVSBQ }, { "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 },{ "CQO", A_CQO }, { "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 }, { "DATAW", A_DATAW }, { "DATAR", A_DATAR }, { 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; /* Optional `+disp` between the ident and `(SB)`. Used by * DATAR to address bytes inside an existing .data slot * (e.g. `DATAR s+8(SB),...`). */ i64 sym_disp = 0; if (*s == '+') { s++; char *end; sym_disp = a_parsenum(s, &end); s = end; } /* 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); out->offset = sym_disp; 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) { /* Big enough for a DATAW emitting a [256]u32 table (1024 bytes * → ~4100 chars of `\xNN` escapes plus directive boilerplate). * Selfhost w6a allocates per-line; this is the cstage equivalent. */ static char buf[32768]; 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 || op == A_DATAW) { /* DATA name(SB),"escaped bytes" — read-only in .text * DATAW name(SB),"escaped bytes" — writable in .data */ 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; }