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:
2026-05-13 06:29:52 +09:00
parent 9133251269
commit 6fd0160c0f
8 changed files with 2859 additions and 357 deletions

View File

@@ -1760,6 +1760,58 @@ cgexpr(Cg *c, Node *n, Local *locals)
int is_ptr = u && u->kind == TY_PTR;
int esz = (u && u->sub) ? (int)u->sub->size : 1;
int elem_is_str = u && u->sub && type_isstr(u->sub);
Type *esub = u ? u->sub : NULL;
Type *esubu = (esub && esub->kind == TY_NAMED)
? esub->under : esub;
int elem_tagged = esubu && esubu->kind == TY_TAGGED;
/* Tagged-union element: route widening through a
* scratch slot, then copy slot bytes to &arr[i].
* Materialising into the scratch first lets us reuse
* the full cg_widen_tagged_store machinery — scalar /
* str / struct / subset payloads, tag remap, nullable
* fold — without duplicating it. The scratch lives in
* the function frame; no cleanup needed. */
if ((is_arr || is_sl || is_ptr) && elem_tagged) {
int ssz = esz;
const char *scrn = mklabel(c, "idxscr");
int scr = local_alloc(c, &locals, scrn, ssz,
cg_frame);
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
for (int k = 0; k < ssz; k += 8)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, scr + k));
cg_widen_tagged_store(c, &locals, esubu,
n->rhs, scr, ssz);
/* Compute &arr[i] → BX. */
cgexpr(c, n->lhs->rhs, locals);
if (ssz > 1) {
ins2(c, A_MOVQ, aimm(ssz), areg(D_CX));
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
}
if (base->kind == N_IDENT && is_arr) {
int boff = localfind(locals, base->str);
ins2(c, A_LEAQ, amem(D_BP, boff),
areg(D_BX));
} else if (base->kind == N_IDENT) {
int boff = localfind(locals, base->str);
ins2(c, A_MOVQ, amem(D_BP, boff),
areg(D_BX));
} else {
ins1(c, A_PUSHQ, areg(D_AX));
cgexpr(c, base, locals);
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
ins1(c, A_POPQ, areg(D_AX));
}
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
/* Copy scratch slot → dest. */
for (int k = 0; k < ssz; k += 8) {
ins2(c, A_MOVQ, amem(D_BP, scr + k),
areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BX, k));
}
break;
}
if (is_arr || is_sl || is_ptr) {
cgexpr(c, n->rhs, locals); /* AX (and BX if str) */
/* str element: also stash len so we can store both */
@@ -3313,6 +3365,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
int esz = 1;
if (u && u->sub) esz = (int)u->sub->size;
if (u && u->kind == TY_STR) esz = 1;
Type *esub = u ? u->sub : NULL;
Type *esubu = (esub && esub->kind == TY_NAMED)
? esub->under : esub;
int elem_tagged = esubu && esubu->kind == TY_TAGGED;
if (n->lhs->kind == N_IDENT && u) {
int off = localfind(locals, n->lhs->str);
@@ -3337,6 +3393,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_CX), areg(D_BX));
break;
}
/* tagged element: load slot words into (AX=tag,
* DX=val0, CX=val1) — matches the tagged-return ABI
* so let-init / match / call-arg paths consume it
* without spilling. Nullable folded element is one
* word in AX (caller treats it as a pointer). */
if (elem_tagged) {
int ssz = (int)esubu->size;
if (ssz > 16)
ins2(c, A_MOVQ, amem(D_BX, 16),
areg(D_CX));
if (ssz > 8)
ins2(c, A_MOVQ, amem(D_BX, 8),
areg(D_DX));
ins2(c, A_MOVQ, amem(D_BX, 0), areg(D_AX));
break;
}
int signed_elem = u && u->sub && (
u->sub->kind == TY_I8 || u->sub->kind == TY_I16 ||
u->sub->kind == TY_I32);
@@ -3368,6 +3440,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, amem(D_AX, 0), areg(D_AX));
break;
}
/* tagged element via fallback base: AX holds the element
* address — copy to BX (the load into AX clobbers it), then
* load slot words. */
if (elem_tagged) {
int ssz = (int)esubu->size;
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
if (ssz > 16)
ins2(c, A_MOVQ, amem(D_BX, 16), areg(D_CX));
if (ssz > 8)
ins2(c, A_MOVQ, amem(D_BX, 8), areg(D_DX));
ins2(c, A_MOVQ, amem(D_BX, 0), areg(D_AX));
break;
}
{
int signed_elem = u && u->sub && (
u->sub->kind == TY_I8 || u->sub->kind == TY_I16 ||