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:
@@ -38,6 +38,16 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
||||
total += sz;
|
||||
};
|
||||
// Carry the let's tnode into the stub so scanlocals can
|
||||
// dispatch on type later in the walk — e.g. detecting
|
||||
// `arr[i] = ...` where arr is a tagged-element array,
|
||||
// which needs an @tagscr scratch slot reservation.
|
||||
let stub: *local = localfindnode(c, n.str);
|
||||
if (stub != nil) {
|
||||
if (stub.tnode == nil) {
|
||||
if (n.lhs != nil) { stub.tnode = n.lhs; };
|
||||
};
|
||||
};
|
||||
};
|
||||
// Multi-let from a tuple-returning call: each binding's size
|
||||
// comes from its annotated type (l.lhs) when present, else from
|
||||
@@ -145,6 +155,128 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-arr/slice index store needs a 24B scratch slot
|
||||
// (`@tagscr`) for cgwidentaggedstore to materialise the source
|
||||
// in before copying to the element address. Reserved once per
|
||||
// function (dedup'd via scanseenmark) regardless of how many
|
||||
// tagged-arr stores the body contains.
|
||||
if (n.kind == nkind.N_ASSIGN) {
|
||||
let alhs: *node = n.lhs;
|
||||
if (alhs != nil) {
|
||||
if (alhs.kind == nkind.N_INDEX) {
|
||||
let abase: *node = alhs.lhs;
|
||||
if (abase != nil) {
|
||||
if (abase.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, abase.str);
|
||||
let btn: *node = nil;
|
||||
if (lc != nil) { btn = lc.tnode; }
|
||||
else { btn = letvartnode(c, abase.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) {
|
||||
if (istaggedtype(c, etn)) {
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Tagged-union return with struct payload or tagged-subset
|
||||
// source — cgreturn materialises in @tagscr then loads
|
||||
// AX/DX/CX. Detect via the same rhsstructpayload predicate
|
||||
// the cgen uses, so we only reserve when the cgen will
|
||||
// actually emit a scratch-using path. `!void` / `!i32`
|
||||
// aliases share N_STRUCTLIT shape but resolve to
|
||||
// non-struct types — they fall through to scalar/str and
|
||||
// don't need scratch.
|
||||
if (n.kind == nkind.N_RETURN) {
|
||||
if (c.fnret != nil) {
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
if (!isnullabletype(c.fnret)) {
|
||||
let rhs: *node = n.lhs;
|
||||
let needs: bool = false;
|
||||
if (rhs != nil) {
|
||||
let sn: str = rhsstructpayload(c, rhs);
|
||||
if (sn.len > 0) { needs = true; };
|
||||
if (rhs.kind == nkind.N_IDENT) {
|
||||
let lc: *local = localfindnode(c, rhs.str);
|
||||
if (lc != nil) {
|
||||
if (istaggedtype(c, lc.tnode)) {
|
||||
needs = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (needs) {
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Call-site struct-payload widening uses @tagscr — when the
|
||||
// arg is a struct literal/ident and the callee's param is
|
||||
// tagged, pushargsrev materialises in scratch and pushes.
|
||||
// Scalar / str args take the direct-push fast path (no
|
||||
// scratch). Tagged-typed ident args also skip widening (the
|
||||
// slot is already laid out, so pushargsrev pushes slot words
|
||||
// directly). Both fast paths agree with C cgen bytewise, so
|
||||
// only struct-payload sites get a scratch reservation.
|
||||
if (n.kind == nkind.N_CALL) {
|
||||
let callee: *node = n.lhs;
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == nkind.N_DOT) { cnm = callee.str; };
|
||||
};
|
||||
if (cnm.len > 0) {
|
||||
let ps: *node = fnparamslookup(c, cnm);
|
||||
let a: *node = n.list;
|
||||
for (a != nil) {
|
||||
if (ps == nil) { a = nil; }
|
||||
else {
|
||||
if (ps.kind == nkind.N_PARAM) {
|
||||
let pt: *node = ps.lhs;
|
||||
if (istaggedtype(c, pt)) {
|
||||
if (!isnullabletype(pt)) {
|
||||
let sn: str = rhsstructpayload(c, a);
|
||||
if (sn.len > 0) {
|
||||
let isidentstruct: bool = false;
|
||||
if (a.kind == nkind.N_IDENT) {
|
||||
// Struct ident as
|
||||
// tagged arg — pushargsrev
|
||||
// still routes through the
|
||||
// scratch path.
|
||||
isidentstruct = true;
|
||||
};
|
||||
let _u: bool = isidentstruct;
|
||||
if (!scanseenmark(c, "@tagscr")) {
|
||||
total += 24;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (a != nil) {
|
||||
a = a.next;
|
||||
ps = ps.next;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (n.lhs != nil) { total += scanlocals(c, n.lhs); };
|
||||
if (n.rhs != nil) { total += scanlocals(c, n.rhs); };
|
||||
if (n.cond != nil) { total += scanlocals(c, n.cond); };
|
||||
|
||||
Reference in New Issue
Block a user