diff --git a/Makefile b/Makefile index 0f9f56bb..8205771f 100644 --- a/Makefile +++ b/Makefile @@ -430,6 +430,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_floatarr_run \ $(BIN)/test_deref_narrow_run \ $(BIN)/test_idx_compound_run \ + $(BIN)/test_ptrarr_index_run \ $(BIN)/test_dotbase_arr_run \ $(BIN)/test_dotbase_addr_slice_run \ $(BIN)/test_structlit_arrfield_run \ @@ -1843,6 +1844,11 @@ $(BIN)/test_idx_compound_run: test/wcc/948_idx_compound_run.c $(BIN)/ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_ptrarr_index_run: test/wcc/949_ptrarr_index_run.c $(BIN)/ww \ + $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_dotbase_arr_run: test/wcc/949_dotbase_arr_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index a3d97115..de4f3b7f 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -3826,14 +3826,21 @@ cgexpr(Cg *c, Node *n, Local *locals) /* Fall through to silent-drop fallback below. */ } if (opnd && opnd->kind == N_INDEX) { - /* &base[i] = base + i*esz, no dereference. */ + /* &base[i] = base + i*esz, no dereference. + * esz via idx_eff (#61): `&p[i]` on `p: *[N]T` + * strides the pointee array's ELEMENT — the + * undrilled bu->sub here was the whole [N]T + * (&p[i]-&p[0] = i*N*size(T), wild pointer). + * Base load still keys off bu (is_arr stays + * false for the ptr → MOVQ of p's value). */ Node *base = opnd->lhs; Node *idx = opnd->rhs; Type *bt = base ? base->type : NULL; Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; - int esz = (bu && bu->sub) - ? (int)bu->sub->size : 1; + Type *eff = idx_eff(bt); + int esz = (eff && eff->sub) + ? (int)eff->sub->size : 1; cgexpr(c, idx, locals); /* idx → AX */ if (esz > 1) { ins2(c, A_MOVQ, aimm(esz), @@ -10259,8 +10266,11 @@ cgexpr(Cg *c, Node *n, Local *locals) * word must survive. Kind-gate on type_isstr||type_isslice, * never size==24: a >16B struct is 24B+ too but takes the * struct-copy path, not this 3-word header load (#10). - * Base is BX. */ - if (u->sub && (type_isstr(u->sub) || type_isslice(u->sub))) { + * Gate on esub (= idx_eff'd element, #61), not u->sub — + * for `*[N]str` u->sub is the ARRAY and the gate missed, + * falling to a 1-word load that dropped len/cap. esub == + * u->sub for every non-ptr-to-array base. Base is BX. */ + if (esub && (type_isstr(esub) || type_isslice(esub))) { cgslicehdr(c, D_BX); break; } @@ -10329,8 +10339,9 @@ cgexpr(Cg *c, Node *n, Local *locals) break; /* str/slice element via fallback base: load the full (ptr, len, * cap) header into (AX, BX, CX). Kind-gate on type_isstr|| - * type_isslice, never size==24 (see Site A). Base is AX. */ - if (u && u->sub && (type_isstr(u->sub) || type_isslice(u->sub))) { + * type_isslice, never size==24 (see Site A). esub, not u->sub + * (#61 — see the ident arm). Base is AX. */ + if (esub && (type_isstr(esub) || type_isslice(esub))) { cgslicehdr(c, D_AX); break; } diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 6fc3f413..ac23e23e 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -1776,6 +1776,11 @@ cexpr(Checker *c, Node *n) return n->type = base; if (u && u->kind == TY_STR) return n->type = ty_str; + /* Retained divergence: *[N]T does NOT decay here — + * `p[lo:hi]` types as [][N]T (C-pointer-slicing), unlike + * the index route (idx_eff) and unlike Hare. Loud on the + * usual []T annotation; for-range likewise. Team task #18 + * (#61-residual A). */ if (u && u->kind == TY_PTR && u->sub) return n->type = type_slice(c->a, u->sub); return n->type = err(c, n->pos, "cannot slice %s", diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 88c1b106..e199fbf2 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -13443,6 +13443,11 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { return tn; }; }; + // Retained divergence: *[N]T does NOT decay here — + // `p[lo:hi]` types as [][N]T (C-pointer-slicing), unlike + // the index route (idxeffti) and unlike Hare. Loud on the + // usual []T annotation; for-range likewise. Team task #18 + // (#61-residual A). if (bu.kind == nkind.N_TPTR && bu.lhs != nil) { let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0); sl.lhs = bu.lhs; @@ -16942,11 +16947,9 @@ fn nodeisstr(c: *cgen, n: *node) bool = { if (lc != nil) { bt = lc.tnode; } else { bt = letvartnode(c, base.str); }; if (bt != nil) { - let elem: *node = nil; - let bk: nkind = bt.kind; - if (bk == nkind.N_TARRAY) { elem = bt.lhs; }; - if (bk == nkind.N_TSLICE) { elem = bt.lhs; }; - if (bk == nkind.N_TPTR) { elem = bt.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee + // array's element (#61). + let elem: *node = idxelemtn(bt); if (elem != nil) { return isstrtype(c, elem); }; @@ -16988,11 +16991,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { if (streq(fi.fname, fld)) { let ft: *node = fi.tnode; if (ft != nil) { - let elem: *node = nil; - let fk: nkind = ft.kind; - if (fk == nkind.N_TPTR) { elem = ft.lhs; }; - if (fk == nkind.N_TSLICE) { elem = ft.lhs; }; - if (fk == nkind.N_TARRAY) { elem = ft.lhs; }; + // idxelemtn: `*[N]T` drill (#61). + let elem: *node = idxelemtn(ft); if (elem != nil) { return isstrtype(c, elem); }; @@ -17040,8 +17040,6 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { // the stamped tinfo so alias/enum recursion lives in lib/ww/typ.ww. fn elemissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; // #65 Phase-N step-2 cleanup: peel TY_NAMED before the .sub read. // #64 now flows per-decl NAMED wrappers, so a NAMED-of-(`*T`/`[]T`/ // `[N]T`) reaching here would read NAMED.sub (nil) instead of the @@ -17049,7 +17047,10 @@ fn elemissignedc(c: *cgen, t: *node) bool = { // before the eff->sub read (:3518-3520). Byte-id-neutral: every // aliased indexable in-tree has a u8 element (typeissigned=false // either way). Transitive peel matches the #63 idiom. - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti additionally drills `*[N]T` to the pointee array (#61) + // so a signed-narrow element behind a pointer-to-array still + // sign-extends on load. + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeissigned(ti.sub); }; @@ -17064,9 +17065,8 @@ fn elemissignedc(c: *cgen, t: *node) bool = { // (the unstamped-base trap that broke the exprfloatkind collapse, #121). fn elemisfloatc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti = TY_NAMED peel + the `*[N]T` drill (#61). + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeisfloat(ti.sub); }; @@ -17075,9 +17075,8 @@ fn elemisfloatc(c: *cgen, t: *node) bool = { // so cgindex picks MOVSS over MOVSD at the #119 element load. fn elemisf32c(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti = TY_NAMED peel + the `*[N]T` drill (#61). + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeisf32(ti.sub); }; @@ -17211,6 +17210,46 @@ export fn localloadop(c: *cgen, tnode: *node) str = { return loadopsz(sigd, sz); }; +// idxeffti — element-effective tinfo for indexing. `*[N]T` auto-derefs +// at an index base, so its esz/element classification must come from +// the pointee ARRAY (stride T), not from the pointer (whose .sub is +// the whole [N]T — the #61 stride bug, N*size(T) off target per index +// step). One peel choke-point: every tinfo-keyed index-element answer +// (elemsizeofc / elemissignedc / elemisfloatc / elemisf32c) routes +// through here. Mirrors cstage idx_eff (cmd/w6c/cgen.c:1163). +fn idxeffti(t0: *tinfo) *tinfo = { + let t: *tinfo = t0; + for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; }; + if (t != nil && t.kind == tykind.TY_PTR) { + let p: *tinfo = t.sub; + for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; }; + if (p != nil && p.kind == tykind.TY_ARRAY) { return p; }; + }; + return t; +}; + +// idxelemtn — element type-NODE for an indexable base tnode, the +// node-keyed companion of idxeffti for the cgen arms that classify +// the element structurally (istaggedtype / isstrtype / isslicetype / +// isfloattype / tnodestoreop). Same `*[N]T` drill: the pointee array's +// OWN element, never the array (#61 — an undrilled elemtn made the +// store-width chooser believe the element IS `[N]T` and emit an +// N*8-byte aggregate copy from an 8B source: caller-frame smash). +// nil for non-indexable kinds (str N_TNAME: callers want nil so +// tnodestoreop falls to the u8 byte store). +fn idxelemtn(tn: *node) *node = { + if (tn == nil) { return nil; }; + let k: nkind = tn.kind; + if (k != nkind.N_TARRAY && k != nkind.N_TSLICE + && k != nkind.N_TPTR) { return nil; }; + let elem: *node = tn.lhs; + if (k == nkind.N_TPTR && elem != nil + && elem.kind == nkind.N_TARRAY) { + return elem.lhs; + }; + return elem; +}; + // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). @@ -17280,9 +17319,15 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = { // 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. + // N_TPTR is excluded (#61): a pointee-array is NOT a nested + // element — `p[i]` on `*[N]T` auto-derefs and strides the array's + // OWN element (idx_eff peels TY_PTR→TY_ARRAY before the .sub + // read). Routing it through this whole-sub-array rule scaled + // every index by N*size(T) — the siphash round() corruption. The + // `*[N][M]T` outer stride still resolves below via idxeffti + // (pointee array's .sub = [M]T, its natural size). 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) { @@ -17303,20 +17348,16 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = { // and the local-array-init STORE (frame-smash). Peel TY_NAMED on the // indexable and on its element, matching the #270-2 nested-array block // above. Structural slotsize fallback stays for the t.type_==nil case. - let ti: *tinfo = t.type_: *tinfo; + // idxeffti folds that peel together with the `*[N]T` TY_PTR→ + // TY_ARRAY drill (#61) so .sub is the array's element, never the + // whole pointee array. + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti != nil) { - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; - if (ti != nil) { - let esub: *tinfo = ti.sub; - for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; }; - if (esub != nil) { return esub.size: i32; }; - }; + let esub: *tinfo = ti.sub; + for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; }; + if (esub != nil) { return esub.size: i32; }; }; - let k: nkind = t.kind; - let elem: *node = nil; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; + let elem: *node = idxelemtn(t); if (elem == nil) { return direct; }; if (elem.kind == nkind.N_TNAME) { let ps: i32 = primsize(elem.str); @@ -18209,11 +18250,8 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = { if (bl != nil) { btn = bl.tnode; } else { btn = letvartnode(c, ibase.str); }; if (btn == nil) { return nil; }; - let bk: nkind = btn.kind; - let etn: *node = nil; - if (bk == nkind.N_TARRAY) { etn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = btn.lhs; }; - if (bk == nkind.N_TPTR) { etn = btn.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee array's element (#61). + let etn: *node = idxelemtn(btn); if (etn == nil) { return nil; }; return resolvetagged(c, etn); }; @@ -21805,6 +21843,9 @@ fn cgindex(c: *cgen, n: *node) void = { // path when the base is a bare ident (mem.ww shape). let base: *node = n.lhs; let idx: *node = n.rhs; + // Direct non-ident index bases that match none of the typed arms + // below (e.g. a cast-expression base) keep this 8B default — + // cs!=ww for narrow elements. Team task #19 (#61-residual B). let esz: i32 = 8; let signed_elem: bool = false; // #119: float element loads route to MOVSS/MOVSD into X0, not the @@ -21928,22 +21969,14 @@ fn cgindex(c: *cgen, n: *node) void = { if (base.kind == nkind.N_IDENT) { let bl: *local = baselocal; let etn: *node = nil; + // idxelemtn drills `*[N]T` to the pointee array's own + // element (#61) — an undrilled etn classified the whole + // array, missing tagged/str/slice elements behind a + // pointer-to-array base. if (bl != nil) { - let btn: *node = bl.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { etn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = btn.lhs; }; - if (bk == nkind.N_TPTR) { etn = btn.lhs; }; - }; + etn = idxelemtn(bl.tnode); } else { - let tn: *node = letvartnode(c, base.str); - if (tn != nil) { - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { etn = tn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = tn.lhs; }; - if (bk == nkind.N_TPTR) { etn = tn.lhs; }; - }; + etn = idxelemtn(letvartnode(c, base.str)); }; if (istaggedtype(c, etn)) { if (!isnullabletype(etn)) { @@ -27162,13 +27195,12 @@ fn cgassign(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - let btn: *node = baselocal.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = btn.lhs; }; - }; + // idxelemtn drills `*[N]T` to the pointee + // array's element (#61): an undrilled elemtn + // made the width chooser believe the element + // IS the whole array (N*8B aggregate copy + // from an 8B source — frame smash). + elemtn = idxelemtn(baselocal.tnode); } else { // #11: store/compound twin of the #10 cgindex // read fix. A global str/slice element store hit @@ -27190,14 +27222,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn != nil) { globalname = bn; esz = elemsizeofc(c, tn); - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { + // idxelemtn: `*[N]T` drill, see the + // local branch above (#61). + elemtn = idxelemtn(tn); + if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; - elemtn = tn.lhs; } else { isglobalptr = true; - if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = tn.lhs; }; }; }; }; @@ -27609,13 +27640,12 @@ fn cgassign(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - let btn: *node = baselocal.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = btn.lhs; }; - }; + // idxelemtn drills `*[N]T` to the pointee + // array's element (#61): an undrilled elemtn + // made the width chooser believe the element + // IS the whole array (N*8B aggregate copy + // from an 8B source — frame smash). + elemtn = idxelemtn(baselocal.tnode); } else { // #11: store/compound twin of the #10 cgindex // read fix. A global str/slice element store hit @@ -27637,14 +27667,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn != nil) { globalname = bn; esz = elemsizeofc(c, tn); - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { + // idxelemtn: `*[N]T` drill, see the + // local branch above (#61). + elemtn = idxelemtn(tn); + if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; - elemtn = tn.lhs; } else { isglobalptr = true; - if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = tn.lhs; }; }; }; }; @@ -27795,12 +27824,10 @@ fn cgassign(c: *cgen, n: *node) void = { let lc: *local = localfindnode(c, idxbase.str); if (lc != nil) { if (lc.tnode != nil) { let tn: *node = lc.tnode; - let elemt: *node = nil; - let baseisarray: bool = false; - let tk: nkind = tn.kind; - if (tk == nkind.N_TSLICE) { elemt = tn.lhs; }; - if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; }; - if (tk == nkind.N_TPTR) { elemt = tn.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee + // array's element (#61). + let elemt: *node = idxelemtn(tn); + let baseisarray: bool = tn.kind == nkind.N_TARRAY; let sname: str; sname.ptr = nil; sname.len = 0; let viaptr: bool = false; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 6f24af03..0ed19a69 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1471,6 +1471,9 @@ fn cgindex(c: *cgen, n: *node) void = { // path when the base is a bare ident (mem.ww shape). let base: *node = n.lhs; let idx: *node = n.rhs; + // Direct non-ident index bases that match none of the typed arms + // below (e.g. a cast-expression base) keep this 8B default — + // cs!=ww for narrow elements. Team task #19 (#61-residual B). let esz: i32 = 8; let signed_elem: bool = false; // #119: float element loads route to MOVSS/MOVSD into X0, not the @@ -1594,22 +1597,14 @@ fn cgindex(c: *cgen, n: *node) void = { if (base.kind == nkind.N_IDENT) { let bl: *local = baselocal; let etn: *node = nil; + // idxelemtn drills `*[N]T` to the pointee array's own + // element (#61) — an undrilled etn classified the whole + // array, missing tagged/str/slice elements behind a + // pointer-to-array base. if (bl != nil) { - let btn: *node = bl.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { etn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = btn.lhs; }; - if (bk == nkind.N_TPTR) { etn = btn.lhs; }; - }; + etn = idxelemtn(bl.tnode); } else { - let tn: *node = letvartnode(c, base.str); - if (tn != nil) { - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { etn = tn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = tn.lhs; }; - if (bk == nkind.N_TPTR) { etn = tn.lhs; }; - }; + etn = idxelemtn(letvartnode(c, base.str)); }; if (istaggedtype(c, etn)) { if (!isnullabletype(etn)) { @@ -6828,13 +6823,12 @@ fn cgassign(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - let btn: *node = baselocal.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = btn.lhs; }; - }; + // idxelemtn drills `*[N]T` to the pointee + // array's element (#61): an undrilled elemtn + // made the width chooser believe the element + // IS the whole array (N*8B aggregate copy + // from an 8B source — frame smash). + elemtn = idxelemtn(baselocal.tnode); } else { // #11: store/compound twin of the #10 cgindex // read fix. A global str/slice element store hit @@ -6856,14 +6850,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn != nil) { globalname = bn; esz = elemsizeofc(c, tn); - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { + // idxelemtn: `*[N]T` drill, see the + // local branch above (#61). + elemtn = idxelemtn(tn); + if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; - elemtn = tn.lhs; } else { isglobalptr = true; - if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = tn.lhs; }; }; }; }; @@ -7275,13 +7268,12 @@ fn cgassign(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - let btn: *node = baselocal.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = btn.lhs; }; - }; + // idxelemtn drills `*[N]T` to the pointee + // array's element (#61): an undrilled elemtn + // made the width chooser believe the element + // IS the whole array (N*8B aggregate copy + // from an 8B source — frame smash). + elemtn = idxelemtn(baselocal.tnode); } else { // #11: store/compound twin of the #10 cgindex // read fix. A global str/slice element store hit @@ -7303,14 +7295,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn != nil) { globalname = bn; esz = elemsizeofc(c, tn); - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { + // idxelemtn: `*[N]T` drill, see the + // local branch above (#61). + elemtn = idxelemtn(tn); + if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; - elemtn = tn.lhs; } else { isglobalptr = true; - if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = tn.lhs; }; }; }; }; @@ -7461,12 +7452,10 @@ fn cgassign(c: *cgen, n: *node) void = { let lc: *local = localfindnode(c, idxbase.str); if (lc != nil) { if (lc.tnode != nil) { let tn: *node = lc.tnode; - let elemt: *node = nil; - let baseisarray: bool = false; - let tk: nkind = tn.kind; - if (tk == nkind.N_TSLICE) { elemt = tn.lhs; }; - if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; }; - if (tk == nkind.N_TPTR) { elemt = tn.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee + // array's element (#61). + let elemt: *node = idxelemtn(tn); + let baseisarray: bool = tn.kind == nkind.N_TARRAY; let sname: str; sname.ptr = nil; sname.len = 0; let viaptr: bool = false; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 7d6e68a7..104613b5 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -1026,11 +1026,9 @@ fn nodeisstr(c: *cgen, n: *node) bool = { if (lc != nil) { bt = lc.tnode; } else { bt = letvartnode(c, base.str); }; if (bt != nil) { - let elem: *node = nil; - let bk: nkind = bt.kind; - if (bk == nkind.N_TARRAY) { elem = bt.lhs; }; - if (bk == nkind.N_TSLICE) { elem = bt.lhs; }; - if (bk == nkind.N_TPTR) { elem = bt.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee + // array's element (#61). + let elem: *node = idxelemtn(bt); if (elem != nil) { return isstrtype(c, elem); }; @@ -1072,11 +1070,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { if (streq(fi.fname, fld)) { let ft: *node = fi.tnode; if (ft != nil) { - let elem: *node = nil; - let fk: nkind = ft.kind; - if (fk == nkind.N_TPTR) { elem = ft.lhs; }; - if (fk == nkind.N_TSLICE) { elem = ft.lhs; }; - if (fk == nkind.N_TARRAY) { elem = ft.lhs; }; + // idxelemtn: `*[N]T` drill (#61). + let elem: *node = idxelemtn(ft); if (elem != nil) { return isstrtype(c, elem); }; @@ -1124,8 +1119,6 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { // the stamped tinfo so alias/enum recursion lives in lib/ww/typ.ww. fn elemissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; // #65 Phase-N step-2 cleanup: peel TY_NAMED before the .sub read. // #64 now flows per-decl NAMED wrappers, so a NAMED-of-(`*T`/`[]T`/ // `[N]T`) reaching here would read NAMED.sub (nil) instead of the @@ -1133,7 +1126,10 @@ fn elemissignedc(c: *cgen, t: *node) bool = { // before the eff->sub read (:3518-3520). Byte-id-neutral: every // aliased indexable in-tree has a u8 element (typeissigned=false // either way). Transitive peel matches the #63 idiom. - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti additionally drills `*[N]T` to the pointee array (#61) + // so a signed-narrow element behind a pointer-to-array still + // sign-extends on load. + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeissigned(ti.sub); }; @@ -1148,9 +1144,8 @@ fn elemissignedc(c: *cgen, t: *node) bool = { // (the unstamped-base trap that broke the exprfloatkind collapse, #121). fn elemisfloatc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti = TY_NAMED peel + the `*[N]T` drill (#61). + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeisfloat(ti.sub); }; @@ -1159,9 +1154,8 @@ fn elemisfloatc(c: *cgen, t: *node) bool = { // so cgindex picks MOVSS over MOVSD at the #119 element load. fn elemisf32c(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti = TY_NAMED peel + the `*[N]T` drill (#61). + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeisf32(ti.sub); }; @@ -1295,6 +1289,46 @@ export fn localloadop(c: *cgen, tnode: *node) str = { return loadopsz(sigd, sz); }; +// idxeffti — element-effective tinfo for indexing. `*[N]T` auto-derefs +// at an index base, so its esz/element classification must come from +// the pointee ARRAY (stride T), not from the pointer (whose .sub is +// the whole [N]T — the #61 stride bug, N*size(T) off target per index +// step). One peel choke-point: every tinfo-keyed index-element answer +// (elemsizeofc / elemissignedc / elemisfloatc / elemisf32c) routes +// through here. Mirrors cstage idx_eff (cmd/w6c/cgen.c:1163). +fn idxeffti(t0: *tinfo) *tinfo = { + let t: *tinfo = t0; + for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; }; + if (t != nil && t.kind == tykind.TY_PTR) { + let p: *tinfo = t.sub; + for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; }; + if (p != nil && p.kind == tykind.TY_ARRAY) { return p; }; + }; + return t; +}; + +// idxelemtn — element type-NODE for an indexable base tnode, the +// node-keyed companion of idxeffti for the cgen arms that classify +// the element structurally (istaggedtype / isstrtype / isslicetype / +// isfloattype / tnodestoreop). Same `*[N]T` drill: the pointee array's +// OWN element, never the array (#61 — an undrilled elemtn made the +// store-width chooser believe the element IS `[N]T` and emit an +// N*8-byte aggregate copy from an 8B source: caller-frame smash). +// nil for non-indexable kinds (str N_TNAME: callers want nil so +// tnodestoreop falls to the u8 byte store). +fn idxelemtn(tn: *node) *node = { + if (tn == nil) { return nil; }; + let k: nkind = tn.kind; + if (k != nkind.N_TARRAY && k != nkind.N_TSLICE + && k != nkind.N_TPTR) { return nil; }; + let elem: *node = tn.lhs; + if (k == nkind.N_TPTR && elem != nil + && elem.kind == nkind.N_TARRAY) { + return elem.lhs; + }; + return elem; +}; + // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). @@ -1364,9 +1398,15 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = { // 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. + // N_TPTR is excluded (#61): a pointee-array is NOT a nested + // element — `p[i]` on `*[N]T` auto-derefs and strides the array's + // OWN element (idx_eff peels TY_PTR→TY_ARRAY before the .sub + // read). Routing it through this whole-sub-array rule scaled + // every index by N*size(T) — the siphash round() corruption. The + // `*[N][M]T` outer stride still resolves below via idxeffti + // (pointee array's .sub = [M]T, its natural size). 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) { @@ -1387,20 +1427,16 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = { // and the local-array-init STORE (frame-smash). Peel TY_NAMED on the // indexable and on its element, matching the #270-2 nested-array block // above. Structural slotsize fallback stays for the t.type_==nil case. - let ti: *tinfo = t.type_: *tinfo; + // idxeffti folds that peel together with the `*[N]T` TY_PTR→ + // TY_ARRAY drill (#61) so .sub is the array's element, never the + // whole pointee array. + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti != nil) { - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; - if (ti != nil) { - let esub: *tinfo = ti.sub; - for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; }; - if (esub != nil) { return esub.size: i32; }; - }; + let esub: *tinfo = ti.sub; + for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; }; + if (esub != nil) { return esub.size: i32; }; }; - let k: nkind = t.kind; - let elem: *node = nil; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; + let elem: *node = idxelemtn(t); if (elem == nil) { return direct; }; if (elem.kind == nkind.N_TNAME) { let ps: i32 = primsize(elem.str); @@ -2293,11 +2329,8 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = { if (bl != nil) { btn = bl.tnode; } else { btn = letvartnode(c, ibase.str); }; if (btn == nil) { return nil; }; - let bk: nkind = btn.kind; - let etn: *node = nil; - if (bk == nkind.N_TARRAY) { etn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = btn.lhs; }; - if (bk == nkind.N_TPTR) { etn = btn.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee array's element (#61). + let etn: *node = idxelemtn(btn); if (etn == nil) { return nil; }; return resolvetagged(c, etn); }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 1aeb9ee4..0a45d12c 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -3116,6 +3116,11 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { return tn; }; }; + // Retained divergence: *[N]T does NOT decay here — + // `p[lo:hi]` types as [][N]T (C-pointer-slicing), unlike + // the index route (idxeffti) and unlike Hare. Loud on the + // usual []T annotation; for-range likewise. Team task #18 + // (#61-residual A). if (bu.kind == nkind.N_TPTR && bu.lhs != nil) { let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0); sl.lhs = bu.lhs; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index fe5a3a8b..17573dde 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -13443,6 +13443,11 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { return tn; }; }; + // Retained divergence: *[N]T does NOT decay here — + // `p[lo:hi]` types as [][N]T (C-pointer-slicing), unlike + // the index route (idxeffti) and unlike Hare. Loud on the + // usual []T annotation; for-range likewise. Team task #18 + // (#61-residual A). if (bu.kind == nkind.N_TPTR && bu.lhs != nil) { let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0); sl.lhs = bu.lhs; @@ -16942,11 +16947,9 @@ fn nodeisstr(c: *cgen, n: *node) bool = { if (lc != nil) { bt = lc.tnode; } else { bt = letvartnode(c, base.str); }; if (bt != nil) { - let elem: *node = nil; - let bk: nkind = bt.kind; - if (bk == nkind.N_TARRAY) { elem = bt.lhs; }; - if (bk == nkind.N_TSLICE) { elem = bt.lhs; }; - if (bk == nkind.N_TPTR) { elem = bt.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee + // array's element (#61). + let elem: *node = idxelemtn(bt); if (elem != nil) { return isstrtype(c, elem); }; @@ -16988,11 +16991,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { if (streq(fi.fname, fld)) { let ft: *node = fi.tnode; if (ft != nil) { - let elem: *node = nil; - let fk: nkind = ft.kind; - if (fk == nkind.N_TPTR) { elem = ft.lhs; }; - if (fk == nkind.N_TSLICE) { elem = ft.lhs; }; - if (fk == nkind.N_TARRAY) { elem = ft.lhs; }; + // idxelemtn: `*[N]T` drill (#61). + let elem: *node = idxelemtn(ft); if (elem != nil) { return isstrtype(c, elem); }; @@ -17040,8 +17040,6 @@ fn typeis8byteprimitive(c: *cgen, t: *node) bool = { // the stamped tinfo so alias/enum recursion lives in lib/ww/typ.ww. fn elemissignedc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; // #65 Phase-N step-2 cleanup: peel TY_NAMED before the .sub read. // #64 now flows per-decl NAMED wrappers, so a NAMED-of-(`*T`/`[]T`/ // `[N]T`) reaching here would read NAMED.sub (nil) instead of the @@ -17049,7 +17047,10 @@ fn elemissignedc(c: *cgen, t: *node) bool = { // before the eff->sub read (:3518-3520). Byte-id-neutral: every // aliased indexable in-tree has a u8 element (typeissigned=false // either way). Transitive peel matches the #63 idiom. - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti additionally drills `*[N]T` to the pointee array (#61) + // so a signed-narrow element behind a pointer-to-array still + // sign-extends on load. + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeissigned(ti.sub); }; @@ -17064,9 +17065,8 @@ fn elemissignedc(c: *cgen, t: *node) bool = { // (the unstamped-base trap that broke the exprfloatkind collapse, #121). fn elemisfloatc(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti = TY_NAMED peel + the `*[N]T` drill (#61). + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeisfloat(ti.sub); }; @@ -17075,9 +17075,8 @@ fn elemisfloatc(c: *cgen, t: *node) bool = { // so cgindex picks MOVSS over MOVSD at the #119 element load. fn elemisf32c(c: *cgen, t: *node) bool = { if (t == nil) { return false; }; - let ti: *tinfo = t.type_: *tinfo; - if (ti == nil) { return false; }; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; + // idxeffti = TY_NAMED peel + the `*[N]T` drill (#61). + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti == nil) { return false; }; return typeisf32(ti.sub); }; @@ -17211,6 +17210,46 @@ export fn localloadop(c: *cgen, tnode: *node) str = { return loadopsz(sigd, sz); }; +// idxeffti — element-effective tinfo for indexing. `*[N]T` auto-derefs +// at an index base, so its esz/element classification must come from +// the pointee ARRAY (stride T), not from the pointer (whose .sub is +// the whole [N]T — the #61 stride bug, N*size(T) off target per index +// step). One peel choke-point: every tinfo-keyed index-element answer +// (elemsizeofc / elemissignedc / elemisfloatc / elemisf32c) routes +// through here. Mirrors cstage idx_eff (cmd/w6c/cgen.c:1163). +fn idxeffti(t0: *tinfo) *tinfo = { + let t: *tinfo = t0; + for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; }; + if (t != nil && t.kind == tykind.TY_PTR) { + let p: *tinfo = t.sub; + for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; }; + if (p != nil && p.kind == tykind.TY_ARRAY) { return p; }; + }; + return t; +}; + +// idxelemtn — element type-NODE for an indexable base tnode, the +// node-keyed companion of idxeffti for the cgen arms that classify +// the element structurally (istaggedtype / isstrtype / isslicetype / +// isfloattype / tnodestoreop). Same `*[N]T` drill: the pointee array's +// OWN element, never the array (#61 — an undrilled elemtn made the +// store-width chooser believe the element IS `[N]T` and emit an +// N*8-byte aggregate copy from an 8B source: caller-frame smash). +// nil for non-indexable kinds (str N_TNAME: callers want nil so +// tnodestoreop falls to the u8 byte store). +fn idxelemtn(tn: *node) *node = { + if (tn == nil) { return nil; }; + let k: nkind = tn.kind; + if (k != nkind.N_TARRAY && k != nkind.N_TSLICE + && k != nkind.N_TPTR) { return nil; }; + let elem: *node = tn.lhs; + if (k == nkind.N_TPTR && elem != nil + && elem.kind == nkind.N_TARRAY) { + return elem.lhs; + }; + return elem; +}; + // elemsizeof — given the type node of an indexable (`*T`, `[]T`, // `[N]T`, `str`), return the byte size of one element (1 for u8/i8/ // bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback). @@ -17280,9 +17319,15 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = { // 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. + // N_TPTR is excluded (#61): a pointee-array is NOT a nested + // element — `p[i]` on `*[N]T` auto-derefs and strides the array's + // OWN element (idx_eff peels TY_PTR→TY_ARRAY before the .sub + // read). Routing it through this whole-sub-array rule scaled + // every index by N*size(T) — the siphash round() corruption. The + // `*[N][M]T` outer stride still resolves below via idxeffti + // (pointee array's .sub = [M]T, its natural size). 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) { @@ -17303,20 +17348,16 @@ fn elemsizeofc(c: *cgen, t: *node) i32 = { // and the local-array-init STORE (frame-smash). Peel TY_NAMED on the // indexable and on its element, matching the #270-2 nested-array block // above. Structural slotsize fallback stays for the t.type_==nil case. - let ti: *tinfo = t.type_: *tinfo; + // idxeffti folds that peel together with the `*[N]T` TY_PTR→ + // TY_ARRAY drill (#61) so .sub is the array's element, never the + // whole pointee array. + let ti: *tinfo = idxeffti(t.type_: *tinfo); if (ti != nil) { - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; - if (ti != nil) { - let esub: *tinfo = ti.sub; - for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; }; - if (esub != nil) { return esub.size: i32; }; - }; + let esub: *tinfo = ti.sub; + for (esub != nil && esub.kind == tykind.TY_NAMED) { esub = esub.under; }; + if (esub != nil) { return esub.size: i32; }; }; - let k: nkind = t.kind; - let elem: *node = nil; - if (k == nkind.N_TPTR) { elem = t.lhs; }; - if (k == nkind.N_TSLICE) { elem = t.lhs; }; - if (k == nkind.N_TARRAY) { elem = t.lhs; }; + let elem: *node = idxelemtn(t); if (elem == nil) { return direct; }; if (elem.kind == nkind.N_TNAME) { let ps: i32 = primsize(elem.str); @@ -18209,11 +18250,8 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = { if (bl != nil) { btn = bl.tnode; } else { btn = letvartnode(c, ibase.str); }; if (btn == nil) { return nil; }; - let bk: nkind = btn.kind; - let etn: *node = nil; - if (bk == nkind.N_TARRAY) { etn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = btn.lhs; }; - if (bk == nkind.N_TPTR) { etn = btn.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee array's element (#61). + let etn: *node = idxelemtn(btn); if (etn == nil) { return nil; }; return resolvetagged(c, etn); }; @@ -21805,6 +21843,9 @@ fn cgindex(c: *cgen, n: *node) void = { // path when the base is a bare ident (mem.ww shape). let base: *node = n.lhs; let idx: *node = n.rhs; + // Direct non-ident index bases that match none of the typed arms + // below (e.g. a cast-expression base) keep this 8B default — + // cs!=ww for narrow elements. Team task #19 (#61-residual B). let esz: i32 = 8; let signed_elem: bool = false; // #119: float element loads route to MOVSS/MOVSD into X0, not the @@ -21928,22 +21969,14 @@ fn cgindex(c: *cgen, n: *node) void = { if (base.kind == nkind.N_IDENT) { let bl: *local = baselocal; let etn: *node = nil; + // idxelemtn drills `*[N]T` to the pointee array's own + // element (#61) — an undrilled etn classified the whole + // array, missing tagged/str/slice elements behind a + // pointer-to-array base. if (bl != nil) { - let btn: *node = bl.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { etn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = btn.lhs; }; - if (bk == nkind.N_TPTR) { etn = btn.lhs; }; - }; + etn = idxelemtn(bl.tnode); } else { - let tn: *node = letvartnode(c, base.str); - if (tn != nil) { - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { etn = tn.lhs; }; - if (bk == nkind.N_TSLICE) { etn = tn.lhs; }; - if (bk == nkind.N_TPTR) { etn = tn.lhs; }; - }; + etn = idxelemtn(letvartnode(c, base.str)); }; if (istaggedtype(c, etn)) { if (!isnullabletype(etn)) { @@ -27162,13 +27195,12 @@ fn cgassign(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - let btn: *node = baselocal.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = btn.lhs; }; - }; + // idxelemtn drills `*[N]T` to the pointee + // array's element (#61): an undrilled elemtn + // made the width chooser believe the element + // IS the whole array (N*8B aggregate copy + // from an 8B source — frame smash). + elemtn = idxelemtn(baselocal.tnode); } else { // #11: store/compound twin of the #10 cgindex // read fix. A global str/slice element store hit @@ -27190,14 +27222,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn != nil) { globalname = bn; esz = elemsizeofc(c, tn); - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { + // idxelemtn: `*[N]T` drill, see the + // local branch above (#61). + elemtn = idxelemtn(tn); + if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; - elemtn = tn.lhs; } else { isglobalptr = true; - if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = tn.lhs; }; }; }; }; @@ -27609,13 +27640,12 @@ fn cgassign(c: *cgen, n: *node) void = { baselocal = localfindnode(c, bn); if (baselocal != nil) { esz = elemsizeofc(c, baselocal.tnode); - let btn: *node = baselocal.tnode; - if (btn != nil) { - let bk: nkind = btn.kind; - if (bk == nkind.N_TARRAY) { elemtn = btn.lhs; }; - if (bk == nkind.N_TSLICE) { elemtn = btn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = btn.lhs; }; - }; + // idxelemtn drills `*[N]T` to the pointee + // array's element (#61): an undrilled elemtn + // made the width chooser believe the element + // IS the whole array (N*8B aggregate copy + // from an 8B source — frame smash). + elemtn = idxelemtn(baselocal.tnode); } else { // #11: store/compound twin of the #10 cgindex // read fix. A global str/slice element store hit @@ -27637,14 +27667,13 @@ fn cgassign(c: *cgen, n: *node) void = { if (tn != nil) { globalname = bn; esz = elemsizeofc(c, tn); - let bk: nkind = tn.kind; - if (bk == nkind.N_TARRAY) { + // idxelemtn: `*[N]T` drill, see the + // local branch above (#61). + elemtn = idxelemtn(tn); + if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; - elemtn = tn.lhs; } else { isglobalptr = true; - if (bk == nkind.N_TSLICE) { elemtn = tn.lhs; }; - if (bk == nkind.N_TPTR) { elemtn = tn.lhs; }; }; }; }; @@ -27795,12 +27824,10 @@ fn cgassign(c: *cgen, n: *node) void = { let lc: *local = localfindnode(c, idxbase.str); if (lc != nil) { if (lc.tnode != nil) { let tn: *node = lc.tnode; - let elemt: *node = nil; - let baseisarray: bool = false; - let tk: nkind = tn.kind; - if (tk == nkind.N_TSLICE) { elemt = tn.lhs; }; - if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; }; - if (tk == nkind.N_TPTR) { elemt = tn.lhs; }; + // idxelemtn: `*[N]T` drills to the pointee + // array's element (#61). + let elemt: *node = idxelemtn(tn); + let baseisarray: bool = tn.kind == nkind.N_TARRAY; let sname: str; sname.ptr = nil; sname.len = 0; let viaptr: bool = false; diff --git a/test/wcc/949_ptrarr_index_run.c b/test/wcc/949_ptrarr_index_run.c new file mode 100644 index 00000000..500530c0 --- /dev/null +++ b/test/wcc/949_ptrarr_index_run.c @@ -0,0 +1,383 @@ +/* + * 949_ptrarr_index_run — runtime + byte-id net for #61: indexing + * through a pointer-to-array (`p[i]`, p: *[N]T) must stride by + * size(T), the pointee array's ELEMENT, never by the whole-array + * byte size. + * + * The family (one root class): + * A. wwstage value-route scaling — elemsizeofc's #270-2 nested-array + * block (selfhost/cmd/wcc/cgenutil.ww) fed a `*[N]T` pointee into + * the "outer stride = whole sub-array" rule that is only correct + * for [N][M]T / [][M]T. Every read/write/compound through `p[i]` + * scaled by N*size(T) (OOB for any i>=1), and the same wrong + * element belief reached the store-width chooser: a var-idx write + * emitted an N*8-byte aggregate copy sourced at the 8B rhs slot — + * OOB read of the frame neighborhood + OOB write at base+N*8*i, + * caller-frame smash. This is exactly lib/hash/siphash round()'s + * `v[0]=v0 .. v[3]=v3` corruption. cstage was runtime-correct + * (idx_eff, cgen.c:1163, peels TY_PTR→TY_ARRAY); wwstage aligned + * UP via the idxeffti/idxelemtn choke-point. + * B. `&p[i]` addr-of route — BOTH stages identically wrong + * (byte-id-BLIND): the TK_AMP &base[i] arm read bu->sub->size + * without the ptr peel, so &p[3]-&a[0] returned 3*N*size(T). + * Both stages converged on the idx_eff'd element size; only the + * RUNTIME rows here can pin this class — the 990-997 byte-id + * gates can never see a both-stages-identical miscompile. + * + * Each row carries (a) a cstage `ww build` + run asserting the exit + * code and (b) a w6c vs w6c_ww `.s` cmp (rule-10 byte-id). Together + * they pin BOTH stages: byte-id + cstage-runtime-correct implies + * wwstage-runtime-correct. The matrix covers the elem widths the + * scaling class is sensitive to ({1,2,4,8}B), const + var indices, + * param / local / cast bases, read / write / compound routes, + * neighbor-corruption guards, the &p[i] pointer-difference (B), and + * the live consumer's mix-in-place shape (siphash round). + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +struct row { const char *label; const char *src; int want_exit; }; + +static const struct row rows[] = { + /* A: read, const idx, param base, 8B elem (ken p2). Pre-fix + * wwstage strode 32 → read past the array. */ + { "rd_u64_param_const", + "package main;\n" + "fn rd(p: *[4]u64) u64 = {\n" + " return p[1];\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" + " return rd(&a): i32;\n" + "};\n", 101 }, + /* A: read, var idx, 1B elem. */ + { "rd_u8_param_var", + "package main;\n" + "fn rd(p: *[4]u8, i: i32) u8 = {\n" + " return p[i];\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u8 = [10u8, 20u8, 30u8, 40u8];\n" + " return rd(&a, 2): i32;\n" + "};\n", 30 }, + /* A: read, 2B elem. */ + { "rd_u16_param", + "package main;\n" + "fn rd(p: *[4]u16) u16 = {\n" + " return p[3];\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u16 = [11u16, 22u16, 33u16, 44u16];\n" + " return rd(&a): i32;\n" + "};\n", 44 }, + /* A: read, 4B elem. */ + { "rd_u32_param", + "package main;\n" + "fn rd(p: *[4]u32) u32 = {\n" + " return p[2];\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u32 = [11u32, 22u32, 33u32, 44u32];\n" + " return rd(&a): i32;\n" + "};\n", 33 }, + /* A: signed-narrow elem behind the ptr — pins the MOVSXD + * sign-extend the idxeffti'd elemissignedc picks (an undrilled + * read of the ptr tinfo classified the ARRAY: unsigned). */ + { "rd_i32_signed", + "package main;\n" + "fn rd(p: *[4]i32) i32 = {\n" + " return p[1];\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]i32 = [7, -5, 9, 1];\n" + " return rd(&a) + 10;\n" + "};\n", 5 }, + /* A: write, const idx (ken p3) — scale-only half. */ + { "wr_u64_param_const", + "package main;\n" + "fn wr(p: *[4]u64) void = {\n" + " p[1] = 7u64;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u64 = [1u64, 2u64, 3u64, 4u64];\n" + " wr(&a);\n" + " return (a[0] + a[1] + a[2]): i32;\n" + "};\n", 11 }, + /* A: write, VAR idx + param rhs (ken p10) — the corruption + * proof: pre-fix wwstage emitted a 32B aggregate copy sourced + * at &x (reading i, p, saved BP) to base+32*i → frame smash / + * SIGSEGV. Neighbor guards assert no byte outside a[1] moved. */ + { "wr_u64_varidx_paramrhs", + "package main;\n" + "fn wr(p: *[4]u64, i: i32, x: u64) void = {\n" + " p[i] = x;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u64 = [10u64, 11u64, 12u64, 13u64];\n" + " let i: i32 = 1;\n" + " wr(&a, i, 77u64);\n" + " if (a[0] != 10u64) { return 1; };\n" + " if (a[1] != 77u64) { return 2; };\n" + " if (a[2] != 12u64) { return 3; };\n" + " if (a[3] != 13u64) { return 4; };\n" + " return 0;\n" + "};\n", 0 }, + /* A: write, 1B elem, neighbor guards at the tightest width. */ + { "wr_u8_neighbors", + "package main;\n" + "fn wr(p: *[4]u8) void = {\n" + " p[1] = 9u8;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" + " wr(&a);\n" + " return (a[0] + a[1] + a[2]): i32;\n" + "};\n", 13 }, + /* A: compound, const idx, 8B elem (ken p9b). */ + { "compound_u64", + "package main;\n" + "fn add5(p: *[4]u64) void = {\n" + " p[1] += 5u64;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" + " add5(&a);\n" + " return a[1]: i32;\n" + "};\n", 106 }, + /* A: compound, var idx, 4B elem. */ + { "compound_u32_varidx", + "package main;\n" + "fn addat(p: *[4]u32, i: i32) void = {\n" + " p[i] += 7u32;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: [4]u32 = [10u32, 20u32, 30u32, 40u32];\n" + " addat(&a, 2);\n" + " return a[2]: i32;\n" + "};\n", 37 }, + /* A: local-ptr base, no call boundary (ken p4). */ + { "rd_localptr", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [4]u64 = [100u64, 101u64, 102u64, 103u64];\n" + " let p: *[4]u64 = &a;\n" + " return p[2]: i32;\n" + "};\n", 102 }, + /* A: cast base (ken p6). */ + { "rd_castbase", + "package main;\n" + "export fn main() i32 = {\n" + " let big: [4]u64 = [5u64, 6u64, 7u64, 8u64];\n" + " let p: *[4]u64 = (&big): *[4]u64;\n" + " return p[1]: i32;\n" + "};\n", 6 }, + /* A: nested *[2][3]u32 (ken p16) — the elemsizeofc N_TPTR + * carve-out must coexist with the #270-2 outer-stride rule for + * the pointee's OWN nesting: inner stride 4, outer 12. Read, + * write, neighbor guards; param + local ptr bases. */ + { "nested_2d", + "package main;\n" + "fn rd(p: *[2][3]u32, i: i32, j: i32) u32 = { return p[i][j]; };\n" + "export fn main() i32 = {\n" + " let a: [2][3]u32;\n" + " a[0][0] = 0: u32; a[0][1] = 1: u32; a[0][2] = 2: u32;\n" + " a[1][0] = 10: u32; a[1][1] = 11: u32; a[1][2] = 12: u32;\n" + " if (rd(&a, 1, 2) != 12: u32) { return 1; };\n" + " if (rd(&a, 0, 1) != 1: u32) { return 2; };\n" + " let p: *[2][3]u32 = &a;\n" + " p[1][0] = 99: u32;\n" + " if (a[1][0] != 99: u32) { return 3; };\n" + " if (a[1][1] != 11: u32) { return 4; };\n" + " if (a[0][2] != 2: u32) { return 5; };\n" + " return 0;\n" + "};\n", 0 }, + /* A: *[3]str — 3-word (ptr,len,cap) header elements; pins the + * read-side str-header gate on the idx_eff'd element. Pre-fix + * BOTH stages were runtime-wrong here, differently: cstage's + * u->sub gate missed the ptr base and dropped len/cap (ken + * p17). */ + { "rd_str_elem", + "package main;\n" + "fn lenof(p: *[3]str, i: i32) i32 = { return p[i].len; };\n" + "export fn main() i32 = {\n" + " let a: [3]str;\n" + " a[0] = \"x\"; a[1] = \"yy\"; a[2] = \"zzz\";\n" + " if (lenof(&a, 2) != 3) { return 1; };\n" + " if (lenof(&a, 0) != 1) { return 2; };\n" + " let p: *[3]str = &a;\n" + " if (p[1].len != 2) { return 3; };\n" + " return 0;\n" + "};\n", 0 }, + /* B: &p[i] pointer difference (ken p8b) — both stages emitted + * the whole-array stride (96) byte-IDENTICALLY pre-fix; only + * this runtime row can see the class. 3 * size(u64) = 24. */ + { "amp_diff_u64", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [4]u64 = [1u64, 2u64, 3u64, 4u64];\n" + " let p: *[4]u64 = &a;\n" + " let d: u64 = (&p[3]): u64 - (&a[0]): u64;\n" + " return d: i32;\n" + "};\n", 24 }, + /* B: &p[i] difference at a narrow width. 3 * size(u16) = 6. */ + { "amp_diff_u16", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [4]u16 = [1u16, 2u16, 3u16, 4u16];\n" + " let p: *[4]u16 = &a;\n" + " let d: u64 = (&p[3]): u64 - (&a[0]): u64;\n" + " return d: i32;\n" + "};\n", 6 }, + /* A: the live consumer's shape — siphash round() mutates all + * four lanes through the param ptr, each read feeding a later + * write. Pre-fix wwstage smashed the caller frame here. */ + { "mix_inplace_round", + "package main;\n" + "fn mix(v: *[4]u64) void = {\n" + " v[0] += v[1];\n" + " v[2] += v[3];\n" + " v[1] += v[0];\n" + " v[3] += v[2];\n" + "};\n" + "export fn main() i32 = {\n" + " let v: [4]u64 = [1u64, 2u64, 3u64, 4u64];\n" + " mix(&v);\n" + " if (v[0] != 3u64) { return 1; };\n" + " if (v[1] != 5u64) { return 2; };\n" + " if (v[2] != 7u64) { return 3; };\n" + " if (v[3] != 11u64) { return 4; };\n" + " return 0;\n" + "};\n", 0 }, + { NULL, NULL, 0 } +}; + +static int +slurp_eq(const char *a, const char *b) +{ + FILE *fa = fopen(a, "rb"); + FILE *fb = fopen(b, "rb"); + if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } + int rc = 0; + for (;;) { + int ca = fgetc(fa); + int cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char w6c[1100], w6c_ww[1100]; + snprintf(w6c, sizeof w6c, "%s/w6c", bin); + snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); + if (access(w6c_ww, X_OK) != 0) { + fprintf(stderr, "ptrarr_index: w6c_ww missing — cannot run " + "the cs==ww byte-id gate\n"); + return 1; + } + + int n = 0, fail = 0; + for (int i = 0; rows[i].src; i++, n++) { + char src[64]; + snprintf(src, sizeof src, "/tmp/wwpai_%d_%d.ww", getpid(), i); + FILE *f = fopen(src, "wb"); + if (f == NULL) { fail++; continue; } + fputs(rows[i].src, f); + fclose(f); + + /* (a) cstage build + run. */ + char tmpdir[64]; + snprintf(tmpdir, sizeof tmpdir, "/tmp/wwpai_%d_d_%d", + getpid(), i); + mkdir(tmpdir, 0755); + + char cmd[2048]; + snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", + tmpdir, bin, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: cstage build failed\n", + rows[i].label); + fail++; + unlink(src); rmdir(tmpdir); + continue; + } + + char outbin[128]; + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + + int got = runwait(outbin); + if (got != rows[i].want_exit) { + fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", + rows[i].label, got, rows[i].want_exit); + fail++; + } + unlink(outbin); rmdir(tmpdir); + + /* (b) cs==ww byte-id gate. */ + char cs_s[64], ws_s[64]; + snprintf(cs_s, sizeof cs_s, "/tmp/wwpai_%d_%d_cs.s", + getpid(), i); + snprintf(ws_s, sizeof ws_s, "/tmp/wwpai_%d_%d_ww.s", + getpid(), i); + + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", + w6c, cs_s, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); + fail++; unlink(src); continue; + } + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", + w6c_ww, ws_s, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww failed\n", + rows[i].label); + fail++; unlink(src); unlink(cs_s); continue; + } + if (slurp_eq(cs_s, ws_s) != 0) { + fprintf(stderr, + "row[%s]: cstage/wwstage .s DIFFER (rule-10 " + "byte-id violation)\n", rows[i].label); + fail++; + } + unlink(src); unlink(cs_s); unlink(ws_s); + } + + if (fail) { + fprintf(stderr, "%d/%d ptrarr-index tests failed\n", fail, n); + return 1; + } + printf("ptrarr_index: %d/%d ok (cstage run + cs==ww byte-id)\n", + n, n); + return 0; +} diff --git a/test/wcc/989_lib_byteid.c b/test/wcc/989_lib_byteid.c index f3d9e6e2..b4359ea2 100644 --- a/test/wcc/989_lib_byteid.c +++ b/test/wcc/989_lib_byteid.c @@ -73,6 +73,8 @@ static const struct ent ents[] = { { .fixture = "lib/hash/crc16/crc16_test.ww", .mode = M_ID }, { .fixture = "lib/hash/crc32/crc32_test.ww", .mode = M_ID }, { .fixture = "lib/hash/crc64/crc64_test.ww", .mode = M_ID }, + /* graduated from #59.7 DIVERGE by the #61 *[N]T index-stride fix */ + { .fixture = "lib/hash/siphash/siphash_test.ww", .mode = M_ID }, { .fixture = "lib/math/checked/checked_test.ww", .mode = M_ID }, { .fixture = "lib/math/random/random_test.ww", .mode = M_ID }, { .fixture = "lib/memio/memiotest.ww", .mode = M_ID }, @@ -118,8 +120,7 @@ static const struct ent ents[] = { .mode = M_DIVERGE, .cite = "#59.5" }, { .fixture = "lib/fmt/fmttest.ww", .mode = M_DIVERGE, .cite = "#59.6" }, - { .fixture = "lib/hash/siphash/siphash_test.ww", - .mode = M_DIVERGE, .cite = "#59.7" }, + /* #59.7 siphash graduated to M_ID above (#61 fix) */ { .fixture = "lib/log/logtest.ww", .mode = M_DIVERGE, .cite = "#59.8" }, { .fixture = "lib/os/stattest.ww",