From 33bd2b1054a0e610c8dbd3b683edf7f25bc1046d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 2 Jun 2026 12:24:43 +0900 Subject: [PATCH] wwstage: nested-array outer-index esz = sub-array size (#270-2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit elemsizeofc drilled a 2D `[N][M]T` base's OUTER-index stride down to the scalar T (the documented elemsizeof FOOTGUN: it bottoms out at the inner prim size, 4 for [M]u32). The `direct != 8` short-circuit then returned that scalar size, so wwstage emitted esz=$4 where cstage emits $12 (the sub-array size, idx_eff(bt)->sub->size = sub.size*elen, type.c:121). The runtime stayed self-consistent (write+read the same wrong stride) so it masked until a CROSS-CELL access — a[0][j] and a[1][j] alias. Fix: detect a nested-array element ([M]T inside [N][M]T) before the short-circuit and return the element-array tinfo's natural .size, the sub-array stride. wwstage-only; aligns up to cstage. w6c unchanged. 949 rows: nest2d_u32/u8/i32 (cross-cell write+readback, byte-id). --- selfhost/cmd/w6c/main.combined.ww | 18 +++++++++++++ selfhost/cmd/wcc/cgenutil.ww | 18 +++++++++++++ selfhost/cmd/wwdump/main.combined.ww | 18 +++++++++++++ test/wcc/949_dotbase_addr_slice_run.c | 37 +++++++++++++++++++++++++++ 4 files changed, 91 insertions(+) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index c9c84697..2946ca17 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -16569,6 +16569,24 @@ fn elemsizeof(t: *node) i32 = { // whose Alias resolves to a tagged union (e.g. `[N]formattable`). fn elemsizeofc(c: *cgen, t: *node) i32 = { if (t == nil) { return 1; }; + // #270-2: a NESTED-array element ([N][M]T) — the OUTER index stride + // is the WHOLE sub-array [M]T, not the scalar T that elemsizeof + // drills down to (the documented elemsizeof FOOTGUN). elemsizeof + // returns the inner prim size (4 for [M]u32), so the `direct != 8` + // short-circuit below would mis-emit esz=$4 where cstage emits the + // sub-array stride $12. Mirror cstage esz = idx_eff(bt)->sub->size + // (cmd/w6c/cgen.c:4923): the element-array tinfo's natural size + // (sub.size*elen, type.c:121) IS the outer stride. + let nk: nkind = t.kind; + let nest: *node = nil; + if (nk == nkind.N_TPTR) { nest = t.lhs; }; + if (nk == nkind.N_TSLICE) { nest = t.lhs; }; + if (nk == nkind.N_TARRAY) { nest = t.lhs; }; + if (nest != nil && nest.kind == nkind.N_TARRAY) { + let eti: *tinfo = nest.type_: *tinfo; + for (eti != nil && eti.kind == tykind.TY_NAMED) { eti = eti.under; }; + if (eti != nil) { return eti.size: i32; }; + }; let direct: i32 = elemsizeof(t); if (direct != 8) { return direct; }; let k: nkind = t.kind; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 2d324afe..65074cfc 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1059,6 +1059,24 @@ fn elemsizeof(t: *node) i32 = { // whose Alias resolves to a tagged union (e.g. `[N]formattable`). fn elemsizeofc(c: *cgen, t: *node) i32 = { if (t == nil) { return 1; }; + // #270-2: a NESTED-array element ([N][M]T) — the OUTER index stride + // is the WHOLE sub-array [M]T, not the scalar T that elemsizeof + // drills down to (the documented elemsizeof FOOTGUN). elemsizeof + // returns the inner prim size (4 for [M]u32), so the `direct != 8` + // short-circuit below would mis-emit esz=$4 where cstage emits the + // sub-array stride $12. Mirror cstage esz = idx_eff(bt)->sub->size + // (cmd/w6c/cgen.c:4923): the element-array tinfo's natural size + // (sub.size*elen, type.c:121) IS the outer stride. + let nk: nkind = t.kind; + let nest: *node = nil; + if (nk == nkind.N_TPTR) { nest = t.lhs; }; + if (nk == nkind.N_TSLICE) { nest = t.lhs; }; + if (nk == nkind.N_TARRAY) { nest = t.lhs; }; + if (nest != nil && nest.kind == nkind.N_TARRAY) { + let eti: *tinfo = nest.type_: *tinfo; + for (eti != nil && eti.kind == tykind.TY_NAMED) { eti = eti.under; }; + if (eti != nil) { return eti.size: i32; }; + }; let direct: i32 = elemsizeof(t); if (direct != 8) { return direct; }; let k: nkind = t.kind; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 1649f964..677410af 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -16569,6 +16569,24 @@ fn elemsizeof(t: *node) i32 = { // whose Alias resolves to a tagged union (e.g. `[N]formattable`). fn elemsizeofc(c: *cgen, t: *node) i32 = { if (t == nil) { return 1; }; + // #270-2: a NESTED-array element ([N][M]T) — the OUTER index stride + // is the WHOLE sub-array [M]T, not the scalar T that elemsizeof + // drills down to (the documented elemsizeof FOOTGUN). elemsizeof + // returns the inner prim size (4 for [M]u32), so the `direct != 8` + // short-circuit below would mis-emit esz=$4 where cstage emits the + // sub-array stride $12. Mirror cstage esz = idx_eff(bt)->sub->size + // (cmd/w6c/cgen.c:4923): the element-array tinfo's natural size + // (sub.size*elen, type.c:121) IS the outer stride. + let nk: nkind = t.kind; + let nest: *node = nil; + if (nk == nkind.N_TPTR) { nest = t.lhs; }; + if (nk == nkind.N_TSLICE) { nest = t.lhs; }; + if (nk == nkind.N_TARRAY) { nest = t.lhs; }; + if (nest != nil && nest.kind == nkind.N_TARRAY) { + let eti: *tinfo = nest.type_: *tinfo; + for (eti != nil && eti.kind == tykind.TY_NAMED) { eti = eti.under; }; + if (eti != nil) { return eti.size: i32; }; + }; let direct: i32 = elemsizeof(t); if (direct != 8) { return direct; }; let k: nkind = t.kind; diff --git a/test/wcc/949_dotbase_addr_slice_run.c b/test/wcc/949_dotbase_addr_slice_run.c index ca835cf9..66878d5c 100644 --- a/test/wcc/949_dotbase_addr_slice_run.c +++ b/test/wcc/949_dotbase_addr_slice_run.c @@ -1020,6 +1020,43 @@ static const struct row rows[] = { "fn mk() T = { let s: T; s.a=1u32;s.b=2u32;s.c=3u32; return s; };\n" "export fn main() i32 = { let v = mk(); return (v.a+v.b+v.c): i32; };\n", 6, 1 }, + /* #270-2: nested-array OUTER-index stride. `a[i][j]` on a 2D + * `[N][M]T` indexes the outer dim by the WHOLE sub-array `[M]T` + * (stride = M*sizeof(T)), then the inner dim by sizeof(T). wwstage's + * elemsizeofc drilled the outer stride down to the scalar T (the + * documented elemsizeof FOOTGUN) → esz=$4 where cstage emits $12 + * (the sub-array size, type.c:121 sub->size*len). Runtime stayed + * self-consistent (write+read the same wrong stride) → masked until a + * CROSS-CELL test writes a[0][j] AND a[1][j] at distinct cells and + * reads both back. byteid=1: post-fix wwstage aligns up to cstage's + * $12. Distinct element widths assert the stride is the sub-array + * size, not a fixed literal. */ + { "nest2d_u32", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [2][3]u32;\n" + " a[0][1] = 11u32;\n" + " a[1][1] = 22u32;\n" + " a[1][2] = 33u32;\n" + " return (a[0][1] + a[1][1] + a[1][2]): i32;\n" + "};\n", 66, 1 }, + { "nest2d_u8", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [2][3]u8;\n" + " a[0][2] = 10u8;\n" + " a[1][0] = 20u8;\n" + " a[1][2] = 30u8;\n" + " return (a[0][2] + a[1][0] + a[1][2]): i32;\n" + "};\n", 60, 1 }, + { "nest2d_i32", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [2][3]i32;\n" + " a[0][1] = 40;\n" + " a[1][1] = 88;\n" + " return (a[1][1] - a[0][1]): i32;\n" + "};\n", 48, 1 }, { NULL, NULL, 0, 0 } };