selfhost: port switch — N_SWITCH parser + cgen + scratch slot

This commit is contained in:
2026-05-12 15:10:00 +09:00
parent ab0976571b
commit f67c07cbae
8 changed files with 502 additions and 0 deletions

View File

@@ -524,6 +524,37 @@ fn emitlabel(s: str) void = {
emitline(":\n");
};
// mkscratchname — fresh local-slot name ".<base>_<labelseq>". Used for
// compiler-synthesised slots (switch scrutinee, forrange index/len)
// that need to be unique per use site but are never referenced by user
// code. Increments labelseq so the same source position lines up with
// C cgen's labelseq stream.
fn mkscratchname(c: *cgen, base: str) str = {
let buf: [128]u8;
let i: i32 = 0;
buf[i] = 46u8; i += 1; // '.'
let j: i32 = 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;
};
// ---- string interning ------------------------------------------------
//
// streq is provided by sym.ww and reused here.

View File

@@ -87,6 +87,11 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
bidx += 1;
};
};
// `switch` allocates an 8B scratch slot for the scrutinee so case
// bodies can spill through SP without losing it. The slot is named
// ".sw_<labelseq>" at cgen time — unique per switch — so it must
// not dedup. Count it here so the frame SUBQ matches.
if (n.kind == nkind.N_SWITCH) { total += 8; };
// Match-arm binding (`case let v: T => ...`) gets a slot too.
// Crucially we do NOT dedup these against c.locals: C cgen
// handles a match as an expression with a by-value locals copy,

View File

@@ -34,6 +34,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
if (k == nkind.N_FOR) { cgfor(c, n); return; };
if (k == nkind.N_SWITCH) { cgswitch(c, n); return; };
if (k == nkind.N_MASSIGN) { cgmassign(c, n); return; };
if (k == nkind.N_MLET) { cgmlet(c, n); return; };
@@ -716,6 +718,65 @@ fn cgmlet(c: *cgen, n: *node) void = {
return;
};
// cgswitch — lower `switch (e) { case 1, 2: ...; case: default; }` to
// a chain of compares against the scrutinee. Scrutinee lands in a
// fresh 8B local slot so case bodies can spill SP without losing it.
// Cases are tried top-to-bottom; the `case:` arm with no exprs is the
// default and runs after all named arms fail. Mirrors cmd/w6c/cgen.c
// N_SWITCH: same labelseq consumption order so labels match byte-for-
// byte.
fn cgswitch(c: *cgen, n: *node) void = {
let swname: str = mkscratchname(c, "sw");
let sloff: i32 = localalloc(c, swname, 8, nil);
if (n.lhs != nil) { cgexpr(c, n.lhs); };
emitline("\tMOVQ\tAX, ");
emitoff(sloff: i64);
emitline("(BP)\n");
let endl: str = mklabel(c, "swend");
let defcase: *node = nil;
let cs: *node = n.list;
for (cs != nil) {
if (cs.list == nil) {
defcase = cs;
cs = cs.next;
continue;
};
let body: str = mklabel(c, "swcase");
let nxt: str = mklabel(c, "swnext");
let e: *node = cs.list;
for (e != nil) {
cgexpr(c, e);
emitline("\tMOVQ\t");
emitoff(sloff: i64);
emitline("(BP), BX\n");
emitline("\tCMPQ\tBX, AX\n");
emitline("\tJE\t");
emitline(body);
emitline("\n");
e = e.next;
};
emitline("\tJMP\t");
emitline(nxt);
emitline("\n");
emitlabel(body);
if (cs.body != nil) { cgstmt(c, cs.body); };
emitline("\tJMP\t");
emitline(endl);
emitline("\n");
emitlabel(nxt);
cs = cs.next;
};
if (defcase != nil) {
if (defcase.body != nil) { cgstmt(c, defcase.body); };
};
emitlabel(endl);
c.lastwasreturn = 0;
return;
};
fn cgbreak(c: *cgen, n: *node) void = {
if (c.looptop > 0) {
let lbl: str = c.loopendbuf[c.looptop - 1];