// selfhost/cmd/wcc/cgenstmt.ww — split out of cgen.ww. // // cgstmt is a thin dispatcher over n.kind; each branch defers to a // per-kind helper: cgblock, cgreturn, cgexprstmt, cglet, cgif, cgfor, // cgmassign, cgbreak, cgcontinue. // // The expression generator (cgexpr) lives in cgenexpr.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) { cgblock(c, n); return; }; if (k == N_RETURN) { cgreturn(c, n); return; }; if (k == N_EXPRSTMT) { cgexprstmt(c, n); return; }; if (k == N_LET) { cglet(c, n); return; }; if (k == N_IF) { cgif(c, n); return; }; if (k == N_FOR) { cgfor(c, n); return; }; if (k == N_MASSIGN) { cgmassign(c, n); return; }; if (k == N_BREAK) { cgbreak(c, n); return; }; if (k == N_CONTINUE) { cgcontinue(c, n); return; }; c.lastwasreturn = 0; }; fn cgblock(c: *cgen, n: *node) void = { let s: *node = n.list; for (s != nil) { cgstmt(c, s); s = s.next; }; return; }; fn cgreturn(c: *cgen, n: *node) void = { 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.lastwasreturn = 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.fnret)) { cgexpr(c, rhs); let idx: i32 = taggedvariantindex(c, c.fnret, 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.lastwasreturn = 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.fnret)) { emitline("\tMOVQ\tBX, DX\n"); }; emitline("\tMOVQ\tBP, SP\n"); emitline("\tPOPQ\tBP\n"); emitline("\tRET\n"); c.lastwasreturn = 1; return; }; fn cgexprstmt(c: *cgen, n: *node) void = { if (n.lhs != nil) { cgexpr(c, n.lhs); }; c.lastwasreturn = 0; return; }; fn cglet(c: *cgen, n: *node) void = { 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.lastwasreturn = 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.lastwasreturn = 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.lastwasreturn = 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.lastwasreturn = 0; return; }; fn cgif(c: *cgen, n: *node) void = { 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.lastwasreturn = 0; return; }; fn cgfor(c: *cgen, n: *node) void = { // Match C cgen's label scheme: _loop_N for the top, // _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.loopendbuf[c.looptop] = endl; c.loopcontbuf[c.looptop] = topl; c.looptop += 1; if (n.body != nil) { cgstmt(c, n.body); }; c.looptop -= 1; if (n.rhs != nil) { cgexpr(c, n.rhs); }; emitline("\tJMP\t"); emitline(topl); emitline("\n"); emitlabel(endl); c.lastwasreturn = 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). fn cgmassign(c: *cgen, n: *node) void = { 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.lastwasreturn = 0; return; }; fn cgbreak(c: *cgen, n: *node) void = { if (c.looptop > 0) { let lbl: str = c.loopendbuf[c.looptop - 1]; emitline("\tJMP\t"); emitline(lbl); emitline("\n"); }; c.lastwasreturn = 0; return; }; fn cgcontinue(c: *cgen, n: *node) void = { if (c.looptop > 0) { let lbl: str = c.loopcontbuf[c.looptop - 1]; emitline("\tJMP\t"); emitline(lbl); emitline("\n"); }; c.lastwasreturn = 0; return; };