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

@@ -20170,20 +20170,10 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
};
};
};
// #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. Mirrors
// cstage cg_widen_tagged_store.
if (tupsrc == nil) {
if (su != nil) {
if (su.kind == tykind.TY_TUPLE) {
let m72: str = "cgwidentaggedstore: tuple-typed source shape unwired (only the bare/cast tuple literal carries a full payload; see #72)\n";
os.write(2, m72.ptr, m72.len: u64);
os.exit(1);
};
};
};
// #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.
// Mirrors cstage cg_widen_tagged_store.
};
if (tupsrc != nil) {
if (su != nil) { if (su.kind == tykind.TY_TUPLE) {
@@ -20335,6 +20325,83 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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 tupeslot strides as the box
// payload the literal loop above fills, so the source-in-memory
// layout already equals the box payload layout — the fill is a
// flat block-copy of sum(tupeslot) bytes from the source address
// into slot_off+8, no re-slotting (a tagged element rides over
// as its already-built box). Kept loud (out of scope): a
// CALL/sret result (#40-kin) and struct-field / array-literal-
// element sources (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. Mirror of cstage cg_widen_tagged_store.
if (su != nil) { if (su.kind == tykind.TY_TUPLE) {
let addrsrc: *node = src;
if (addrsrc.kind == nkind.N_CAST) {
if (addrsrc.lhs != nil) { addrsrc = addrsrc.lhs; };
};
let okkind: bool = false;
if (addrsrc.kind == nkind.N_IDENT) { okkind = true; };
if (addrsrc.kind == nkind.N_INDEX) { okkind = true; };
if (addrsrc.kind == nkind.N_UN) {
if (addrsrc.op == tkind.TK_STAR) { okkind = true; };
};
if (!okkind) {
let mk: str = "cgwidentaggedstore: tuple-typed source shape unwired (only the bare/cast tuple literal and the addressable ident/index/deref trio carry a full payload; see #116)\n";
os.write(2, mk.ptr, mk.len: u64);
os.exit(1);
};
let ntag: i32 = flatvariantidxt(dt, src.type_: *tinfo, false);
if (ntag < 0) {
let mt: str = "cgwidentaggedstore: tuple-in-union variant tag unresolved (untyped/literal tuple element; see #242 / #241)\n";
os.write(2, mt.ptr, mt.len: u64);
os.exit(1);
};
// The tuple's type-table size IS sum(tupeslot) under the 8B-slot
// tuple layout (every walk takes its stride from tupeslot; 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 a wwstage tuple tinfo carries no per-element
// params list anyway. Mirror of cstage cg_widen_tagged_store.
let ntotal: i32 = su.size: i32;
if (8 + ntotal > slot_sz) {
let mp: str = "cgwidentaggedstore: tuple-in-union payload needs SysV eightbyte packing (narrow elements share an eightbyte; see #242 follow-up)\n";
os.write(2, mp.ptr, mp.len: u64);
os.exit(1);
};
if (!cgplaceaddr(c, addrsrc, "SI")) {
let ma: str = "cgwidentaggedstore: addressable tuple source address unresolved (see #116)\n";
os.write(2, ma.ptr, ma.len: u64);
os.exit(1);
};
emitline("\tXORQ\tAX, AX\n");
let nzk: i32 = 0;
for (nzk < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + nzk): i64);
emitline("(BP)\n");
nzk += 8;
};
let nck: i32 = 0;
for (nck < ntotal) {
emitline("\tMOVQ\t");
emitoff(nck: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8 + nck): i64);
emitline("(BP)\n");
nck += 8;
};
emitline("\tMOVQ\t$");
emitint(ntag: i64);
emitline(", ");
emitoff(slot_off: i64);
emitline("(BP)\n");
return;
}; };
// Struct payload (literal or ident).
let sname: str = rhsstructpayload(c, src);
if (sname.len > 0) {

View File

@@ -3998,20 +3998,10 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
};
};
};
// #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. Mirrors
// cstage cg_widen_tagged_store.
if (tupsrc == nil) {
if (su != nil) {
if (su.kind == tykind.TY_TUPLE) {
let m72: str = "cgwidentaggedstore: tuple-typed source shape unwired (only the bare/cast tuple literal carries a full payload; see #72)\n";
os.write(2, m72.ptr, m72.len: u64);
os.exit(1);
};
};
};
// #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.
// Mirrors cstage cg_widen_tagged_store.
};
if (tupsrc != nil) {
if (su != nil) { if (su.kind == tykind.TY_TUPLE) {
@@ -4163,6 +4153,83 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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 tupeslot strides as the box
// payload the literal loop above fills, so the source-in-memory
// layout already equals the box payload layout — the fill is a
// flat block-copy of sum(tupeslot) bytes from the source address
// into slot_off+8, no re-slotting (a tagged element rides over
// as its already-built box). Kept loud (out of scope): a
// CALL/sret result (#40-kin) and struct-field / array-literal-
// element sources (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. Mirror of cstage cg_widen_tagged_store.
if (su != nil) { if (su.kind == tykind.TY_TUPLE) {
let addrsrc: *node = src;
if (addrsrc.kind == nkind.N_CAST) {
if (addrsrc.lhs != nil) { addrsrc = addrsrc.lhs; };
};
let okkind: bool = false;
if (addrsrc.kind == nkind.N_IDENT) { okkind = true; };
if (addrsrc.kind == nkind.N_INDEX) { okkind = true; };
if (addrsrc.kind == nkind.N_UN) {
if (addrsrc.op == tkind.TK_STAR) { okkind = true; };
};
if (!okkind) {
let mk: str = "cgwidentaggedstore: tuple-typed source shape unwired (only the bare/cast tuple literal and the addressable ident/index/deref trio carry a full payload; see #116)\n";
os.write(2, mk.ptr, mk.len: u64);
os.exit(1);
};
let ntag: i32 = flatvariantidxt(dt, src.type_: *tinfo, false);
if (ntag < 0) {
let mt: str = "cgwidentaggedstore: tuple-in-union variant tag unresolved (untyped/literal tuple element; see #242 / #241)\n";
os.write(2, mt.ptr, mt.len: u64);
os.exit(1);
};
// The tuple's type-table size IS sum(tupeslot) under the 8B-slot
// tuple layout (every walk takes its stride from tupeslot; 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 a wwstage tuple tinfo carries no per-element
// params list anyway. Mirror of cstage cg_widen_tagged_store.
let ntotal: i32 = su.size: i32;
if (8 + ntotal > slot_sz) {
let mp: str = "cgwidentaggedstore: tuple-in-union payload needs SysV eightbyte packing (narrow elements share an eightbyte; see #242 follow-up)\n";
os.write(2, mp.ptr, mp.len: u64);
os.exit(1);
};
if (!cgplaceaddr(c, addrsrc, "SI")) {
let ma: str = "cgwidentaggedstore: addressable tuple source address unresolved (see #116)\n";
os.write(2, ma.ptr, ma.len: u64);
os.exit(1);
};
emitline("\tXORQ\tAX, AX\n");
let nzk: i32 = 0;
for (nzk < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + nzk): i64);
emitline("(BP)\n");
nzk += 8;
};
let nck: i32 = 0;
for (nck < ntotal) {
emitline("\tMOVQ\t");
emitoff(nck: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8 + nck): i64);
emitline("(BP)\n");
nck += 8;
};
emitline("\tMOVQ\t$");
emitint(ntag: i64);
emitline(", ");
emitoff(slot_off: i64);
emitline("(BP)\n");
return;
}; };
// Struct payload (literal or ident).
let sname: str = rhsstructpayload(c, src);
if (sname.len > 0) {

View File

@@ -20170,20 +20170,10 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
};
};
};
// #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. Mirrors
// cstage cg_widen_tagged_store.
if (tupsrc == nil) {
if (su != nil) {
if (su.kind == tykind.TY_TUPLE) {
let m72: str = "cgwidentaggedstore: tuple-typed source shape unwired (only the bare/cast tuple literal carries a full payload; see #72)\n";
os.write(2, m72.ptr, m72.len: u64);
os.exit(1);
};
};
};
// #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.
// Mirrors cstage cg_widen_tagged_store.
};
if (tupsrc != nil) {
if (su != nil) { if (su.kind == tykind.TY_TUPLE) {
@@ -20335,6 +20325,83 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
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 tupeslot strides as the box
// payload the literal loop above fills, so the source-in-memory
// layout already equals the box payload layout — the fill is a
// flat block-copy of sum(tupeslot) bytes from the source address
// into slot_off+8, no re-slotting (a tagged element rides over
// as its already-built box). Kept loud (out of scope): a
// CALL/sret result (#40-kin) and struct-field / array-literal-
// element sources (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. Mirror of cstage cg_widen_tagged_store.
if (su != nil) { if (su.kind == tykind.TY_TUPLE) {
let addrsrc: *node = src;
if (addrsrc.kind == nkind.N_CAST) {
if (addrsrc.lhs != nil) { addrsrc = addrsrc.lhs; };
};
let okkind: bool = false;
if (addrsrc.kind == nkind.N_IDENT) { okkind = true; };
if (addrsrc.kind == nkind.N_INDEX) { okkind = true; };
if (addrsrc.kind == nkind.N_UN) {
if (addrsrc.op == tkind.TK_STAR) { okkind = true; };
};
if (!okkind) {
let mk: str = "cgwidentaggedstore: tuple-typed source shape unwired (only the bare/cast tuple literal and the addressable ident/index/deref trio carry a full payload; see #116)\n";
os.write(2, mk.ptr, mk.len: u64);
os.exit(1);
};
let ntag: i32 = flatvariantidxt(dt, src.type_: *tinfo, false);
if (ntag < 0) {
let mt: str = "cgwidentaggedstore: tuple-in-union variant tag unresolved (untyped/literal tuple element; see #242 / #241)\n";
os.write(2, mt.ptr, mt.len: u64);
os.exit(1);
};
// The tuple's type-table size IS sum(tupeslot) under the 8B-slot
// tuple layout (every walk takes its stride from tupeslot; 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 a wwstage tuple tinfo carries no per-element
// params list anyway. Mirror of cstage cg_widen_tagged_store.
let ntotal: i32 = su.size: i32;
if (8 + ntotal > slot_sz) {
let mp: str = "cgwidentaggedstore: tuple-in-union payload needs SysV eightbyte packing (narrow elements share an eightbyte; see #242 follow-up)\n";
os.write(2, mp.ptr, mp.len: u64);
os.exit(1);
};
if (!cgplaceaddr(c, addrsrc, "SI")) {
let ma: str = "cgwidentaggedstore: addressable tuple source address unresolved (see #116)\n";
os.write(2, ma.ptr, ma.len: u64);
os.exit(1);
};
emitline("\tXORQ\tAX, AX\n");
let nzk: i32 = 0;
for (nzk < slot_sz) {
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + nzk): i64);
emitline("(BP)\n");
nzk += 8;
};
let nck: i32 = 0;
for (nck < ntotal) {
emitline("\tMOVQ\t");
emitoff(nck: i64);
emitline("(SI), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8 + nck): i64);
emitline("(BP)\n");
nck += 8;
};
emitline("\tMOVQ\t$");
emitint(ntag: i64);
emitline(", ");
emitoff(slot_off: i64);
emitline("(BP)\n");
return;
}; };
// Struct payload (literal or ident).
let sname: str = rhsstructpayload(c, src);
if (sname.len > 0) {