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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user