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

@@ -903,6 +903,24 @@ fn cgident(c: *cgen, n: *node) void = {
// (AX, BX[, CX]). Names that aren't lets either (typos,
// never-defined) drop through to the silent return.
if (isletvar(c, nm)) {
// C-t3 (#48, rule 7): a GLOBAL tuple as a first-class VALUE
// (`let q = g;` / `return g;` / `f(g)`) has no slot-to-cursor
// path (cgtupleslottocursor is BP-relative) — pre-fix it fell
// to the scalar MOVQ below, loading word0 only, and the
// receive read a STALE cursor for words 1+. Element reads
// (g.N) are the supported surface. Mirrors the cstage cgexpr
// non-local ident guard.
let gtt: *node = letvartnode(c, nm);
for (gtt != nil && gtt.kind == nkind.N_TNAME) {
gtt = aliaslookup(c, gtt.str);
};
if (gtt != nil) {
if (gtt.kind == nkind.N_TTUPLE) {
let mgt: str = "#48: global tuple as a first-class value unwired (element reads only; rule 7)\n";
os.write(2, mgt.ptr, mgt.len: u64);
os.exit(1);
};
};
let isstr: bool = letvarisstr(c, nm);
let issl: bool = letvarisslice(c, nm);
if (isstr || issl) {
@@ -2805,6 +2823,79 @@ fn cgdot(c: *cgen, n: *node) void = {
};
};
};
// Top-level TUPLE global positional read (C-t3, #48): `g.N` —
// LEAQ name(SB) into CX, then load at the element's SLOT offset
// (C-t0 layout), the element's natural width. Mirrors the local
// N_TTUPLE arm above and cstage's N_DOT TY_TUPLE global base.
// Pre-C-t3 the tuple global wasn't in collectlets at all (no
// DATA) and the module-leaf fallback mis-emitted the field index
// as a symbol (`MOVQ 0(SB), AX`).
if (lhs != nil) {
if (lhs.kind == nkind.N_IDENT) {
let gtt: *node = letvartnode(c, lhs.str);
for (gtt != nil && gtt.kind == nkind.N_TNAME) {
gtt = aliaslookup(c, gtt.str);
};
if (gtt != nil) {
if (gtt.kind == nkind.N_TTUPLE) {
let gidx: i32 = fldnumidx(fld);
if (gidx >= 0) {
let gtp: *node = gtt.list;
let gfoff: i32 = 0;
let gi: i32 = 0;
for (gi < gidx) {
if (gtp == nil) { gi = gidx; }
else {
gfoff += slotsize(c, gtp.lhs);
gtp = gtp.next;
gi += 1;
};
};
if (gtp != nil) {
let gpt: *node = gtp.lhs;
emitline("\tLEAQ\t");
emitsymname(c, lhs.str);
emitline("(SB), CX\n");
if (isstrtype(c, gpt)) {
// CX (the base) is written LAST so
// it survives the +0/+8 reads.
emitline("\tMOVQ\t");
emitdispreg((gfoff + 0): i64, "CX");
emitline(", AX\n");
emitline("\tMOVQ\t");
emitdispreg((gfoff + 8): i64, "CX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((gfoff + 16): i64, "CX");
emitline(", CX\n");
return;
};
if (isfloattype(c, gpt)) {
let mov: str = "MOVSD";
if (isf32type(c, gpt)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t");
emitdispreg(gfoff: i64, "CX");
emitline(", X0\n");
return;
};
let gnsz: i32 = 8;
let gpti: *tinfo = gpt.type_: *tinfo;
if (gpti != nil) { gnsz = gpti.size: i32; };
let gop: str = tnodeloadop(c, gpt, gnsz);
emitline("\t");
emitline(gop);
emitline("\t");
emitdispreg(gfoff: i64, "CX");
emitline(", AX\n");
return;
};
};
};
};
};
};
// Top-level struct global field read — LEAQ name(SB), CX then
// load at fi.foff(CX). Mirrors the local "Direct struct local"
// branch above, swapping the BP frame slot for the global VA.
@@ -5661,6 +5752,45 @@ fn cgcall(c: *cgen, n: *node) void = {
};
};
};
// C-t3 (#48): GLOBAL tuple len(g.N) —
// LEAQ name(SB) into CX, .len word at
// the SLOT offset + 8. Graduates the
// C5 loud-stop this shape previously
// hit; twin of the cgdot global-tuple
// arm and cstage's #235 global base.
if (lc == nil) {
let gtn: *node = letvartnode(c, a.lhs.str);
for (gtn != nil && gtn.kind == nkind.N_TNAME) {
gtn = aliaslookup(c, gtn.str);
};
if (gtn != nil) {
if (gtn.kind == nkind.N_TTUPLE) {
let gidx: i32 = fldnumidx(a.str);
if (gidx >= 0) {
let gtp: *node = gtn.list;
let gfoff: i32 = 0;
let gi: i32 = 0;
for (gi < gidx) {
if (gtp == nil) { gi = gidx; }
else {
gfoff += slotsize(c, gtp.lhs);
gtp = gtp.next;
gi += 1;
};
};
if (gtp != nil) {
emitline("\tLEAQ\t");
emitsymname(c, a.lhs.str);
emitline("(SB), CX\n");
emitline("\tMOVQ\t");
emitdispreg((gfoff + 8): i64, "CX");
emitline(", AX\n");
return;
};
};
};
};
};
// struct-field N_DOT (`len(s.field)`): the
// old fallback returned .ptr as the length.
// Falls to the resolver route below.