w6c+selfhost: match-arm scope/spill + alloc(structlit) sugar
Three gaps in the wwstage cgen relative to C w6c, plus a matching
C-side bug surfaced along the way.
cgmatch (selfhost) handles non-ident scrutinees: `match (foo())` now
spills the AX:DX:CX return triple into a 24B `@match_spill` slot
rather than reading garbage off BP+0. scanlocals counts the slot so
the prologue SUBQ stays in sync. For N_CALL we recover the return
type via fnretlookup so nullable dispatch picks the pointer-vs-null
discriminator. Mirrors @match_spill in cmd/w6c/cgen.c N_MATCH.
cgcall (selfhost) special-cases `alloc(structlit{...})`: lower to
rt_alloc(totsize) + per-field MOV* at the struct's field offsets,
mirroring cmd/w6c/cgen.c's existing path. Previously the structlit
fell into pushargsrev and produced wrong code.
check.ww's N_MCASE branch now pushes a fresh scope around each arm
body. Without this, `case let e: str` inside a fn with an outer
`let e: *T` collided with scopedefine's same-scope dedup, the inner
binding silently dropped, and references to `e` inside the arm
resolved through the outer type.
Both cgens save/restore the locals head around case bodies so arm
binds (and nested arm-body lets) don't leak past the arm — code
after the match resolves names back through the outer scope.
w6c gains `local_alloc`: same as `localoff` minus the dedup. N_MATCH
case-bind allocation switches to it. Previously `let e: *T` (8B)
shadowed by `case let e: str` (16B) reused the outer 8B slot and the
inner str.len store overflowed into the saved BP, segfaulting on
return.
Tests 26/26.
This commit is contained in:
@@ -720,6 +720,26 @@ localoff(Cg *c, Local **head, const char *name, int size, int *frame)
|
||||
return off;
|
||||
}
|
||||
|
||||
/* local_alloc — always push a fresh slot, never dedup by name. Used for
|
||||
* match-arm bindings, where `case let e: T` must shadow any outer `e`
|
||||
* with a slot sized to T — localoff's dedup would reuse the outer's
|
||||
* (possibly smaller) slot and let multi-word writes overflow into the
|
||||
* saved BP / return address. localfind walks from the head, so the
|
||||
* fresh entry still wins inside the arm body. */
|
||||
static int
|
||||
local_alloc(Cg *c, Local **head, const char *name, int size, int *frame)
|
||||
{
|
||||
int al = 8;
|
||||
*frame = (*frame + size + al - 1) & ~(al - 1);
|
||||
int off = -*frame;
|
||||
Local *l = amalloc(c->a, sizeof *l);
|
||||
l->name = name;
|
||||
l->off = off;
|
||||
l->next = *head;
|
||||
*head = l;
|
||||
return off;
|
||||
}
|
||||
|
||||
static int
|
||||
localfind(Local *head, const char *name)
|
||||
{
|
||||
@@ -2232,6 +2252,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
for (Node *cs = n->list; cs; cs = cs->next) {
|
||||
char *next = mklabel(c, "match_next");
|
||||
/* Per-arm scope: save the locals head, restore it
|
||||
* after the body runs. Mirrors check.c's saved/restore
|
||||
* around cstmt — the case bind (and any lets inside
|
||||
* the arm) shouldn't leak past the arm, where a
|
||||
* matching outer name would otherwise resolve to the
|
||||
* shadow instead of the original. */
|
||||
Local *arm_locals_saved = locals;
|
||||
if (cs->type != NULL) {
|
||||
int tag = cg_tag_for_variant(su, cs->type);
|
||||
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
||||
@@ -2281,9 +2308,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* value IS the slot's pointer word; no
|
||||
* payload to copy. void binding is
|
||||
* unusable (size 0), so only emit for
|
||||
* the *T variant. */
|
||||
* the *T variant. local_alloc (not
|
||||
* localoff): the bind must NEVER reuse
|
||||
* an outer same-named slot. */
|
||||
if (bu && bu->kind == TY_PTR) {
|
||||
int voff = localoff(c, &locals,
|
||||
int voff = local_alloc(c, &locals,
|
||||
cs->str, 8, cg_frame);
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, sl_off + 0),
|
||||
@@ -2294,7 +2323,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
} else {
|
||||
int bsz = (bu && bu->kind == TY_STR)
|
||||
? 16 : 8;
|
||||
int voff = localoff(c, &locals, cs->str,
|
||||
/* local_alloc to dodge name-collision
|
||||
* dedup — a 16B str bind shadowing an
|
||||
* 8B outer would otherwise overflow
|
||||
* into the saved BP. */
|
||||
int voff = local_alloc(c, &locals, cs->str,
|
||||
bsz, cg_frame);
|
||||
int nwords = (bsz + 7) / 8;
|
||||
for (int w = 0; w < nwords; w++) {
|
||||
@@ -2307,6 +2340,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
}
|
||||
cgstmt(c, cs->body, &locals, cg_frame);
|
||||
locals = arm_locals_saved;
|
||||
ins1(c, A_JMP, abranch(end));
|
||||
label(c, next);
|
||||
}
|
||||
|
||||
@@ -4215,14 +4215,21 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
|
||||
// `match (e) { case let v: T => stmt; ... }` — the binding `v`
|
||||
// is declared by the case arm and visible inside its body.
|
||||
// is declared by the case arm and visible inside its body. Push a
|
||||
// fresh scope so `case let e: str` doesn't collide with an outer
|
||||
// `let e: *T` (scopedefine drops same-scope dupes silently and
|
||||
// would leave references to `e` resolving to the outer type).
|
||||
// Mirrors cmd/wcc/check.c's newscope/saved-restore around cstmt.
|
||||
if (k == nkind.N_MCASE) {
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
let outer: *scope = c.cur;
|
||||
c.cur = newscope(c.a, outer);
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
c.cur = outer;
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -6852,6 +6859,42 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, ?, etc.). Spill into a
|
||||
// 24B `@match_spill` scratch slot and dispatch off it.
|
||||
// Tagged returns follow the AX:DX:CX convention, so store
|
||||
// all three words at +0/+8/+16; nullable returns are
|
||||
// single-word (AX = ptr) and only read +0, so the extra
|
||||
// stores are harmless. For N_CALL we recover the return
|
||||
// type via fnretlookup so nullable dispatch can pick the
|
||||
// pointer-vs-null discriminator. Mirrors C cgen's
|
||||
// @match_spill path in cmd/w6c/cgen.c N_MATCH.
|
||||
scrutoff = localalloc(c, "@match_spill", 24, nil);
|
||||
if (scrut.kind == nkind.N_CALL) {
|
||||
let callee: *node = scrut.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) { scrutt = resolvetype(c, rt); };
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, scrut);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (!isnullabletype(scrutt)) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
@@ -6865,6 +6908,14 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
let pat: *node = cs.lhs;
|
||||
let nullable: bool = isnullabletype(scrutt);
|
||||
// Per-arm scope: save c.locals before allocating the bind
|
||||
// and restore after the body runs, so the arm's bind (and
|
||||
// any nested lets) don't leak past the arm. Matches the
|
||||
// checker's newscope/restore around N_MCASE. Without this,
|
||||
// `let e: *T = ...; match (r) { case let e: str => ... };
|
||||
// use e` would resolve `e` after the match to the inner
|
||||
// str slot instead of the outer ptr.
|
||||
let arm_locals_saved: *local = c.locals;
|
||||
// Compute the variant tag for this arm. Default arm
|
||||
// (no pattern) skips the tag check.
|
||||
if (pat != nil) {
|
||||
@@ -6968,6 +7019,10 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Body. Match arms are statements; we cgstmt them.
|
||||
if (cs.body != nil) { cgstmt(c, cs.body); };
|
||||
// Restore the locals head — pop everything the arm pushed
|
||||
// so post-match code resolves names to their original (outer)
|
||||
// bindings.
|
||||
c.locals = arm_locals_saved;
|
||||
emitline("\tJMP\t");
|
||||
emitline(endl);
|
||||
emitline("\n");
|
||||
@@ -7562,6 +7617,72 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// cgalloc — `alloc(value)` builtin lowering. Allocate sizeof(value)
|
||||
// bytes via rt_alloc, then write the value's bytes into the new
|
||||
// region. For an N_STRUCTLIT arg, allocate the struct's totsize and
|
||||
// emit per-field stores at each field's offset. For a scalar/ptr,
|
||||
// allocate 8 bytes and store one word. Mirrors cmd/w6c/cgen.c's
|
||||
// alloc-special branch in N_CALL. Returns the heap ptr in AX.
|
||||
fn cgalloc(c: *cgen, n: *node) void = {
|
||||
let v: *node = n.list;
|
||||
let sz: i32 = 8;
|
||||
let si: *structinfo = nil;
|
||||
if (v.kind == nkind.N_STRUCTLIT) {
|
||||
let trefn: *node = v.lhs;
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (trefn != nil) {
|
||||
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
|
||||
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
|
||||
};
|
||||
si = structlookup(c, sname);
|
||||
if (si != nil) { sz = si.totsize; };
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(sz: i64);
|
||||
emitline(", DI\n");
|
||||
emitline("\tCALL\trt_alloc(SB)\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (v.kind == nkind.N_STRUCTLIT) {
|
||||
if (si != nil) {
|
||||
let f: *node = v.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_FIELD) {
|
||||
let fname: str = f.str;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fname)) {
|
||||
cgexpr(c, f.lhs);
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
let sop: str = fieldstoreop(fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitint(fi.foff: i64);
|
||||
emitline("(BX)\n");
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
let sop: str = "MOVQ";
|
||||
if (sz == 1) { sop = "MOVB"; }
|
||||
else { if (sz == 4) { sop = "MOVL"; }; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
};
|
||||
|
||||
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||
// Each value gets:
|
||||
@@ -7705,6 +7826,16 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `alloc(value)` builtin: heap-init a fresh *T with the
|
||||
// value's bytes. For struct literals, lower to rt_alloc
|
||||
// + per-field stores. Mirrors cmd/w6c/cgen.c's N_CALL
|
||||
// alloc path.
|
||||
if (streq(callee.str, "alloc")) {
|
||||
if (n.list != nil) {
|
||||
cgalloc(c, n);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9698,6 +9829,16 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};};
|
||||
};
|
||||
};
|
||||
// `match (non-ident)` needs a 24B `@match_spill` scratch slot for
|
||||
// cgmatch to land the AX:DX:CX return triple. Mirrors C cgen's
|
||||
// localoff("@match_spill", ...). N_IDENT scrutinees read the slot
|
||||
// directly off the local — no spill needed.
|
||||
if (n.kind == nkind.N_MATCH) {
|
||||
let sc: *node = n.lhs;
|
||||
if (sc != nil) {
|
||||
if (sc.kind != nkind.N_IDENT) { total += 24; };
|
||||
};
|
||||
};
|
||||
// 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,
|
||||
|
||||
@@ -119,6 +119,16 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};};
|
||||
};
|
||||
};
|
||||
// `match (non-ident)` needs a 24B `@match_spill` scratch slot for
|
||||
// cgmatch to land the AX:DX:CX return triple. Mirrors C cgen's
|
||||
// localoff("@match_spill", ...). N_IDENT scrutinees read the slot
|
||||
// directly off the local — no spill needed.
|
||||
if (n.kind == nkind.N_MATCH) {
|
||||
let sc: *node = n.lhs;
|
||||
if (sc != nil) {
|
||||
if (sc.kind != nkind.N_IDENT) { total += 24; };
|
||||
};
|
||||
};
|
||||
// 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,
|
||||
|
||||
@@ -691,6 +691,42 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, ?, etc.). Spill into a
|
||||
// 24B `@match_spill` scratch slot and dispatch off it.
|
||||
// Tagged returns follow the AX:DX:CX convention, so store
|
||||
// all three words at +0/+8/+16; nullable returns are
|
||||
// single-word (AX = ptr) and only read +0, so the extra
|
||||
// stores are harmless. For N_CALL we recover the return
|
||||
// type via fnretlookup so nullable dispatch can pick the
|
||||
// pointer-vs-null discriminator. Mirrors C cgen's
|
||||
// @match_spill path in cmd/w6c/cgen.c N_MATCH.
|
||||
scrutoff = localalloc(c, "@match_spill", 24, nil);
|
||||
if (scrut.kind == nkind.N_CALL) {
|
||||
let callee: *node = scrut.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) { scrutt = resolvetype(c, rt); };
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, scrut);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (!isnullabletype(scrutt)) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
@@ -704,6 +740,14 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
let pat: *node = cs.lhs;
|
||||
let nullable: bool = isnullabletype(scrutt);
|
||||
// Per-arm scope: save c.locals before allocating the bind
|
||||
// and restore after the body runs, so the arm's bind (and
|
||||
// any nested lets) don't leak past the arm. Matches the
|
||||
// checker's newscope/restore around N_MCASE. Without this,
|
||||
// `let e: *T = ...; match (r) { case let e: str => ... };
|
||||
// use e` would resolve `e` after the match to the inner
|
||||
// str slot instead of the outer ptr.
|
||||
let arm_locals_saved: *local = c.locals;
|
||||
// Compute the variant tag for this arm. Default arm
|
||||
// (no pattern) skips the tag check.
|
||||
if (pat != nil) {
|
||||
@@ -807,6 +851,10 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Body. Match arms are statements; we cgstmt them.
|
||||
if (cs.body != nil) { cgstmt(c, cs.body); };
|
||||
// Restore the locals head — pop everything the arm pushed
|
||||
// so post-match code resolves names to their original (outer)
|
||||
// bindings.
|
||||
c.locals = arm_locals_saved;
|
||||
emitline("\tJMP\t");
|
||||
emitline(endl);
|
||||
emitline("\n");
|
||||
@@ -1401,6 +1449,72 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// cgalloc — `alloc(value)` builtin lowering. Allocate sizeof(value)
|
||||
// bytes via rt_alloc, then write the value's bytes into the new
|
||||
// region. For an N_STRUCTLIT arg, allocate the struct's totsize and
|
||||
// emit per-field stores at each field's offset. For a scalar/ptr,
|
||||
// allocate 8 bytes and store one word. Mirrors cmd/w6c/cgen.c's
|
||||
// alloc-special branch in N_CALL. Returns the heap ptr in AX.
|
||||
fn cgalloc(c: *cgen, n: *node) void = {
|
||||
let v: *node = n.list;
|
||||
let sz: i32 = 8;
|
||||
let si: *structinfo = nil;
|
||||
if (v.kind == nkind.N_STRUCTLIT) {
|
||||
let trefn: *node = v.lhs;
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (trefn != nil) {
|
||||
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
|
||||
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
|
||||
};
|
||||
si = structlookup(c, sname);
|
||||
if (si != nil) { sz = si.totsize; };
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(sz: i64);
|
||||
emitline(", DI\n");
|
||||
emitline("\tCALL\trt_alloc(SB)\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (v.kind == nkind.N_STRUCTLIT) {
|
||||
if (si != nil) {
|
||||
let f: *node = v.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_FIELD) {
|
||||
let fname: str = f.str;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fname)) {
|
||||
cgexpr(c, f.lhs);
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
let sop: str = fieldstoreop(fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitint(fi.foff: i64);
|
||||
emitline("(BX)\n");
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
let sop: str = "MOVQ";
|
||||
if (sz == 1) { sop = "MOVB"; }
|
||||
else { if (sz == 4) { sop = "MOVL"; }; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
};
|
||||
|
||||
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||
// Each value gets:
|
||||
@@ -1544,6 +1658,16 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `alloc(value)` builtin: heap-init a fresh *T with the
|
||||
// value's bytes. For struct literals, lower to rt_alloc
|
||||
// + per-field stores. Mirrors cmd/w6c/cgen.c's N_CALL
|
||||
// alloc path.
|
||||
if (streq(callee.str, "alloc")) {
|
||||
if (n.list != nil) {
|
||||
cgalloc(c, n);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -181,14 +181,21 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
|
||||
// `match (e) { case let v: T => stmt; ... }` — the binding `v`
|
||||
// is declared by the case arm and visible inside its body.
|
||||
// is declared by the case arm and visible inside its body. Push a
|
||||
// fresh scope so `case let e: str` doesn't collide with an outer
|
||||
// `let e: *T` (scopedefine drops same-scope dupes silently and
|
||||
// would leave references to `e` resolving to the outer type).
|
||||
// Mirrors cmd/wcc/check.c's newscope/saved-restore around cstmt.
|
||||
if (k == nkind.N_MCASE) {
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
let outer: *scope = c.cur;
|
||||
c.cur = newscope(c.a, outer);
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
c.cur = outer;
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -4215,14 +4215,21 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
|
||||
// `match (e) { case let v: T => stmt; ... }` — the binding `v`
|
||||
// is declared by the case arm and visible inside its body.
|
||||
// is declared by the case arm and visible inside its body. Push a
|
||||
// fresh scope so `case let e: str` doesn't collide with an outer
|
||||
// `let e: *T` (scopedefine drops same-scope dupes silently and
|
||||
// would leave references to `e` resolving to the outer type).
|
||||
// Mirrors cmd/wcc/check.c's newscope/saved-restore around cstmt.
|
||||
if (k == nkind.N_MCASE) {
|
||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||
let outer: *scope = c.cur;
|
||||
c.cur = newscope(c.a, outer);
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
||||
};
|
||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||
c.cur = outer;
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -6852,6 +6859,42 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
scrutoff = lc.off;
|
||||
scrutt = resolvetype(c, lc.tnode);
|
||||
};
|
||||
} else {
|
||||
// Non-ident scrutinee (call result, ?, etc.). Spill into a
|
||||
// 24B `@match_spill` scratch slot and dispatch off it.
|
||||
// Tagged returns follow the AX:DX:CX convention, so store
|
||||
// all three words at +0/+8/+16; nullable returns are
|
||||
// single-word (AX = ptr) and only read +0, so the extra
|
||||
// stores are harmless. For N_CALL we recover the return
|
||||
// type via fnretlookup so nullable dispatch can pick the
|
||||
// pointer-vs-null discriminator. Mirrors C cgen's
|
||||
// @match_spill path in cmd/w6c/cgen.c N_MATCH.
|
||||
scrutoff = localalloc(c, "@match_spill", 24, nil);
|
||||
if (scrut.kind == nkind.N_CALL) {
|
||||
let callee: *node = scrut.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) { scrutt = resolvetype(c, rt); };
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, scrut);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
emitline("(BP)\n");
|
||||
if (!isnullabletype(scrutt)) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((scrutoff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((scrutoff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
@@ -6865,6 +6908,14 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let nxt: str = mklabel(c, "match_next");
|
||||
let pat: *node = cs.lhs;
|
||||
let nullable: bool = isnullabletype(scrutt);
|
||||
// Per-arm scope: save c.locals before allocating the bind
|
||||
// and restore after the body runs, so the arm's bind (and
|
||||
// any nested lets) don't leak past the arm. Matches the
|
||||
// checker's newscope/restore around N_MCASE. Without this,
|
||||
// `let e: *T = ...; match (r) { case let e: str => ... };
|
||||
// use e` would resolve `e` after the match to the inner
|
||||
// str slot instead of the outer ptr.
|
||||
let arm_locals_saved: *local = c.locals;
|
||||
// Compute the variant tag for this arm. Default arm
|
||||
// (no pattern) skips the tag check.
|
||||
if (pat != nil) {
|
||||
@@ -6968,6 +7019,10 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
};
|
||||
// Body. Match arms are statements; we cgstmt them.
|
||||
if (cs.body != nil) { cgstmt(c, cs.body); };
|
||||
// Restore the locals head — pop everything the arm pushed
|
||||
// so post-match code resolves names to their original (outer)
|
||||
// bindings.
|
||||
c.locals = arm_locals_saved;
|
||||
emitline("\tJMP\t");
|
||||
emitline(endl);
|
||||
emitline("\n");
|
||||
@@ -7562,6 +7617,72 @@ fn cgbin(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// cgalloc — `alloc(value)` builtin lowering. Allocate sizeof(value)
|
||||
// bytes via rt_alloc, then write the value's bytes into the new
|
||||
// region. For an N_STRUCTLIT arg, allocate the struct's totsize and
|
||||
// emit per-field stores at each field's offset. For a scalar/ptr,
|
||||
// allocate 8 bytes and store one word. Mirrors cmd/w6c/cgen.c's
|
||||
// alloc-special branch in N_CALL. Returns the heap ptr in AX.
|
||||
fn cgalloc(c: *cgen, n: *node) void = {
|
||||
let v: *node = n.list;
|
||||
let sz: i32 = 8;
|
||||
let si: *structinfo = nil;
|
||||
if (v.kind == nkind.N_STRUCTLIT) {
|
||||
let trefn: *node = v.lhs;
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
if (trefn != nil) {
|
||||
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
|
||||
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
|
||||
};
|
||||
si = structlookup(c, sname);
|
||||
if (si != nil) { sz = si.totsize; };
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(sz: i64);
|
||||
emitline(", DI\n");
|
||||
emitline("\tCALL\trt_alloc(SB)\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (v.kind == nkind.N_STRUCTLIT) {
|
||||
if (si != nil) {
|
||||
let f: *node = v.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_FIELD) {
|
||||
let fname: str = f.str;
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
let fn_: str = fi.fname;
|
||||
if (streq(fn_, fname)) {
|
||||
cgexpr(c, f.lhs);
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
let sop: str = fieldstoreop(fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitint(fi.foff: i64);
|
||||
emitline("(BX)\n");
|
||||
fi = nil;
|
||||
} else {
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
emitline("\tMOVQ\t(SP), BX\n");
|
||||
let sop: str = "MOVQ";
|
||||
if (sz == 1) { sop = "MOVB"; }
|
||||
else { if (sz == 4) { sop = "MOVL"; }; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
};
|
||||
|
||||
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||
// Each value gets:
|
||||
@@ -7705,6 +7826,16 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `alloc(value)` builtin: heap-init a fresh *T with the
|
||||
// value's bytes. For struct literals, lower to rt_alloc
|
||||
// + per-field stores. Mirrors cmd/w6c/cgen.c's N_CALL
|
||||
// alloc path.
|
||||
if (streq(callee.str, "alloc")) {
|
||||
if (n.list != nil) {
|
||||
cgalloc(c, n);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9698,6 +9829,16 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};};
|
||||
};
|
||||
};
|
||||
// `match (non-ident)` needs a 24B `@match_spill` scratch slot for
|
||||
// cgmatch to land the AX:DX:CX return triple. Mirrors C cgen's
|
||||
// localoff("@match_spill", ...). N_IDENT scrutinees read the slot
|
||||
// directly off the local — no spill needed.
|
||||
if (n.kind == nkind.N_MATCH) {
|
||||
let sc: *node = n.lhs;
|
||||
if (sc != nil) {
|
||||
if (sc.kind != nkind.N_IDENT) { total += 24; };
|
||||
};
|
||||
};
|
||||
// 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,
|
||||
|
||||
Reference in New Issue
Block a user