wcc_ww/cgen: #63 alias-named struct-lit fill via structlookupchain (let-init + sret-return)

cglet's N_STRUCTLIT init arm resolved the struct by a bare
structlookup(c, sname). For an alias-NAMED literal
(`type rep2 = rep; let r = rep2{id=6}`) the type ref carries the
alias name "rep2" but only the base `rep` is registered, so the
lookup returned nil and the field-fill never fired. The nil then
split by slot size into two symptoms of one root:
  - <=8B: the small-let scalar default zeroed the slot and DROPPED
    the literal (SILENT wrong — the field read 0), and
  - >8B: no fill arm matched, falling to the cglet "unhandled rhs
    shape" LOUD (task #7/rule-7).

Route the arm through structlookupchain (the #92/W2 SSoT already
adopted at cgenstmt:1974/:2687), which chases the alias chain to the
base struct. trefn (rhs.lhs) is already the N_IDENT/N_TNAME type ref
structlookupchain accepts, so the bare sname extraction is dropped.
cstage operates on the resolved Type* via type_chase_named and was
always correct: ww-only align-UP, cs UNTOUCHED.

ROUTED (the two reachable silent sites, one class):
  :2421  local N_STRUCTLIT let-init — the #63 repro hits it for
         both the <=8B silent-zero and the >24B loud symptoms.
  :1250  >24B sret RETURN twin (reviewer-63). sretretsize chases
         the alias for the size GATE so this sret arm fires, but
         the fill used the same bare structlookup(sname) — for an
         alias-named >24B literal it returned nil and the fill was
         SKIPPED, so the callee returned an uninitialised sret
         buffer (SILENT wrong, runtime-0; cs correct). Same root,
         same symptom, sibling site → folded by construction.
