921 lines
25 KiB
Plaintext
921 lines
25 KiB
Plaintext
// selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c.
|
|
//
|
|
// Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c`
|
|
// producing byte-identical output to C-side `w6c` for the same source,
|
|
// then by assembling + linking + running the result.
|
|
//
|
|
// Current coverage:
|
|
// - decls: nkind.N_FILE, nkind.N_FNDECL (params, frame for locals, prologue
|
|
// + dual-epilogue suppression; FFI body-less fn skipped)
|
|
// - stmts: nkind.N_BLOCK, nkind.N_RETURN, nkind.N_EXPRSTMT, nkind.N_LET (no init),
|
|
// nkind.N_LET (int-literal / ident / call / nkind.N_BIN init),
|
|
// nkind.N_IF (with optional else), nkind.N_FOR (cond-only and full
|
|
// init/cond/post), nkind.N_BREAK, nkind.N_CONTINUE
|
|
// - exprs: nkind.N_INTLIT, nkind.N_IDENT (local/param), nkind.N_BIN with full op
|
|
// coverage (+/-/*/// %, &/|/^, <</>>, comparisons with
|
|
// signed-vs-unsigned dispatch, &&/||), nkind.N_UN (- ! ~ & *),
|
|
// nkind.N_CALL (recursive R-to-L push, pop into argregs L-to-R),
|
|
// nkind.N_ASSIGN to local idents (plain and compound +=/-=)
|
|
//
|
|
// Type info is shallow — frame slots are 8 bytes per local, all loads
|
|
// /stores are MOVQ. Programs that mix i8/i32/i64 locals work but spill
|
|
// 8 bytes per local. Float, str, slice, struct, match, defer, alloc,
|
|
// tagged-union return — none of those are wired yet.
|
|
|
|
use os;
|
|
use mem;
|
|
use ast;
|
|
use tok;
|
|
use typ;
|
|
use sym;
|
|
use strconv;
|
|
// Split files. Bundler pulls these in transitively so consumers only
|
|
// need `use cgen;`. Order matters for the flat-bundle concat — utils
|
|
// first so cgenexpr/stmt/decl can reference helpers defined here.
|
|
use cgenutil;
|
|
use cgenexpr;
|
|
use cgenstmt;
|
|
use cgendecl;
|
|
|
|
// ---- typedef alias registry -----------------------------------------
|
|
//
|
|
// `type error = str;` makes `error` a struct-shape alias. We track
|
|
// alias→target so isstrtype / isslicetype / structlookup can
|
|
// resolve through the chain. Only direct nkind.N_TNAME aliases are mapped;
|
|
// `type p = struct {...}` is handled by collectstructs.
|
|
|
|
type aliasent = struct {
|
|
aname: str,
|
|
target: *node, // the rhs type expr
|
|
aanext: *aliasent,
|
|
};
|
|
|
|
fn collectaliases(c: *cgen, file: *node) void = {
|
|
c.aliases = nil;
|
|
let d: *node = file.list;
|
|
for (d != nil) {
|
|
if (d.kind == nkind.N_TYPEDECL) {
|
|
let body: *node = d.lhs;
|
|
if (body != nil) {
|
|
if (body.kind != nkind.N_TSTRUCT) {
|
|
let a: *aliasent = amalloc(c.a, 32u64): *aliasent;
|
|
a.aname = d.str;
|
|
a.target = body;
|
|
a.aanext = c.aliases;
|
|
c.aliases = a;
|
|
};
|
|
};
|
|
};
|
|
d = d.next;
|
|
};
|
|
};
|
|
|
|
fn aliaslookup(c: *cgen, name: str) *node = {
|
|
let a: *aliasent = c.aliases;
|
|
for (a != nil) {
|
|
let an: str = a.aname;
|
|
if (streq(an, name)) { return a.target; };
|
|
a = a.aanext;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
// ---- enum registry --------------------------------------------------
|
|
//
|
|
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
|
|
// every `type Foo = enum [storage] { ... }`, pre-compute each
|
|
// member's u64 value (supporting auto-increment and sibling refs),
|
|
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
|
|
|
|
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
|
|
if (e == nil) { return false; };
|
|
let k: i32 = e.kind;
|
|
if (k == nkind.N_INTLIT) { *out = e.uval; return true; };
|
|
if (k == nkind.N_RUNELIT) { *out = e.uval; return true; };
|
|
if (k == nkind.N_TRUE) { *out = 1u64; return true; };
|
|
if (k == nkind.N_FALSE) { *out = 0u64; return true; };
|
|
if (k == nkind.N_IDENT) {
|
|
let m: *enummember = prev;
|
|
for (m != nil) {
|
|
if (streq(m.mname, e.str)) {
|
|
*out = m.mval;
|
|
return true;
|
|
};
|
|
m = m.emnext;
|
|
};
|
|
return false;
|
|
};
|
|
if (k == nkind.N_BIN) {
|
|
let a: u64;
|
|
let b: u64;
|
|
if (!enumevalmember(prev, e.lhs, &a)) { return false; };
|
|
if (!enumevalmember(prev, e.rhs, &b)) { return false; };
|
|
let op: i32 = e.op;
|
|
if (op == tkind.TK_PLUS) { *out = a + b; return true; };
|
|
if (op == tkind.TK_MINUS) { *out = a - b; return true; };
|
|
if (op == tkind.TK_STAR) { *out = a * b; return true; };
|
|
if (op == tkind.TK_SLASH) {
|
|
if (b == 0u64) { return false; };
|
|
*out = a / b; return true;
|
|
};
|
|
if (op == tkind.TK_PERCENT) {
|
|
if (b == 0u64) { return false; };
|
|
*out = a % b; return true;
|
|
};
|
|
if (op == tkind.TK_AMP) { *out = a & b; return true; };
|
|
if (op == tkind.TK_PIPE) { *out = a | b; return true; };
|
|
if (op == tkind.TK_CARET) { *out = a ^ b; return true; };
|
|
if (op == tkind.TK_LSHIFT) { *out = a << b; return true; };
|
|
if (op == tkind.TK_RSHIFT) { *out = a >> b; return true; };
|
|
return false;
|
|
};
|
|
if (k == nkind.N_UN) {
|
|
let v: u64;
|
|
if (!enumevalmember(prev, e.lhs, &v)) { return false; };
|
|
let op: i32 = e.op;
|
|
if (op == tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
|
|
if (op == tkind.TK_TILDE) { *out = ~v; return true; };
|
|
if (op == tkind.TK_PLUS) { *out = v; return true; };
|
|
return false;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
fn collectenums(c: *cgen, file: *node) void = {
|
|
c.enums = nil;
|
|
let d: *node = file.list;
|
|
for (d != nil) {
|
|
if (d.kind == nkind.N_TYPEDECL) {
|
|
let body: *node = d.lhs;
|
|
if (body != nil) {
|
|
if (body.kind == nkind.N_TENUM) {
|
|
let et: *enumtype = amalloc(c.a, 48u64): *enumtype;
|
|
et.ename = d.str;
|
|
et.storage = body.lhs;
|
|
et.members = nil;
|
|
let prev: u64 = (-1i64): u64;
|
|
let mhead: *enummember = nil;
|
|
let mtail: *enummember = nil;
|
|
let m: *node = body.list;
|
|
for (m != nil) {
|
|
let val: u64;
|
|
if (m.lhs == nil) {
|
|
val = prev + 1u64;
|
|
} else {
|
|
if (!enumevalmember(mhead, m.lhs, &val)) {
|
|
val = prev + 1u64;
|
|
};
|
|
};
|
|
prev = val;
|
|
let em: *enummember = amalloc(c.a, 32u64): *enummember;
|
|
em.mname = m.str;
|
|
em.mval = val;
|
|
em.emnext = nil;
|
|
if (mhead == nil) { mhead = em; mtail = em; }
|
|
else { mtail.emnext = em; mtail = em; };
|
|
m = m.next;
|
|
};
|
|
et.members = mhead;
|
|
et.etnext = c.enums;
|
|
c.enums = et;
|
|
};
|
|
};
|
|
};
|
|
d = d.next;
|
|
};
|
|
};
|
|
|
|
fn enumlookup(c: *cgen, name: str) *enumtype = {
|
|
// Strip any `pkg.` prefix and key off the leaf — driver-side
|
|
// concatenation flattens the namespace, so `os.whence` and
|
|
// `whence` refer to the same registered enum.
|
|
let leaf: str = name;
|
|
let i: i32 = name.len - 1;
|
|
for (i >= 0) {
|
|
if (name[i] == 46u8) { // '.'
|
|
leaf.ptr = name.ptr + (i + 1): u64;
|
|
leaf.len = name.len - (i + 1);
|
|
break;
|
|
};
|
|
i -= 1;
|
|
};
|
|
let e: *enumtype = c.enums;
|
|
for (e != nil) {
|
|
if (streq(e.ename, leaf)) { return e; };
|
|
e = e.etnext;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
|
|
let m: *enummember = en.members;
|
|
for (m != nil) {
|
|
if (streq(m.mname, mname)) {
|
|
*out = m.mval;
|
|
return true;
|
|
};
|
|
m = m.emnext;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// resolvetype — follow typedef alias chains to a "canonical" type
|
|
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
|
|
fn resolvetype(c: *cgen, t: *node) *node = {
|
|
let cur: *node = t;
|
|
let depth: i32 = 0;
|
|
for (depth < 16) {
|
|
if (cur == nil) { return nil; };
|
|
if (cur.kind != nkind.N_TNAME) { return cur; };
|
|
let nm: str = cur.str;
|
|
let next: *node = aliaslookup(c, nm);
|
|
if (next == nil) { return cur; };
|
|
cur = next;
|
|
depth += 1;
|
|
};
|
|
return cur;
|
|
};
|
|
|
|
// ---- struct registry ------------------------------------------------
|
|
//
|
|
// Per-file map from struct name → list of fields with computed offsets
|
|
// and sizes. Built when cgfile walks nkind.N_TYPEDECL with nkind.N_TSTRUCT lhs.
|
|
// nkind.N_DOT and nkind.N_ASSIGN consult this to resolve `s.field` for struct or
|
|
// *struct bases.
|
|
|
|
type fieldinfo = struct {
|
|
fname: str,
|
|
foff: i32,
|
|
fsz: i32,
|
|
tnode: *node, // the field type expr, for nested struct lookups
|
|
finext: *fieldinfo,
|
|
};
|
|
|
|
type structinfo = struct {
|
|
sname: str,
|
|
fields: *fieldinfo,
|
|
totsize: i32,
|
|
sinext: *structinfo,
|
|
};
|
|
|
|
// ---- locals / frame --------------------------------------------------
|
|
|
|
type local = struct {
|
|
name: str,
|
|
off: i32,
|
|
tnode: *node, // declared type expr (nkind.N_TNAME / nkind.N_TPTR / ...) or nil
|
|
lnext: *local,
|
|
};
|
|
|
|
// strlit — interned string literal record. Emitted as a DATA directive
|
|
// after all functions; cgexpr nkind.N_STRLIT loads (LEAQ ptr, MOVQ len).
|
|
type strlit = struct {
|
|
label: str, // "_S_<seq>"
|
|
bytes: str,
|
|
slnext: *strlit,
|
|
};
|
|
|
|
// ffi — `@symbol("name")` mapping. Body-less fn `foo` with this attr
|
|
// gets its CALL target rewritten to `name`.
|
|
type ffi = struct {
|
|
ident: str,
|
|
symbol: str,
|
|
fnext: *ffi,
|
|
};
|
|
|
|
// enummember — one (name, value) pair belonging to a registered enum.
|
|
// Values are pre-computed at collect time (Hare allows sibling refs
|
|
// like `RDWR = READ | WRITE`, so we walk the value expr against the
|
|
// already-resolved siblings). Lookup is linear; enum cardinality is
|
|
// usually small.
|
|
type enummember = struct {
|
|
mname: str,
|
|
mval: u64,
|
|
emnext: *enummember,
|
|
};
|
|
|
|
type enumtype = struct {
|
|
ename: str,
|
|
storage: *node, // AST type expr for the storage type (i32 by default)
|
|
members: *enummember,
|
|
etnext: *enumtype,
|
|
};
|
|
|
|
def LOOP_MAX: i32 = 16;
|
|
def DEFER_MAX: i32 = 16;
|
|
|
|
type cgen = struct {
|
|
a: *arena,
|
|
locals: *local,
|
|
frame: i32,
|
|
lastwasreturn: i32,
|
|
labelseq: i32,
|
|
strlitseq: i32,
|
|
strlits: *strlit,
|
|
ffis: *ffi,
|
|
defs: *defent,
|
|
fnrets: *fnret,
|
|
aliases: *aliasent,
|
|
structs: *structinfo,
|
|
enums: *enumtype,
|
|
mods: *modent, // non-exported decls → originating module
|
|
fnname: str,
|
|
fnret: *node, // declared return type of current fn (or nil)
|
|
looptop: i32,
|
|
loopendbuf: *str, // stack of end labels for break
|
|
loopcontbuf: *str, // stack of cont labels for continue
|
|
yieldtop: i32,
|
|
yieldbuf: *str, // stack of match end labels for yield
|
|
defertop: i32,
|
|
deferbuf: **node, // stack of deferred exprs (LIFO at return)
|
|
};
|
|
|
|
fn cgeninit(c: *cgen, a: *arena) void = {
|
|
c.a = a;
|
|
c.locals = nil;
|
|
c.frame = 0;
|
|
c.lastwasreturn = 0;
|
|
c.labelseq = 0;
|
|
// Note: strlit_seq, strlits, ffis are *not* reset here; they
|
|
// persist across cgfn calls within one file. cgfile resets them
|
|
// at the start of each compilation unit.
|
|
c.looptop = 0;
|
|
c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
|
c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
|
c.yieldtop = 0;
|
|
c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str;
|
|
c.defertop = 0;
|
|
c.deferbuf = amalloc(a, (DEFER_MAX: u64) * 8u64): **node;
|
|
};
|
|
|
|
// localalloc — append a slot for `name` without dedup. Used for
|
|
// match-arm bindings, which C cgen allocates via cgexpr's by-value
|
|
// `locals` list — so two separate matches each get fresh slots even
|
|
// when their bind names collide. scanlocals follows the same rule
|
|
// for nkind.N_MCASE.
|
|
fn localalloc(c: *cgen, name: str, sz: i32, tnode: *node) i32 = {
|
|
let asz: i32 = sz;
|
|
if (asz < 8) { asz = 8; };
|
|
if ((asz & 7) != 0) { asz = (asz + 7) & ~7; };
|
|
c.frame += asz;
|
|
let off: i32 = 0 - c.frame;
|
|
let l: *local = amalloc(c.a, 48u64): *local;
|
|
l.name = name;
|
|
l.off = off;
|
|
l.tnode = tnode;
|
|
l.lnext = c.locals;
|
|
c.locals = l;
|
|
return off;
|
|
};
|
|
|
|
fn localadd(c: *cgen, name: str, sz: i32, tnode: *node) i32 = {
|
|
// Name-based slot reuse for N_LETs and params: if `name` is
|
|
// already declared in this function, return its existing
|
|
// offset. Mirrors C cgen (cmd/w6c/cgen.c:localoff). Two
|
|
// disjoint scopes that declare the same name share one slot —
|
|
// so `escape` in wwdump (three `let cp: pos;` across separate
|
|
// branches) reserves one slot, not three. scanlocals does
|
|
// the matching dedup at prologue time so the SUBQ stays in
|
|
// sync.
|
|
//
|
|
// On a dedup hit we also overwrite the stored tnode to match
|
|
// the new declaration's type. C reads `n->lhs->type` (filled
|
|
// by the checker) at every nkind.N_DOT/nkind.N_CAST site; we read
|
|
// `lc.tnode`, so it must follow source order. Without this,
|
|
// a later `let m: *node` inside a branch keeps an earlier
|
|
// `let m: i32`'s tnode and `m.next` falls into the SB fallback.
|
|
let cur: *local = c.locals;
|
|
for (cur != nil) {
|
|
let cn: str = cur.name;
|
|
if (streq(cn, name)) {
|
|
cur.tnode = tnode;
|
|
return cur.off;
|
|
};
|
|
cur = cur.lnext;
|
|
};
|
|
return localalloc(c, name, sz, tnode);
|
|
};
|
|
|
|
// scanseenmark — called by scanlocals on every let / match-bind
|
|
// site. Returns true if `name` is already tracked in c.locals (so
|
|
// the slot will be shared at emission time — no new frame bump).
|
|
// Otherwise appends a name-only stub and returns false. Stubs are
|
|
// thrown away when cgfn resets c.locals before emission.
|
|
fn scanseenmark(c: *cgen, name: str) bool = {
|
|
if (localfindnode(c, name) != nil) { return true; };
|
|
let l: *local = amalloc(c.a, 48u64): *local;
|
|
l.name = name;
|
|
l.off = 0;
|
|
l.tnode = nil;
|
|
l.lnext = c.locals;
|
|
c.locals = l;
|
|
return false;
|
|
};
|
|
|
|
fn localfindnode(c: *cgen, name: str) *local = {
|
|
let l: *local = c.locals;
|
|
for (l != nil) {
|
|
let ln: str = l.name;
|
|
if (streq(ln, name)) { return l; };
|
|
l = l.lnext;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
fn localfind(c: *cgen, name: str) i32 = {
|
|
let l: *local = c.locals;
|
|
for (l != nil) {
|
|
let ln: str = l.name;
|
|
if (ln.len == name.len) {
|
|
let i: i32 = 0;
|
|
let eq: bool = true;
|
|
for (i < name.len) {
|
|
if (ln[i] != name[i]) { eq = false; i = name.len; }
|
|
else { i += 1; };
|
|
};
|
|
if (eq) { return l.off; };
|
|
};
|
|
l = l.lnext;
|
|
};
|
|
return 0;
|
|
};
|
|
|
|
// ---- emit helpers ---------------------------------------------------
|
|
|
|
fn emitline(s: str) void = { os.write(1, s.ptr, s.len: u64); };
|
|
|
|
fn emitint(v: i64) void = {
|
|
let buf: [32]u8;
|
|
let n: i32 = strconv.i64tos(buf[0:32], v);
|
|
os.write(1, buf.ptr, n: u64);
|
|
};
|
|
|
|
fn emituint(v: u64) void = {
|
|
let buf: [32]u8;
|
|
let n: i32 = strconv.u64tos(buf[0:32], v);
|
|
os.write(1, buf.ptr, n: u64);
|
|
};
|
|
|
|
// emitdispreg — print "disp(reg)" or "(reg)" when disp == 0, the
|
|
// way Plan 9 6c/6a do.
|
|
fn emitdispreg(off: i64, reg: str) void = {
|
|
if (off != 0i64) { emitint(off); };
|
|
emitline("(");
|
|
emitline(reg);
|
|
emitline(")");
|
|
};
|
|
|
|
// emitoff — print an integer offset, suppressing it entirely when 0.
|
|
// Use before any emitline("(BP)...") or emitline("(SB)...") sequence.
|
|
// Plan 9 cc convention: "(BP)" not "0(BP)".
|
|
fn emitoff(v: i64) void = {
|
|
if (v != 0i64) { emitint(v); };
|
|
};
|
|
|
|
// mklabel — fresh label "<fnname>_<base>_<seq>". Returns an
|
|
// arena-owned str. Mirrors C cgen's mklabel so diffs match.
|
|
fn mklabel(c: *cgen, base: str) str = {
|
|
let buf: [128]u8;
|
|
let i: i32 = 0;
|
|
let fname: str = c.fnname;
|
|
let j: i32 = 0;
|
|
for (j < fname.len) {
|
|
buf[i] = fname[j];
|
|
i += 1; j += 1;
|
|
};
|
|
buf[i] = 95u8; i += 1; // '_'
|
|
j = 0;
|
|
for (j < base.len) {
|
|
buf[i] = base[j];
|
|
i += 1; j += 1;
|
|
};
|
|
buf[i] = 95u8; i += 1; // '_'
|
|
let n: i32 = strconv.i64tos(buf[i:128], c.labelseq: i64);
|
|
c.labelseq += 1;
|
|
let total: i32 = i + n;
|
|
let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8;
|
|
let k: i32 = 0;
|
|
for (k < total) {
|
|
p[k] = buf[k];
|
|
k += 1;
|
|
};
|
|
p[total] = 0u8;
|
|
let r: str;
|
|
r.ptr = p;
|
|
r.len = total;
|
|
return r;
|
|
};
|
|
|
|
fn emitlabel(s: str) void = {
|
|
os.write(1, s.ptr, s.len: u64);
|
|
emitline(":\n");
|
|
};
|
|
|
|
// ---- string interning ------------------------------------------------
|
|
//
|
|
// streq is provided by sym.ww and reused here.
|
|
|
|
// internstrlit — return a stable label for `bytes`. Dedups by content
|
|
// so identical literals share storage.
|
|
fn internstrlit(c: *cgen, bytes: str) str = {
|
|
let s: *strlit = c.strlits;
|
|
for (s != nil) {
|
|
let bs: str = s.bytes;
|
|
if (streq(bs, bytes)) {
|
|
return s.label;
|
|
};
|
|
s = s.slnext;
|
|
};
|
|
// New label "_S_<seq>".
|
|
let buf: [32]u8;
|
|
buf[0] = 95u8; buf[1] = 83u8; buf[2] = 95u8; // "_S_"
|
|
let n: i32 = strconv.i64tos(buf[3:32], c.strlitseq: i64);
|
|
c.strlitseq += 1;
|
|
let total: i32 = 3 + n;
|
|
let p: *u8 = amalloc(c.a, (total: u64) + 1u64): *u8;
|
|
let i: i32 = 0;
|
|
for (i < total) { p[i] = buf[i]; i += 1; };
|
|
p[total] = 0u8;
|
|
let lab: str;
|
|
lab.ptr = p;
|
|
lab.len = total;
|
|
let nw: *strlit = amalloc(c.a, 48u64): *strlit;
|
|
nw.label = lab;
|
|
nw.bytes = bytes;
|
|
nw.slnext = c.strlits;
|
|
c.strlits = nw;
|
|
return lab;
|
|
};
|
|
|
|
// emitdefconstants — DATA directive per top-level int-literal `def`.
|
|
// 8 bytes little-endian to match what the C cgen emits.
|
|
fn emitdefconstants(c: *cgen, file: *node) void = {
|
|
let d: *node = file.list;
|
|
for (d != nil) {
|
|
if (d.kind == nkind.N_DEF) {
|
|
let r: *node = d.rhs;
|
|
let v: u64 = 0u64;
|
|
let ok: bool = false;
|
|
if (r != nil) {
|
|
if (r.kind == nkind.N_INTLIT) { v = r.uval; ok = true; };
|
|
if (r.kind == nkind.N_RUNELIT) { v = r.uval; ok = true; };
|
|
if (r.kind == nkind.N_TRUE) { v = 1u64; ok = true; };
|
|
if (r.kind == nkind.N_FALSE) { v = 0u64; ok = true; };
|
|
if (r.kind == nkind.N_NIL) { v = 0u64; ok = true; };
|
|
};
|
|
if (ok) {
|
|
emitline("DATA ");
|
|
if (d.exported == 0) {
|
|
if (d.module.len > 0) {
|
|
os.write(1, d.module.ptr, d.module.len: u64);
|
|
os.write(1, ".".ptr, 1u64);
|
|
};
|
|
};
|
|
let nm: str = d.str;
|
|
os.write(1, nm.ptr, nm.len: u64);
|
|
emitline("(SB),\"");
|
|
let i: i32 = 0;
|
|
let n: u64 = v;
|
|
for (i < 8) {
|
|
let b: u8 = (n & 255u64): u8;
|
|
n = n >> 8u64;
|
|
// C emit_defs only special-cases " and \;
|
|
// every other non-printable goes as \xHH.
|
|
if (b == 34u8) { emitline("\\\""); }
|
|
else { if (b == 92u8) { emitline("\\\\"); }
|
|
else {
|
|
if (b < 32u8) {
|
|
emitline("\\x");
|
|
let hi: u8 = b >> 4u8;
|
|
let lo: u8 = b & 15u8;
|
|
let bb: [2]u8;
|
|
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
|
else { bb[0] = (hi - 10u8) + 97u8; };
|
|
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
|
else { bb[1] = (lo - 10u8) + 97u8; };
|
|
os.write(1, bb.ptr, 2u64);
|
|
} else {
|
|
if (b >= 127u8) {
|
|
emitline("\\x");
|
|
let hi: u8 = b >> 4u8;
|
|
let lo: u8 = b & 15u8;
|
|
let bb: [2]u8;
|
|
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
|
else { bb[0] = (hi - 10u8) + 97u8; };
|
|
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
|
else { bb[1] = (lo - 10u8) + 97u8; };
|
|
os.write(1, bb.ptr, 2u64);
|
|
} else {
|
|
let bb: [1]u8;
|
|
bb[0] = b;
|
|
os.write(1, bb.ptr, 1u64);
|
|
};
|
|
};
|
|
};};
|
|
i += 1;
|
|
};
|
|
emitline("\"\n");
|
|
};
|
|
};
|
|
d = d.next;
|
|
};
|
|
};
|
|
|
|
// emitdatasection — DATA directives for every interned strlit.
|
|
// Trailing NUL appended so .ptr can be used as a C string by syscalls.
|
|
fn emitdatasection(c: *cgen) void = {
|
|
let s: *strlit = c.strlits;
|
|
for (s != nil) {
|
|
emitline("DATA ");
|
|
let lab: str = s.label;
|
|
os.write(1, lab.ptr, lab.len: u64);
|
|
emitline("(SB),\"");
|
|
let bs: str = s.bytes;
|
|
let i: i32 = 0;
|
|
for (i < bs.len) {
|
|
let b: u8 = bs[i];
|
|
if (b == 34u8) { emitline("\\\""); } // "
|
|
else { if (b == 92u8) { emitline("\\\\"); } // \
|
|
else { if (b == 10u8) { emitline("\\n"); }
|
|
else { if (b == 9u8) { emitline("\\t"); }
|
|
else { if (b == 13u8) { emitline("\\r"); }
|
|
else {
|
|
if (b < 32u8) {
|
|
emitline("\\x");
|
|
let hi: u8 = b >> 4u8;
|
|
let lo: u8 = b & 15u8;
|
|
let bb: [2]u8;
|
|
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
|
else { bb[0] = (hi - 10u8) + 97u8; };
|
|
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
|
else { bb[1] = (lo - 10u8) + 97u8; };
|
|
os.write(1, bb.ptr, 2u64);
|
|
} else {
|
|
if (b >= 127u8) {
|
|
emitline("\\x");
|
|
let hi: u8 = b >> 4u8;
|
|
let lo: u8 = b & 15u8;
|
|
let bb: [2]u8;
|
|
if (hi < 10u8) { bb[0] = hi + 48u8; }
|
|
else { bb[0] = (hi - 10u8) + 97u8; };
|
|
if (lo < 10u8) { bb[1] = lo + 48u8; }
|
|
else { bb[1] = (lo - 10u8) + 97u8; };
|
|
os.write(1, bb.ptr, 2u64);
|
|
} else {
|
|
let bb: [1]u8;
|
|
bb[0] = b;
|
|
os.write(1, bb.ptr, 1u64);
|
|
};
|
|
};
|
|
};};};};};
|
|
i += 1;
|
|
};
|
|
emitline("\\x00\"\n");
|
|
s = s.slnext;
|
|
};
|
|
};
|
|
|
|
// ---- fn return-type map ---------------------------------------------
|
|
//
|
|
// Per-file: ident → ret-type-node. Used to decide whether to shuffle
|
|
// (AX, DX) → (AX, BX) after a CALL — needed for str-returning fns so
|
|
// the value flows through cgen as the canonical (AX, BX) str pair.
|
|
|
|
type fnret = struct {
|
|
fname: str,
|
|
rtype: *node,
|
|
frnext: *fnret,
|
|
};
|
|
|
|
fn collectfnrets(c: *cgen, file: *node) void = {
|
|
c.fnrets = nil;
|
|
let d: *node = file.list;
|
|
for (d != nil) {
|
|
if (d.kind == nkind.N_FNDECL) {
|
|
let f: *fnret = amalloc(c.a, 32u64): *fnret;
|
|
f.fname = d.str;
|
|
f.rtype = d.lhs;
|
|
f.frnext = c.fnrets;
|
|
c.fnrets = f;
|
|
};
|
|
d = d.next;
|
|
};
|
|
};
|
|
|
|
fn fnretlookup(c: *cgen, name: str) *node = {
|
|
let f: *fnret = c.fnrets;
|
|
for (f != nil) {
|
|
let fn_: str = f.fname;
|
|
if (streq(fn_, name)) { return f.rtype; };
|
|
f = f.frnext;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
// ---- def-constant registry ------------------------------------------
|
|
//
|
|
// `def NAME: T = LIT;` becomes a DATA symbol the C-side w6c emits; an
|
|
// ident reference loads it via `MOVQ NAME(SB), AX`. We collect them at
|
|
// file load and consult on nkind.N_IDENT lookup.
|
|
|
|
type defent = struct {
|
|
dname: str,
|
|
drhs: *node,
|
|
dnext: *defent,
|
|
};
|
|
|
|
fn collectdefs(c: *cgen, file: *node) void = {
|
|
c.defs = nil;
|
|
let d: *node = file.list;
|
|
for (d != nil) {
|
|
if (d.kind == nkind.N_DEF) {
|
|
let e: *defent = amalloc(c.a, 32u64): *defent;
|
|
e.dname = d.str;
|
|
e.drhs = d.rhs;
|
|
e.dnext = c.defs;
|
|
c.defs = e;
|
|
};
|
|
d = d.next;
|
|
};
|
|
};
|
|
|
|
fn deflookup(c: *cgen, name: str) bool = {
|
|
let e: *defent = c.defs;
|
|
for (e != nil) {
|
|
let dn: str = e.dname;
|
|
if (streq(dn, name)) { return true; };
|
|
e = e.dnext;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// Returns the rhs init node for a top-level `def`, or nil if `name`
|
|
// doesn't name a def. Used by cgdot to inline `.ptr`/`.len` on
|
|
// `def NAME: str = "..."` — those aren't laid out in memory.
|
|
fn deflookuprhs(c: *cgen, name: str) *node = {
|
|
let e: *defent = c.defs;
|
|
for (e != nil) {
|
|
let dn: str = e.dname;
|
|
if (streq(dn, name)) { return e.drhs; };
|
|
e = e.dnext;
|
|
};
|
|
return nil;
|
|
};
|
|
|
|
// ---- module-private symbol map --------------------------------------
|
|
//
|
|
// Non-exported top-level decls live in their originating module's
|
|
// namespace. cgen mangles those names to `<module>.<name>` at emission
|
|
// time, both at the def site (TEXT/DATA) and at every call/load site,
|
|
// so two modules can each privately define `cstrlen` without colliding
|
|
// at link time. Exported decls and FFI-bound decls keep their bare name.
|
|
|
|
type modent = struct {
|
|
mname: str, // the bare ident as it appears in source
|
|
module: str, // the originating module (`// MODULE: foo`)
|
|
mnext: *modent,
|
|
};
|
|
|
|
fn collectmods(c: *cgen, file: *node) void = {
|
|
c.mods = nil;
|
|
if (file == nil) { return; };
|
|
let d: *node = file.list;
|
|
for (d != nil) {
|
|
// Mirror collectfnrets' shape exactly (plain prepend in one
|
|
// branch). Earlier nested-if/early-return variants tickled a
|
|
// wwstage cgen bug that dropped most prepends.
|
|
if (d.kind == nkind.N_FNDECL) {
|
|
if (d.exported == 0) {
|
|
if (d.module.len > 0) {
|
|
if (!streq(d.str, "main")) {
|
|
let m: *modent = amalloc(c.a, 48u64): *modent;
|
|
m.mname = d.str;
|
|
m.module = d.module;
|
|
m.mnext = c.mods;
|
|
c.mods = m;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
if (d.kind == nkind.N_DEF) {
|
|
if (d.exported == 0) {
|
|
if (d.module.len > 0) {
|
|
let m: *modent = amalloc(c.a, 48u64): *modent;
|
|
m.mname = d.str;
|
|
m.module = d.module;
|
|
m.mnext = c.mods;
|
|
c.mods = m;
|
|
};
|
|
};
|
|
};
|
|
if (d.kind == nkind.N_TYPEDECL) {
|
|
if (d.exported == 0) {
|
|
if (d.module.len > 0) {
|
|
let m: *modent = amalloc(c.a, 48u64): *modent;
|
|
m.mname = d.str;
|
|
m.module = d.module;
|
|
m.mnext = c.mods;
|
|
c.mods = m;
|
|
};
|
|
};
|
|
};
|
|
if (d.kind == nkind.N_LET) {
|
|
if (d.exported == 0) {
|
|
if (d.module.len > 0) {
|
|
let m: *modent = amalloc(c.a, 48u64): *modent;
|
|
m.mname = d.str;
|
|
m.module = d.module;
|
|
m.mnext = c.mods;
|
|
c.mods = m;
|
|
};
|
|
};
|
|
};
|
|
d = d.next;
|
|
};
|
|
};
|
|
|
|
fn modlookup(c: *cgen, name: str) str = {
|
|
let m: *modent = c.mods;
|
|
for (m != nil) {
|
|
if (streq(m.mname, name)) { return m.module; };
|
|
m = m.mnext;
|
|
};
|
|
let empty: str;
|
|
empty.ptr = nil;
|
|
empty.len = 0;
|
|
return empty;
|
|
};
|
|
|
|
// emitsymname — write the asm symbol name for `ident`. Honours, in
|
|
// order: FFI mapping (@symbol), module mangling (private decls), bare
|
|
// name. Use everywhere a top-level name is emitted before `(SB)` or in
|
|
// a `TEXT name,$N` header.
|
|
fn emitsymname(c: *cgen, ident: str) void = {
|
|
let resolved: str = ffiresolve(c, ident);
|
|
if (resolved.ptr != ident.ptr) {
|
|
// FFI hit — emit the mapped linker symbol verbatim.
|
|
os.write(1, resolved.ptr, resolved.len: u64);
|
|
return;
|
|
};
|
|
let mod: str = modlookup(c, ident);
|
|
if (mod.len > 0) {
|
|
os.write(1, mod.ptr, mod.len: u64);
|
|
os.write(1, ".".ptr, 1u64);
|
|
};
|
|
os.write(1, ident.ptr, ident.len: u64);
|
|
};
|
|
|
|
// ---- FFI map ---------------------------------------------------------
|
|
|
|
fn fficollect(c: *cgen, file: *node) void = {
|
|
c.ffis = nil;
|
|
if (file == nil) { return; };
|
|
let d: *node = file.list;
|
|
for (d != nil) {
|
|
if (d.kind == nkind.N_FNDECL) {
|
|
let a: *node = d.attr;
|
|
for (a != nil) {
|
|
if (a.kind == nkind.N_ATTR) {
|
|
let aname: str = a.str;
|
|
if (streq(aname, "symbol")) {
|
|
let symnode: *node = a.list;
|
|
if (symnode != nil) {
|
|
if (symnode.kind == nkind.N_STRLIT) {
|
|
let f: *ffi = amalloc(c.a, 48u64): *ffi;
|
|
f.ident = d.str;
|
|
f.symbol = symnode.str;
|
|
f.fnext = c.ffis;
|
|
c.ffis = f;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
a = a.next;
|
|
};
|
|
};
|
|
d = d.next;
|
|
};
|
|
};
|
|
|
|
fn ffiresolve(c: *cgen, ident: str) str = {
|
|
let f: *ffi = c.ffis;
|
|
for (f != nil) {
|
|
let id: str = f.ident;
|
|
if (streq(id, ident)) { return f.symbol; };
|
|
f = f.fnext;
|
|
};
|
|
return ident;
|
|
};
|
|
|
|
// ---- ABI argreg helpers ---------------------------------------------
|
|
|
|
fn argregname(i: i32) str = {
|
|
if (i == 0) { return "DI"; };
|
|
if (i == 1) { return "SI"; };
|
|
if (i == 2) { return "DX"; };
|
|
if (i == 3) { return "CX"; };
|
|
if (i == 4) { return "R8"; };
|
|
if (i == 5) { return "R9"; };
|
|
return "?";
|
|
};
|