w6c+selfhost: tagged-arr element ABI + full selfhost mirror
Closes the remaining tagged-union gaps after the prior two commits:
1. Tagged element in an array/slice (cstage). N_INDEX load now reads
slot words into AX/DX/CX, matching the tagged-return ABI so match
/ call-arg / let-init paths consume `arr[i]` uniformly. N_INDEX
store routes through a scratch slot + cg_widen_tagged_store +
byte-copy to &arr[i], so the full widening machinery (scalar /
str / struct payload / tagged subset / nullable fold) lights up
for element writes too.
2. Selfhost mirror — the cgen widen helpers (struct payload,
tagged-subset, spread-flatten) C cgen has had for two commits
finally land in selfhost:
cgwidentaggedstore — single writer for nullable / tagged ident /
tagged via AX:DX:CX / struct (lit + ident) /
str / scalar source shapes.
cgwidentagremap — CMPQ-chain tag remap for variant-subset.
rhsstructpayload — struct-name predicate; filters `!void` /
`!i32` aliases that share N_STRUCTLIT shape
but aren't structs.
rhstaggedident,
rhstaggedabicall — source-shape predicates.
flatvariantidx — spread-aware variant index lookup. Walks
`(...inner | T)` entries by resolving the
alias and inlining the inner's variants so
wwstage's tag order matches the check.c
flattening cstage does at type resolution.
cglet tagged init, cgassign tagged-ident reassign, cgreturn struct
/ subset payload, pushargsrev struct payload, cgindex tagged
element load, cgassign N_INDEX tagged element store all delegate
to these. cgmatch picks up scrutt from N_INDEX bases (element
type) and uses flatvariantidx for case dispatch.
3. Selfhost frame accounting: scanlocals reserves a 24B @tagscr slot
when the body contains a tagged-arr store, a struct-payload
tagged return, or a struct-payload call arg — dedup'd via
scanseenmark so multiple sites share one slot. N_LET stubs now
carry tnode so walk-time type checks see the array element type.
slotsize TARRAY learned to size tagged / struct / ptr / aliased
elements (was 8B-default for anything not N_TNAME-primitive,
undersizing tagged-element arrays).
Scalar / str call-arg widening keeps its direct-push fast path
(no scratch), so wwstage's asm on selfhost source remains
byte-identical to cstage's — 993/995 still pass.
700_e2e: 9 new rows — scalar/str/struct/subset/nullable variants in
arrays and slices, plus pass-arg / let-init / return / match shapes.
This commit is contained in:
@@ -112,7 +112,7 @@ fn cgexpr(c: *cgen, n: *node) void = {
|
||||
// tagged-union type expression `tagged`. -1 if `tagged` isn't an
|
||||
// nkind.N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch
|
||||
// does inline; pulled out so `is` / `as` can reuse it.
|
||||
fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
||||
fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (vt == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
@@ -120,16 +120,7 @@ fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
|
||||
want.ptr = nil; want.len = 0;
|
||||
if (vt.kind == nkind.N_TNAME) { want = vt.str; };
|
||||
if (want.len == 0) { return -1; };
|
||||
let v: *node = tagged.list;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (streq(v.str, want)) { return idx; };
|
||||
};
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
return -1;
|
||||
return flatvariantidx(c, tagged, want);
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
@@ -246,7 +237,7 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||
if (want < 0) { want = 0; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(scrutoff: i64);
|
||||
@@ -334,7 +325,7 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
|
||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||
if (want < 0) { want = 0; };
|
||||
let okl: str = mklabel(c, "asrt_ok");
|
||||
emitline("\tMOVQ\t");
|
||||
@@ -566,6 +557,43 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
esz = indexbaseesz(c, base);
|
||||
};};
|
||||
};
|
||||
// Tagged-union element: load slot words into (AX=tag, DX=val0,
|
||||
// CX=val1) matching the tagged-return ABI so call-arg / let /
|
||||
// match consumers see the same shape as a tagged-returning fn.
|
||||
// Slot size = esz (8/16/24); nullable folded element is one
|
||||
// word, which the fallthrough below handles via MOVQ AX.
|
||||
let elem_tagged: bool = false;
|
||||
let elem_slot_sz: i32 = esz;
|
||||
if (base != nil) {
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let bl: *local = baselocal;
|
||||
let etn: *node = nil;
|
||||
if (bl != nil) {
|
||||
let btn: *node = bl.tnode;
|
||||
if (btn != nil) {
|
||||
let bk: nkind = btn.kind;
|
||||
if (bk == nkind.N_TARRAY) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { etn = btn.lhs; };
|
||||
};
|
||||
} else {
|
||||
let tn: *node = letvartnode(c, base.str);
|
||||
if (tn != nil) {
|
||||
let bk: nkind = tn.kind;
|
||||
if (bk == nkind.N_TARRAY) { etn = tn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { etn = tn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { etn = tn.lhs; };
|
||||
};
|
||||
};
|
||||
if (istaggedtype(c, etn)) {
|
||||
if (!isnullabletype(etn)) {
|
||||
elem_tagged = true;
|
||||
elem_slot_sz = slotsize(c, etn);
|
||||
esz = elem_slot_sz;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -584,6 +612,16 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (elem_tagged) {
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
if (elem_slot_sz > 8) {
|
||||
emitline("\tMOVQ\t8(BX), DX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(BX), AX\n");
|
||||
return;
|
||||
};
|
||||
if (esz == 16) {
|
||||
emitline("\tMOVQ\t8(BX), CX\n");
|
||||
emitline("\tMOVQ\t(BX), AX\n");
|
||||
@@ -612,6 +650,16 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (elem_tagged) {
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
if (elem_slot_sz > 8) {
|
||||
emitline("\tMOVQ\t8(BX), DX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(BX), AX\n");
|
||||
return;
|
||||
};
|
||||
// str element (16B): load (ptr, len) into (AX, BX) so
|
||||
// the value flows through the str-rhs convention.
|
||||
if (esz == 16) {
|
||||
@@ -633,6 +681,19 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, base);
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tADDQ\tBX, AX\n");
|
||||
if (elem_tagged) {
|
||||
// AX holds the element address. Copy to BX (loading slot+0
|
||||
// into AX clobbers it), then read slot words.
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
if (elem_slot_sz > 16) {
|
||||
emitline("\tMOVQ\t16(BX), CX\n");
|
||||
};
|
||||
if (elem_slot_sz > 8) {
|
||||
emitline("\tMOVQ\t8(BX), DX\n");
|
||||
};
|
||||
emitline("\tMOVQ\t(BX), AX\n");
|
||||
return;
|
||||
};
|
||||
if (esz == 16) {
|
||||
emitline("\tMOVQ\t8(AX), BX\n");
|
||||
emitline("\tMOVQ\t(AX), AX\n");
|
||||
@@ -747,15 +808,16 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
scrutt = resolvetagged(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.
|
||||
// Non-ident scrutinee (call result, arr[i], ?, etc.).
|
||||
// Spill into a 24B `@match_spill` scratch slot and
|
||||
// dispatch off it. Tagged returns (N_CALL) follow the
|
||||
// AX:DX:CX convention; tagged-element loads (N_INDEX)
|
||||
// after the cgindex fix produce the same triple.
|
||||
// Nullable returns are single-word (AX = ptr); only +0
|
||||
// is read, so the extra stores are harmless. We
|
||||
// recover the scrutinee type from fnretlookup (N_CALL)
|
||||
// or the base local's array element type (N_INDEX) so
|
||||
// dispatch can compute variant indices.
|
||||
scrutoff = localalloc(c, "@match_spill", 24, nil);
|
||||
if (scrut.kind == nkind.N_CALL) {
|
||||
let callee: *node = scrut.lhs;
|
||||
@@ -770,6 +832,27 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
if (scrut.kind == nkind.N_INDEX) {
|
||||
let ibase: *node = scrut.lhs;
|
||||
if (ibase != nil) {
|
||||
if (ibase.kind == nkind.N_IDENT) {
|
||||
let bl: *local = localfindnode(c, ibase.str);
|
||||
let btn: *node = nil;
|
||||
if (bl != nil) { btn = bl.tnode; }
|
||||
else { btn = letvartnode(c, ibase.str); };
|
||||
if (btn != nil) {
|
||||
let bk: nkind = btn.kind;
|
||||
let etn: *node = nil;
|
||||
if (bk == nkind.N_TARRAY) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { etn = btn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { etn = btn.lhs; };
|
||||
if (etn != nil) {
|
||||
scrutt = resolvetagged(c, etn);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, scrut);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
@@ -832,23 +915,8 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
let patname: str;
|
||||
patname.ptr = nil; patname.len = 0;
|
||||
if (pat.kind == nkind.N_TNAME) { patname = pat.str; };
|
||||
let v: *node = scrutt.list;
|
||||
let idx: i32 = 0;
|
||||
let found: bool = false;
|
||||
for (v != nil) {
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.str, patname)) {
|
||||
want = idx;
|
||||
found = true;
|
||||
v = nil;
|
||||
};
|
||||
};
|
||||
if (v != nil) {
|
||||
v = v.next;
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
if (!found) { want = 0; };
|
||||
let r: i32 = flatvariantidx(c, scrutt, patname);
|
||||
if (r >= 0) { want = r; };
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
@@ -2156,6 +2224,25 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union local reassignment: `r = expr;` where r has a
|
||||
// tagged-union type. Delegate to cgwidentaggedstore (same path
|
||||
// as cglet's tagged-init). Covers nullable fold, tagged source,
|
||||
// struct payload, str payload, scalar payload, with tag remap.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
if (istaggedtype(c, lc.tnode)) {
|
||||
let lsz: i32 = slotsize(c, lc.tnode);
|
||||
cgwidentaggedstore(c, lc.tnode,
|
||||
n.rhs, lc.off, lsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// `*p = v` — deref-assign. Element width comes from the
|
||||
// pointer's declared type. Mirrors C cgen: eval rhs (AX,
|
||||
// and BX if str), push, eval pointer, pop value, store.
|
||||
@@ -2256,12 +2343,20 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let isglobalptr: bool = false;
|
||||
let globalname: str;
|
||||
globalname.ptr = nil; globalname.len = 0;
|
||||
let elemtn: *node = nil;
|
||||
if (base != nil) {
|
||||
if (base.kind == nkind.N_IDENT) {
|
||||
let bn: str = base.str;
|
||||
baselocal = localfindnode(c, bn);
|
||||
if (baselocal != nil) {
|
||||
esz = elemsizeof(baselocal.tnode);
|
||||
let btn: *node = baselocal.tnode;
|
||||
if (btn != nil) {
|
||||
let bk: nkind = btn.kind;
|
||||
if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; };
|
||||
if (bk == nkind.N_TPTR) { elemtn = btn.lhs; };
|
||||
};
|
||||
} else {
|
||||
let tn: *node = letvartnode(c, bn);
|
||||
if (tn != nil) {
|
||||
@@ -2269,11 +2364,13 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
isglobalarr = true;
|
||||
globalname = bn;
|
||||
esz = elemsizeof(tn);
|
||||
elemtn = tn.lhs;
|
||||
};
|
||||
if (tn.kind == nkind.N_TPTR) {
|
||||
isglobalptr = true;
|
||||
globalname = bn;
|
||||
esz = elemsizeof(tn);
|
||||
elemtn = tn.lhs;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -2281,6 +2378,80 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
esz = indexbaseesz(c, base);
|
||||
};};
|
||||
};
|
||||
// Tagged-union element: materialize source in a shared
|
||||
// scratch slot via cgwidentaggedstore (handles struct /
|
||||
// str / scalar / subset / nullable variants uniformly),
|
||||
// then compute &arr[i] and byte-copy. The scratch
|
||||
// (@tagscr) is reused across all tagged-arr stores in
|
||||
// the function and counted once in scanlocals.
|
||||
if (elemtn != nil) {
|
||||
if (istaggedtype(c, elemtn)) {
|
||||
let slot_sz: i32 = slotsize(c, elemtn);
|
||||
let scroff: i32 = localadd(c, "@tagscr",
|
||||
24, nil);
|
||||
// Pre-zero scratch (matches push helper).
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zz: i32 = 0;
|
||||
for (zz < slot_sz) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((scroff + zz): i64);
|
||||
emitline("(BP)\n");
|
||||
zz += 8;
|
||||
};
|
||||
cgwidentaggedstore(c, elemtn, n.rhs,
|
||||
scroff, slot_sz);
|
||||
cgexpr(c, idx);
|
||||
if (slot_sz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(slot_sz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (isglobalarr) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, globalname);
|
||||
emitline("(SB), BX\n");
|
||||
} else { if (isglobalptr) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitsymname(c, globalname);
|
||||
emitline("(SB), BX\n");
|
||||
} else { if (baselocal != nil) {
|
||||
let tn: *node = baselocal.tnode;
|
||||
let isarr: bool = false;
|
||||
if (tn != nil) {
|
||||
if (tn.kind == nkind.N_TARRAY) {
|
||||
isarr = true;
|
||||
};
|
||||
};
|
||||
if (isarr) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
};};};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
let cc: i32 = 0;
|
||||
for (cc < slot_sz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((scroff + cc): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(cc: i64);
|
||||
emitline("(BX)\n");
|
||||
cc += 8;
|
||||
};
|
||||
return;
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.rhs); // value → AX
|
||||
if (esz == 16) { emitline("\tPUSHQ\tBX\n"); };
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
|
||||
Reference in New Issue
Block a user