w6c/cgen: #61 alias-NAMED struct param classify — transitive chase at the ABI choke

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.
This commit is contained in:
2026-06-05 11:26:22 +09:00
parent 9bd0d8bc81
commit b9dd29706b
2 changed files with 91 additions and 7 deletions

View File

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

View File

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