From b9dd29706b5172d45f7068b076a434d7a56d1af5 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 11:26:22 +0900 Subject: [PATCH] =?UTF-8?q?w6c/cgen:=20#61=20alias-NAMED=20struct=20param?= =?UTF-8?q?=20classify=20=E2=80=94=20transitive=20chase=20at=20the=20ABI?= =?UTF-8?q?=20choke?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 2-level alias param (`type row = st; type st = struct{a,b,c}`) fell through the single NAMED peel at every classify site, so BOTH ends of the call moved one eightbyte of a multi-word struct: the caller's node_isstructarg/node_isaggarg said scalar, the callee prologue spilled ONLY DI, and s.b/s.c read 8(BP)/(BP) — saved-BP/return-address garbage. SILENT runtime-wrong (F0 m5_arg/m8_arg1/m8b_arg1lit: cs exit 1, ww correct, byte-id NO). Route the four classify chokes through type_chase_named: struct_arg_size + aggarg_size (shared by caller push AND the size axes), struct_float_ class (the #165 SSE eightbyte leg), and the fn-prologue param classify pu. Caller and callee key off the same helpers, so the pair cannot half-land. cs converges to wwstage's already-correct asm — all probe rows graduate to byte-id YES; bootstrap asm cmp-identical vs master (2-level alias params unused in selfhost). test: 944_alias_accept_run +5 rows — fwd-ref / lit-init / 40B 5-eightbyte aggarg leg / f64 struct_float_class leg, every row checking the LAST field with distinct values, + base-named control. Mutation- checked at 738d7f4: the four alias rows exit 1 (the silent-wrong signature) and byte-id-diff there; 55/55 green here. --- cmd/w6c/cgen.c | 20 ++++++--- test/wcc/944_alias_accept_run.c | 78 +++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 537d6752..49e9f2e5 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -558,8 +558,11 @@ localloadop(Type *t) static int struct_arg_size(Type *t) { - if (t == NULL) return 0; - if (t->kind == TY_NAMED) t = t->under; + /* Transitive chase (#61): `type row = st; type st = struct` is two + * NAMED layers — the single peel classified the param SCALAR, so + * caller and callee both moved one eightbyte of a 3-word struct + * (silent caller-frame garbage reads in the callee). */ + t = type_chase_named(t); if (t == NULL || t->kind != TY_STRUCT) return 0; return (int)t->size; } @@ -580,8 +583,8 @@ struct_arg_size(Type *t) static int struct_float_class(Type *t, int *cls) { - if (t == NULL) return 0; - if (t->kind == TY_NAMED) t = t->under; + /* Transitive chase (#61) — same classify choke as struct_arg_size. */ + t = type_chase_named(t); if (t == NULL || t->kind != TY_STRUCT) return 0; int sz = (int)t->size; if (sz <= 0 || sz > 16) return 0; @@ -777,8 +780,8 @@ node_isstructarg(Node *n) static int aggarg_size(Type *t) { - if (t == NULL) return 0; - if (t->kind == TY_NAMED) t = t->under; + /* Transitive chase (#61) — same classify choke as struct_arg_size. */ + t = type_chase_named(t); if (t == NULL) return 0; if (t->kind == TY_STRUCT || t->kind == TY_ARRAY) return (int)t->size; @@ -13837,7 +13840,10 @@ cgfn(Cg *c, FILE *out, Node *fn) continue; } Type *pt = tp ? tp->type : NULL; - Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt; + /* Transitive chase (#61): the callee-side classify twin of + * struct_arg_size/aggarg_size — a 2-level alias param fell to + * the scalar arm and spilled ONLY DI. */ + Type *pu = type_chase_named(pt); int slice = (pu && pu->kind == TY_SLICE); int is_str = type_isstr(pt); int is_struct = pu && pu->kind == TY_STRUCT && pu->size <= 16; diff --git a/test/wcc/944_alias_accept_run.c b/test/wcc/944_alias_accept_run.c index 91d0b41f..5a00fa1a 100644 --- a/test/wcc/944_alias_accept_run.c +++ b/test/wcc/944_alias_accept_run.c @@ -32,6 +32,10 @@ * | half is the task-#60 esz family (F2 | * | batch 1) — flip to K_RUN when it | * | lands | 0(cs) + * arg_2level_* | task #61: alias-NAMED struct PARAM | + * | classify (fwd-ref / lit-init / 40B | + * | 5-eightbyte / f64 SSE class legs + | + * | base control) | 0 * float_alias_param* / | ken-v3 leg: 2-level f64/str/slice | * str_alias_2level / | aliases at the cgen KIND classifiers | * slice_alias_param | (slice leg cs-only, #60) | 0 @@ -573,6 +577,80 @@ static const struct row rows[] = { " if (first(s) != 1000) { return 1; };\n" " return 0;\n" "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + /* ---- task #61: cstage alias-NAMED struct PARAM classify. + * The single peel classified a 2-level-alias param SCALAR: caller + * pushed and callee spilled ONE eightbyte, fields read saved-BP/ + * return-address garbage (F0 m5_arg/m8b_arg1lit, cs exit 1 SILENT, + * ww correct). Fix = struct_arg_size/aggarg_size/struct_float_ + * class + the prologue classify chase. Values are distinct and the + * LAST field is always checked (multi-eightbyte exit-checked). */ + { "arg_2level_fwdref", + "package main;\n" + "type ali = base;\n" + "type base = struct { a: size, b: size, c: size };\n" + "fn take(s: ali) size = {\n" + " return s.a + s.b + s.c;\n" + "};\n" + "export fn main() i32 = {\n" + " let x: ali;\n" + " x.a = 4; x.b = 9; x.c = 13;\n" + " if (take(x) != 26) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "arg_2level_litinit", + "package main;\n" + "type st = struct { a: size, b: size, c: size };\n" + "type row = st;\n" + "fn take(s: row) size = {\n" + " return s.a + s.b + s.c;\n" + "};\n" + "export fn main() i32 = {\n" + " let x: st = st { a = 4: size, b = 9: size, c = 13: size };\n" + " if (take(x) != 26) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* 40B struct: 5 GP eightbytes through the aggarg (>16B) leg. */ + { "arg_2level_5word", + "package main;\n" + "type big = struct { a: size, b: size, c: size, d: size, e: size };\n" + "type big2 = big;\n" + "fn take(s: big2) size = {\n" + " return s.a + s.e;\n" + "};\n" + "export fn main() i32 = {\n" + " let v: big2;\n" + " v.a = 1000; v.b = 2; v.c = 3; v.d = 4; v.e = 5000;\n" + " if (take(v) != 6000) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* f64-bearing 16B struct: the struct_float_class SSE leg. */ + { "arg_2level_floatclass", + "package main;\n" + "type fs = struct { x: f64, n: size };\n" + "type fs2 = fs;\n" + "fn take(s: fs2) size = {\n" + " if (s.x != 2.5) { return 99; };\n" + " return s.n;\n" + "};\n" + "export fn main() i32 = {\n" + " let v: fs2;\n" + " v.x = 2.5; v.n = 7;\n" + " if (take(v) != 7) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* control: base-named param spelling stays green + byte-id. */ + { "arg_base_control", + "package main;\n" + "type base = struct { a: size, b: size, c: size };\n" + "fn take(s: base) size = {\n" + " return s.a + s.b + s.c;\n" + "};\n" + "export fn main() i32 = {\n" + " let x: base;\n" + " x.a = 4; x.b = 9; x.c = 13;\n" + " if (take(x) != 26) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, }; static int