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/w6a/a.h Normal file
View File

@@ -0,0 +1,107 @@
/*
* 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.
*/
#ifndef SIX_A_H
#define SIX_A_H
#include "6.out.h"
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef struct Aoperand Aoperand;
typedef struct Aprog Aprog;
typedef struct Asym Asym;
typedef struct Areloc Areloc;
typedef struct Asm Asm;
struct Aoperand {
int type; /* D_NONE, D_AX..D_R15, D_CONST, D_INDIR, D_EXTERN, D_BRANCH */
int reg; /* base register for D_INDIR */
i64 offset; /* immediate or displacement */
const char *sym;
};
struct Aprog {
int as; /* opcode (A_*) */
Aoperand from;
Aoperand to;
int line;
const char *label; /* label preceding this prog, if any */
Aprog *link;
/* for A_DATA: raw payload bytes interned by the parser */
u8 *bytes;
u64 nbytes;
};
struct Asym {
const char *name;
int defined; /* 1 if we own its address */
int is_text; /* if 1, address is in .text */
int is_global; /* exported (TEXT) */
u64 addr; /* offset within section if defined */
int idx; /* ELF symtab index, filled at emit time */
Asym *next;
};
struct Areloc {
u64 off; /* offset within .text where relocation lands */
int kind; /* R_X86_64_PLT32 (4), R_X86_64_PC32 (2) */
Asym *sym;
i64 addend;
Areloc *next;
};
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;
/* symbols */
Asym *syms;
Areloc *relocs;
int errs;
};
/* lex.c / parse.c */
void a_init(Asm*, const char *file, const char *src, u64 len);
int a_parse(Asm*);
/* asm.c */
int a_encode(Asm*);
/* obj.c */
int a_emit_elf(Asm*, FILE *out);
/* helpers */
Asym *a_intern(Asm*, const char *name);
void a_emit_byte(Asm*, u8);
void a_emit_u32(Asm*, u32);
void a_addreloc(Asm*, u64 off, int kind, Asym *s, i64 add);
#endif

689
cmd/w6a/asm.c Normal file
View File

