wcc/cgen: split cgexpr→cgen_expr.ww, cgstmt→cgen_stmt.ww (rob pike #5)

This commit is contained in:
2026-05-11 15:35:16 +09:00
parent e301a198f4
commit 328bcd743c
6 changed files with 4314 additions and 4193 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,353 @@
// selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww.
//
// Houses cgstmt — statements + control flow (N_BLOCK, N_IF, N_FOR,
// N_BREAK/N_CONTINUE, N_RETURN, N_LET, N_ASSIGN, etc.). The
// expression generator (cgexpr) lives in cgen_expr.ww; the foundation
// (types, emit primitives, collect* tables, FFI/module maps) lives in
// cgen.ww.
use os;
use mem;
use ast;
use tok;
use typ;
use sym;
use strconv;
// ---- statement cgen --------------------------------------------------
fn cgstmt(c: *cgen, n: *node) void = {
if (n == nil) { return; };
let k: i32 = n.kind;
if (k == N_BLOCK) {
let s: *node = n.list;
for (s != nil) {
cgstmt(c, s);
s = s.next;
};
return;
};
if (k == N_RETURN) {
let rhs: *node = n.lhs;
if (rhs != nil) {
// Tuple return `return a, b;` — pack as (AX=v0, DX=v1).
// Matches C cgen: evaluate v1 first (PUSHQ), then v0
// into AX, then POPQ DX. End state: AX = v0, DX = v1.
if (rhs.kind == N_TUPLE) {
let v: *node = rhs.list;
if (v != nil) {
let v2: *node = v.next;
if (v2 != nil) {
cgexpr(c, v2);
emitline("\tPUSHQ\tAX\n");
cgexpr(c, v);
emitline("\tPOPQ\tDX\n");
} else {
cgexpr(c, v);
};
};
emitline("\tMOVQ\tBP, SP\n");
emitline("\tPOPQ\tBP\n");
emitline("\tRET\n");
c.last_was_return = 1;
return;
};
// Tagged-union return: pack as (AX=tag, DX=value0, CX=value1).
// For str variant, cgexpr leaves (AX=ptr, BX=len), so we
// shuffle DX←AX (ptr) and CX←BX (len), then load tag.
// For other variants, cgexpr leaves AX, shuffle DX←AX.
if (istaggedtype(c.fn_ret)) {
cgexpr(c, rhs);
let idx: i32 = taggedvariantindex(c, c.fn_ret, rhs);
if (nodeisstr(c, rhs)) {
emitline("\tMOVQ\tBX, CX\n");
emitline("\tMOVQ\tAX, DX\n");
} else {
emitline("\tMOVQ\tAX, DX\n");
};
emitline("\tMOVQ\t$");
if (idx < 0) { idx = 0; };
emitint(idx: i64);
emitline(", AX\n");
emitline("\tMOVQ\tBP, SP\n");
emitline("\tPOPQ\tBP\n");
emitline("\tRET\n");
c.last_was_return = 1;
return;
};
cgexpr(c, rhs);
} else {
// Bare `return;` in a void fn — zero AX so the caller
// sees a deterministic value (matches C cgen, which
// always falls through to `cgexpr_int(c, 0)`).
emitline("\tMOVQ\t$0, AX\n");
};
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
// cgexpr leaves str in (AX, BX); shuffle BX→DX.
if (isstrtype(c, c.fn_ret)) {
emitline("\tMOVQ\tBX, DX\n");
};
emitline("\tMOVQ\tBP, SP\n");
emitline("\tPOPQ\tBP\n");
emitline("\tRET\n");
c.last_was_return = 1;
return;
};
if (k == N_EXPRSTMT) {
if (n.lhs != nil) { cgexpr(c, n.lhs); };
c.last_was_return = 0;
return;
};
if (k == N_LET) {
let nm: str = n.str;
let sz: i32 = slotsize(c, n.lhs);
let off: i32 = localadd(c, nm, sz, n.lhs);
if (n.rhs != nil) {
let rhs: *node = n.rhs;
// Tagged-union init: `let r: (T | E) = expr;`.
// - If rhs is a CALL to a fn returning tagged-union,
// the result is already in (AX=tag, DX=v0, CX=v1);
// just spill all three.
// - Otherwise rhs is a bare variant value: pack tag +
// value(s).
if (istaggedtype(n.lhs)) {
let rhsreturnstagged: bool = false;
if (rhs.kind == N_CALL) {
let callee: *node = rhs.lhs;
if (callee != nil) {
let calleename: str;
calleename.ptr = nil; calleename.len = 0;
if (callee.kind == N_IDENT) { calleename = callee.str; };
if (callee.kind == N_DOT) { calleename = callee.str; };
if (calleename.len > 0) {
let rt: *node = fnretlookup(c, calleename);
if (istaggedtype(rt)) { rhsreturnstagged = true; };
};
};
};
cgexpr(c, rhs);
if (rhsreturnstagged) {
emitline("\tMOVQ\tAX, ");
emitoff(off: i64);
emitline("(BP)\n");
emitline("\tMOVQ\tDX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
c.last_was_return = 0;
return;
};
let tagidx: i32 = taggedvariantindex(c, n.lhs, rhs);
if (tagidx < 0) { tagidx = 0; };
if (nodeisstr(c, rhs)) {
emitline("\tMOVQ\tAX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
} else {
emitline("\tMOVQ\tAX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
};
emitline("\tMOVQ\t$");
emitint(tagidx: i64);
emitline(", ");
emitoff(off: i64);
emitline("(BP)\n");
c.last_was_return = 0;
return;
};
// Struct literal init: `let p: point = point{x=..., y=...};`.
// For each field in the lit, evaluate its value and store at
// the field's offset within the slot. Field-name → offset
// from the struct registry.
if (rhs.kind == N_STRUCTLIT) {
let trefn: *node = rhs.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (trefn != nil) {
if (trefn.kind == N_IDENT) { sname = trefn.str; }
else { if (trefn.kind == N_TNAME) { sname = trefn.str; }; };
};
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fieldnode: *node = rhs.list;
for (fieldnode != nil) {
if (fieldnode.kind == N_FIELD) {
let fname: str = fieldnode.str;
let fi: *fieldinfo = si.fields;
for (fi != nil) {
let fn_: str = fi.fname;
if (streq(fn_, fname)) {
cgexpr(c, fieldnode.lhs);
let sop: str = fieldstoreop(fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitoff((off + fi.foff): i64);
emitline("(BP)\n");
fi = nil;
} else {
fi = fi.finext;
};
};
};
fieldnode = fieldnode.next;
};
c.last_was_return = 0;
return;
};
};
cgexpr(c, rhs);
emitline("\tMOVQ\tAX, ");
emitoff(off: i64);
emitline("(BP)\n");
// str init: cgexpr also leaves len in BX; store both.
if (sz == 16) {
emitline("\tMOVQ\tBX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
};
// slice init: ptr/len/cap in AX/BX/CX.
if (sz == 24) {
emitline("\tMOVQ\tBX, ");
emitoff((off + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + 16): i64);
emitline("(BP)\n");
};
} else {
// Bare `let x: T;` with no initializer. C cgen
// (cmd/w6c/cgen.c:2181-2183) zero-inits only when
// the underlying type's natural size is 8 — pointers,
// i64/u64, function pointers, ints. Structs/arrays/
// slices/strings/tagged/tuples are left for per-field
// writes. ww's slotsize pads struct slots up to 8,
// so we can't just check sz == 8: walk the type AST
// directly to make the same call.
if (typeis8byteprimitive(c, n.lhs)) {
emitline("\tMOVQ\t$0, ");
emitoff(off: i64);
emitline("(BP)\n");
};
};
c.last_was_return = 0;
return;
};
if (k == N_IF) {
let els: str = mklabel(c, "else");
let endl: str = mklabel(c, "end");
cgexpr(c, n.cond);
emitline("\tCMPQ\t$0, AX\n");
emitline("\tJE\t");
if (n.els != nil) { emitline(els); }
else { emitline(endl); };
emitline("\n");
if (n.body != nil) { cgstmt(c, n.body); };
if (n.els != nil) {
emitline("\tJMP\t"); emitline(endl); emitline("\n");
emitlabel(els);
cgstmt(c, n.els);
};
emitlabel(endl);
c.last_was_return = 0;
return;
};
if (k == N_FOR) {
// Match C cgen's label scheme: <fn>_loop_N for the top,
// <fn>_endloop_N for the post-body merge. No separate cont
// label when there's no post-expression.
let topl: str = mklabel(c, "loop");
let endl: str = mklabel(c, "endloop");
if (n.lhs != nil) { cgstmt(c, n.lhs); };
emitlabel(topl);
if (n.cond != nil) {
cgexpr(c, n.cond);
emitline("\tCMPQ\t$0, AX\n");
emitline("\tJE\t"); emitline(endl); emitline("\n");
};
c.loop_end_buf[c.loop_top] = endl;
c.loop_cont_buf[c.loop_top] = topl;
c.loop_top += 1;
if (n.body != nil) { cgstmt(c, n.body); };
c.loop_top -= 1;
if (n.rhs != nil) { cgexpr(c, n.rhs); };
emitline("\tJMP\t"); emitline(topl); emitline("\n");
emitlabel(endl);
c.last_was_return = 0;
return;
};
// Tuple-destructure assign: `a, b = call();`. The call's tuple
// return lands in (AX, DX); push DX to free it, store AX into
// the first lvalue, then pop DX into the second. Mirrors
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
// as C — no fixture uses >2 today).
if (k == N_MASSIGN) {
if (n.rhs != nil) { cgexpr(c, n.rhs); };
emitline("\tPUSHQ\tDX\n");
let l0: *node = n.list;
let l1: *node = nil;
if (l0 != nil) { l1 = l0.next; };
if (l0 != nil) {
if (l0.kind == N_IDENT) {
let off: i32 = localfind(c, l0.str);
if (off != 0) {
emitline("\tMOVQ\tAX, ");
emitoff(off: i64);
emitline("(BP)\n");
};
};
};
emitline("\tPOPQ\tDX\n");
if (l1 != nil) {
if (l1.kind == N_IDENT) {
let off: i32 = localfind(c, l1.str);
if (off != 0) {
emitline("\tMOVQ\tDX, ");
emitoff(off: i64);
emitline("(BP)\n");
};
};
};
c.last_was_return = 0;
return;
};
if (k == N_BREAK) {
if (c.loop_top > 0) {
let lbl: str = c.loop_end_buf[c.loop_top - 1];
emitline("\tJMP\t"); emitline(lbl); emitline("\n");
};
c.last_was_return = 0;
return;
};
if (k == N_CONTINUE) {
if (c.loop_top > 0) {
let lbl: str = c.loop_cont_buf[c.loop_top - 1];
emitline("\tJMP\t"); emitline(lbl); emitline("\n");
};
c.last_was_return = 0;
return;
};
c.last_was_return = 0;
};

File diff suppressed because it is too large Load Diff