wcc/cgen: extract per-kind helpers from cgexpr/cgstmt (rob pike #5)

This commit is contained in:
2026-05-11 16:01:40 +09:00
parent 02ec429a37
commit 3f8d64e01b
4 changed files with 3789 additions and 3654 deletions

View File

@@ -4506,9 +4506,14 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
// MODULE: wcc // MODULE: wcc
// selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww. // selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww.
// //
// Houses cgexpr — the per-expression code generator. The remainder of // cgexpr is a thin dispatcher over n.kind; each non-trivial branch
// the cgen lives in cgen.ww (foundation: types, emit primitives, the // lives in a per-kind helper (cgstrlit, cgident, cgindex, cgmatch,
// collect* tables, FFI/module maps) and cgen_stmt.ww (cgstmt). // cgdot, cgun, cgbin, cgcall, cgassign). Trivial literal loads
// (N_INTLIT, N_RUNELIT, N_TRUE/FALSE/NIL, N_CAST) stay inline.
//
// The remainder of cgen lives in cgen.ww (foundation: types, emit
// primitives, the collect* tables, FFI/module maps) and cgen_stmt.ww
// (cgstmt).
// //
// `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports // `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports
// this file, so any caller of cgen transitively gets cgexpr. // this file, so any caller of cgen transitively gets cgexpr.
@@ -4541,19 +4546,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
emitline(", AX\n"); emitline(", AX\n");
return; return;
}; };
if (k == N_STRLIT) { if (k == N_STRLIT) { cgstrlit(c, n); return; };
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
// sites that expect a str arg pick these up directly.
let nstr: str = n.str;
let lab: str = internstrlit(c, nstr);
emitline("\tLEAQ\t");
os.write(1, lab.ptr, lab.len: u64);
emitline("(SB), AX\n");
emitline("\tMOVQ\t$");
emitint(nstr.len: i64);
emitline(", BX\n");
return;
};
if (k == N_TRUE) { if (k == N_TRUE) {
emitline("\tMOVQ\t$1, AX\n"); emitline("\tMOVQ\t$1, AX\n");
return; return;
@@ -4567,7 +4560,48 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
if (k == N_IDENT) { if (k == N_IDENT) { cgident(c, n); return; };
if (k == N_INDEX) { cgindex(c, n); return; };
if (k == N_MATCH) { cgmatch(c, n); return; };
if (k == N_CAST) {
// Type casts are mostly no-ops at the asm level for our
// integer-shaped operands. Evaluate the source; AX holds
// the bits unchanged. (Sign- or zero-extending narrow loads
// to wider types is the loader's job, not cast's, in this
// minimal cgen.)
cgexpr(c, n.lhs);
return;
};
if (k == N_DOT) { cgdot(c, n); return; };
if (k == N_UN) { cgun(c, n); return; };
if (k == N_BIN) { cgbin(c, n); return; };
if (k == N_CALL) { cgcall(c, n); return; };
if (k == N_ASSIGN) { cgassign(c, n); return; };
};
fn cgstrlit(c: *cgen, n: *node) void = {
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
// sites that expect a str arg pick these up directly.
let nstr: str = n.str;
let lab: str = internstrlit(c, nstr);
emitline("\tLEAQ\t");
os.write(1, lab.ptr, lab.len: u64);
emitline("(SB), AX\n");
emitline("\tMOVQ\t$");
emitint(nstr.len: i64);
emitline(", BX\n");
return;
};
fn cgident(c: *cgen, n: *node) void = {
let nm: str = n.str; let nm: str = n.str;
let lc: *local = localfindnode(c, nm); let lc: *local = localfindnode(c, nm);
if (lc != nil) { if (lc != nil) {
@@ -4612,9 +4646,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_INDEX) { fn cgindex(c: *cgen, n: *node) void = {
// Element-size-aware load: u8-element bases use MOVZBQ, // Element-size-aware load: u8-element bases use MOVZBQ,
// everything else MOVQ. Fast path when the base is a bare // everything else MOVQ. Fast path when the base is a bare
// ident (mem.ww shape). // ident (mem.ww shape).
@@ -4677,9 +4711,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); }
else { emitline("\tMOVQ\t(AX), AX\n"); }; else { emitline("\tMOVQ\t(AX), AX\n"); };
return; return;
}; };
if (k == N_MATCH) { fn cgmatch(c: *cgen, n: *node) void = {
// match (e) { case let v: T => stmt; ... } // match (e) { case let v: T => stmt; ... }
// //
// Read the tagged-union slot and dispatch by tag. Slot // Read the tagged-union slot and dispatch by tag. Slot
@@ -4778,19 +4812,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
emitlabel(endl); emitlabel(endl);
return; return;
}; };
if (k == N_CAST) { fn cgdot(c: *cgen, n: *node) void = {
// Type casts are mostly no-ops at the asm level for our
// integer-shaped operands. Evaluate the source; AX holds
// the bits unchanged. (Sign- or zero-extending narrow loads
// to wider types is the loader's job, not cast's, in this
// minimal cgen.)
cgexpr(c, n.lhs);
return;
};
if (k == N_DOT) {
let lhs: *node = n.lhs; let lhs: *node = n.lhs;
let fld: str = n.str; let fld: str = n.str;
if (lhs != nil) { if (lhs != nil) {
@@ -5005,9 +5029,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
}; };
if (k == N_UN) { fn cgun(c: *cgen, n: *node) void = {
// Match C cgen ordering: evaluate operand first (load into AX), // Match C cgen ordering: evaluate operand first (load into AX),
// then apply the unary op. AMP / STAR override AX with the // then apply the unary op. AMP / STAR override AX with the
// address / deref. The wasted load before AMP keeps our asm // address / deref. The wasted load before AMP keeps our asm
@@ -5045,9 +5069,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_BIN) { fn cgbin(c: *cgen, n: *node) void = {
let unsignd: bool = nodeisunsigned(c, n.lhs); let unsignd: bool = nodeisunsigned(c, n.lhs);
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };
@@ -5110,9 +5134,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_CALL) { fn cgcall(c: *cgen, n: *node) void = {
let nargs: i32 = pushargsrev(c, n.list); let nargs: i32 = pushargsrev(c, n.list);
let i: i32 = 0; let i: i32 = 0;
for (i < nargs) { for (i < nargs) {
@@ -5214,9 +5238,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
}; };
if (k == N_ASSIGN) { fn cgassign(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs; let lhs: *node = n.lhs;
// `*p = v` — deref-assign. Element width comes from the // `*p = v` — deref-assign. Element width comes from the
// pointer's declared type. Mirrors C cgen: eval rhs (AX, // pointer's declared type. Mirrors C cgen: eval rhs (AX,
@@ -5578,18 +5602,20 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
};
}; };
// MODULE: wcc // MODULE: wcc
// selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww. // selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww.
// //
// Houses cgstmt — statements + control flow (N_BLOCK, N_IF, N_FOR, // cgstmt is a thin dispatcher over n.kind; each branch defers to a
// N_BREAK/N_CONTINUE, N_RETURN, N_LET, N_ASSIGN, etc.). The // per-kind helper: cgblock, cgreturn, cgexprstmt, cglet, cgif, cgfor,
// expression generator (cgexpr) lives in cgen_expr.ww; the foundation // cgmassign, cgbreak, cgcontinue.
// (types, emit primitives, collect* tables, FFI/module maps) lives in //
// cgen.ww. // 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 os;
use mem; use mem;
@@ -5605,16 +5631,36 @@ fn cgstmt(c: *cgen, n: *node) void = {
if (n == nil) { return; }; if (n == nil) { return; };
let k: i32 = n.kind; let k: i32 = n.kind;
if (k == N_BLOCK) { 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.last_was_return = 0;
};
fn cgblock(c: *cgen, n: *node) void = {
let s: *node = n.list; let s: *node = n.list;
for (s != nil) { for (s != nil) {
cgstmt(c, s); cgstmt(c, s);
s = s.next; s = s.next;
}; };
return; return;
}; };
if (k == N_RETURN) { fn cgreturn(c: *cgen, n: *node) void = {
let rhs: *node = n.lhs; let rhs: *node = n.lhs;
if (rhs != nil) { if (rhs != nil) {
// Tuple return `return a, b;` — pack as (AX=v0, DX=v1). // Tuple return `return a, b;` — pack as (AX=v0, DX=v1).
@@ -5679,15 +5725,15 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitline("\tRET\n"); emitline("\tRET\n");
c.last_was_return = 1; c.last_was_return = 1;
return; return;
}; };
if (k == N_EXPRSTMT) { fn cgexprstmt(c: *cgen, n: *node) void = {
if (n.lhs != nil) { cgexpr(c, n.lhs); }; if (n.lhs != nil) { cgexpr(c, n.lhs); };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_LET) { fn cglet(c: *cgen, n: *node) void = {
let nm: str = n.str; let nm: str = n.str;
let sz: i32 = slotsize(c, n.lhs); let sz: i32 = slotsize(c, n.lhs);
let off: i32 = localadd(c, nm, sz, n.lhs); let off: i32 = localadd(c, nm, sz, n.lhs);
@@ -5827,9 +5873,9 @@ fn cgstmt(c: *cgen, n: *node) void = {
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_IF) { fn cgif(c: *cgen, n: *node) void = {
let els: str = mklabel(c, "else"); let els: str = mklabel(c, "else");
let endl: str = mklabel(c, "end"); let endl: str = mklabel(c, "end");
cgexpr(c, n.cond); cgexpr(c, n.cond);
@@ -5847,9 +5893,9 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitlabel(endl); emitlabel(endl);
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_FOR) { fn cgfor(c: *cgen, n: *node) void = {
// Match C cgen's label scheme: <fn>_loop_N for the top, // Match C cgen's label scheme: <fn>_loop_N for the top,
// <fn>_endloop_N for the post-body merge. No separate cont // <fn>_endloop_N for the post-body merge. No separate cont
// label when there's no post-expression. // label when there's no post-expression.
@@ -5878,14 +5924,14 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitlabel(endl); emitlabel(endl);
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
// Tuple-destructure assign: `a, b = call();`. The call's tuple // Tuple-destructure assign: `a, b = call();`. The call's tuple
// return lands in (AX, DX); push DX to free it, store AX into // return lands in (AX, DX); push DX to free it, store AX into
// the first lvalue, then pop DX into the second. Mirrors // the first lvalue, then pop DX into the second. Mirrors
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same // cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
// as C — no fixture uses >2 today). // as C — no fixture uses >2 today).
if (k == N_MASSIGN) { fn cgmassign(c: *cgen, n: *node) void = {
if (n.rhs != nil) { cgexpr(c, n.rhs); }; if (n.rhs != nil) { cgexpr(c, n.rhs); };
emitline("\tPUSHQ\tDX\n"); emitline("\tPUSHQ\tDX\n");
let l0: *node = n.list; let l0: *node = n.list;
@@ -5914,29 +5960,28 @@ fn cgstmt(c: *cgen, n: *node) void = {
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_BREAK) { fn cgbreak(c: *cgen, n: *node) void = {
if (c.loop_top > 0) { if (c.loop_top > 0) {
let lbl: str = c.loop_end_buf[c.loop_top - 1]; let lbl: str = c.loop_end_buf[c.loop_top - 1];
emitline("\tJMP\t"); emitline(lbl); emitline("\n"); emitline("\tJMP\t"); emitline(lbl); emitline("\n");
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_CONTINUE) {
fn cgcontinue(c: *cgen, n: *node) void = {
if (c.loop_top > 0) { if (c.loop_top > 0) {
let lbl: str = c.loop_cont_buf[c.loop_top - 1]; let lbl: str = c.loop_cont_buf[c.loop_top - 1];
emitline("\tJMP\t"); emitline(lbl); emitline("\n"); emitline("\tJMP\t"); emitline(lbl); emitline("\n");
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
};
c.last_was_return = 0;
}; };
// MODULE: wcc // MODULE: wcc
// selfhost/cmd/wcc/cgen_decl.ww — split out of cgen.ww. // selfhost/cmd/wcc/cgen_decl.ww — split out of cgen.ww.
// //

View File

@@ -1,8 +1,13 @@
// selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww. // selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww.
// //
// Houses cgexpr — the per-expression code generator. The remainder of // cgexpr is a thin dispatcher over n.kind; each non-trivial branch
// the cgen lives in cgen.ww (foundation: types, emit primitives, the // lives in a per-kind helper (cgstrlit, cgident, cgindex, cgmatch,
// collect* tables, FFI/module maps) and cgen_stmt.ww (cgstmt). // cgdot, cgun, cgbin, cgcall, cgassign). Trivial literal loads
// (N_INTLIT, N_RUNELIT, N_TRUE/FALSE/NIL, N_CAST) stay inline.
//
// The remainder of cgen lives in cgen.ww (foundation: types, emit
// primitives, the collect* tables, FFI/module maps) and cgen_stmt.ww
// (cgstmt).
// //
// `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports // `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports
// this file, so any caller of cgen transitively gets cgexpr. // this file, so any caller of cgen transitively gets cgexpr.
@@ -35,19 +40,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
emitline(", AX\n"); emitline(", AX\n");
return; return;
}; };
if (k == N_STRLIT) { if (k == N_STRLIT) { cgstrlit(c, n); return; };
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
// sites that expect a str arg pick these up directly.
let nstr: str = n.str;
let lab: str = internstrlit(c, nstr);
emitline("\tLEAQ\t");
os.write(1, lab.ptr, lab.len: u64);
emitline("(SB), AX\n");
emitline("\tMOVQ\t$");
emitint(nstr.len: i64);
emitline(", BX\n");
return;
};
if (k == N_TRUE) { if (k == N_TRUE) {
emitline("\tMOVQ\t$1, AX\n"); emitline("\tMOVQ\t$1, AX\n");
return; return;
@@ -61,7 +54,48 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
if (k == N_IDENT) { if (k == N_IDENT) { cgident(c, n); return; };
if (k == N_INDEX) { cgindex(c, n); return; };
if (k == N_MATCH) { cgmatch(c, n); return; };
if (k == N_CAST) {
// Type casts are mostly no-ops at the asm level for our
// integer-shaped operands. Evaluate the source; AX holds
// the bits unchanged. (Sign- or zero-extending narrow loads
// to wider types is the loader's job, not cast's, in this
// minimal cgen.)
cgexpr(c, n.lhs);
return;
};
if (k == N_DOT) { cgdot(c, n); return; };
if (k == N_UN) { cgun(c, n); return; };
if (k == N_BIN) { cgbin(c, n); return; };
if (k == N_CALL) { cgcall(c, n); return; };
if (k == N_ASSIGN) { cgassign(c, n); return; };
};
fn cgstrlit(c: *cgen, n: *node) void = {
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
// sites that expect a str arg pick these up directly.
let nstr: str = n.str;
let lab: str = internstrlit(c, nstr);
emitline("\tLEAQ\t");
os.write(1, lab.ptr, lab.len: u64);
emitline("(SB), AX\n");
emitline("\tMOVQ\t$");
emitint(nstr.len: i64);
emitline(", BX\n");
return;
};
fn cgident(c: *cgen, n: *node) void = {
let nm: str = n.str; let nm: str = n.str;
let lc: *local = localfindnode(c, nm); let lc: *local = localfindnode(c, nm);
if (lc != nil) { if (lc != nil) {
@@ -106,9 +140,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_INDEX) { fn cgindex(c: *cgen, n: *node) void = {
// Element-size-aware load: u8-element bases use MOVZBQ, // Element-size-aware load: u8-element bases use MOVZBQ,
// everything else MOVQ. Fast path when the base is a bare // everything else MOVQ. Fast path when the base is a bare
// ident (mem.ww shape). // ident (mem.ww shape).
@@ -171,9 +205,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); }
else { emitline("\tMOVQ\t(AX), AX\n"); }; else { emitline("\tMOVQ\t(AX), AX\n"); };
return; return;
}; };
if (k == N_MATCH) { fn cgmatch(c: *cgen, n: *node) void = {
// match (e) { case let v: T => stmt; ... } // match (e) { case let v: T => stmt; ... }
// //
// Read the tagged-union slot and dispatch by tag. Slot // Read the tagged-union slot and dispatch by tag. Slot
@@ -272,19 +306,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
emitlabel(endl); emitlabel(endl);
return; return;
}; };
if (k == N_CAST) { fn cgdot(c: *cgen, n: *node) void = {
// Type casts are mostly no-ops at the asm level for our
// integer-shaped operands. Evaluate the source; AX holds
// the bits unchanged. (Sign- or zero-extending narrow loads
// to wider types is the loader's job, not cast's, in this
// minimal cgen.)
cgexpr(c, n.lhs);
return;
};
if (k == N_DOT) {
let lhs: *node = n.lhs; let lhs: *node = n.lhs;
let fld: str = n.str; let fld: str = n.str;
if (lhs != nil) { if (lhs != nil) {
@@ -499,9 +523,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
}; };
if (k == N_UN) { fn cgun(c: *cgen, n: *node) void = {
// Match C cgen ordering: evaluate operand first (load into AX), // Match C cgen ordering: evaluate operand first (load into AX),
// then apply the unary op. AMP / STAR override AX with the // then apply the unary op. AMP / STAR override AX with the
// address / deref. The wasted load before AMP keeps our asm // address / deref. The wasted load before AMP keeps our asm
@@ -539,9 +563,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_BIN) { fn cgbin(c: *cgen, n: *node) void = {
let unsignd: bool = nodeisunsigned(c, n.lhs); let unsignd: bool = nodeisunsigned(c, n.lhs);
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };
@@ -604,9 +628,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_CALL) { fn cgcall(c: *cgen, n: *node) void = {
let nargs: i32 = pushargsrev(c, n.list); let nargs: i32 = pushargsrev(c, n.list);
let i: i32 = 0; let i: i32 = 0;
for (i < nargs) { for (i < nargs) {
@@ -708,9 +732,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
}; };
if (k == N_ASSIGN) { fn cgassign(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs; let lhs: *node = n.lhs;
// `*p = v` — deref-assign. Element width comes from the // `*p = v` — deref-assign. Element width comes from the
// pointer's declared type. Mirrors C cgen: eval rhs (AX, // pointer's declared type. Mirrors C cgen: eval rhs (AX,
@@ -1072,6 +1096,6 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
};
}; };

View File

@@ -1,10 +1,12 @@
// selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww. // selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww.
// //
// Houses cgstmt — statements + control flow (N_BLOCK, N_IF, N_FOR, // cgstmt is a thin dispatcher over n.kind; each branch defers to a
// N_BREAK/N_CONTINUE, N_RETURN, N_LET, N_ASSIGN, etc.). The // per-kind helper: cgblock, cgreturn, cgexprstmt, cglet, cgif, cgfor,
// expression generator (cgexpr) lives in cgen_expr.ww; the foundation // cgmassign, cgbreak, cgcontinue.
// (types, emit primitives, collect* tables, FFI/module maps) lives in //
// cgen.ww. // 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 os;
use mem; use mem;
@@ -20,16 +22,36 @@ fn cgstmt(c: *cgen, n: *node) void = {
if (n == nil) { return; }; if (n == nil) { return; };
let k: i32 = n.kind; let k: i32 = n.kind;
if (k == N_BLOCK) { 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.last_was_return = 0;
};
fn cgblock(c: *cgen, n: *node) void = {
let s: *node = n.list; let s: *node = n.list;
for (s != nil) { for (s != nil) {
cgstmt(c, s); cgstmt(c, s);
s = s.next; s = s.next;
}; };
return; return;
}; };
if (k == N_RETURN) { fn cgreturn(c: *cgen, n: *node) void = {
let rhs: *node = n.lhs; let rhs: *node = n.lhs;
if (rhs != nil) { if (rhs != nil) {
// Tuple return `return a, b;` — pack as (AX=v0, DX=v1). // Tuple return `return a, b;` — pack as (AX=v0, DX=v1).
@@ -94,15 +116,15 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitline("\tRET\n"); emitline("\tRET\n");
c.last_was_return = 1; c.last_was_return = 1;
return; return;
}; };
if (k == N_EXPRSTMT) { fn cgexprstmt(c: *cgen, n: *node) void = {
if (n.lhs != nil) { cgexpr(c, n.lhs); }; if (n.lhs != nil) { cgexpr(c, n.lhs); };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_LET) { fn cglet(c: *cgen, n: *node) void = {
let nm: str = n.str; let nm: str = n.str;
let sz: i32 = slotsize(c, n.lhs); let sz: i32 = slotsize(c, n.lhs);
let off: i32 = localadd(c, nm, sz, n.lhs); let off: i32 = localadd(c, nm, sz, n.lhs);
@@ -242,9 +264,9 @@ fn cgstmt(c: *cgen, n: *node) void = {
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_IF) { fn cgif(c: *cgen, n: *node) void = {
let els: str = mklabel(c, "else"); let els: str = mklabel(c, "else");
let endl: str = mklabel(c, "end"); let endl: str = mklabel(c, "end");
cgexpr(c, n.cond); cgexpr(c, n.cond);
@@ -262,9 +284,9 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitlabel(endl); emitlabel(endl);
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_FOR) { fn cgfor(c: *cgen, n: *node) void = {
// Match C cgen's label scheme: <fn>_loop_N for the top, // Match C cgen's label scheme: <fn>_loop_N for the top,
// <fn>_endloop_N for the post-body merge. No separate cont // <fn>_endloop_N for the post-body merge. No separate cont
// label when there's no post-expression. // label when there's no post-expression.
@@ -293,14 +315,14 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitlabel(endl); emitlabel(endl);
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
// Tuple-destructure assign: `a, b = call();`. The call's tuple // Tuple-destructure assign: `a, b = call();`. The call's tuple
// return lands in (AX, DX); push DX to free it, store AX into // return lands in (AX, DX); push DX to free it, store AX into
// the first lvalue, then pop DX into the second. Mirrors // the first lvalue, then pop DX into the second. Mirrors
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same // cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
// as C — no fixture uses >2 today). // as C — no fixture uses >2 today).
if (k == N_MASSIGN) { fn cgmassign(c: *cgen, n: *node) void = {
if (n.rhs != nil) { cgexpr(c, n.rhs); }; if (n.rhs != nil) { cgexpr(c, n.rhs); };
emitline("\tPUSHQ\tDX\n"); emitline("\tPUSHQ\tDX\n");
let l0: *node = n.list; let l0: *node = n.list;
@@ -329,25 +351,24 @@ fn cgstmt(c: *cgen, n: *node) void = {
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_BREAK) { fn cgbreak(c: *cgen, n: *node) void = {
if (c.loop_top > 0) { if (c.loop_top > 0) {
let lbl: str = c.loop_end_buf[c.loop_top - 1]; let lbl: str = c.loop_end_buf[c.loop_top - 1];
emitline("\tJMP\t"); emitline(lbl); emitline("\n"); emitline("\tJMP\t"); emitline(lbl); emitline("\n");
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_CONTINUE) {
fn cgcontinue(c: *cgen, n: *node) void = {
if (c.loop_top > 0) { if (c.loop_top > 0) {
let lbl: str = c.loop_cont_buf[c.loop_top - 1]; let lbl: str = c.loop_cont_buf[c.loop_top - 1];
emitline("\tJMP\t"); emitline(lbl); emitline("\n"); emitline("\tJMP\t"); emitline(lbl); emitline("\n");
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
};
c.last_was_return = 0;
}; };

View File

@@ -4506,9 +4506,14 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
// MODULE: wcc // MODULE: wcc
// selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww. // selfhost/cmd/wcc/cgen_expr.ww — split out of cgen.ww.
// //
// Houses cgexpr — the per-expression code generator. The remainder of // cgexpr is a thin dispatcher over n.kind; each non-trivial branch
// the cgen lives in cgen.ww (foundation: types, emit primitives, the // lives in a per-kind helper (cgstrlit, cgident, cgindex, cgmatch,
// collect* tables, FFI/module maps) and cgen_stmt.ww (cgstmt). // cgdot, cgun, cgbin, cgcall, cgassign). Trivial literal loads
// (N_INTLIT, N_RUNELIT, N_TRUE/FALSE/NIL, N_CAST) stay inline.
//
// The remainder of cgen lives in cgen.ww (foundation: types, emit
// primitives, the collect* tables, FFI/module maps) and cgen_stmt.ww
// (cgstmt).
// //
// `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports // `use cgen_expr;` is unnecessary at consumer sites — cgen.ww imports
// this file, so any caller of cgen transitively gets cgexpr. // this file, so any caller of cgen transitively gets cgexpr.
@@ -4541,19 +4546,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
emitline(", AX\n"); emitline(", AX\n");
return; return;
}; };
if (k == N_STRLIT) { if (k == N_STRLIT) { cgstrlit(c, n); return; };
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
// sites that expect a str arg pick these up directly.
let nstr: str = n.str;
let lab: str = internstrlit(c, nstr);
emitline("\tLEAQ\t");
os.write(1, lab.ptr, lab.len: u64);
emitline("(SB), AX\n");
emitline("\tMOVQ\t$");
emitint(nstr.len: i64);
emitline(", BX\n");
return;
};
if (k == N_TRUE) { if (k == N_TRUE) {
emitline("\tMOVQ\t$1, AX\n"); emitline("\tMOVQ\t$1, AX\n");
return; return;
@@ -4567,7 +4560,48 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
if (k == N_IDENT) { if (k == N_IDENT) { cgident(c, n); return; };
if (k == N_INDEX) { cgindex(c, n); return; };
if (k == N_MATCH) { cgmatch(c, n); return; };
if (k == N_CAST) {
// Type casts are mostly no-ops at the asm level for our
// integer-shaped operands. Evaluate the source; AX holds
// the bits unchanged. (Sign- or zero-extending narrow loads
// to wider types is the loader's job, not cast's, in this
// minimal cgen.)
cgexpr(c, n.lhs);
return;
};
if (k == N_DOT) { cgdot(c, n); return; };
if (k == N_UN) { cgun(c, n); return; };
if (k == N_BIN) { cgbin(c, n); return; };
if (k == N_CALL) { cgcall(c, n); return; };
if (k == N_ASSIGN) { cgassign(c, n); return; };
};
fn cgstrlit(c: *cgen, n: *node) void = {
// Result is the (ptr, len) pair: ptr in AX, len in BX. Call
// sites that expect a str arg pick these up directly.
let nstr: str = n.str;
let lab: str = internstrlit(c, nstr);
emitline("\tLEAQ\t");
os.write(1, lab.ptr, lab.len: u64);
emitline("(SB), AX\n");
emitline("\tMOVQ\t$");
emitint(nstr.len: i64);
emitline(", BX\n");
return;
};
fn cgident(c: *cgen, n: *node) void = {
let nm: str = n.str; let nm: str = n.str;
let lc: *local = localfindnode(c, nm); let lc: *local = localfindnode(c, nm);
if (lc != nil) { if (lc != nil) {
@@ -4612,9 +4646,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_INDEX) { fn cgindex(c: *cgen, n: *node) void = {
// Element-size-aware load: u8-element bases use MOVZBQ, // Element-size-aware load: u8-element bases use MOVZBQ,
// everything else MOVQ. Fast path when the base is a bare // everything else MOVQ. Fast path when the base is a bare
// ident (mem.ww shape). // ident (mem.ww shape).
@@ -4677,9 +4711,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); } if (esz == 1) { emitline("\tMOVZBQ\t(AX), AX\n"); }
else { emitline("\tMOVQ\t(AX), AX\n"); }; else { emitline("\tMOVQ\t(AX), AX\n"); };
return; return;
}; };
if (k == N_MATCH) { fn cgmatch(c: *cgen, n: *node) void = {
// match (e) { case let v: T => stmt; ... } // match (e) { case let v: T => stmt; ... }
// //
// Read the tagged-union slot and dispatch by tag. Slot // Read the tagged-union slot and dispatch by tag. Slot
@@ -4778,19 +4812,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
emitlabel(endl); emitlabel(endl);
return; return;
}; };
if (k == N_CAST) { fn cgdot(c: *cgen, n: *node) void = {
// Type casts are mostly no-ops at the asm level for our
// integer-shaped operands. Evaluate the source; AX holds
// the bits unchanged. (Sign- or zero-extending narrow loads
// to wider types is the loader's job, not cast's, in this
// minimal cgen.)
cgexpr(c, n.lhs);
return;
};
if (k == N_DOT) {
let lhs: *node = n.lhs; let lhs: *node = n.lhs;
let fld: str = n.str; let fld: str = n.str;
if (lhs != nil) { if (lhs != nil) {
@@ -5005,9 +5029,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
}; };
if (k == N_UN) { fn cgun(c: *cgen, n: *node) void = {
// Match C cgen ordering: evaluate operand first (load into AX), // Match C cgen ordering: evaluate operand first (load into AX),
// then apply the unary op. AMP / STAR override AX with the // then apply the unary op. AMP / STAR override AX with the
// address / deref. The wasted load before AMP keeps our asm // address / deref. The wasted load before AMP keeps our asm
@@ -5045,9 +5069,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_BIN) { fn cgbin(c: *cgen, n: *node) void = {
let unsignd: bool = nodeisunsigned(c, n.lhs); let unsignd: bool = nodeisunsigned(c, n.lhs);
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); }; if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };
@@ -5110,9 +5134,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
return; return;
}; };
return; return;
}; };
if (k == N_CALL) { fn cgcall(c: *cgen, n: *node) void = {
let nargs: i32 = pushargsrev(c, n.list); let nargs: i32 = pushargsrev(c, n.list);
let i: i32 = 0; let i: i32 = 0;
for (i < nargs) { for (i < nargs) {
@@ -5214,9 +5238,9 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
}; };
if (k == N_ASSIGN) { fn cgassign(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs; let lhs: *node = n.lhs;
// `*p = v` — deref-assign. Element width comes from the // `*p = v` — deref-assign. Element width comes from the
// pointer's declared type. Mirrors C cgen: eval rhs (AX, // pointer's declared type. Mirrors C cgen: eval rhs (AX,
@@ -5578,18 +5602,20 @@ fn cgexpr(c: *cgen, n: *node) void = {
}; };
}; };
return; return;
};
}; };
// MODULE: wcc // MODULE: wcc
// selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww. // selfhost/cmd/wcc/cgen_stmt.ww — split out of cgen.ww.
// //
// Houses cgstmt — statements + control flow (N_BLOCK, N_IF, N_FOR, // cgstmt is a thin dispatcher over n.kind; each branch defers to a
// N_BREAK/N_CONTINUE, N_RETURN, N_LET, N_ASSIGN, etc.). The // per-kind helper: cgblock, cgreturn, cgexprstmt, cglet, cgif, cgfor,
// expression generator (cgexpr) lives in cgen_expr.ww; the foundation // cgmassign, cgbreak, cgcontinue.
// (types, emit primitives, collect* tables, FFI/module maps) lives in //
// cgen.ww. // 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 os;
use mem; use mem;
@@ -5605,16 +5631,36 @@ fn cgstmt(c: *cgen, n: *node) void = {
if (n == nil) { return; }; if (n == nil) { return; };
let k: i32 = n.kind; let k: i32 = n.kind;
if (k == N_BLOCK) { 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.last_was_return = 0;
};
fn cgblock(c: *cgen, n: *node) void = {
let s: *node = n.list; let s: *node = n.list;
for (s != nil) { for (s != nil) {
cgstmt(c, s); cgstmt(c, s);
s = s.next; s = s.next;
}; };
return; return;
}; };
if (k == N_RETURN) { fn cgreturn(c: *cgen, n: *node) void = {
let rhs: *node = n.lhs; let rhs: *node = n.lhs;
if (rhs != nil) { if (rhs != nil) {
// Tuple return `return a, b;` — pack as (AX=v0, DX=v1). // Tuple return `return a, b;` — pack as (AX=v0, DX=v1).
@@ -5679,15 +5725,15 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitline("\tRET\n"); emitline("\tRET\n");
c.last_was_return = 1; c.last_was_return = 1;
return; return;
}; };
if (k == N_EXPRSTMT) { fn cgexprstmt(c: *cgen, n: *node) void = {
if (n.lhs != nil) { cgexpr(c, n.lhs); }; if (n.lhs != nil) { cgexpr(c, n.lhs); };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_LET) { fn cglet(c: *cgen, n: *node) void = {
let nm: str = n.str; let nm: str = n.str;
let sz: i32 = slotsize(c, n.lhs); let sz: i32 = slotsize(c, n.lhs);
let off: i32 = localadd(c, nm, sz, n.lhs); let off: i32 = localadd(c, nm, sz, n.lhs);
@@ -5827,9 +5873,9 @@ fn cgstmt(c: *cgen, n: *node) void = {
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_IF) { fn cgif(c: *cgen, n: *node) void = {
let els: str = mklabel(c, "else"); let els: str = mklabel(c, "else");
let endl: str = mklabel(c, "end"); let endl: str = mklabel(c, "end");
cgexpr(c, n.cond); cgexpr(c, n.cond);
@@ -5847,9 +5893,9 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitlabel(endl); emitlabel(endl);
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_FOR) { fn cgfor(c: *cgen, n: *node) void = {
// Match C cgen's label scheme: <fn>_loop_N for the top, // Match C cgen's label scheme: <fn>_loop_N for the top,
// <fn>_endloop_N for the post-body merge. No separate cont // <fn>_endloop_N for the post-body merge. No separate cont
// label when there's no post-expression. // label when there's no post-expression.
@@ -5878,14 +5924,14 @@ fn cgstmt(c: *cgen, n: *node) void = {
emitlabel(endl); emitlabel(endl);
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
// Tuple-destructure assign: `a, b = call();`. The call's tuple // Tuple-destructure assign: `a, b = call();`. The call's tuple
// return lands in (AX, DX); push DX to free it, store AX into // return lands in (AX, DX); push DX to free it, store AX into
// the first lvalue, then pop DX into the second. Mirrors // the first lvalue, then pop DX into the second. Mirrors
// cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same // cmd/w6c/cgen.c:2424-2440. Lvalues beyond two are dropped (same
// as C — no fixture uses >2 today). // as C — no fixture uses >2 today).
if (k == N_MASSIGN) { fn cgmassign(c: *cgen, n: *node) void = {
if (n.rhs != nil) { cgexpr(c, n.rhs); }; if (n.rhs != nil) { cgexpr(c, n.rhs); };
emitline("\tPUSHQ\tDX\n"); emitline("\tPUSHQ\tDX\n");
let l0: *node = n.list; let l0: *node = n.list;
@@ -5914,29 +5960,28 @@ fn cgstmt(c: *cgen, n: *node) void = {
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_BREAK) { fn cgbreak(c: *cgen, n: *node) void = {
if (c.loop_top > 0) { if (c.loop_top > 0) {
let lbl: str = c.loop_end_buf[c.loop_top - 1]; let lbl: str = c.loop_end_buf[c.loop_top - 1];
emitline("\tJMP\t"); emitline(lbl); emitline("\n"); emitline("\tJMP\t"); emitline(lbl); emitline("\n");
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
}; };
if (k == N_CONTINUE) {
fn cgcontinue(c: *cgen, n: *node) void = {
if (c.loop_top > 0) { if (c.loop_top > 0) {
let lbl: str = c.loop_cont_buf[c.loop_top - 1]; let lbl: str = c.loop_cont_buf[c.loop_top - 1];
emitline("\tJMP\t"); emitline(lbl); emitline("\n"); emitline("\tJMP\t"); emitline(lbl); emitline("\n");
}; };
c.last_was_return = 0; c.last_was_return = 0;
return; return;
};
c.last_was_return = 0;
}; };
// MODULE: wcc // MODULE: wcc
// selfhost/cmd/wcc/cgen_decl.ww — split out of cgen.ww. // selfhost/cmd/wcc/cgen_decl.ww — split out of cgen.ww.
// //