DECLINED (traced, not blind-routed; rule-11 + the #101 precedent):
  :2625  N_IDENT struct-copy — also bare-structlookup but the copy
         falls through to a generic path byte-identical with cstage;
         both stages run correct. The post-copy field-READ diverges
         (cs direct-offset vs ww LEAQ-indirect) = the #81/#65 alias
         field-read class, out of #63 scope.
  :2511  N_CALL struct-recv — blocked UPSTREAM by the aggregate-
         return shape (#272/#277); ww louds at the sender.
  :1363  <=24B register RETURN — alias case louds via the same
         scalar-default catch (#277), not silently wrong.
The 2 already-chasing sites (1974/2687) untouched.

CONVERGENCE: m3_letinit_typed + m3_letinit_untyped (ww silent-zero ->
6/6 byte-id) + m6_letlit_alias (ww loud -> 7 byte-id) + sret_return_-
alias32 (ww silent-0 -> 10 byte-id), plus a non-alias control
(no-regress). Bootstrap byte-id NEUTRAL (selfhost has no
alias-struct-litinit/return; all 4 selfhost tools cs==ww confirmed).

Test: 944_alias_structlit_init_run (5 rows x cs-run + ww-run +
cs==ww byte-id = 15 checks), Makefile-wired.
This commit is contained in:
2026-06-06 10:36:16 +09:00
parent 45f5415209
commit 3546673756
5 changed files with 329 additions and 42 deletions

View File

@@ -33730,14 +33730,20 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
if (okrhs) {
if (rhs.kind == nkind.N_STRUCTLIT) {
// #63: the >24B sret RETURN twin of the :2421
// let-init fix. sretretsize chases the alias for
// the size GATE (so this sret arm fires for a >24B
// alias struct), but the field-fill resolved the
// struct by a bare structlookup(c, sname): for an
// alias-NAMED literal (`type biga = big; return
// biga{...}`) sname is "biga", unregistered, so
// sret_si was nil and the fill was SKIPPED — the
// callee returned an uninitialised sret buffer
// (SILENT wrong, runtime-0). structlookupchain chases
// to the base struct; cs fills via the resolved
// Type*, runtime-correct.
let trefn: *node = rhs.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (trefn != nil) {
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
};
let sret_si: *structinfo = structlookup(c, sname);
let sret_si: *structinfo = structlookupchain(c, trefn);
if (sret_si != nil) {
let emptys: str;
emptys.ptr = nil; emptys.len = 0;
@@ -34898,14 +34904,17 @@ fn cglet(c: *cgen, n: *node) void = {
// (the #17 silent-zero fix). Mirror of cstage cgen.c N_LET
// structlit branch.
if (rhs.kind == nkind.N_STRUCTLIT) {
// #63: an alias-NAMED struct literal (`type rep2 = rep;
// let r = rep2{id=6}`) parses its type ref as N_IDENT/N_TNAME
// "rep2", but only the base `rep` is registered — bare
// structlookup(c, "rep2") returns nil, so the fill never
// fired: the slot zeroed + the lit DROPPED (≤8B silent) or
// fell to the :2920 LOUD (>8B). structlookupchain chases the
// alias chain to the base struct, the #92/W2 SSoT already
// adopted at cgenstmt:1974/:2687. cs chases via
// type_chase_named, runtime-correct.
let trefn: *node = rhs.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (trefn != nil) {
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
};
let si: *structinfo = structlookup(c, sname);
let si: *structinfo = structlookupchain(c, trefn);
if (si != nil) {
cgstructlitfillbp(c, si, rhs, off);
c.lastwasreturn = 0;

View File

@@ -1240,14 +1240,20 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
if (okrhs) {
if (rhs.kind == nkind.N_STRUCTLIT) {
// #63: the >24B sret RETURN twin of the :2421
// let-init fix. sretretsize chases the alias for
// the size GATE (so this sret arm fires for a >24B
// alias struct), but the field-fill resolved the
// struct by a bare structlookup(c, sname): for an
// alias-NAMED literal (`type biga = big; return
// biga{...}`) sname is "biga", unregistered, so
// sret_si was nil and the fill was SKIPPED — the
// callee returned an uninitialised sret buffer
// (SILENT wrong, runtime-0). structlookupchain chases
// to the base struct; cs fills via the resolved
// Type*, runtime-correct.
let trefn: *node = rhs.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (trefn != nil) {
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
};
let sret_si: *structinfo = structlookup(c, sname);
let sret_si: *structinfo = structlookupchain(c, trefn);
if (sret_si != nil) {
let emptys: str;
emptys.ptr = nil; emptys.len = 0;
@@ -2408,14 +2414,17 @@ fn cglet(c: *cgen, n: *node) void = {
// (the #17 silent-zero fix). Mirror of cstage cgen.c N_LET
// structlit branch.
if (rhs.kind == nkind.N_STRUCTLIT) {
// #63: an alias-NAMED struct literal (`type rep2 = rep;
// let r = rep2{id=6}`) parses its type ref as N_IDENT/N_TNAME
// "rep2", but only the base `rep` is registered — bare
// structlookup(c, "rep2") returns nil, so the fill never
// fired: the slot zeroed + the lit DROPPED (≤8B silent) or
// fell to the :2920 LOUD (>8B). structlookupchain chases the
// alias chain to the base struct, the #92/W2 SSoT already
// adopted at cgenstmt:1974/:2687. cs chases via
// type_chase_named, runtime-correct.
let trefn: *node = rhs.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (trefn != nil) {
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
};
let si: *structinfo = structlookup(c, sname);
let si: *structinfo = structlookupchain(c, trefn);
if (si != nil) {
cgstructlitfillbp(c, si, rhs, off);
c.lastwasreturn = 0;

View File

@@ -33730,14 +33730,20 @@ fn cgreturn(c: *cgen, n: *node) void = {
};
if (okrhs) {
if (rhs.kind == nkind.N_STRUCTLIT) {
// #63: the >24B sret RETURN twin of the :2421
// let-init fix. sretretsize chases the alias for
// the size GATE (so this sret arm fires for a >24B
// alias struct), but the field-fill resolved the
// struct by a bare structlookup(c, sname): for an
// alias-NAMED literal (`type biga = big; return
// biga{...}`) sname is "biga", unregistered, so
// sret_si was nil and the fill was SKIPPED — the
// callee returned an uninitialised sret buffer
// (SILENT wrong, runtime-0). structlookupchain chases
// to the base struct; cs fills via the resolved
// Type*, runtime-correct.
let trefn: *node = rhs.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (trefn != nil) {
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
};
let sret_si: *structinfo = structlookup(c, sname);
let sret_si: *structinfo = structlookupchain(c, trefn);
if (sret_si != nil) {
let emptys: str;
emptys.ptr = nil; emptys.len = 0;
@@ -34898,14 +34904,17 @@ fn cglet(c: *cgen, n: *node) void = {
// (the #17 silent-zero fix). Mirror of cstage cgen.c N_LET
// structlit branch.
if (rhs.kind == nkind.N_STRUCTLIT) {
// #63: an alias-NAMED struct literal (`type rep2 = rep;
// let r = rep2{id=6}`) parses its type ref as N_IDENT/N_TNAME
// "rep2", but only the base `rep` is registered — bare
// structlookup(c, "rep2") returns nil, so the fill never
// fired: the slot zeroed + the lit DROPPED (≤8B silent) or
// fell to the :2920 LOUD (>8B). structlookupchain chases the
// alias chain to the base struct, the #92/W2 SSoT already
// adopted at cgenstmt:1974/:2687. cs chases via
// type_chase_named, runtime-correct.
let trefn: *node = rhs.lhs;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (trefn != nil) {
if (trefn.kind == nkind.N_IDENT) { sname = trefn.str; }
else { if (trefn.kind == nkind.N_TNAME) { sname = trefn.str; }; };
};
let si: *structinfo = structlookup(c, sname);
let si: *structinfo = structlookupchain(c, trefn);
if (si != nil) {
cgstructlitfillbp(c, si, rhs, off);
c.lastwasreturn = 0;