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;
|
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
|
static int
|
||||||
localfind(Local *head, const char *name)
|
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) {
|
for (Node *cs = n->list; cs; cs = cs->next) {
|
||||||
char *next = mklabel(c, "match_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) {
|
if (cs->type != NULL) {
|
||||||
int tag = cg_tag_for_variant(su, cs->type);
|
int tag = cg_tag_for_variant(su, cs->type);
|
||||||
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
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
|
* value IS the slot's pointer word; no
|
||||||
* payload to copy. void binding is
|
* payload to copy. void binding is
|
||||||
* unusable (size 0), so only emit for
|
* 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) {
|
if (bu && bu->kind == TY_PTR) {
|
||||||
int voff = localoff(c, &locals,
|
int voff = local_alloc(c, &locals,
|
||||||
cs->str, 8, cg_frame);
|
cs->str, 8, cg_frame);
|
||||||
ins2(c, A_MOVQ,
|
ins2(c, A_MOVQ,
|
||||||
amem(D_BP, sl_off + 0),
|
amem(D_BP, sl_off + 0),
|
||||||
@@ -2294,7 +2323,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
} else {
|
} else {
|
||||||
int bsz = (bu && bu->kind == TY_STR)
|
int bsz = (bu && bu->kind == TY_STR)
|
||||||
? 16 : 8;
|
? 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);
|
bsz, cg_frame);
|
||||||
int nwords = (bsz + 7) / 8;
|
int nwords = (bsz + 7) / 8;
|
||||||
for (int w = 0; w < nwords; w++) {
|
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);
|
cgstmt(c, cs->body, &locals, cg_frame);
|
||||||
|
locals = arm_locals_saved;
|
||||||
ins1(c, A_JMP, abranch(end));
|
ins1(c, A_JMP, abranch(end));
|
||||||
label(c, next);
|
label(c, next);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4215,14 +4215,21 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// `match (e) { case let v: T => stmt; ... }` — the binding `v`
|
// `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 (k == nkind.N_MCASE) {
|
||||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||||
|
let outer: *scope = c.cur;
|
||||||
|
c.cur = newscope(c.a, outer);
|
||||||
let nm: str = n.str;
|
let nm: str = n.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
||||||
};
|
};
|
||||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||||
|
c.cur = outer;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6852,6 +6859,42 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
scrutoff = lc.off;
|
scrutoff = lc.off;
|
||||||
scrutt = resolvetype(c, lc.tnode);
|
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");
|
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 nxt: str = mklabel(c, "match_next");
|
||||||
let pat: *node = cs.lhs;
|
let pat: *node = cs.lhs;
|
||||||
let nullable: bool = isnullabletype(scrutt);
|
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
|
// Compute the variant tag for this arm. Default arm
|
||||||
// (no pattern) skips the tag check.
|
// (no pattern) skips the tag check.
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
@@ -6968,6 +7019,10 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Body. Match arms are statements; we cgstmt them.
|
// Body. Match arms are statements; we cgstmt them.
|
||||||
if (cs.body != nil) { cgstmt(c, cs.body); };
|
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("\tJMP\t");
|
||||||
emitline(endl);
|
emitline(endl);
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
@@ -7562,6 +7617,72 @@ fn cgbin(c: *cgen, n: *node) void = {
|
|||||||
return;
|
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.
|
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||||
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||||
// Each value gets:
|
// 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.
|
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||||
// handles a match as an expression with a by-value locals copy,
|
// 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.
|
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||||
// handles a match as an expression with a by-value locals copy,
|
// 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;
|
scrutoff = lc.off;
|
||||||
scrutt = resolvetype(c, lc.tnode);
|
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");
|
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 nxt: str = mklabel(c, "match_next");
|
||||||
let pat: *node = cs.lhs;
|
let pat: *node = cs.lhs;
|
||||||
let nullable: bool = isnullabletype(scrutt);
|
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
|
// Compute the variant tag for this arm. Default arm
|
||||||
// (no pattern) skips the tag check.
|
// (no pattern) skips the tag check.
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
@@ -807,6 +851,10 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Body. Match arms are statements; we cgstmt them.
|
// Body. Match arms are statements; we cgstmt them.
|
||||||
if (cs.body != nil) { cgstmt(c, cs.body); };
|
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("\tJMP\t");
|
||||||
emitline(endl);
|
emitline(endl);
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
@@ -1401,6 +1449,72 @@ fn cgbin(c: *cgen, n: *node) void = {
|
|||||||
return;
|
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.
|
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||||
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||||
// Each value gets:
|
// 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`
|
// `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 (k == nkind.N_MCASE) {
|
||||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||||
|
let outer: *scope = c.cur;
|
||||||
|
c.cur = newscope(c.a, outer);
|
||||||
let nm: str = n.str;
|
let nm: str = n.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
||||||
};
|
};
|
||||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||||
|
c.cur = outer;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4215,14 +4215,21 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// `match (e) { case let v: T => stmt; ... }` — the binding `v`
|
// `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 (k == nkind.N_MCASE) {
|
||||||
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
if (n.lhs != nil) { resolvewalk(c, n.lhs); };
|
||||||
|
let outer: *scope = c.cur;
|
||||||
|
c.cur = newscope(c.a, outer);
|
||||||
let nm: str = n.str;
|
let nm: str = n.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
scopedefine(c.cur, nm, skind.SK_VAR, nil, n);
|
||||||
};
|
};
|
||||||
if (n.body != nil) { resolvewalk(c, n.body); };
|
if (n.body != nil) { resolvewalk(c, n.body); };
|
||||||
|
c.cur = outer;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6852,6 +6859,42 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
scrutoff = lc.off;
|
scrutoff = lc.off;
|
||||||
scrutt = resolvetype(c, lc.tnode);
|
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");
|
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 nxt: str = mklabel(c, "match_next");
|
||||||
let pat: *node = cs.lhs;
|
let pat: *node = cs.lhs;
|
||||||
let nullable: bool = isnullabletype(scrutt);
|
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
|
// Compute the variant tag for this arm. Default arm
|
||||||
// (no pattern) skips the tag check.
|
// (no pattern) skips the tag check.
|
||||||
if (pat != nil) {
|
if (pat != nil) {
|
||||||
@@ -6968,6 +7019,10 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
// Body. Match arms are statements; we cgstmt them.
|
// Body. Match arms are statements; we cgstmt them.
|
||||||
if (cs.body != nil) { cgstmt(c, cs.body); };
|
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("\tJMP\t");
|
||||||
emitline(endl);
|
emitline(endl);
|
||||||
emitline("\n");
|
emitline("\n");
|
||||||
@@ -7562,6 +7617,72 @@ fn cgbin(c: *cgen, n: *node) void = {
|
|||||||
return;
|
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.
|
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.
|
||||||
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
// Mirrors cmd/w6c/cgen.c's N_CALL append branch (rt::ensure model).
|
||||||
// Each value gets:
|
// 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.
|
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||||
// handles a match as an expression with a by-value locals copy,
|
// handles a match as an expression with a by-value locals copy,
|
||||||
|
|||||||
Reference in New Issue
Block a user