@@ -0,0 +1,689 @@
/*
* 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]
* MOVQ reg, reg — 89 /r (REX.W)
* MOVQ off(reg), reg — 8B /r (REX.W)
* MOVQ reg, off(reg) — 89 /r (REX.W)
* ADDQ/SUBQ/AND/OR/XOR — 01/29/21/09/31 /r (REX.W) [reg→reg]
* ADDQ $imm, reg — 81 /0 imm32 (REX.W)
* SUBQ $imm, reg — 81 /5 imm32 (REX.W) (likewise CMPQ)
* IMULQ reg, reg — 0F AF /r (REX.W)
* IDIVQ reg — F7 /7 (REX.W)
* DIVQ reg — F7 /6 (REX.W) (unsigned)
* NEGQ/NOTQ reg — F7 /3, F7 /2 (REX.W)
* SHLQ/SHRQ CL, reg — D3 /4, D3 /5 (REX.W)
* CMPQ reg, reg — 39 /r (REX.W)
* CMPQ $imm, reg — 81 /7 imm32 (REX.W)
* PUSHQ reg — 50+rd (REX.B for high)
* POPQ reg — 58+rd (REX.B for high)
* LEAQ name(SB), reg — 48 8D /r RIP-relative; reloc PC32
* LEAQ off(reg), reg — 48 8D /r
* CALL name(SB) — E8 cd reloc PLT32
* CALL reg — FF /2 (REX.W not strictly needed)
* RET — C3
* JMP/Jcc label — E9 cd / 0F 8x cd rel32 to local label
* SYSCALL — 0F 05
*/
#include "a.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void
a_emit_byte(Asm *a, u8 b)
{
if (a->textlen + 1 > a->textcap) {
u64 nc = a->textcap ? a->textcap * 2 : 4096;
a->text = realloc(a->text, nc);
a->textcap = nc;
}
a->text[a->textlen++] = b;
}
void
a_emit_u32(Asm *a, u32 v)
{
a_emit_byte(a, (u8)(v & 0xff));
a_emit_byte(a, (u8)((v >> 8) & 0xff));
a_emit_byte(a, (u8)((v >> 16) & 0xff));
a_emit_byte(a, (u8)((v >> 24) & 0xff));
}
void
a_addreloc(Asm *a, u64 off, int kind, Asym *s, i64 add)
{
Areloc *r = calloc(1, sizeof *r);
r->off = off;
r->kind = kind;
r->sym = s;
r->addend = add;
r->next = a->relocs;
a->relocs = r;
}
/* ------ register codes ------------------------------------------- */
/* low 3 bits of register encoding */
static int
rcode(int r)
{
switch (r) {
case D_AX: return 0; case D_CX: return 1;
case D_DX: return 2; case D_BX: return 3;
case D_SP: return 4; case D_BP: return 5;
case D_SI: return 6; case D_DI: return 7;
case D_R8: return 0; case D_R9: return 1;
case D_R10:return 2; case D_R11:return 3;
case D_R12:return 4; case D_R13:return 5;
case D_R14:return 6; case D_R15:return 7;
case D_X0: return 0; case D_X1: return 1;
case D_X2: return 2; case D_X3: return 3;
case D_X4: return 4; case D_X5: return 5;
case D_X6: return 6; case D_X7: return 7;
case D_X8: return 0; case D_X9: return 1;
case D_X10:return 2; case D_X11:return 3;
case D_X12:return 4; case D_X13:return 5;
case D_X14:return 6; case D_X15:return 7;
}
return 0;
}
/* 1 if r needs the high bit (REX.R or REX.B) */
static int
rhi(int r)
{
if (r >= D_R8 && r <= D_R15) return 1;
if (r >= D_X8 && r <= D_X15) return 1;
return 0;
}
static int
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)
{
u8 b = 0x40;
if (w) b |= 0x08;
if (regbit) b |= 0x04;
if (rmbit) b |= 0x01;
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).
*/
static void
emit_modrm_mem(Asm *a, int reg_field, int base, i64 disp)
{
int rm = rcode(base);
int mod;
int needsib = (rm == 4); /* SP requires SIB */
int forced_disp = (rm == 5 && disp == 0); /* BP needs explicit disp8 */
if (disp == 0 && !forced_disp) mod = 0;
else if (disp >= -128 && disp <= 127) mod = 1;
else mod = 2;
a_emit_byte(a, modrm(mod, reg_field, rm));
if (needsib)
a_emit_byte(a, (u8)(0x24)); /* SIB: scale=0 idx=4(none) base=4 */
if (mod == 1)
a_emit_byte(a, (u8)(disp & 0xff));
else if (mod == 2)
a_emit_u32(a, (u32)disp);
}
/* Plan 9 op order: src, dst. Generic two-reg encoding for ops that
* use the standard "reg, r/m" form (89 /r, 01 /r, etc.) — opcode
* implies the REX.W and the direction; we emit "src register goes
* into reg field, dst register into rm field". */
static void
encode_rr(Asm *a, u8 opcode, int src, int dst)
{
emit_rex(a, rhi(src), rhi(dst), 1);
a_emit_byte(a, opcode);
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)
{
emit_rex(a, rhi(src_reg), rhi(base), 1);
a_emit_byte(a, opcode);
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)
{
emit_rex(a, rhi(dst_reg), rhi(base), 1);
a_emit_byte(a, opcode);
emit_modrm_mem(a, rcode(dst_reg), base, disp);
}
/* OPCODE /n imm32 reg form. E.g. ADDQ $imm, reg */
static void
encode_ri_imm32(Asm *a, u8 opcode, int subop, int dst, i32 imm)
{
emit_rex(a, 0, rhi(dst), 1);
a_emit_byte(a, opcode);
a_emit_byte(a, modrm(3, subop, rcode(dst)));
a_emit_u32(a, (u32)imm);
}
/* unary-on-reg: F7 /n reg, etc. */
static void
encode_unary(Asm *a, u8 opcode, int subop, int dst)
{
emit_rex(a, 0, rhi(dst), 1);
a_emit_byte(a, opcode);
a_emit_byte(a, modrm(3, subop, rcode(dst)));
}
/* SSE2 helpers. Plan 9 syntax: source first, destination second.
* For ADDSD-style ops we put dst in the reg field, src in r/m. */
static void
sse_rr(Asm *a, u8 prefix, u8 op2, int reg_op, int rm_op)
{
if (prefix) a_emit_byte(a, prefix);
emit_rex(a, rhi(reg_op), rhi(rm_op), 0);
a_emit_byte(a, 0x0F);
a_emit_byte(a, op2);
a_emit_byte(a, modrm(3, rcode(reg_op), rcode(rm_op)));
}
static void
sse_mr_load(Asm *a, u8 prefix, u8 op2, int reg_op, int base, i64 disp)
{
if (prefix) a_emit_byte(a, prefix);
emit_rex(a, rhi(reg_op), rhi(base), 0);
a_emit_byte(a, 0x0F);
a_emit_byte(a, op2);
emit_modrm_mem(a, rcode(reg_op), base, disp);
}
/* like sse_mr_load 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)
{
if (prefix) a_emit_byte(a, prefix);
emit_rex(a, rhi(reg_op), rhi(rm_op), 1);
a_emit_byte(a, 0x0F);
a_emit_byte(a, op2);
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)
{
for (Asym *s = a->syms; s; s = s->next)
if (s->defined && strcmp(s->name, name) == 0)
return s->addr;
return 0;
}
static int
label_defined(Asm *a, const char *name)
{
for (Asym *s = a->syms; s; s = s->next)
if (s->defined && strcmp(s->name, name) == 0) return 1;
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;
struct Fixup {
u64 off; /* where the rel32 lands */
const char *label;
Fixup *next;
};
static Fixup *fixups;
static void
add_fixup(u64 off, const char *label)
{
Fixup *f = calloc(1, sizeof *f);
f->off = off;
f->label = strdup(label);
f->next = fixups;
fixups = f;
}
int
a_encode(Asm *a)
{
fixups = NULL;
const char *cur_text = NULL; /* current TEXT name */
(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;
s->is_text = 1;
s->addr = a->textlen;
}
switch (p->as) {
case A_NOP:
break;
case A_TEXT: {
Asym *s = a_intern(a, p->to.sym);
s->defined = 1;
s->is_text = 1;
s->is_global = 1;
s->addr = a->textlen;
cur_text = p->to.sym;
break;
}
case A_DATA: {
Asym *s = a_intern(a, p->to.sym);
s->defined = 1;
s->is_text = 1; /* we lay it out at the end of .text */
s->is_global = 1;
s->addr = a->textlen;
for (u64 i = 0; i < p->nbytes; i++)
a_emit_byte(a, p->bytes[i]);
break;
}
case A_RET:
a_emit_byte(a, 0xC3);
break;
case A_SYSCALL:
a_emit_byte(a, 0x0F); a_emit_byte(a, 0x05);
break;
case A_PUSHQ:
if (rhi(p->to.type)) a_emit_byte(a, 0x41);
a_emit_byte(a, (u8)(0x50 + rcode(p->to.type)));
break;
case A_POPQ:
if (rhi(p->to.type)) a_emit_byte(a, 0x41);
a_emit_byte(a, (u8)(0x58 + rcode(p->to.type)));
break;
case A_NEGQ:
encode_unary(a, 0xF7, 3, p->to.type); break;
case A_NOTQ:
encode_unary(a, 0xF7, 2, p->to.type); break;
case A_IDIVQ:
encode_unary(a, 0xF7, 7, p->to.type); break;
case A_DIVQ:
/* unsigned divide; shares the F7 group with IDIVQ but
* uses /6 instead of /7. */
encode_unary(a, 0xF7, 6, p->to.type); break;
case A_MOVQ:
if (p->from.type == D_CONST && p->to.type >= D_AX && p->to.type <= D_R15) {
i64 v = p->from.offset;
if (v >= -2147483648LL && v <= 2147483647LL) {
/* C7 /0 imm32, sign-extended */
encode_ri_imm32(a, 0xC7, 0, p->to.type, (i32)v);
} else {
/* movabs r64, imm64: REX.W B8+rd imm64 */
emit_rex(a, 0, rhi(p->to.type), 1);
a_emit_byte(a, (u8)(0xB8 + rcode(p->to.type)));
for (int k = 0; k < 8; k++)
a_emit_byte(a, (u8)((v >> (k * 8)) & 0xff));
}
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type >= D_AX && p->to.type <= D_R15) {
encode_rr(a, 0x89, p->from.type, p->to.type);
} else if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15) {
encode_mr(a, 0x8B, p->to.type, p->from.reg, p->from.offset);
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type == D_INDIR) {
encode_rm(a, 0x89, p->from.type, p->to.reg, p->to.offset);
} else if (p->from.type == D_CONST
&& p->to.type == D_INDIR) {
/* MOVQ $imm32, r/m64 — C7 /0 (REX.W) imm32.
* The CPU sign-extends imm32 into 64 bits, so
* any value within i32 range works. */
emit_rex(a, 0, rhi(p->to.reg), 1);
a_emit_byte(a, 0xC7);
emit_modrm_mem(a, 0, p->to.reg, p->to.offset);
a_emit_u32(a, (u32)(i32)p->from.offset);
} else if (p->from.type == D_EXTERN
&& p->to.type >= D_AX && p->to.type <= D_R15) {
/* RIP-relative load: 48 8B /r mod=00 rm=5 disp32 */
emit_rex(a, rhi(p->to.type), 0, 1);
a_emit_byte(a, 0x8B);
a_emit_byte(a, modrm(0, rcode(p->to.type), 5));
u64 reloff = a->textlen;
a_emit_u32(a, 0);
Asym *s = a_intern(a, p->from.sym);
a_addreloc(a, reloff, 2, s, -4);
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type == D_EXTERN) {
/* RIP-relative store: 48 89 /r mod=00 rm=5 disp32 */
emit_rex(a, rhi(p->from.type), 0, 1);
a_emit_byte(a, 0x89);
a_emit_byte(a, modrm(0, rcode(p->from.type), 5));
u64 reloff = a->textlen;
a_emit_u32(a, 0);
Asym *s = a_intern(a, p->to.sym);
a_addreloc(a, reloff, 2, s, -4);
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVQ shape\n", p->line);
a->errs++;
}
break;
case A_MOVB:
/* MOV r/m8, r8 — 88 /r. No REX.W. We always emit REX
* to allow access to SIL/DIL/BPL/SPL. */
if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type == D_INDIR) {
emit_rex(a, rhi(p->from.type), rhi(p->to.reg), 0);
a_emit_byte(a, 0x88);
emit_modrm_mem(a, rcode(p->from.type),
p->to.reg, p->to.offset);
} else if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 0);
a_emit_byte(a, 0x8A); /* MOV r8, r/m8 */
emit_modrm_mem(a, rcode(p->to.type),
p->from.reg, p->from.offset);
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVB shape\n", p->line);
a->errs++;
}
break;
case A_MOVZBQ:
/* MOVZX r64, r/m8 — 0F B6 /r with REX.W */
if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 1);
a_emit_byte(a, 0x0F);
a_emit_byte(a, 0xB6);
emit_modrm_mem(a, rcode(p->to.type),
p->from.reg, p->from.offset);
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVZBQ shape\n", p->line);
a->errs++;
}
break;
case A_MOVL:
/* MOV r/m32, r32 (89 /r) and MOV r32, r/m32 (8B /r),
* both without REX.W. The CPU zero-extends 32-bit ops
* into the 64-bit reg, so reads of u32 fields are safe.
* Sign-extension lives in MOVSXD. */
if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type == D_INDIR) {
emit_rex(a, rhi(p->from.type), rhi(p->to.reg), 0);
a_emit_byte(a, 0x89);
emit_modrm_mem(a, rcode(p->from.type),
p->to.reg, p->to.offset);
} else if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 0);
a_emit_byte(a, 0x8B);
emit_modrm_mem(a, rcode(p->to.type),
p->from.reg, p->from.offset);
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->from.type), rhi(p->to.type), 0);
a_emit_byte(a, 0x89);
a_emit_byte(a, modrm(3,
rcode(p->from.type), rcode(p->to.type)));
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVL shape\n", p->line);
a->errs++;
}
break;
case A_MOVSXD:
/* MOVSXD r64, r/m32 — 63 /r with REX.W */
if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15) {
emit_rex(a, rhi(p->to.type), rhi(p->from.reg), 1);
a_emit_byte(a, 0x63);
emit_modrm_mem(a, rcode(p->to.type),
p->from.reg, p->from.offset);
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVSXD shape\n", p->line);
a->errs++;
}
break;
case A_MOVSD:
/* xmm←mem (load): F2 0F 10 /r */
/* xmm←xmm: F2 0F 10 /r */
/* mem←xmm (store):F2 0F 11 /r */
if (is_xmm(p->from.type) && is_xmm(p->to.type)) {
sse_rr(a, 0xF2, 0x10, p->to.type, p->from.type);
} else if (p->from.type == D_INDIR && is_xmm(p->to.type)) {
sse_mr_load(a, 0xF2, 0x10, p->to.type,
p->from.reg, p->from.offset);
} else if (is_xmm(p->from.type) && p->to.type == D_INDIR) {
sse_mr_load(a, 0xF2, 0x11, p->from.type,
p->to.reg, p->to.offset);
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVSD shape\n", p->line);
a->errs++;
}
break;
case A_ADDSD:
sse_rr(a, 0xF2, 0x58, p->to.type, p->from.type);
break;
case A_SUBSD:
sse_rr(a, 0xF2, 0x5C, p->to.type, p->from.type);
break;
case A_MULSD:
sse_rr(a, 0xF2, 0x59, p->to.type, p->from.type);
break;
case A_DIVSD:
sse_rr(a, 0xF2, 0x5E, p->to.type, p->from.type);
break;
case A_UCOMISD:
sse_rr(a, 0x66, 0x2E, p->to.type, p->from.type);
break;
case A_CVTTSD2SI:
/* int_reg ← xmm: F2 REX.W 0F 2C /r ; reg=int rm=xmm */
sse_rr_w(a, 0xF2, 0x2C, p->to.type, p->from.type);
break;
case A_CVTSI2SD:
/* xmm ← int_reg: F2 REX.W 0F 2A /r ; reg=xmm rm=int */
sse_rr_w(a, 0xF2, 0x2A, p->to.type, p->from.type);
break;
case A_MOVSS:
if (is_xmm(p->from.type) && is_xmm(p->to.type)) {
sse_rr(a, 0xF3, 0x10, p->to.type, p->from.type);
} else if (p->from.type == D_INDIR && is_xmm(p->to.type)) {
sse_mr_load(a, 0xF3, 0x10, p->to.type,
p->from.reg, p->from.offset);
} else if (is_xmm(p->from.type) && p->to.type == D_INDIR) {
sse_mr_load(a, 0xF3, 0x11, p->from.type,
p->to.reg, p->to.offset);
} else {
fprintf(stderr, "w6a: line %d: unsupported MOVSS shape\n", p->line);
a->errs++;
}
break;
case A_ADDSS:
sse_rr(a, 0xF3, 0x58, p->to.type, p->from.type); break;
case A_SUBSS:
sse_rr(a, 0xF3, 0x5C, p->to.type, p->from.type); break;
case A_MULSS:
sse_rr(a, 0xF3, 0x59, p->to.type, p->from.type); break;
case A_DIVSS:
sse_rr(a, 0xF3, 0x5E, p->to.type, p->from.type); break;
case A_UCOMISS:
sse_rr(a, 0x00, 0x2E, p->to.type, p->from.type); break;
case A_CVTTSS2SI:
sse_rr_w(a, 0xF3, 0x2C, p->to.type, p->from.type); break;
case A_CVTSI2SS:
sse_rr_w(a, 0xF3, 0x2A, p->to.type, p->from.type); break;
case A_CVTSD2SS:
/* xmm←xmm: F2 0F 5A /r ; reg=dst rm=src */
sse_rr(a, 0xF2, 0x5A, p->to.type, p->from.type); break;
case A_CVTSS2SD:
sse_rr(a, 0xF3, 0x5A, p->to.type, p->from.type); break;
case A_ADDQ:
if (p->from.type == D_CONST
&& p->to.type >= D_AX && p->to.type <= D_R15)
encode_ri_imm32(a, 0x81, 0, p->to.type, (i32)p->from.offset);
else if (p->from.type == D_CONST && p->to.type == D_INDIR) {
/* ADD r/m64, imm32 — 81 /0 (REX.W) */
emit_rex(a, 0, rhi(p->to.reg), 1);
a_emit_byte(a, 0x81);
emit_modrm_mem(a, 0, p->to.reg, p->to.offset);
a_emit_u32(a, (u32)(i32)p->from.offset);
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type == D_INDIR)
encode_rm(a, 0x01, p->from.type, p->to.reg, p->to.offset);
else if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15)
encode_mr(a, 0x03, p->to.type, p->from.reg, p->from.offset);
else
encode_rr(a, 0x01, p->from.type, p->to.type);
break;
case A_SUBQ:
if (p->from.type == D_CONST
&& p->to.type >= D_AX && p->to.type <= D_R15)
encode_ri_imm32(a, 0x81, 5, p->to.type, (i32)p->from.offset);
else if (p->from.type == D_CONST && p->to.type == D_INDIR) {
emit_rex(a, 0, rhi(p->to.reg), 1);
a_emit_byte(a, 0x81);
emit_modrm_mem(a, 5, p->to.reg, p->to.offset);
a_emit_u32(a, (u32)(i32)p->from.offset);
} else if (p->from.type >= D_AX && p->from.type <= D_R15
&& p->to.type == D_INDIR)
encode_rm(a, 0x29, p->from.type, p->to.reg, p->to.offset);
else if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15)
encode_mr(a, 0x2B, p->to.type, p->from.reg, p->from.offset);
else
encode_rr(a, 0x29, p->from.type, p->to.type);
break;
case A_ANDQ: encode_rr(a, 0x21, p->from.type, p->to.type); break;
case A_ORQ: encode_rr(a, 0x09, p->from.type, p->to.type); break;
case A_XORQ:
if (p->from.type == D_CONST
&& p->to.type >= D_AX && p->to.type <= D_R15)
encode_ri_imm32(a, 0x81, 6, p->to.type, (i32)p->from.offset);
else
encode_rr(a, 0x31, p->from.type, p->to.type);
break;
case A_IMULQ:
emit_rex(a, rhi(p->to.type), rhi(p->from.type), 1);
a_emit_byte(a, 0x0F); a_emit_byte(a, 0xAF);
a_emit_byte(a, modrm(3, rcode(p->to.type), rcode(p->from.type)));
break;
case A_SHLQ:
encode_unary(a, 0xD3, 4, p->to.type); break;
case A_SHRQ:
encode_unary(a, 0xD3, 5, p->to.type); break;
case A_CMPQ:
if (p->from.type == D_CONST && p->to.type >= D_AX && p->to.type <= D_R15)
encode_ri_imm32(a, 0x81, 7, p->to.type, (i32)p->from.offset);
else
encode_rr(a, 0x39, p->from.type, p->to.type);
break;
case A_LEAQ:
if (p->from.type == D_INDIR
&& p->to.type >= D_AX && p->to.type <= D_R15) {
encode_mr(a, 0x8D, p->to.type, p->from.reg, p->from.offset);
} else if (p->from.type == D_EXTERN
&& p->to.type >= D_AX && p->to.type <= D_R15) {
/* RIP-relative: 48 8D /r mod=00 rm=5 disp32 */
emit_rex(a, rhi(p->to.type), 0, 1);
a_emit_byte(a, 0x8D);
a_emit_byte(a, modrm(0, rcode(p->to.type), 5));
u64 reloff = a->textlen;
a_emit_u32(a, 0);
Asym *s = a_intern(a, p->from.sym);
/* R_X86_64_PC32 (2) with addend -4 */
a_addreloc(a, reloff, 2, s, -4);
}
break;
case A_CALL:
if (p->to.type == D_EXTERN) {
a_emit_byte(a, 0xE8);
u64 reloff = a->textlen;
a_emit_u32(a, 0);
Asym *s = a_intern(a, p->to.sym);
/* 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);
} else if (p->to.type >= D_AX && p->to.type <= D_R15) {
if (rhi(p->to.type)) a_emit_byte(a, 0x41);
a_emit_byte(a, 0xFF);
a_emit_byte(a, modrm(3, 2, rcode(p->to.type)));
}
break;
case A_JMP:
a_emit_byte(a, 0xE9);
add_fixup(a->textlen, p->to.sym);
a_emit_u32(a, 0);
break;
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: {
u8 cc = 0;
switch (p->as) {
case A_JE: case A_JZ: cc = 0x84; break;
case A_JNE: case A_JNZ: cc = 0x85; break;
case A_JL: cc = 0x8C; break;
case A_JLE: cc = 0x8E; break;
case A_JG: cc = 0x8F; break;
case A_JGE: cc = 0x8D; break;
case A_JB: cc = 0x82; break;
case A_JBE: cc = 0x86; break;
case A_JA: cc = 0x87; break;
case A_JAE: cc = 0x83; break;
default: break;
}
a_emit_byte(a, 0x0F);
a_emit_byte(a, cc);
add_fixup(a->textlen, p->to.sym);
a_emit_u32(a, 0);
break;
}
default:
fprintf(stderr, "w6a: unsupported opcode %d on line %d\n", p->as, p->line);
a->errs++;
}
}
/* 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);
a->errs++;
continue;
}
u64 target = resolve_label(a, f->label);
i64 rel = (i64)target - ((i64)f->off + 4);
i32 rel32 = (i32)rel;
a->text[f->off + 0] = (u8)(rel32 & 0xff);
a->text[f->off + 1] = (u8)((rel32 >> 8) & 0xff);
a->text[f->off + 2] = (u8)((rel32 >> 16) & 0xff);
a->text[f->off + 3] = (u8)((rel32 >> 24) & 0xff);
}
return a->errs;
}

31
cmd/w6a/lex.c Normal file
View File

@@ -0,0 +1,31 @@
/*
* 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>
#include <string.h>
int
a_isidstart(int c)
{
return c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
int
a_isidcont(int c)
{
return a_isidstart(c) || (c >= '0' && c <= '9') || c == '.';
}
i64
a_parsenum(const char *s, char **end)
{
/* Let strtoll handle the sign itself: hand-stripping '-' then
* negating the result fails for LLONG_MIN because the positive
* magnitude (2^63) doesn't fit in long long, strtoll clamps to
* LLONG_MAX, and the negation lands one short. */
return (i64)strtoll(s, end, 0);
}

