w6c+w6c_ww: tagged-union struct-lit payload fills via the canonical fill (#23)

The widen choke-point's struct-payload arm carried its own inline
N_STRUCTLIT field loop -- a parallel fill that drifted from
cg_structlit_fill/cgstructlitfill: no tagged-field widen arm, so a
(void|T)-typed field's raw scalar landed in the field's TAG word
(silent truncation past the first tagged field, both stages,
byte-id, gate-blind; prober-9 PG5). Delete both loops and delegate
to the canonical fill at the payload base: one fill path, one widen
path, mutually recursive. Inherits the nested-struct/call/arrlit
field arms and closes a latent fsz==2 cs!=ww (old ww loop's
fieldstoreop MOVW vs cstage MOVQ). Test 938: 15-row table-driven
runtime readback (incl. ellipsis autofill, offset-0 tagged field,
(void|str) payload, 3-level widen-fill recursion torture), all 13
bug rows silent-fail at master 6699158; 2 rows skip the byte-id
check loudly (pre-existing match-on-tagged-FIELD readback cs!=ww,
master-confirmed, separate family).
This commit is contained in:
2026-06-05 02:07:49 +09:00
parent 66991585d6
commit 413aafa599
6 changed files with 629 additions and 202 deletions

View File

@@ -19565,58 +19565,24 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
let tag: i32 = taggedvariantindext(c, dt, src);
if (tag < 0) { tag = 0; };
if (src.kind == nkind.N_STRUCTLIT) {
let fnode: *node = src.list;
for (fnode != nil) {
if (fnode.kind == nkind.N_FIELD) {
let fname: str = fnode.str;
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fname)) {
cgexpr(c, fnode.lhs);
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) {
mov = "MOVSS";
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
} else { if (isstrtype(c, fi.tnode)
|| isslicetype(c, fi.tnode)) {
// str IS []u8 and a slice is the
// same 3-word {ptr,len,cap} header
// from cgexpr's AX/BX/CX (#1/Phase
// 3). Slice arm rides #38b's regex-
// shaped consumer — the prior str-
// only gate dropped .len/.cap (#24
// gap's widener twin).
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot_off + 8 + fi.foff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((slot_off + 8 + fi.foff + 16): i64);
emitline("(BP)\n");
} else {
let sop: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
}; };
fi = nil;
} else {
fi = fi.finext;
};
};
};
fnode = fnode.next;
};
// #23: delegate to the single fill path. The
// inline field loop this replaces was a
// parallel fill that drifted: it lacked the
// tagged-field widen arm, so a (void|T)-typed
// field's raw scalar landed in the field's
// TAG word (silent truncation past the first
// tagged field, both stages). Delegation also
// inherits the nested-struct / call / array-
// lit field arms; float / str / slice /
// scalar fields emit byte-identically to the
// old loop EXCEPT fsz==2 scalars, where the
// old loop's fieldstoreop emitted MOVW
// against cstage's MOVQ — a latent cs≠ww the
// fill's #13-pinned dispatch closes. Mirror
// of cstage cg_widen_tagged_store's
// N_STRUCTLIT arm.
cgstructlitfill(c, si, src, 0, 0, "",
slot_off + 8);
} else {
// Struct ident source: byte-copy struct words to slot+8+k.
let lc: *local = localfindnode(c, src.str);

View File

@@ -3573,58 +3573,24 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
let tag: i32 = taggedvariantindext(c, dt, src);
if (tag < 0) { tag = 0; };
if (src.kind == nkind.N_STRUCTLIT) {
let fnode: *node = src.list;
for (fnode != nil) {
if (fnode.kind == nkind.N_FIELD) {
let fname: str = fnode.str;
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fname)) {
cgexpr(c, fnode.lhs);
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) {
mov = "MOVSS";
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
} else { if (isstrtype(c, fi.tnode)
|| isslicetype(c, fi.tnode)) {
// str IS []u8 and a slice is the
// same 3-word {ptr,len,cap} header
// from cgexpr's AX/BX/CX (#1/Phase
// 3). Slice arm rides #38b's regex-
// shaped consumer — the prior str-
// only gate dropped .len/.cap (#24
// gap's widener twin).
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot_off + 8 + fi.foff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((slot_off + 8 + fi.foff + 16): i64);
emitline("(BP)\n");
} else {
let sop: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
}; };
fi = nil;
} else {
fi = fi.finext;
};
};
};
fnode = fnode.next;
};
// #23: delegate to the single fill path. The
// inline field loop this replaces was a
// parallel fill that drifted: it lacked the
// tagged-field widen arm, so a (void|T)-typed
// field's raw scalar landed in the field's
// TAG word (silent truncation past the first
// tagged field, both stages). Delegation also
// inherits the nested-struct / call / array-
// lit field arms; float / str / slice /
// scalar fields emit byte-identically to the
// old loop EXCEPT fsz==2 scalars, where the
// old loop's fieldstoreop emitted MOVW
// against cstage's MOVQ — a latent cs≠ww the
// fill's #13-pinned dispatch closes. Mirror
// of cstage cg_widen_tagged_store's
// N_STRUCTLIT arm.
cgstructlitfill(c, si, src, 0, 0, "",
slot_off + 8);
} else {
// Struct ident source: byte-copy struct words to slot+8+k.
let lc: *local = localfindnode(c, src.str);

View File

@@ -19565,58 +19565,24 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
let tag: i32 = taggedvariantindext(c, dt, src);
if (tag < 0) { tag = 0; };
if (src.kind == nkind.N_STRUCTLIT) {
let fnode: *node = src.list;
for (fnode != nil) {
if (fnode.kind == nkind.N_FIELD) {
let fname: str = fnode.str;
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fname)) {
cgexpr(c, fnode.lhs);
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, fi.tnode)) {
mov = "MOVSS";
};
emitline("\t");
emitline(mov);
emitline("\tX0, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
} else { if (isstrtype(c, fi.tnode)
|| isslicetype(c, fi.tnode)) {
// str IS []u8 and a slice is the
// same 3-word {ptr,len,cap} header
// from cgexpr's AX/BX/CX (#1/Phase
// 3). Slice arm rides #38b's regex-
// shaped consumer — the prior str-
// only gate dropped .len/.cap (#24
// gap's widener twin).
emitline("\tMOVQ\tAX, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((slot_off + 8 + fi.foff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((slot_off + 8 + fi.foff + 16): i64);
emitline("(BP)\n");
} else {
let sop: str = fieldstoreop(c, fi);
emitline("\t");
emitline(sop);
emitline("\tAX, ");
emitoff((slot_off + 8 + fi.foff): i64);
emitline("(BP)\n");
}; };
fi = nil;
} else {
fi = fi.finext;
};
};
};
fnode = fnode.next;
};
// #23: delegate to the single fill path. The
// inline field loop this replaces was a
// parallel fill that drifted: it lacked the
// tagged-field widen arm, so a (void|T)-typed
// field's raw scalar landed in the field's
// TAG word (silent truncation past the first
// tagged field, both stages). Delegation also
// inherits the nested-struct / call / array-
// lit field arms; float / str / slice /
// scalar fields emit byte-identically to the
// old loop EXCEPT fsz==2 scalars, where the
// old loop's fieldstoreop emitted MOVW
// against cstage's MOVQ — a latent cs≠ww the
// fill's #13-pinned dispatch closes. Mirror
// of cstage cg_widen_tagged_store's
// N_STRUCTLIT arm.
cgstructlitfill(c, si, src, 0, 0, "",
slot_off + 8);
} else {
// Struct ident source: byte-copy struct words to slot+8+k.
let lc: *local = localfindnode(c, src.str);