From 418dd21f34c578d8d27c72048206cd08db9843af Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 2 Jun 2026 15:26:03 +0900 Subject: [PATCH] w6c+wwstage: wwstage alias-aggregate-return loud-stop + #276 citations (#272 review) Review fixes for the #272 fold (reviewer272b gate; rob+ken ruling). Bundled because the wwstage catch-all message carries the citation and the combined.ww regen covers both .ww edits. - wwstage cgreturn close-by-construction catch-all keyed on the SYNTACTIC return-type node (N_TARRAY / N_TNAME+structlookup), so a named-alias aggregate return type (type a=[N]T / type a=struct) bypassed both the handling arms AND the loud-stop, falling to the scalar default = silent segfault/truncation; cstage (type_chase_named at all 4 N_RETURN sites) stayed correct. Re-key the catch-all on the RESOLVED tinfo (chase TY_NAMED -> TY_ARRAY/TY_STRUCT) so wwstage LOUD-STOPS (rule 7) instead of miscompiling. cstage stays correct; the full wwstage tinfo-kind dispatch (align UP, byte-id) is #277. Established wwstage-stricter divergence (cf #264), no bootstrap consumer (990-997 green). - #276 citations at-site (both stages): the cstage >24B array-literal return loud-stop and the <=24B STRUCT global-receive residual now cite #276. The wwstage >24B array-literal routes through the tinfo-keyed catch-all (#272/#276/#277). Correction: ALL <=24B struct globals truncate symmetrically (byte-id-clean), not only float-bearing -- #276 broadened. - Cosmetic: fix a double-encoded U+2264 (mojibake) in the cgen.c commit-2 comment. combined.ww regenerated (#110). --- cmd/w6c/cgen.c | 16 +++++++++++----- selfhost/cmd/w6c/main.combined.ww | 27 ++++++++++++++++++++------- selfhost/cmd/wcc/cgenexpr.ww | 8 +++++--- selfhost/cmd/wcc/cgenstmt.ww | 19 +++++++++++++++---- selfhost/cmd/wwdump/main.combined.ww | 27 ++++++++++++++++++++------- 5 files changed, 71 insertions(+), 26 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 0dfac600..1c42fcb5 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -5693,13 +5693,19 @@ cgexpr(Cg *c, Node *n, Local *locals) && n->rhs && n->rhs->kind == N_CALL && n->op == TK_ASSIGN) { /* #272: `g = f();` where g is a GLOBAL - * aggregate ≤24B. The callee leaves the result + * aggregate ≤24B. The callee leaves the result * in AX/DX/CX (#272 reg-return); the scalar IDENT * fall-through below would store only MOVQ AX, * g(SB) = the first word. The asm has no `g+8(SB)` * operand form, so LEAQ the symbol into DI and * store the full+tail words. Mirrors the str/slice - * global arm above and the #220 sret-to-symbol path. */ + * global arm above and the #220 sret-to-symbol path. + * #276: this arm is TY_ARRAY-only — a ≤24B STRUCT + * global receive can be float-class (X0/X1, not + * AX/DX/CX) so it stays at its pre-existing symmetric + * fall-through; closing it needs struct_float_class + * here. No consumer. Arrays are never float-class, so + * AX/DX/CX is always correct for this arm. */ int sz = (int)lu->size; cgexpr(c, n->rhs, locals); ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_DI)); @@ -9740,11 +9746,11 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) k += 1; } } else if (n->lhs->kind == N_ARRLIT) { - /* #272: a >24B array-literal return has no - * consumer and the ptr-relative element fill + /* #272/#276: a >24B array-literal return has + * no consumer and the ptr-relative element fill * is untested. Loud-stop (rule 7) rather than * fall to the scalar default. ≤24B is wired. */ - fatal("#272: >24B array-literal return " + fatal("#272/#276: >24B array-literal return " "unsupported (rule 7, no consumer)"); } else { /* #272: N_DOT / N_INDEX / deref — land the diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 33eb9231..bbfba37d 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -27101,9 +27101,11 @@ fn cgassign(c: *cgen, n: *node) void = { // transport); the scalar store below would truncate to // MOVQ AX, g(SB). LEAQ the symbol into DI, store the // full+tail words. Mirror of cstage cgen.c ≤24B global - // arm. A ≤24B STRUCT global receive can be float-class - // (X0/X1) so it stays at its pre-existing behaviour — no - // consumer (rule-10 aligned with cstage). + // arm. #276: a ≤24B STRUCT global receive can be + // float-class (X0/X1) so it stays at its pre-existing + // behaviour — no consumer (rule-10 aligned with cstage; + // note non-float struct globals also truncate symmetrically + // here, byte-id-clean — #276 covers both). if (n.op == tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == nkind.N_CALL && lvftn != nil && lvftn.kind == nkind.N_TARRAY) { @@ -29045,15 +29047,26 @@ fn cgreturn(c: *cgen, n: *node) void = { // here would truncate to AX silently — loud-stop (rule 7), // mirroring cstage cgen.c N_RETURN. { + // #277: key on the RESOLVED tinfo, not the syntactic node — a + // NAMED-ALIAS aggregate return type (type a=[N]T / type a=struct) + // presents as N_TNAME and is TY_ARRAY/TY_STRUCT only after the + // alias chase, so the syntactic N_TARRAY/N_TNAME-structlookup arms + // above never fire on it. Without this chase it would fall to the + // scalar default = silent miscompile (cstage chases via + // type_chase_named and stays correct). Loud-stop (rule 7) until + // wwstage handles aliases via tinfo-kind dispatch (#277); the >24B + // array-literal return (no consumer) also lands here (#276). let aggret: bool = false; if (c.fnret != nil) { - if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; }; - if (c.fnret.kind == nkind.N_TNAME) { - if (structlookup(c, c.fnret.str) != nil) { aggret = true; }; + let rti: *tinfo = c.fnret.type_: *tinfo; + for (rti != nil && rti.kind == tykind.TY_NAMED) { rti = rti.under; }; + if (rti != nil) { + if (rti.kind == tykind.TY_ARRAY) { aggret = true; }; + if (rti.kind == tykind.TY_STRUCT) { aggret = true; }; }; }; if (aggret && rhs.kind != nkind.N_CALL) { - let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n"; + let m6: str = "#272/#276/#277: aggregate return reaches scalar default — unclosed shape (named-alias aggregate return or >24B array-literal; wwstage tinfo-dispatch deferred #277)\n"; os.write(2, m6.ptr, m6.len: u64); os.exit(1); }; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index f89a07bb..6a2c855e 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -7475,9 +7475,11 @@ fn cgassign(c: *cgen, n: *node) void = { // transport); the scalar store below would truncate to // MOVQ AX, g(SB). LEAQ the symbol into DI, store the // full+tail words. Mirror of cstage cgen.c ≤24B global - // arm. A ≤24B STRUCT global receive can be float-class - // (X0/X1) so it stays at its pre-existing behaviour — no - // consumer (rule-10 aligned with cstage). + // arm. #276: a ≤24B STRUCT global receive can be + // float-class (X0/X1) so it stays at its pre-existing + // behaviour — no consumer (rule-10 aligned with cstage; + // note non-float struct globals also truncate symmetrically + // here, byte-id-clean — #276 covers both). if (n.op == tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == nkind.N_CALL && lvftn != nil && lvftn.kind == nkind.N_TARRAY) { diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 209f9535..011577a6 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1408,15 +1408,26 @@ fn cgreturn(c: *cgen, n: *node) void = { // here would truncate to AX silently — loud-stop (rule 7), // mirroring cstage cgen.c N_RETURN. { + // #277: key on the RESOLVED tinfo, not the syntactic node — a + // NAMED-ALIAS aggregate return type (type a=[N]T / type a=struct) + // presents as N_TNAME and is TY_ARRAY/TY_STRUCT only after the + // alias chase, so the syntactic N_TARRAY/N_TNAME-structlookup arms + // above never fire on it. Without this chase it would fall to the + // scalar default = silent miscompile (cstage chases via + // type_chase_named and stays correct). Loud-stop (rule 7) until + // wwstage handles aliases via tinfo-kind dispatch (#277); the >24B + // array-literal return (no consumer) also lands here (#276). let aggret: bool = false; if (c.fnret != nil) { - if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; }; - if (c.fnret.kind == nkind.N_TNAME) { - if (structlookup(c, c.fnret.str) != nil) { aggret = true; }; + let rti: *tinfo = c.fnret.type_: *tinfo; + for (rti != nil && rti.kind == tykind.TY_NAMED) { rti = rti.under; }; + if (rti != nil) { + if (rti.kind == tykind.TY_ARRAY) { aggret = true; }; + if (rti.kind == tykind.TY_STRUCT) { aggret = true; }; }; }; if (aggret && rhs.kind != nkind.N_CALL) { - let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n"; + let m6: str = "#272/#276/#277: aggregate return reaches scalar default — unclosed shape (named-alias aggregate return or >24B array-literal; wwstage tinfo-dispatch deferred #277)\n"; os.write(2, m6.ptr, m6.len: u64); os.exit(1); }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 1fcdbc39..5ab1cba4 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -27101,9 +27101,11 @@ fn cgassign(c: *cgen, n: *node) void = { // transport); the scalar store below would truncate to // MOVQ AX, g(SB). LEAQ the symbol into DI, store the // full+tail words. Mirror of cstage cgen.c ≤24B global - // arm. A ≤24B STRUCT global receive can be float-class - // (X0/X1) so it stays at its pre-existing behaviour — no - // consumer (rule-10 aligned with cstage). + // arm. #276: a ≤24B STRUCT global receive can be + // float-class (X0/X1) so it stays at its pre-existing + // behaviour — no consumer (rule-10 aligned with cstage; + // note non-float struct globals also truncate symmetrically + // here, byte-id-clean — #276 covers both). if (n.op == tkind.TK_ASSIGN && n.rhs != nil && n.rhs.kind == nkind.N_CALL && lvftn != nil && lvftn.kind == nkind.N_TARRAY) { @@ -29045,15 +29047,26 @@ fn cgreturn(c: *cgen, n: *node) void = { // here would truncate to AX silently — loud-stop (rule 7), // mirroring cstage cgen.c N_RETURN. { + // #277: key on the RESOLVED tinfo, not the syntactic node — a + // NAMED-ALIAS aggregate return type (type a=[N]T / type a=struct) + // presents as N_TNAME and is TY_ARRAY/TY_STRUCT only after the + // alias chase, so the syntactic N_TARRAY/N_TNAME-structlookup arms + // above never fire on it. Without this chase it would fall to the + // scalar default = silent miscompile (cstage chases via + // type_chase_named and stays correct). Loud-stop (rule 7) until + // wwstage handles aliases via tinfo-kind dispatch (#277); the >24B + // array-literal return (no consumer) also lands here (#276). let aggret: bool = false; if (c.fnret != nil) { - if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; }; - if (c.fnret.kind == nkind.N_TNAME) { - if (structlookup(c, c.fnret.str) != nil) { aggret = true; }; + let rti: *tinfo = c.fnret.type_: *tinfo; + for (rti != nil && rti.kind == tykind.TY_NAMED) { rti = rti.under; }; + if (rti != nil) { + if (rti.kind == tykind.TY_ARRAY) { aggret = true; }; + if (rti.kind == tykind.TY_STRUCT) { aggret = true; }; }; }; if (aggret && rhs.kind != nkind.N_CALL) { - let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n"; + let m6: str = "#272/#276/#277: aggregate return reaches scalar default — unclosed shape (named-alias aggregate return or >24B array-literal; wwstage tinfo-dispatch deferred #277)\n"; os.write(2, m6.ptr, m6.len: u64); os.exit(1); };