wcc+w6c+w6c_ww: global tuple lets — DATA emit + element reads + len(g.N) (C-t3, #48)

Global tuple lets were WHOLLY unwired, silently: let_emit_size returned
0 so emit_lets SKIPPED the definition (no DATA, no diagnostic), then
cstage's t.N read and #235 len arm read BP-frame garbage (localfind→0)
while wwstage — with the tuple never in collectlets — mis-emitted the
field index as a symbol (`MOVQ 0(SB), AX`). ken's #48 was the len()
facet of this.

Now: let_emit_size/letemitsize admit TY_TUPLE (slot-sum size, rides
C-t0); emit_tuple_data/emittupledata lay the slot-format DATAW row —
a scalar element one 8B LE word, a str element its 24B header slot
with a DATAR ptr patch at the element's slot offset (the #18 [N]str
per-element pattern; strlits pre-interned in element order) — and any
element that doesn't reduce to an int/str literal dies LOUD instead
of skipped. The t.N read and len arms gain the global base (LEAQ
sym(SB) into CX, the struct-field-global pattern; wwstage's C5 len
loud-stop graduates to the working path). A GLOBAL tuple as a
first-class VALUE (`let q = g;`) loud-stops on both stages — pre-fix
it byte-identically loaded word0 only and read a stale cursor for
words 1+ (element reads are the supported surface).

941 grows the t3 rows: global element reads (str+i64 and packed
u32,u32 incl. len(g.0)) + rejects (float-element init, whole-value
use, pre-existing element-write anchor). 7/82 checks fail at the
C-t2 parent (cs silent-garbage runtime, ww C5 build-fail, both
rejects vacuous-or-absent).
This commit is contained in:
2026-06-04 19:02:41 +09:00
parent 6426fac6f2
commit 8578ad0533
6 changed files with 1262 additions and 16 deletions

View File

