w6c_ww: tinfo re-key of the cgdot N_INDEX-base arm (F7/F10)

The wwstage `arr[i].field` read arm was syntactic where cstage is
type-table-driven: element typing keyed on tnode KINDs (N_TSLICE/
N_TARRAY/N_TPTR) with an N_TNAME element resolved by structlookup
NAME — any base typed via an alias (`type result = []capture`,
p11b/F10) missed every gate and died at the interim C2 loud guard
(pre-C2: fell silently to the SB fallback). Re-key the arm onto the
checker-stamped tinfo (lhs.type_ element / idxbase.type_ base, NAMED
peeled), mirroring cstage cgen.c case N_DOT's N_INDEX-lhs arm 1:1 —
the #209/#211 name-keyed->tinfo-SSoT cluster. Emission sequence is
unchanged; the C2 "C3/task #8" guard retires with the arm wired
(task #37's ptr-chain guard is untouched). Global classification
mirrors cstage let_islet || def_isarraydef via isletvar /
defvartnode-N_TARRAY (the cgplaceaddr C2 pattern).

F7/FA5/FA3-ww/FA6 (non-ident idxbase shapes, task #17's ww halves)
were already closed by C2's read-resolver recursion; p7_composed,
pA5, pA9 run exit-0 byte-id and are pinned as rows here. Task #29's
asserttyped bail (strings.frombytes over a slice-expr in the
composed context) no longer reproduces at HEAD — dissolved during
the C1.25->C2 arc; p7_composed builds clean on w6c_ww, runs 0,
byte-id.

Behind the retired guard three alias-blind NON-cgdot sites surface
(`let l: wlist = []` checker reject / alias-array global emits no
DATA / alias-array arrlit-init under-copies 8B per element, cs!=ww
runtime): filed as task #38, same name-keyed class, separate sites.

test/805: +5 rows — alias-slice field-kind matrix, alias-array +
viaptr-element bases, p11b-essence let-bound alias reads (the
task-#14 `?` factored out), the composed p7 match/compound hot
shape, and the pA9 free()-operand acceptance row (FA6).

Task #8; the last cgen gate before regex tranche B.
This commit is contained in:
2026-06-04 11:34:16 +09:00
parent 76994a8279
commit 074e68f05d
4 changed files with 665 additions and 468 deletions

View File

@@ -2879,152 +2879,168 @@ fn cgdot(c: *cgen, n: *node) void = {
// element), so the leaf load is `(field.offset)(AX)` either way.
// Bypasses cgindex deliberately — cgindex's final MOVQ would
// truncate a value-struct element to 8 bytes.
// C3 (task #8): keyed on the checker-stamped tinfo (lhs.type_ /
// idxbase.type_), not tnode KINDs + structlookup-by-name — the
// name-key dropped any base typed via an N_TNAME alias (`type
// result = []capture`, F10) into the silent fallbacks below.
// Mirrors cstage cgen.c case N_DOT N_INDEX-lhs arm 1:1; #209/#211
// name-keyed→tinfo-SSoT cluster.
if (lhs != nil) {
if (lhs.kind == nkind.N_INDEX) {
let idxbase: *node = lhs.lhs;
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, idxbase.str);
// #21 (READ twin of #11): a module-GLOBAL base makes
// localfindnode return nil, so this field-offset-aware
// branch was skipped and `g[i].field` fell through to
// the module-qualified fallback below (garbage — no
// main.g load at all). Resolve the global's tnode via
// letvartnode/defvartnode (the same source cgindex's
// global arm uses) and dispatch the base load by shape:
// array -> LEAQ name(SB) (the symbol IS the storage),
// slice/ptr -> MOVQ name(SB) (the symbol's first word
// IS the .ptr). Mirrors cstage cgen.c's #21 arm.
let tn: *node = nil;
let isglobal: bool = false;
let gname: str;
gname.ptr = nil; gname.len = 0;
if (lc != nil) {
tn = lc.tnode;
} else {
tn = letvartnode(c, idxbase.str);
if (tn == nil) { tn = defvartnode(c, idxbase.str); };
if (tn != nil) { isglobal = true; gname = idxbase.str; };
};
if (tn != nil) {
let elemt: *node = nil;
let baseisarray: bool = false;
let tk: nkind = tn.kind;
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
let sname: str;
sname.ptr = nil; sname.len = 0;
let viaptr: bool = false;
if (elemt != nil) {
if (elemt.kind == nkind.N_TPTR) {
let inner: *node = elemt.lhs;
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
sname = inner.str;
viaptr = true;
};};
} else { if (elemt.kind == nkind.N_TNAME) {
sname = elemt.str;
let elemt: *tinfo = lhs.type_: *tinfo;
let elemu: *tinfo = elemt;
for (elemu != nil && elemu.kind == tykind.TY_NAMED) { elemu = elemu.under; };
let st: *tinfo = nil;
let viaptr: bool = false;
if (elemu != nil) {
if (elemu.kind == tykind.TY_PTR) {
let pin: *tinfo = elemu.sub;
for (pin != nil && pin.kind == tykind.TY_NAMED) { pin = pin.under; };
if (pin != nil) { if (pin.kind == tykind.TY_STRUCT) {
st = pin;
viaptr = true;
};};
} else { if (elemu.kind == tykind.TY_STRUCT) {
st = elemu;
};};
};
if (st != nil) {
let fnd: *tfield = nil;
let fwalk: *tfield = st.fields;
for (fwalk != nil) {
if (streq(fwalk.name, fld)) { fnd = fwalk; break; };
fwalk = fwalk.tnext;
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fld)) {
let esz: i32 = elemsizeofc(c, tn);
cgexpr(c, lhs.rhs); // idx → AX
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (isglobal) {
if (baseisarray) {
emitline("\tLEAQ\t");
emitsymname(c, gname);
emitline("(SB), BX\n");
} else {
emitline("\tMOVQ\t");
emitsymname(c, gname);
emitline("(SB), BX\n");
};
} else {
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) {
emitline("\tMOVQ\t(BX), AX\n");
} else {
emitline("\tMOVQ\tBX, AX\n");
};
// #270-1a: an `[N]T`-typed field of an
// array element (`a[i].m[j]`) — leave the
// field's ADDRESS, a base for the outer
// index, NEVER deref. AX holds &a[i]; the
// field address is &a[i]+foff. The #135
// read-side for `d.m[i]`, applied to an
// array-element base. Without this an array
// field fell to fieldloadop below and loaded
// its first 8 bytes as a value → garbage
// base → SEGFAULT in the outer index.
if (tinfoisarray(fi.tnode.type_: *tinfo)) {
if (fi.foff != 0) {
emitline("\tADDQ\t$");
emitint(fi.foff: i64);
emitline(", AX\n");
};
return;
};
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
// str/slice: the 3-word {ptr,len,cap}
// slice header (#1). AX holds the
// element base, so load .ptr (which
// targets AX) LAST. Matches the
// caseB *struct slice arm and
// cgslicehdr(D_AX).
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "AX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "AX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "AX");
emitline(", AX\n");
return;
};
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t");
emitdispreg(fi.foff: i64, "AX");
emitline(", X0\n");
return;
};
let lop: str = fieldloadop(c, fi);
emitline("\t");
emitline(lop);
emitline("\t");
emitdispreg(fi.foff: i64, "AX");
emitline(", AX\n");
return;
};
fi = fi.finext;
let bu: *tinfo = idxbase.type_: *tinfo;
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
let baseisarray: bool = false;
let baseok: bool = false;
if (bu != nil) {
if (bu.kind == tykind.TY_ARRAY) { baseisarray = true; baseok = true; };
if (bu.kind == tykind.TY_SLICE) { baseok = true; };
if (bu.kind == tykind.TY_PTR) { baseok = true; };
};
let lc: *local = localfindnode(c, idxbase.str);
// #21 (READ twin of #11): a module-GLOBAL base makes
// localfindnode return nil, so this field-offset-aware
// branch was skipped and `g[i].field` fell through to
// the module-qualified fallback below (garbage — no
// main.g load at all). Classify via the let registry /
// array-typed def registry (cstage let_islet ||
// def_isarraydef) and dispatch the base load by shape:
// array -> LEAQ name(SB) (the symbol IS the storage),
// slice/ptr -> MOVQ name(SB) (the symbol's first word
// IS the .ptr). Mirrors cstage cgen.c's #21 arm.
let isglobal: bool = false;
if (lc == nil) {
if (isletvar(c, idxbase.str)) {
isglobal = true;
} else {
let dtn: *node = defvartnode(c, idxbase.str);
if (dtn != nil) {
if (dtn.kind == nkind.N_TARRAY) { isglobal = true; };
};
};
};
if (fnd != nil && baseok && (lc != nil || isglobal)) {
let esz: i32 = elemt.size: i32;
cgexpr(c, lhs.rhs); // idx → AX
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
if (isglobal) {
if (baseisarray) {
emitline("\tLEAQ\t");
emitsymname(c, idxbase.str);
emitline("(SB), BX\n");
} else {
emitline("\tMOVQ\t");
emitsymname(c, idxbase.str);
emitline("(SB), BX\n");
};
} else {
if (baseisarray) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), BX\n");
};
};
emitline("\tADDQ\tAX, BX\n");
if (viaptr) {
emitline("\tMOVQ\t(BX), AX\n");
} else {
emitline("\tMOVQ\tBX, AX\n");
};
let foff: i64 = fnd.offset: i64;
let ft: *tinfo = fnd.type_;
let fu: *tinfo = ft;
for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; };
// #270-1a: an `[N]T`-typed field of an
// array element (`a[i].m[j]`) — leave the
// field's ADDRESS, a base for the outer
// index, NEVER deref. AX holds &a[i]; the
// field address is &a[i]+foff. The #135
// read-side for `d.m[i]`, applied to an
// array-element base. Without this an array
// field fell to the scalar load below and loaded
// its first 8 bytes as a value → garbage
// base → SEGFAULT in the outer index.
if (fu != nil && fu.kind == tykind.TY_ARRAY) {
if (foff != 0) {
emitline("\tADDQ\t$");
emitint(foff);
emitline(", AX\n");
};
return;
};
if (fu != nil && (fu.kind == tykind.TY_STR
|| fu.kind == tykind.TY_SLICE)) {
// str/slice: the 3-word {ptr,len,cap}
// slice header (#1). AX holds the
// element base, so load .ptr (which
// targets AX) LAST. Matches the
// caseB *struct slice arm and
// cgslicehdr(D_AX).
emitline("\tMOVQ\t");
emitdispreg(foff + 8, "AX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg(foff + 16, "AX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg(foff, "AX");
emitline(", AX\n");
return;
};
if (typeisfloat(ft)) {
let mov: str = "MOVSD";
if (typeisf32(ft)) { mov = "MOVSS"; };
emitline("\t");
emitline(mov);
emitline("\t");
emitdispreg(foff, "AX");
emitline(", X0\n");
return;
};
let fsz: i32 = 8;
if (ft != nil) { fsz = ft.size: i32; };
let lop: str = loadopsz(typeissigned(ft), fsz);
emitline("\t");
emitline(lop);
emitline("\t");
emitdispreg(foff, "AX");
emitline(", AX\n");
return;
};
};
};};
};
@@ -3553,24 +3569,6 @@ fn cgdot(c: *cgen, n: *node) void = {
};
};
};
// C2/C3 boundary: `xs[i].fld` with an IDENT index base is the
// enumerated N_INDEX-base arm's territory (cstage handles it
// type-keyed); reaching HERE means wwstage's name-keyed arm above
// missed (the F10 alias-tnode class). Routing it through the
// read-resolver would emit a different (albeit correct) sequence
// than cstage's arm → cs≠ww. Loud until C3 re-keys that arm onto
// stamped tinfo (task #8).
if (lhs != nil) {
if (lhs.kind == nkind.N_INDEX) {
if (lhs.lhs != nil) {
if (lhs.lhs.kind == nkind.N_IDENT) {
let mg: str = "cgdot: ident-indexed field read unwired in wwstage (C3/task #8)\n";
os.write(2, mg.ptr, mg.len: u64);
os.exit(1);
};
};
};
};
// cstage reads ptr-chained fields (`a.p.f`, any root) in its
// chained-*struct arm; wwstage's #70 mirror above is gated
// root-local — the uncovered remainder (global / indexed roots)