wcc/cgen: #116 non-literal tuple source into a tagged box (both-stage)

cg_widen_tagged_store only handled a tuple LITERAL (N_TUPLE / cast-of-
N_TUPLE) widened into a tagged box; any addressable non-literal tuple
source -- IDENT var, INDEX tbl[i], DEREF *p -- hit the `else fatal`
("tuple-typed source shape unwired"). Both stages loud-identical
(honest, no silent miscompile). This blocked indexing a const tuple
table into a union (regex charclass_map[i] -> charset union).

Add an addressable-tuple-source arm, both stages (cgen.c +
cgenutil.ww twin). It resolves the source address via the cgplaceaddr
place-spine (covering ident/index/deref -- one mechanism, so the trio
is family-closed) and block-copies the tuple's type-table ->size bytes
into the box payload (after the 8B tag), then stamps the variant tag.
No re-slotting: a tuple's in-memory layout uses the same eslot strides
(str=24B header, *fn=8B, ...) as the box payload the literal loop
fills, so source-layout == dest-layout. The existing narrow-pack and
tag-unresolved guards stay as the honest boundary; CALL/sret tuple
sources (different receive, #68-kin) stay loud.

align-BOTH: both stages were loud (no runtime reference), and byte-id
is structurally blind to an identical-wrong emission -- so correctness
is proven by a RUNTIME read-back pin (944_nonlit_tuple_widen_run, per
shape: match-extract + assert str header + call the fn-ptr elem with
distinct fns so a stale pointer is caught). 936's old reject row
graduates to a run row. Both stages byte-identical (990-997 green).
This commit is contained in:
2026-06-06 19:27:28 +09:00
parent da30f10f70
commit f8be2ae8dd
7 changed files with 638 additions and 60 deletions

View File

@@ -2737,14 +2737,9 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
else if (src->kind == N_CAST && src->lhs
&& src->lhs->kind == N_TUPLE)
tupsrc = src->lhs;
/* #72: any OTHER tuple-typed source (ident, call result,
* match binding) would fall to the scalar arm below and
* silently drop payload slot 1+ — loud-stop (rule 7) until
* the word-copy / cursor-receive arms are wired. */
if (tupsrc == NULL)
fatal("cg_widen_tagged_store: tuple-typed source "
"shape unwired (only the bare/cast tuple literal "
"carries a full payload; see #72)");
/* #116: a NON-LITERAL tuple-typed source (ident, index,
* deref) is no longer loud here — it routes to the
* addressable block-copy arm just below the literal arm. */
}
if (tupsrc != NULL) {
int tag = cg_tag_for_variant(du, st);
@@ -2833,6 +2828,62 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
if (via_outer) goto copy_out;
return;
}
/* #116: a NON-LITERAL but ADDRESSABLE tuple source — a tuple IDENT
* var, a slice/array INDEX (tbl[i]), or a DEREF (*p). The tuple in
* memory uses the SAME tuple_eslot strides as the box payload the
* literal loop above fills (a scalar 8B, a slice/str its 24B header,
* a tagged element its boxed tag+payload), so the source-in-memory
* layout already equals the box payload layout — the fill is a flat
* block-copy of sum(tuple_eslot) bytes from the source address into
* write_off+8, no re-slotting (a tagged element rides over as its
* already-built box, so no recursion is needed). Kept loud (out of
* scope): a CALL/sret result (the tuple sits at the sret address,
* #40-kin) and a struct-field / array-literal-element source (loud
* EARLIER at construction, #49 / #270-1c). A cast wrapping a
* concrete-variant tuple (`(tbl[i]: ci)`) survived the widen-cast
* peel above; its operand is the addressable expr. */
if (su && su->kind == TY_TUPLE) {
Node *addrsrc = src;
if (addrsrc->kind == N_CAST && addrsrc->lhs)
addrsrc = addrsrc->lhs;
if (addrsrc->kind != N_IDENT && addrsrc->kind != N_INDEX
&& !(addrsrc->kind == N_UN && addrsrc->op == TK_STAR))
fatal("cg_widen_tagged_store: tuple-typed source shape "
"unwired (only the bare/cast tuple literal and the "
"addressable ident/index/deref trio carry a full "
"payload; see #116)");
int tag = cg_tag_for_variant(du, st);
if (tag < 0)
fatal("cg_widen_tagged_store: tuple-in-union variant "
"tag unresolved (untyped/literal tuple element; "
"see #242 / #241)");
/* The tuple's type-table size IS sum(tuple_eslot) under the
* 8B-slot tuple layout (every walk takes its stride from
* tuple_eslot; the type's size is their sum), so the payload
* byte-count routes through the type table (rule 13) without
* re-walking the elements — and matches the wwstage twin, whose
* tuple tinfo carries no per-element params list. */
int total = (int)su->size;
if (8 + total > sz)
fatal("cg_widen_tagged_store: tuple-in-union payload "
"needs SysV eightbyte packing (narrow elements "
"share an eightbyte; see #242 follow-up)");
if (!cgplaceaddr(c, addrsrc, D_SI, *locals_p))
fatal("cg_widen_tagged_store: addressable tuple source "
"address unresolved (see #116)");
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
for (int k = 0; k < sz; k += 8)
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, write_off + k));
for (int k = 0; k < total; k += 8) {
ins2(c, A_MOVQ, amem(D_SI, k), areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, write_off + 8 + k));
}
ins2(c, A_MOVQ, aimm(tag), amem(D_BP, write_off + 0));
if (via_outer) goto copy_out;
return;
}
/* Struct payload: zero the whole slot, then write fields/words
* at slot+8+ — keeping the tag word at slot+0 from the zero-fill,
* then patch it with the variant tag. */