@@ -996,6 +996,27 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
};
return alen * esz;
};
// C-t3 (#48): tuple global — per-element slot sum (C-t0
// layout: a str/slice its header, everything else one 8B
// eightbyte). Mirrors cstage let_emit_size TY_TUPLE (u->size,
// the checker slot sum). Pre-C-t3 the 0 here kept tuple
// globals out of collectlets entirely — no DATA emitted, and
// the module-leaf fallback mis-emitted the field index as a
// symbol (`MOVQ 0(SB), AX`).
if (t.kind == nkind.N_TTUPLE) {
let tsum: i32 = 0;
let p: *node = t.list;
for (p != nil) {
let et: *node = p.lhs;
if (isstrtype(c, et) || isslicetype(c, et)) {
tsum += (tyslicesize(): i32);
} else {
tsum += 8;
};
p = p.next;
};
return tsum;
};
if (t.kind != nkind.N_TNAME) { return 0; };
let nm: str = t.str;
if (letscalarprim(nm)) { return 8; };
@@ -1330,6 +1351,42 @@ export fn letpreintern(c: *cgen, file: *node) void = {
};
};
};
// C-t3 (#48): tuple global — pre-intern str-element
// literals in element order so emitletdataw's tuple
// arm's DATAR rows find their _S_ rodata rows (the
// #18 array-arm pattern; cstage let_pre_intern twin).
if (!handled && r != nil && d.lhs != nil) {
if (r.kind == nkind.N_TUPLE) {
let tlt: *node = d.lhs;
for (tlt != nil && tlt.kind == nkind.N_TNAME) {
tlt = aliaslookup(c, tlt.str);
};
if (tlt != nil) {
if (tlt.kind == nkind.N_TTUPLE) {
handled = true;
let tp: *node = tlt.list;
let e: *node = r.list;
for (e != nil && tp != nil) {
let et: *node = tp.lhs;
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) {
ev = ev.lhs;
};
if (ev != nil) {
if (ev.kind == nkind.N_STRLIT
&& (isstrtype(c, et) || isslicetype(c, et))) {
if (ev.str.len > 0) {
internstrlit(c, ev.str);
};
};
};
e = e.next;
tp = tp.next;
};
};
};
};
};
if (!handled) {
let sz: i32 = letemitsize(c, d);
// #43: route the str-let gate through primtypesize so
@@ -2138,6 +2195,128 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
emitline(".d(SB)\n");
};
// emittupledata — module-level `let g: (T0, T1, ...) = (v0, ...);`
// static init (C-t3, #48). Slot layout (C-t0): a scalar element is one
// 8B LE word, a str element its 24B header slot (8 zero ptr placeholder
// + LE len + 8 zero cap) with a DATAR patching the ptr word at the
// element's slot offset — the per-element twin of the scalar-str-global
// arm, offset like emitstrarraydata's rows. Elements must reduce to int
// (foldintliteral) or str literals; anything else returns false and the
// caller loud-stops (rule 7 — pre-C-t3 the whole definition was
// SILENTLY skipped and reads saw garbage). rhs == nil zero-inits the
// slot. Mirror of cstage emit_tuple_data.
fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool = {
if (tt == nil) { return false; };
if (rhs == nil) {
let zsz: i32 = 0;
let p0: *node = tt.list;
for (p0 != nil) {
let et0: *node = p0.lhs;
if (isstrtype(c, et0) || isslicetype(c, et0)) {
zsz += (tyslicesize(): i32);
} else {
zsz += 8;
};
p0 = p0.next;
};
emitline("DATAW ");
emitsymnamehint(c, name, module);
emitline("(SB),\"");
let zi: i32 = 0;
for (zi < zsz) { emitdatawbyte(0u8); zi += 1; };
emitline("\"\n");
return true;
};
if (rhs.kind != nkind.N_TUPLE) { return false; };
// Validate first (two-pass, emitarraydata precedent) so a partial
// row never reaches the output.
let tp: *node = tt.list;
let e: *node = rhs.list;
for (e != nil) {
let et: *node = nil;
if (tp != nil) { et = tp.lhs; };
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
if (ev == nil) { return false; };
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
if (wide) {
if (ev.kind != nkind.N_STRLIT) { return false; };
} else {
let v: u64 = 0u64;
if (!foldintliteral(ev, &v)) { return false; };
};
e = e.next;
if (tp != nil) { tp = tp.next; };
};
emitline("DATAW ");
emitsymnamehint(c, name, module);
emitline("(SB),\"");
tp = tt.list;
e = rhs.list;
for (e != nil) {
let et: *node = nil;
if (tp != nil) { et = tp.lhs; };
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
if (wide) {
let i: i32 = 0;
for (i < 8) { emitdatawbyte(0u8); i += 1; };
let lv: u64 = ev.str.len: u64;
i = 0;
for (i < 8) {
emitdatawbyte((lv & 255u64): u8);
lv = lv >> 8u64;
i += 1;
};
i = 16;
let ssz: i32 = primtypesize("str"): i32;
for (i < ssz) { emitdatawbyte(0u8); i += 1; };
} else {
let v: u64 = 0u64;
foldintliteral(ev, &v);
let i: i32 = 0;
let nv: u64 = v;
for (i < 8) {
emitdatawbyte((nv & 255u64): u8);
nv = nv >> 8u64;
i += 1;
};
};
e = e.next;
if (tp != nil) { tp = tp.next; };
};
emitline("\"\n");
let foff: i32 = 0;
tp = tt.list;
e = rhs.list;
for (e != nil) {
let et: *node = nil;
if (tp != nil) { et = tp.lhs; };
let ev: *node = e;
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
if (wide) {
if (ev.str.len > 0) {
let lab: str = internstrlit(c, ev.str);
emitline("DATAR ");
emitsymnamehint(c, name, module);
emitline("+");
emitint(foff: i64);
emitline("(SB),");
emitbytes( lab.ptr, lab.len: u64);
emitline("(SB)\n");
};
foff += (tyslicesize(): i32);
} else {
foff += 8;
};
e = e.next;
if (tp != nil) { tp = tp.next; };
};
return true;
};
fn emitletdataw(c: *cgen, file: *node) void = {
let d: *node = file.list;
for (d != nil) {
@@ -2147,6 +2326,36 @@ fn emitletdataw(c: *cgen, file: *node) void = {
let sz: i32 = letemitsize(c, d);
let issg: bool = letvarisstruct(c, nm);
let fsz: i32 = letvarisfloat(c, nm);
// C-t3 (#48): tuple global — slot-laid DATAW
// row (+ DATAR ptr patches for str elements)
// via emittupledata. Unsupported element
// inits die LOUD; pre-C-t3 the definition was
// silently skipped (no DATA, no diagnostic)
// and reads saw garbage. The istup gate also
// keeps a tuple out of the sz==8 / str-size
// arms below (a 24B tuple == str size).
let tlt: *node = d.lhs;
for (tlt != nil && tlt.kind == nkind.N_TNAME) {
tlt = aliaslookup(c, tlt.str);
};
let istup: bool = false;
if (tlt != nil) {
if (tlt.kind == nkind.N_TTUPLE) {
istup = true;
};
};
if (istup) {
let tr: *node = d.rhs;
for (tr != nil) {
if (tr.kind != nkind.N_CAST) { break; };
tr = tr.lhs;
};
if (!emittupledata(c, nm, d.nmod, tlt, tr)) {
let mtg: str = "global tuple let: unsupported element init (int/str literals only; rule 7)\n";
os.write(2, mtg.ptr, mtg.len: u64);
os.exit(1);
};
};
if (fsz > 0) {
// Float global: routes through the
// emitfloatlitdata SSoT helper, shared
@@ -2181,7 +2390,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
if (d.lhs != nil) {
if (d.lhs.kind == nkind.N_TARRAY) { isarr8 = true; };
};
if (sz == 8 && !issg && fsz == 0 && !isarr8) {
if (sz == 8 && !issg && fsz == 0 && !isarr8 && !istup) {
let v: u64 = 0u64;
let ok: bool = true;
if (d.rhs != nil) {
@@ -2212,7 +2421,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
emitline("\"\n");
};
};
if (sz == primtypesize("str"): i32 && !issg && !letvarisslice(c, nm)) {
if (sz == primtypesize("str"): i32 && !issg && !istup && !letvarisslice(c, nm)) {
let r: *node = d.rhs;
for (r != nil) {
if (r.kind != nkind.N_CAST) { break; };