// 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: nkind = 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: tkind = 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: tkind = 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_" 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 lets: *letvar, // top-level mutable scalar `let` bindings 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) }; // Top-level mutable `let` registry. Mirrors cmd/w6c/cgen.c LetVar. // Populated alongside modents; consulted by cgassign, cgdot, cgident // and the TK_AMP path so reads/writes hit a RIP-relative DATAW slot // instead of being silently dropped. tnode is the declared type AST // node — needed to distinguish scalar (8B) from str (16B) globals // when picking the load/store sequence. type letvar = struct { name: str, tnode: *node, lvnext: *letvar, }; 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 "__". 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_". 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; }; // letscalarprim — recognise the bare type-name keywords whose values // fit in an 8-byte .data slot and load back with a plain MOVQ. Float // types deliberately excluded; they need MOVSS/MOVSD. Mirrors C // `let_emit_size`'s 8-byte branch. fn letscalarprim(nm: str) bool = { if (streq(nm, "bool")) { return true; }; if (streq(nm, "rune")) { return true; }; if (streq(nm, "i8")) { return true; }; if (streq(nm, "i16")) { return true; }; if (streq(nm, "i32")) { return true; }; if (streq(nm, "i64")) { return true; }; if (streq(nm, "u8")) { return true; }; if (streq(nm, "u16")) { return true; }; if (streq(nm, "u32")) { return true; }; if (streq(nm, "u64")) { return true; }; if (streq(nm, "int")) { return true; }; if (streq(nm, "uint")) { return true; }; if (streq(nm, "uintptr")) { return true; }; return false; }; // letemitsize — slot size in bytes for a top-level `let`, or 0 if // the type isn't yet supported as a writable global. Walks type // aliases so byte output matches C cgen, which resolves Type kinds. // 8 → scalar (literal init supported) // 16 → str (only zero-init / nil / "" supported) // 24 → slice (only zero-init supported) // varies → struct (zero-init only; field reads/scalar-field writes) fn letemitsize(c: *cgen, d: *node) i32 = { if (d == nil) { return 0; }; let t: *node = d.lhs; for (t != nil) { if (t.kind == nkind.N_TPTR) { return 8; }; if (t.kind == nkind.N_TSLICE) { return 24; }; if (t.kind != nkind.N_TNAME) { return 0; }; let nm: str = t.str; if (letscalarprim(nm)) { return 8; }; if (streq(nm, "str")) { return 16; }; let si: *structinfo = structlookup(c, nm); if (si != nil) { return si.totsize; }; let next: *node = aliaslookup(c, nm); if (next == nil) { return 0; }; t = next; }; return 0; }; fn collectlets(c: *cgen, file: *node) void = { c.lets = nil; if (file == nil) { return; }; let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_LET) { let nm: str = d.str; if (nm.len > 0) { if (letemitsize(c, d) > 0) { let lv: *letvar = amalloc(c.a, 48u64): *letvar; lv.name = nm; lv.tnode = d.lhs; lv.lvnext = c.lets; c.lets = lv; }; }; }; d = d.next; }; }; fn isletvar(c: *cgen, name: str) bool = { let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, name)) { return true; }; lv = lv.lvnext; }; return false; }; // letvarisstr — is the named top-level let a str global? Resolves // aliases to mirror C cgen's `let_isstr`. Used by cgident/cgdot/ // cgassign to pick the (LEAQ, MOVQ, MOVQ) sequence over the bare // MOVQ scalar load. fn letvarisstr(c: *cgen, name: str) bool = { let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, name)) { let t: *node = lv.tnode; for (t != nil) { if (t.kind != nkind.N_TNAME) { return false; }; let nm: str = t.str; if (streq(nm, "str")) { return true; }; let nx: *node = aliaslookup(c, nm); if (nx == nil) { return false; }; t = nx; }; return false; }; lv = lv.lvnext; }; return false; }; // letvarisslice — is the named top-level let a slice global? // Slice headers are 24 bytes; the ABI flows as (AX, BX, CX) so the // load sequence ends with `MOVQ 16(CX), CX` (overwrites the // address holder with the cap). Mirrors C cgen's `let_isslice`. fn letvarisslice(c: *cgen, name: str) bool = { let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, name)) { let t: *node = lv.tnode; if (t == nil) { return false; }; if (t.kind == nkind.N_TSLICE) { return true; }; return false; }; lv = lv.lvnext; }; return false; }; // letvarisstruct — is the named top-level let a struct global? // Struct globals use LEAQ name(SB), CX as the field-access base; the // cgdot read and cgassign write paths branch on this to skip the // frame-relative addressing they use for locals. fn letvarisstruct(c: *cgen, name: str) bool = { let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, name)) { let t: *node = lv.tnode; for (t != nil) { if (t.kind != nkind.N_TNAME) { return false; }; let nm: str = t.str; if (structlookup(c, nm) != nil) { return true; }; let nx: *node = aliaslookup(c, nm); if (nx == nil) { return false; }; t = nx; }; return false; }; lv = lv.lvnext; }; return false; }; // letvarstructinfo — for a struct global, return its structinfo // so the cgdot/cgassign paths can look up fields. nil if the let // isn't a struct (or wasn't found). fn letvarstructinfo(c: *cgen, name: str) *structinfo = { let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, name)) { let t: *node = lv.tnode; for (t != nil) { if (t.kind != nkind.N_TNAME) { return nil; }; let nm: str = t.str; let si: *structinfo = structlookup(c, nm); if (si != nil) { return si; }; let nx: *node = aliaslookup(c, nm); if (nx == nil) { return nil; }; t = nx; }; return nil; }; lv = lv.lvnext; }; return nil; }; // emitdatawbyte — write one byte of an asm string literal using // the same escape rules as emitdefconstants / emitdatasection. fn emitdatawbyte(b: u8) void = { if (b == 34u8) { emitline("\\\""); return; }; if (b == 92u8) { emitline("\\\\"); return; }; 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); return; }; 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); return; }; let bb: [1]u8; bb[0] = b; os.write(1, bb.ptr, 1u64); }; // letpreintern — intern strlits referenced from top-level str-let // initialisers BEFORE emitdatasection runs. Mirrors cmd/w6c/cgen.c // let_pre_intern: emitletdataw later looks up the same label, and // emitdatasection emits the DATA row in the same .s file. Running // emitletdataw after emitdatasection would flip the (DATA strlits, // DATAW lets) section order and break byte-identity. export fn letpreintern(c: *cgen, file: *node) void = { if (file == nil) { return; }; let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_LET) { let sz: i32 = letemitsize(c, d); if (sz == 16) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; r = r.lhs; }; if (r != nil) { if (r.kind == nkind.N_STRLIT) { if (r.str.len > 0) { internstrlit(c, r.str); }; }; }; }; }; d = d.next; }; }; // emitletdataw — DATAW directive per top-level `let` global. // 8B scalar with int/rune/bool/nil literal init (or no init). // 16B str — no init / `nil` / `""` → 16 zero bytes; or non-empty // strlit init → 8 zero placeholder + 8 LE len bytes plus a // DATAR slot+0,strlit reloc that the linker patches at load. // sz struct — zero only. // Non-literal scalar inits and unsupported shapes are skipped so the // link surfaces an undefined-symbol error if the binding is used. fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_LET) { let nm: str = d.str; if (nm.len > 0) { let sz: i32 = letemitsize(c, d); let issg: bool = letvarisstruct(c, nm); if (sz == 8 && !issg) { let v: u64 = 0u64; let ok: bool = true; if (d.rhs != nil) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; r = r.lhs; }; ok = 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("DATAW "); emitsymname(c, nm); emitline("(SB),\""); let i: i32 = 0; let n: u64 = v; for (i < 8) { let b: u8 = (n & 255u64): u8; n = n >> 8u64; emitdatawbyte(b); i += 1; }; emitline("\"\n"); }; }; if (sz == 16 && !issg) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; r = r.lhs; }; // str-literal init (non-empty): emit // the 16B payload as 8 placeholder zero // bytes + 8 LE bytes of length, then a // DATAR reloc to patch the ptr half with // the strlit's runtime VA. let strlitinit: bool = false; if (r != nil) { if (r.kind == nkind.N_STRLIT) { if (r.str.len > 0) { strlitinit = true; }; }; }; if (strlitinit) { let lab: str = internstrlit(c, r.str); let v: u64 = r.str.len: u64; emitline("DATAW "); emitsymname(c, nm); emitline("(SB),\""); let i: i32 = 0; for (i < 8) { emitdatawbyte(0u8); i += 1; }; i = 0; let nv: u64 = v; for (i < 8) { emitdatawbyte((nv & 255u64): u8); nv = nv >> 8u64; i += 1; }; emitline("\"\n"); emitline("DATAR "); emitsymname(c, nm); emitline("+0(SB),"); os.write(1, lab.ptr, lab.len: u64); emitline("(SB)\n"); } else { // zero-init: accept no rhs, nil, // or empty strlit. let ok: bool = true; if (d.rhs != nil) { ok = false; if (r != nil) { if (r.kind == nkind.N_NIL) { ok = true; }; if (r.kind == nkind.N_STRLIT) { if (r.str.len == 0) { ok = true; }; }; }; }; if (ok) { emitline("DATAW "); emitsymname(c, nm); emitline("(SB),\""); let i: i32 = 0; for (i < 16) { emitdatawbyte(0u8); i += 1; }; emitline("\"\n"); }; }; }; if (sz == 24 && !issg) { // Slice: zero-init only (no slice-literal // syntax to honour). Any rhs other than // `nil` is skipped → undefined symbol at // link. let ok: bool = true; if (d.rhs != nil) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; r = r.lhs; }; ok = false; if (r != nil) { if (r.kind == nkind.N_NIL) { ok = true; }; }; }; if (ok) { emitline("DATAW "); emitsymname(c, nm); emitline("(SB),\""); let i: i32 = 0; for (i < 24) { emitdatawbyte(0u8); i += 1; }; emitline("\"\n"); }; }; // Struct globals — any size, zero-init only. // A struct literal init isn't compile-time // evaluated yet; skip and the link will surface // an undefined-symbol error if referenced. if (issg) { if (d.rhs == nil) { emitline("DATAW "); emitsymname(c, nm); emitline("(SB),\""); let i: i32 = 0; for (i < sz) { emitdatawbyte(0u8); i += 1; }; emitline("\"\n"); }; }; }; }; d = d.next; }; }; // 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 `.` 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 "?"; };