59
cmd/w6a/main.c Normal file
View File

@@ -0,0 +1,59 @@
/*
* w6a — amd64 assembler driver. Read .s, parse, encode, emit ELF .o.
*/
#include "a.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int
slurp(const char *path, char **buf, u64 *len)
{
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 (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; }
b[n] = 0;
fclose(f);
*buf = b;
*len = (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++) {
if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) out = argv[++i];
else if (argv[i][0] == '-') {
fprintf(stderr, "w6a: unknown flag %s\n", argv[i]); return 2;
} else if (src == NULL) src = argv[i];
else { fprintf(stderr, "w6a: only one input\n"); return 2; }
}
if (src == NULL || out == NULL) {
fputs("usage: w6a -o file.o file.s\n", stderr);
return 2;
}
char *buf;
u64 len;
if (slurp(src, &buf, &len) < 0) {
fprintf(stderr, "w6a: cannot read %s\n", src);
return 1;
}
Asm a;
a_init(&a, src, buf, len);
if (a_parse(&a) != 0) return 1;
if (a_encode(&a) != 0) return 1;
FILE *f = fopen(out, "wb");
if (f == NULL) { fprintf(stderr, "w6a: cannot open %s\n", out); return 1; }
int rc = a_emit_elf(&a, f);
fclose(f);
free(buf);
return rc;
}

