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) {
@@ -4614,7 +4648,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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).
@@ -4679,7 +4713,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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
@@ -4780,17 +4814,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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) {
@@ -5007,7 +5031,7 @@ 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
@@ -5047,7 +5071,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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); };
@@ -5112,7 +5136,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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) {
@@ -5216,7 +5240,7 @@ 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,
@@ -5579,17 +5603,19 @@ 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,7 +5631,27 @@ 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);
@@ -5614,7 +5660,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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).
@@ -5681,13 +5727,13 @@ fn cgstmt(c: *cgen, n: *node) void = {
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);
@@ -5829,7 +5875,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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);
@@ -5849,7 +5895,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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.
@@ -5885,7 +5931,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
// 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;
@@ -5916,7 +5962,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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");
@@ -5924,7 +5970,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
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");
@@ -5933,8 +5980,6 @@ fn cgstmt(c: *cgen, n: *node) void = {
return; return;
}; };
c.last_was_return = 0;
};
// MODULE: wcc // MODULE: wcc

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) {
@@ -108,7 +142,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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).
@@ -173,7 +207,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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
@@ -274,17 +308,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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) {
@@ -501,7 +525,7 @@ 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
@@ -541,7 +565,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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); };
@@ -606,7 +630,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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) {
@@ -710,7 +734,7 @@ 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,
@@ -1073,5 +1097,5 @@ 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,7 +22,27 @@ 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);
@@ -29,7 +51,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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).
@@ -96,13 +118,13 @@ fn cgstmt(c: *cgen, n: *node) void = {
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);
@@ -244,7 +266,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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);
@@ -264,7 +286,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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.
@@ -300,7 +322,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
// 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;
@@ -331,7 +353,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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");
@@ -339,7 +361,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
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");
@@ -348,6 +371,4 @@ fn cgstmt(c: *cgen, n: *node) void = {
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) {
@@ -4614,7 +4648,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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).
@@ -4679,7 +4713,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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
@@ -4780,17 +4814,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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) {
@@ -5007,7 +5031,7 @@ 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
@@ -5047,7 +5071,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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); };
@@ -5112,7 +5136,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
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) {
@@ -5216,7 +5240,7 @@ 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,
@@ -5579,17 +5603,19 @@ 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,7 +5631,27 @@ 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);
@@ -5614,7 +5660,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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).
@@ -5681,13 +5727,13 @@ fn cgstmt(c: *cgen, n: *node) void = {
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);
@@ -5829,7 +5875,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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);
@@ -5849,7 +5895,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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.
@@ -5885,7 +5931,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
// 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;
@@ -5916,7 +5962,7 @@ fn cgstmt(c: *cgen, n: *node) void = {
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");
@@ -5924,7 +5970,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
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");
@@ -5933,8 +5980,6 @@ fn cgstmt(c: *cgen, n: *node) void = {
return; return;
}; };
c.last_was_return = 0;
};
// MODULE: wcc // MODULE: wcc