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).
This commit is contained in:
2026-06-02 15:26:03 +09:00
parent 9d81ba77b7
commit 418dd21f34
5 changed files with 71 additions and 26 deletions

View File

@@ -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

View File

@@ -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);
};

View File

@@ -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) {

View File

@@ -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);
};

View File

@@ -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);
};