242
cmd/w6a/obj.c Normal file
View File

@@ -0,0 +1,242 @@
/*
* obj.c — emit a tiny ELF64 relocatable object.
*
* Layout (in file order):
* [0] ELF header
* [1] Section .text (program bytes)
* [2] Section .rela.text (relocations)
* [3] Section .symtab
* [4] Section .strtab
* [5] Section .shstrtab
* [6] Section header table
*
* Symtab indices: 0 = STN_UNDEF, 1 = file (skipped), 2.. = our syms.
* For simplicity we emit GLOBAL symbols only (no LOCAL ordering rules
* to worry about).
*/
#include "a.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/* ELF constants */
#define ELFMAG "\x7f""ELF"
#define ELFCLASS64 2
#define ELFDATA2LSB 1
#define EV_CURRENT 1
#define ET_REL 1
#define EM_X86_64 62
#define SHT_NULL 0
#define SHT_PROGBITS 1
#define SHT_SYMTAB 2
#define SHT_STRTAB 3
#define SHT_RELA 4
#define SHF_ALLOC 0x2
#define SHF_EXECINSTR 0x4
#define SHF_INFO_LINK 0x40
#define STB_LOCAL 0
#define STB_GLOBAL 1
#define STT_NOTYPE 0
#define STT_FUNC 2
#define ELF64_ST_INFO(b,t) (((b) << 4) + ((t) & 0xf))
#define R_X86_64_PC32 2
#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; };
static void
bput(Buf *b, const void *src, size_t n)
{
if (b->n + n > b->cap) {
size_t nc = b->cap ? b->cap * 2 : 256;
while (nc < b->n + n) nc *= 2;
b->p = realloc(b->p, nc);
b->cap = nc;
}
memcpy(b->p + b->n, src, n);
b->n += n;
}
static u32 stput(Buf *st, const char *s) {
u32 off = (u32)st->n;
bput(st, s, strlen(s) + 1);
return off;
}
#pragma pack(push, 1)
typedef struct {
u8 e_ident[16];
u16 e_type, e_machine;
u32 e_version;
u64 e_entry, e_phoff, e_shoff;
u32 e_flags;
u16 e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx;
} Ehdr;
typedef struct {
u32 sh_name, sh_type;
u64 sh_flags, sh_addr, sh_offset, sh_size;
u32 sh_link, sh_info;
u64 sh_addralign, sh_entsize;
} Shdr;
typedef struct {
u32 st_name;
u8 st_info, st_other;
u16 st_shndx;
u64 st_value, st_size;
} Sym64;
typedef struct {
u64 r_offset;
u64 r_info;
i64 r_addend;
} Rela64;
#pragma pack(pop)
int
a_emit_elf(Asm *a, FILE *f)
{
Buf shstr = {0}, str = {0}, sym = {0}, rela = {0};
stput(&shstr, ""); /* idx 0 = empty */
stput(&str, "");
/* Section name offsets */
u32 shn_text = stput(&shstr, ".text");
u32 shn_rela = stput(&shstr, ".rela.text");
u32 shn_symtab = stput(&shstr, ".symtab");
u32 shn_strtab = stput(&shstr, ".strtab");
u32 shn_shstrtab = stput(&shstr, ".shstrtab");
/* Symbol 0 — STN_UNDEF */
{
Sym64 z = {0};
bput(&sym, &z, sizeof z);
}
/* Section indices: 1=.text, 2=.rela.text, 3=.symtab, 4=.strtab, 5=.shstrtab */
const u16 SH_TEXT = 1;
/* Build symbols (defined = global; undefined = global UND) */
int idx = 1;
for (Asym *s = a->syms; s; s = s->next) {
Sym64 e = {0};
e.st_name = stput(&str, s->name);
if (s->defined) {
e.st_info = ELF64_ST_INFO(STB_GLOBAL, STT_FUNC);
e.st_shndx = SH_TEXT;
e.st_value = s->addr;
e.st_size = 0;
} else {
e.st_info = ELF64_ST_INFO(STB_GLOBAL, STT_NOTYPE);
e.st_shndx = 0;
}
bput(&sym, &e, sizeof e);
s->idx = idx++;
}
/* Build relocations */
for (Areloc *r = a->relocs; r; r = r->next) {
Rela64 re;
re.r_offset = r->off;
re.r_info = ELF64_R_INFO((u64)r->sym->idx, (u64)r->kind);
re.r_addend = r->addend;
bput(&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;
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 = 6; /* null + 5 real */
Ehdr eh = {0};
memcpy(eh.e_ident, "\x7f""ELF", 4);
eh.e_ident[4] = ELFCLASS64;
eh.e_ident[5] = ELFDATA2LSB;
eh.e_ident[6] = EV_CURRENT;
eh.e_type = ET_REL;
eh.e_machine = EM_X86_64;
eh.e_version = EV_CURRENT;
eh.e_shoff = off_shdr;
eh.e_ehsize = sizeof(Ehdr);
eh.e_shentsize = sizeof(Shdr);
eh.e_shnum = NSECT;
eh.e_shstrndx = 5;
fwrite(&eh, 1, sizeof eh, f);
if (a->textlen) fwrite(a->text, 1, a->textlen, f);
fwrite(rela.p, 1, rela.n, f);
fwrite(sym.p, 1, sym.n, f);
fwrite(str.p, 1, str.n, f);
fwrite(shstr.p, 1, shstr.n, f);
while ((u64)ftell(f) % 8) fputc(0, f);
/* section header table */
Shdr sh = {0};
fwrite(&sh, 1, sizeof sh, f); /* SHT_NULL */
memset(&sh, 0, sizeof sh);
sh.sh_name = shn_text;
sh.sh_type = SHT_PROGBITS;
sh.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
sh.sh_offset = off_text;
sh.sh_size = a->textlen;
sh.sh_addralign = 1;
fwrite(&sh, 1, sizeof sh, f);
memset(&sh, 0, sizeof sh);
sh.sh_name = shn_rela;
sh.sh_type = SHT_RELA;
sh.sh_flags = SHF_INFO_LINK;
sh.sh_offset = off_rela;
sh.sh_size = rela.n;
sh.sh_link = 3; /* symtab */
sh.sh_info = 1; /* applies to .text */
sh.sh_addralign = 8;
sh.sh_entsize = sizeof(Rela64);
fwrite(&sh, 1, sizeof sh, f);
memset(&sh, 0, sizeof sh);
sh.sh_name = shn_symtab;
sh.sh_type = SHT_SYMTAB;
sh.sh_offset = off_sym;
sh.sh_size = sym.n;
sh.sh_link = 4; /* strtab */
sh.sh_info = 1; /* one local: STN_UNDEF */
sh.sh_addralign = 8;
sh.sh_entsize = sizeof(Sym64);
fwrite(&sh, 1, sizeof sh, f);
memset(&sh, 0, sizeof sh);
sh.sh_name = shn_strtab;
sh.sh_type = SHT_STRTAB;
sh.sh_offset = off_str;
sh.sh_size = str.n;
sh.sh_addralign = 1;
fwrite(&sh, 1, sizeof sh, f);
memset(&sh, 0, sizeof sh);
sh.sh_name = shn_shstrtab;
sh.sh_type = SHT_STRTAB;
sh.sh_offset = off_shstr;
sh.sh_size = shstr.n;
sh.sh_addralign = 1;
fwrite(&sh, 1, sizeof sh, f);
free(shstr.p); free(str.p); free(sym.p); free(rela.p);
return 0;
}

405
cmd/w6a/parse.c Normal file
View 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;
}