From da81a4c86ed5124ba7e34ab16a0ac7f1a9ee9d0a Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 19:38:54 +0900 Subject: [PATCH] =?UTF-8?q?wcc=5Fww/cgen:=20#60+#79=20alias-NAMED=20array/?= =?UTF-8?q?slice=20ELEMENT=20paths=20read=20the=20chased=20tinfo=20?= =?UTF-8?q?=E2=80=94=20tichase=20lands,=20SEGV=20families=20graduate=20byt?= =?UTF-8?q?e-id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One class: alias-blind base+esz at the array/slice ELEMENT paths — index read/write, slice-expr, for-range, and literal-init store. The wwstage cgen derived element size and base addressing from the type-AST tnode; an alias-typed base (`type arr = [4]int; let a: arr`) shows only the N_TNAME leaf, so esz fell to a sentinel (1 on the read side, 8 on the init-store side) and the base classified as a POINTER (MOVQ of array words, no IMULQ): m8b_idx1/range1 SEGV 139, m8b_slice1 silent-wrong past little-endian prefix-luck (m8c_slice1big exit 2), m7c global [2]row read SEGV via the alias-blind element-is-array classify, and (#79, ken F2a1 oracle) `type A=[4]u32; let a:A=[...]` stored MOVQ stride-8 over a stride-4 slot — elements 2/3 landed at 0(BP)/+8(BP), a saved-BP/RIP smash masked whenever esz==8. cstage reads everything off the chased stamped type (type_chase_named/ idx_eff, correct post-F1), so every fixed shape graduates ww-SEGV/silent-wrong -> 0/0 byte-id. New tichase() in cgenutil.ww: nil-passthrough transitive TY_NAMED peel, exact twin of cmd/wcc/type.c:160-162. Routed sites, all gated on the stamped type being TY_NAMED (non-alias paths byte-identical): - cgindex (cgenexpr.ww): elem facts (esz/signed/float/f32) off tichase(n.type_); etn falls back to n for the tagged/str/slice classify; LEAQ-vs-MOVQ base off the chased kind; elem-is-array supplemented by tinfoisarray(n.type_) for alias ELEMENTS (m7c). - cgassign N_INDEX store + compound arms (cgenexpr.ww): esz + elemtn=lhs (the stamped-element idiom of the N_DOT/N_INDEX arms); chased-kind base classify at all four LEAQ/MOVQ sites. - cgslice + cgbasecap (cgenexpr.ww): esz, base classify, default-hi (TY_ARRAY -> $alen / TY_SLICE|TY_STR -> +8 len), cap word at +16; global-str cap keeps the #73 carve-out. - cgforrange (cgenstmt.ww, cross-file leg: the range pin cannot green without it): esz, element-node synthesis off .sub (FC0 precedent), isarr/isslicestr classify, alen off the chased tinfo. - cgarrlitfillbp (cgenstmt.ww, #79): an alias [count]T arrtn is the N_TNAME leaf (elemn nil) — synthesise the element node off the chased sub so the existing prim/agg/slice/tagged/narrow dispatch works unchanged; `...` repeat bound off the chased alen (cstage cg_arrlit_fill_bp receives the pre-chased bu and reads bu->alen). #8-PAIR COVERAGE: this is the STORE half of #8's two size-sources. The elemsizeofc READ half chases the ELEMENT internally (idxeffti + esub peel, the #8 fix) but NOT an alias-typed INDEXABLE node — that leg is covered at its #60-family call sites by the gates above (cgindex/cgslice/store/compound/cgforrange/pusharg). Remaining alias-blind elemsizeofc callers are enumerated as residuals below. - bare-let classify (cgenstmt.ww, #79 rider): `let a: arrk;` with an alias-to-array type took the composite zero-fill cstage doesn't emit (cstage keys the no-init shape on the chased lu->kind: arrays keep the per-index-write contract; an 8B alias-array still falls to the single MOVQ $0 arm). Required for the loopfill_1024 pin's byte-id; closes the array kind of the uninit-alias divergence. - pusharg N_SLICE (cgenutil.ww, pulled in by the same pin rule: the 944 slice_of_alias_arg row is a distinct lowering from cgslice): esz, base classify, default-hi. Tests: new 944_alias_idx_family_run (19 rows: idx/slice/range/init controls + 1-level + 2-level + decl-order permutations + index store + compound (+=, *=) + #79 [4]u32 literal-init + alias `[v...]` repeat + uninit [1024] loop-fill + slice1big (1000 elems, values >255, LAST-element readback, default-hi, .cap, range count) + re-slice of an alias slice + range over an alias slice + m7c global 2D + GLOBAL alias-slice indexed read + slice-as-call-arg; dual-stage run + per-row byte-id; LAST elements asserted throughout). The six 944_alias_accept_run rows citing "#60 (F2 batch 1)" flip K_RUN_CS -> K_RUN (incl. slicefield_wholeread_2lvl: its 738d7f4-era receive-spine divergence no longer reproduces at the F1-merged base, verified byte-id + 0/0). 989_lib_byteid checked: no DIVERGE entry graduates (the test fails loudly on graduation; lib has no alias-base consumers — the shape SEGVed before this fix). NOT pinned (g-fold territory, #77/#78): direct alias-typed global ARRAY rows. Expected state probe-verified UNCHANGED by this diff: `let g: arr = [...]` -> ww link-ERR (no DATA emitted), cs 1-level runs 0, cs 2-level runs WRONG (silent). The alias-GLOBAL base legs added here (isglobalarr reclassify, global default-hi/cap) are cs-aligned but runtime-unreachable until the DATA emit lands. Residuals filed with the team: alias-blind elemsizeofc callers not in the #60 pin family — cgun &a[i] addr-of (cgenexpr.ww:4638 region, task #82), append() on an alias-typed slice local (:5287), `alloc([], n)` into an alias-slice let (cgenstmt.ww:2159), arr[i].field= float store (:8536); tagged-element READ under an alias base keeps the ident-arm nullable semantics; checker asserttyped on `untyped_lit * rangevar` over an alias slice (pre-existing, check.ww is batch 4, task #80); uninit alias-to-STRUCT zero-fill unchanged (correct: cstage fills composites); range-destructure over alias-to-tuple-slice. selfhost/cmd/{w6c,wwdump}/main.combined.ww regenerated (cgen*.ww are embedded sources). --- Makefile | 7 + selfhost/cmd/w6c/main.combined.ww | 421 +++++++++++++++++++++++- selfhost/cmd/wcc/cgenexpr.ww | 248 +++++++++++++- selfhost/cmd/wcc/cgenstmt.ww | 89 +++++ selfhost/cmd/wcc/cgenutil.ww | 84 ++++- selfhost/cmd/wwdump/main.combined.ww | 421 +++++++++++++++++++++++- test/wcc/944_alias_accept_run.c | 32 +- test/wcc/944_alias_idx_family_run.c | 475 +++++++++++++++++++++++++++ 8 files changed, 1727 insertions(+), 50 deletions(-) create mode 100644 test/wcc/944_alias_idx_family_run.c diff --git a/Makefile b/Makefile index 01bf67c3..3aa83b68 100644 --- a/Makefile +++ b/Makefile @@ -304,6 +304,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_deref_slice_store_run \ $(BIN)/test_alias_decl_order_size_run \ $(BIN)/test_alias_accept_run \ + $(BIN)/test_alias_idx_family_run \ $(BIN)/test_tuple_nary_destructure_run \ $(BIN)/test_rvalue_tuple_destructure_run \ $(BIN)/test_overcap_tuple_field_store_run \ @@ -1460,6 +1461,12 @@ $(BIN)/test_alias_accept_run: test/wcc/944_alias_accept_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_alias_idx_family_run: test/wcc/944_alias_idx_family_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_tuple_nary_destructure_run: test/wcc/945_tuple_nary_destructure_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 4a52c997..fa62e401 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -16542,6 +16542,20 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { dotbu = dotbu.under; }; };}; + // #60 (alias arc #5): alias-NAMED N_IDENT base — the tnode + // reads below see only the N_TNAME leaf (esz 1-sentinel, + // MOVQ base). Re-key off the chased stamped tinfo, the + // pusharg twin of the cgslice fix (cstage pushargs N_SLICE + // reads bu = type_chase_named(base->type) uniformly). + let basealias: bool = false; + let bu60: *tinfo = nil; + if (base != nil) { if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + basealias = true; + bu60 = tichase(bt60); + };}; + };}; // esz from the type table for an N_IDENT base (#76; mirrors // the cgindex idiom) or an N_DOT array/slice-field base (#257: // scale by the field's element width, not esz=1 -> silently @@ -16556,26 +16570,34 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { } else { if (dotbu != nil && dotbu.sub != nil) { esz = dotbu.sub.size: i32; };};}; + if (bu60 != nil) { + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; // base address → push if (baselocal != nil) { let tn: *node = baselocal.tnode; + let isarr60: bool = false; if (tn != nil) { - if (tn.kind == nkind.N_TARRAY) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), AX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), AX\n"); - }; + if (tn.kind == nkind.N_TARRAY) { isarr60 = true; }; + }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (bu60 != nil) { isarr60 = bu60.kind == tykind.TY_ARRAY; }; + if (isarr60) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), AX\n"); }; } else { if (globaltn != nil) { - if (globaltn.kind == nkind.N_TARRAY) { + let gisarr60: bool = globaltn.kind == nkind.N_TARRAY; + // #60: alias-NAMED base — chased kind; runtime- + // unreachable until #77/#78 global DATA. + if (bu60 != nil) { gisarr60 = bu60.kind == tykind.TY_ARRAY; }; + if (gisarr60) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), AX\n"); @@ -16620,6 +16642,20 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { }; };};}; }; + // #60: alias-NAMED base default-hi — chased kind + // (see the cgslice twin). + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + };}; + }; } else { if (globaltn != nil) { if (globaltn.kind == nkind.N_TARRAY) { let lenn: *node = globaltn.rhs; @@ -16636,6 +16672,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { emitline("(SB), CX\n"); emitline("\tMOVQ\t8(CX), AX\n"); };}; + // #60: alias-NAMED global base default-hi — chased + // kind; runtime-unreachable until #77/#78 global DATA. + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), CX\n"); + emitline("\tMOVQ\t8(CX), AX\n"); + };}; + }; } else { emitline("\tMOVQ\t$0, AX\n"); };};}; @@ -17324,6 +17375,19 @@ fn elemisarrayc(c: *cgen, t: *node) bool = { return elem.kind == nkind.N_TARRAY; }; +// tichase — transitive TY_NAMED peel, nil-passthrough. Exact wwstage +// twin of cstage type_chase_named (cmd/wcc/type.c:160-162, alias arc +// #5): chain-of-aliases stacks TY_NAMED layers, so any single peel +// leaves a kind-gated consumer staring at TY_NAMED and falling to a +// scalar shape (#60's esz=1/pointer-base SEGV family). One chased +// accessor is the only spelled way to dealias; raw `.under` reads +// outside it are the lint target (rob F2 ruling). +fn tichase(t0: *tinfo) *tinfo = { + let t: *tinfo = t0; + for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; }; + return t; +}; + // tinfoisarray — TY_ARRAY (NAMED-aware), the tinfo-keyed companion to // elemisarrayc for cgindex's N_DOT/N_INDEX base branches, where the // element type comes from n.type_ (stamped tinfo) not a tnode. Same @@ -22633,6 +22697,13 @@ fn cgindex(c: *cgen, n: *node) void = { // class; sentinel 754). let elemisstr: bool = false; let elemisslice: bool = false; + // #60 (alias arc #5): the base's declared type is an N_IDENT alias + // (`type arr = [4]int`). The tnode walks below see only the N_TNAME + // leaf — esz fell to the 1-sentinel and the base classified as a + // POINTER (MOVQ + no IMULQ → SEGV / prefix-luck reads). Set once + // here, consumed by the elem-fact override, the etn fallback and + // the LEAQ-vs-MOVQ base classify. + let basealias: bool = false; let baselocal: *local = nil; // Global `[N]T` array or `*T` pointer used as an index base. // The local-ident lookup above misses it; we need LEAQ name(SB) @@ -22693,6 +22764,42 @@ fn cgindex(c: *cgen, n: *node) void = { }; }; }; + // #60: element facts off the chased checker-stamped + // tinfos — cstage reads them via type_chase_named/ + // idx_eff uniformly (cmd/w6c/cgen.c N_INDEX). + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(n.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + signed_elem = typeissigned(et60); + float_elem = typeisfloat(et60); + f32_elem = typeisf32(et60); + }; + // global base: array-vs-pointer re-keyed off the + // chased BASE kind (cstage isglobal && u->kind == + // TY_ARRAY → LEAQ). Runtime-unreachable until the + // alias-typed global DATA emit lands (#77/#78); + // kept so the read leg is already cs-aligned. + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + // Alias-typed ELEMENT under an ident base (`[2]row`, + // row = [3]int): elemisarrayc's node walk can't see + // through the element's N_TNAME — the chased stamped + // element tinfo is the authority (cstage gates on + // esubu = type_chase_named(esub) == TY_ARRAY). + if (!elem_isarray) { + elem_isarray = tinfoisarray(n.type_: *tinfo); + }; } else { if (base.kind == nkind.N_DOT || (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) { // `s.ptr[i]` / struct-field index: stride is the @@ -22746,6 +22853,11 @@ fn cgindex(c: *cgen, n: *node) void = { } else { etn = idxelemtn(letvartnode(c, base.str)); }; + // #60: an alias-NAMED base has no element tnode + // (idxelemtn sees the N_TNAME leaf, nil) — classify + // off the stamped index-result n.type_, the same + // source the N_DOT/N_INDEX arms read. + if (basealias) { etn = n; }; if (istaggedtype(c, etn)) { if (!isnullabletype(etn)) { elem_tagged = true; @@ -22852,6 +22964,12 @@ fn cgindex(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — LEAQ-vs-MOVQ off the chased stamped + // kind (cstage u->kind == TY_ARRAY, cmd/w6c/cgen.c N_INDEX). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -23022,6 +23140,31 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { return true; }; }; + // #60: alias-NAMED base — chased kind (cstage cg_base_cap + // receives bu pre-chased by the N_SLICE arm, cgen.c:1888). + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + };}; return false; }; let gt: *node = letvartnode(c, base.str); @@ -23050,6 +23193,36 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { emitline("\n"); return true; }; + // #60: alias-NAMED global base — chased kind; global STR keeps + // the #73 cap=len carve-out (cstage isglobal && TY_STR → 0). + // Runtime-unreachable until #77/#78 global DATA. + let gbt60: *tinfo = base.type_: *tinfo; + if (gbt60 != nil) { if (gbt60.kind == tykind.TY_NAMED) { + let gbu60: *tinfo = tichase(gbt60); + if (gbu60 != nil) { + if (gbu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(gbu60.alen: i64); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (gbu60.kind == tykind.TY_SLICE) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + emitline("\tMOVQ\t16("); + emitline(dst); + emitline("), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + };}; return false; }; @@ -23101,6 +23274,20 @@ fn cgslice(c: *cgen, n: *node) void = { if (base != nil) { if (base.kind == nkind.N_ARRLIT) { arrlittn = base.lhs; };}; + // #60 (alias arc #5): alias-NAMED N_IDENT base — the tnode reads + // below see only the N_TNAME leaf (esz 1-sentinel, MOVQ base, + // $0 default-hi, cap=len). All four reads re-key off the chased + // stamped tinfo, mirroring cstage N_SLICE's single + // bu = type_chase_named(base->type) source (cmd/w6c/cgen.c). + let basealias: bool = false; + let bu60: *tinfo = nil; + if (base != nil) { if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + basealias = true; + bu60 = tichase(bt60); + };}; + };}; // esz from the type table for an N_IDENT base (#76; mirrors the // cgindex idiom) or an N_DOT array/slice-field base (#252: scale by // the field's element width, not esz=1 — silently wrong for non-u8). @@ -23115,6 +23302,10 @@ fn cgslice(c: *cgen, n: *node) void = { } else { if (arrlittn != nil) { esz = elemsizeofc(c, arrlittn); };};};}; + if (bu60 != nil) { + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; // base address if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -23122,6 +23313,8 @@ fn cgslice(c: *cgen, n: *node) void = { if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -23135,7 +23328,11 @@ fn cgslice(c: *cgen, n: *node) void = { // Top-level let: [N]T → LEAQ name(SB); pointer/slice/str // → MOVQ name(SB) (the symbol holds the {ptr,len,cap} or // {ptr,len} or pointer value). - if (globaltn.kind == nkind.N_TARRAY) { + let gisarr: bool = globaltn.kind == nkind.N_TARRAY; + // #60: alias-NAMED base — chased kind; runtime-unreachable + // until #77/#78 global DATA (see cgindex twin). + if (bu60 != nil) { gisarr = bu60.kind == tykind.TY_ARRAY; }; + if (gisarr) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), AX\n"); @@ -23214,6 +23411,23 @@ fn cgslice(c: *cgen, n: *node) void = { }; };};}; }; + // #60: alias-NAMED base default-hi — chased kind (cstage + // N_SLICE hi-default reads bu uniformly: TY_ARRAY → $alen, + // TY_SLICE/TY_STR → len word at +8). + if (!handled && bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + handled = true; + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + };}; + }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { if (globaltn != nil) { let handled: bool = false; @@ -23234,6 +23448,24 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tMOVQ\t8(CX), AX\n"); handled = true; };}; + // #60: alias-NAMED global base default-hi — chased kind; + // runtime-unreachable until #77/#78 global DATA (cstage + // emits LEAQ+8 for global SLICE/STR alike). + if (!handled && bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + handled = true; + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), CX\n"); + emitline("\tMOVQ\t8(CX), AX\n"); + handled = true; + };}; + }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { if (dotbu != nil && dotbu.kind == tykind.TY_ARRAY) { // #252: default-hi `s.obuf[lo:]` on a struct array-field → @@ -28553,6 +28785,7 @@ fn cgassign(c: *cgen, n: *node) void = { let globalname: str; globalname.ptr = nil; globalname.len = 0; let elemtn: *node = nil; + let basealias: bool = false; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; @@ -28626,6 +28859,37 @@ fn cgassign(c: *cgen, n: *node) void = { }; };};}; }; + // #60 (write spine): alias-NAMED N_IDENT base — the + // tnode walk above is blind (esz 1-sentinel, elemtn + // nil → pointer-treated base, wrong-stride store). + // Adopt the stamped-element idiom of the N_DOT/ + // N_INDEX arms (lhs.type_ IS the element tinfo); + // cstage N_INDEX store reads idx_eff(base->type)->sub + // uniformly (cmd/w6c/cgen.c:3517-3518). + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(lhs.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + elemtn = lhs; + }; + // see cgindex twin: cs-aligned, runtime- + // unreachable until #77/#78 global DATA. + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + }; + }; // Tagged-union element: materialize source in a shared // scratch slot via cgwidentaggedstore (handles struct / // str / scalar / subset / nullable variants uniformly), @@ -28671,6 +28935,12 @@ fn cgassign(c: *cgen, n: *node) void = { isarr = true; }; }; + // #60: alias-NAMED base — chased kind + // (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarr = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarr) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -28793,6 +29063,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn2: *node = baselocal.tnode; let isarr2: bool = false; if (tn2 != nil) { if (tn2.kind == nkind.N_TARRAY) { isarr2 = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarr2 = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarr2) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -28920,6 +29195,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -29061,6 +29341,31 @@ fn cgassign(c: *cgen, n: *node) void = { }; };};}; }; + // #60 (compound spine): alias-NAMED N_IDENT base — + // same stamped-element adoption as the store arm. + let basealias: bool = false; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(lhs.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + elemtn = lhs; + }; + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + }; + }; // #133-expanded: hard-error unwired payload kinds // LOUD (rule-7) — replaces prior silent skip. if (elemtn != nil) { @@ -29107,6 +29412,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -33830,6 +34140,30 @@ fn cgexprstmt(c: *cgen, n: *node) void = { // #25); `rhs` the literal. Twin of cstage cg_arrlit_fill_bp. fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = { let elemn: *node = arrtn.lhs; + // #79 (#60 rider): alias-NAMED [count]T (`type A = [4]u32; let + // a: A = [...]`) — arrtn is the N_TNAME leaf: elemn nil, esz + // stayed the 8 sentinel and the per-element store strode MOVQ + // over a stride-4 slot (saved-BP/RIP smash; masked when esz==8). + // This is the STORE half of the #8 pair (the elemsizeofc READ + // half chases the ELEMENT via idxeffti; alias-typed INDEXABLES + // are chased at its call sites). Synthesise the element node off + // the chased stamped sub — the cgforrange FC0 precedent — so the + // prim/agg/slice/tagged/narrow dispatch below works unchanged; + // stash alen for the `...` repeat bound (cstage cg_arrlit_fill_bp + // receives the pre-chased bu and reads bu->alen). + let aliasalen: i32 = -1; + let ati79: *tinfo = arrtn.type_: *tinfo; + if (ati79 != nil) { if (ati79.kind == tykind.TY_NAMED) { + let au79: *tinfo = tichase(ati79); + if (au79 != nil) { if (au79.kind == tykind.TY_ARRAY + && au79.sub != nil) { + let en79: *node = newnode(nkind.N_TNAME, arrtn.file, arrtn.line, arrtn.col); + en79.str = au79.sub.name; + en79.type_ = au79.sub: *void; + elemn = en79; + aliasalen = au79.alen: i32; + };}; + };}; let esz: i32 = 8; let isstrel: bool = false; if (elemn != nil) { @@ -34024,6 +34358,9 @@ fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = { }; }; }; + // #79: alias arrtn has no length tnode — bound off the + // chased tinfo (see the synthesis block at fn top). + if (aliasalen >= 0) { total = aliasalen; }; for (idx < total) { if (isstrel || isslicel) { emitline("\tMOVQ\tAX, "); @@ -34933,6 +35270,27 @@ fn cglet(c: *cgen, n: *node) void = { if (n.lhs != nil) { if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; }; }; + // #79 rider (#60 family): alias-NAMED bare let — the array + // classify must see through the N_TNAME leaf or an alias- + // to-array takes the composite zero-fill cstage doesn't + // emit (cstage keys on the chased lu->kind: arrays keep the + // per-index-write contract). An 8B alias-array stays + // isarr=false so it falls to the zsz==8 MOVQ $0 arm, + // matching cstage's 8B single-store shape. + if (!isarr && n.lhs != nil) { + if (n.lhs.kind == nkind.N_TNAME) { + let ati79: *tinfo = n.lhs.type_: *tinfo; + if (ati79 != nil) { if (ati79.kind == tykind.TY_NAMED) { + let au79: *tinfo = tichase(ati79); + if (au79 != nil) { + if (au79.kind == tykind.TY_ARRAY + && au79.size: i32 != 8) { + isarr = true; + }; + }; + };}; + }; + }; // Zero-fill extent. cstage sizes the run on `lu->size` // (the natural ABI size from the type table, cgen.c:8397); // wwstage's `sz` from letslotsize is slot-padded (round-to-8), @@ -35531,6 +35889,31 @@ fn cgforrange(c: *cgen, n: *node) void = { }; }; }; + // #60 (alias arc #5): alias-NAMED scrutinee (`let a: arr`, arr = + // [4]int) — the tnode peek above sees only the N_TNAME leaf: + // elemt nil, esz 1-sentinel, neither isarr nor isslicestr, so the + // per-iteration base walked the array words as a POINTER (SEGV). + // Chase the stamped tinfo (cstage N_FORRANGE u = type_chase_named + // (slc->type) feeds esz/alen/base classify uniformly) and + // synthesise the element node off .sub — the FC0 non-ident + // precedent below. + let rti60: *tinfo = nil; + if (slctn != nil) { + if (slctn.kind == nkind.N_TNAME) { + let st60: *tinfo = slctn.type_: *tinfo; + if (st60 != nil) { + if (st60.kind == tykind.TY_NAMED) { rti60 = tichase(st60); }; + }; + }; + }; + if (rti60 != nil && elemt == nil) { + if (rti60.sub != nil) { + let en60: *node = newnode(nkind.N_TNAME, slctn.file, slctn.line, slctn.col); + en60.str = rti60.sub.name; + en60.type_ = rti60.sub: *void; + elemt = en60; + }; + }; // esz: raw elem byte size. For tuple-element slices `[](T0, T1)`, // C cgen reads the resolved tuple's size (sum of raw param sizes, // no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8. @@ -35542,6 +35925,12 @@ fn cgforrange(c: *cgen, n: *node) void = { // cs≠ww). elemsizeofc recovers the width from the stamped tinfo // (the #8 named-narrow precedent). let esz: i32 = elemsizeofc(c, slctn); + // #60: alias-NAMED scrutinee — stride off the chased stamped + // element (cstage esz = u->sub->size). + if (rti60 != nil) { + let es60: *tinfo = tichase(rti60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; if (elemt != nil) { if (elemt.kind == nkind.N_TTUPLE) { let total: i32 = 0; @@ -35712,6 +36101,13 @@ fn cgforrange(c: *cgen, n: *node) void = { if (streq(slctn.str, "str")) { isslicestr = true; }; }; }; + // #60: alias-NAMED scrutinee — classify off the chased stamped + // kind (cstage gates on u->kind TY_SLICE/TY_STR vs TY_ARRAY). + if (rti60 != nil) { + if (rti60.kind == tykind.TY_ARRAY) { isarr = true; }; + if (rti60.kind == tykind.TY_SLICE) { isslicestr = true; }; + if (rti60.kind == tykind.TY_STR) { isslicestr = true; }; + }; if (isslicestr) { if (slc.kind == nkind.N_IDENT) { if (slclocal != nil) { @@ -35728,6 +36124,9 @@ fn cgforrange(c: *cgen, n: *node) void = { if (slctn.rhs != nil) { if (slctn.rhs.kind == nkind.N_INTLIT) { alen = slctn.rhs.uval: i64; }; }; + // #60: alias-NAMED scrutinee has no length tnode — bound off + // the chased tinfo (cstage aimm(u->alen)). + if (rti60 != nil) { alen = rti60.alen: i64; }; emitline("\tMOVQ\t$"); emitint(alen); emitline(", "); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index fc01976f..b6205d0a 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1727,6 +1727,13 @@ fn cgindex(c: *cgen, n: *node) void = { // class; sentinel 754). let elemisstr: bool = false; let elemisslice: bool = false; + // #60 (alias arc #5): the base's declared type is an N_IDENT alias + // (`type arr = [4]int`). The tnode walks below see only the N_TNAME + // leaf — esz fell to the 1-sentinel and the base classified as a + // POINTER (MOVQ + no IMULQ → SEGV / prefix-luck reads). Set once + // here, consumed by the elem-fact override, the etn fallback and + // the LEAQ-vs-MOVQ base classify. + let basealias: bool = false; let baselocal: *local = nil; // Global `[N]T` array or `*T` pointer used as an index base. // The local-ident lookup above misses it; we need LEAQ name(SB) @@ -1787,6 +1794,42 @@ fn cgindex(c: *cgen, n: *node) void = { }; }; }; + // #60: element facts off the chased checker-stamped + // tinfos — cstage reads them via type_chase_named/ + // idx_eff uniformly (cmd/w6c/cgen.c N_INDEX). + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(n.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + signed_elem = typeissigned(et60); + float_elem = typeisfloat(et60); + f32_elem = typeisf32(et60); + }; + // global base: array-vs-pointer re-keyed off the + // chased BASE kind (cstage isglobal && u->kind == + // TY_ARRAY → LEAQ). Runtime-unreachable until the + // alias-typed global DATA emit lands (#77/#78); + // kept so the read leg is already cs-aligned. + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + // Alias-typed ELEMENT under an ident base (`[2]row`, + // row = [3]int): elemisarrayc's node walk can't see + // through the element's N_TNAME — the chased stamped + // element tinfo is the authority (cstage gates on + // esubu = type_chase_named(esub) == TY_ARRAY). + if (!elem_isarray) { + elem_isarray = tinfoisarray(n.type_: *tinfo); + }; } else { if (base.kind == nkind.N_DOT || (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) { // `s.ptr[i]` / struct-field index: stride is the @@ -1840,6 +1883,11 @@ fn cgindex(c: *cgen, n: *node) void = { } else { etn = idxelemtn(letvartnode(c, base.str)); }; + // #60: an alias-NAMED base has no element tnode + // (idxelemtn sees the N_TNAME leaf, nil) — classify + // off the stamped index-result n.type_, the same + // source the N_DOT/N_INDEX arms read. + if (basealias) { etn = n; }; if (istaggedtype(c, etn)) { if (!isnullabletype(etn)) { elem_tagged = true; @@ -1946,6 +1994,12 @@ fn cgindex(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — LEAQ-vs-MOVQ off the chased stamped + // kind (cstage u->kind == TY_ARRAY, cmd/w6c/cgen.c N_INDEX). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -2116,6 +2170,31 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { return true; }; }; + // #60: alias-NAMED base — chased kind (cstage cg_base_cap + // receives bu pre-chased by the N_SLICE arm, cgen.c:1888). + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + };}; return false; }; let gt: *node = letvartnode(c, base.str); @@ -2144,6 +2223,36 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { emitline("\n"); return true; }; + // #60: alias-NAMED global base — chased kind; global STR keeps + // the #73 cap=len carve-out (cstage isglobal && TY_STR → 0). + // Runtime-unreachable until #77/#78 global DATA. + let gbt60: *tinfo = base.type_: *tinfo; + if (gbt60 != nil) { if (gbt60.kind == tykind.TY_NAMED) { + let gbu60: *tinfo = tichase(gbt60); + if (gbu60 != nil) { + if (gbu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(gbu60.alen: i64); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (gbu60.kind == tykind.TY_SLICE) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + emitline("\tMOVQ\t16("); + emitline(dst); + emitline("), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + };}; return false; }; @@ -2195,6 +2304,20 @@ fn cgslice(c: *cgen, n: *node) void = { if (base != nil) { if (base.kind == nkind.N_ARRLIT) { arrlittn = base.lhs; };}; + // #60 (alias arc #5): alias-NAMED N_IDENT base — the tnode reads + // below see only the N_TNAME leaf (esz 1-sentinel, MOVQ base, + // $0 default-hi, cap=len). All four reads re-key off the chased + // stamped tinfo, mirroring cstage N_SLICE's single + // bu = type_chase_named(base->type) source (cmd/w6c/cgen.c). + let basealias: bool = false; + let bu60: *tinfo = nil; + if (base != nil) { if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + basealias = true; + bu60 = tichase(bt60); + };}; + };}; // esz from the type table for an N_IDENT base (#76; mirrors the // cgindex idiom) or an N_DOT array/slice-field base (#252: scale by // the field's element width, not esz=1 — silently wrong for non-u8). @@ -2209,6 +2332,10 @@ fn cgslice(c: *cgen, n: *node) void = { } else { if (arrlittn != nil) { esz = elemsizeofc(c, arrlittn); };};};}; + if (bu60 != nil) { + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; // base address if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -2216,6 +2343,8 @@ fn cgslice(c: *cgen, n: *node) void = { if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -2229,7 +2358,11 @@ fn cgslice(c: *cgen, n: *node) void = { // Top-level let: [N]T → LEAQ name(SB); pointer/slice/str // → MOVQ name(SB) (the symbol holds the {ptr,len,cap} or // {ptr,len} or pointer value). - if (globaltn.kind == nkind.N_TARRAY) { + let gisarr: bool = globaltn.kind == nkind.N_TARRAY; + // #60: alias-NAMED base — chased kind; runtime-unreachable + // until #77/#78 global DATA (see cgindex twin). + if (bu60 != nil) { gisarr = bu60.kind == tykind.TY_ARRAY; }; + if (gisarr) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), AX\n"); @@ -2308,6 +2441,23 @@ fn cgslice(c: *cgen, n: *node) void = { }; };};}; }; + // #60: alias-NAMED base default-hi — chased kind (cstage + // N_SLICE hi-default reads bu uniformly: TY_ARRAY → $alen, + // TY_SLICE/TY_STR → len word at +8). + if (!handled && bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + handled = true; + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + };}; + }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { if (globaltn != nil) { let handled: bool = false; @@ -2328,6 +2478,24 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tMOVQ\t8(CX), AX\n"); handled = true; };}; + // #60: alias-NAMED global base default-hi — chased kind; + // runtime-unreachable until #77/#78 global DATA (cstage + // emits LEAQ+8 for global SLICE/STR alike). + if (!handled && bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + handled = true; + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), CX\n"); + emitline("\tMOVQ\t8(CX), AX\n"); + handled = true; + };}; + }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { if (dotbu != nil && dotbu.kind == tykind.TY_ARRAY) { // #252: default-hi `s.obuf[lo:]` on a struct array-field → @@ -7647,6 +7815,7 @@ fn cgassign(c: *cgen, n: *node) void = { let globalname: str; globalname.ptr = nil; globalname.len = 0; let elemtn: *node = nil; + let basealias: bool = false; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; @@ -7720,6 +7889,37 @@ fn cgassign(c: *cgen, n: *node) void = { }; };};}; }; + // #60 (write spine): alias-NAMED N_IDENT base — the + // tnode walk above is blind (esz 1-sentinel, elemtn + // nil → pointer-treated base, wrong-stride store). + // Adopt the stamped-element idiom of the N_DOT/ + // N_INDEX arms (lhs.type_ IS the element tinfo); + // cstage N_INDEX store reads idx_eff(base->type)->sub + // uniformly (cmd/w6c/cgen.c:3517-3518). + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(lhs.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + elemtn = lhs; + }; + // see cgindex twin: cs-aligned, runtime- + // unreachable until #77/#78 global DATA. + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + }; + }; // Tagged-union element: materialize source in a shared // scratch slot via cgwidentaggedstore (handles struct / // str / scalar / subset / nullable variants uniformly), @@ -7765,6 +7965,12 @@ fn cgassign(c: *cgen, n: *node) void = { isarr = true; }; }; + // #60: alias-NAMED base — chased kind + // (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarr = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarr) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -7887,6 +8093,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn2: *node = baselocal.tnode; let isarr2: bool = false; if (tn2 != nil) { if (tn2.kind == nkind.N_TARRAY) { isarr2 = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarr2 = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarr2) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -8014,6 +8225,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -8155,6 +8371,31 @@ fn cgassign(c: *cgen, n: *node) void = { }; };};}; }; + // #60 (compound spine): alias-NAMED N_IDENT base — + // same stamped-element adoption as the store arm. + let basealias: bool = false; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(lhs.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + elemtn = lhs; + }; + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + }; + }; // #133-expanded: hard-error unwired payload kinds // LOUD (rule-7) — replaces prior silent skip. if (elemtn != nil) { @@ -8201,6 +8442,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index fb49d3a7..28608fae 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1863,6 +1863,30 @@ fn cgexprstmt(c: *cgen, n: *node) void = { // #25); `rhs` the literal. Twin of cstage cg_arrlit_fill_bp. fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = { let elemn: *node = arrtn.lhs; + // #79 (#60 rider): alias-NAMED [count]T (`type A = [4]u32; let + // a: A = [...]`) — arrtn is the N_TNAME leaf: elemn nil, esz + // stayed the 8 sentinel and the per-element store strode MOVQ + // over a stride-4 slot (saved-BP/RIP smash; masked when esz==8). + // This is the STORE half of the #8 pair (the elemsizeofc READ + // half chases the ELEMENT via idxeffti; alias-typed INDEXABLES + // are chased at its call sites). Synthesise the element node off + // the chased stamped sub — the cgforrange FC0 precedent — so the + // prim/agg/slice/tagged/narrow dispatch below works unchanged; + // stash alen for the `...` repeat bound (cstage cg_arrlit_fill_bp + // receives the pre-chased bu and reads bu->alen). + let aliasalen: i32 = -1; + let ati79: *tinfo = arrtn.type_: *tinfo; + if (ati79 != nil) { if (ati79.kind == tykind.TY_NAMED) { + let au79: *tinfo = tichase(ati79); + if (au79 != nil) { if (au79.kind == tykind.TY_ARRAY + && au79.sub != nil) { + let en79: *node = newnode(nkind.N_TNAME, arrtn.file, arrtn.line, arrtn.col); + en79.str = au79.sub.name; + en79.type_ = au79.sub: *void; + elemn = en79; + aliasalen = au79.alen: i32; + };}; + };}; let esz: i32 = 8; let isstrel: bool = false; if (elemn != nil) { @@ -2057,6 +2081,9 @@ fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = { }; }; }; + // #79: alias arrtn has no length tnode — bound off the + // chased tinfo (see the synthesis block at fn top). + if (aliasalen >= 0) { total = aliasalen; }; for (idx < total) { if (isstrel || isslicel) { emitline("\tMOVQ\tAX, "); @@ -2966,6 +2993,27 @@ fn cglet(c: *cgen, n: *node) void = { if (n.lhs != nil) { if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; }; }; + // #79 rider (#60 family): alias-NAMED bare let — the array + // classify must see through the N_TNAME leaf or an alias- + // to-array takes the composite zero-fill cstage doesn't + // emit (cstage keys on the chased lu->kind: arrays keep the + // per-index-write contract). An 8B alias-array stays + // isarr=false so it falls to the zsz==8 MOVQ $0 arm, + // matching cstage's 8B single-store shape. + if (!isarr && n.lhs != nil) { + if (n.lhs.kind == nkind.N_TNAME) { + let ati79: *tinfo = n.lhs.type_: *tinfo; + if (ati79 != nil) { if (ati79.kind == tykind.TY_NAMED) { + let au79: *tinfo = tichase(ati79); + if (au79 != nil) { + if (au79.kind == tykind.TY_ARRAY + && au79.size: i32 != 8) { + isarr = true; + }; + }; + };}; + }; + }; // Zero-fill extent. cstage sizes the run on `lu->size` // (the natural ABI size from the type table, cgen.c:8397); // wwstage's `sz` from letslotsize is slot-padded (round-to-8), @@ -3564,6 +3612,31 @@ fn cgforrange(c: *cgen, n: *node) void = { }; }; }; + // #60 (alias arc #5): alias-NAMED scrutinee (`let a: arr`, arr = + // [4]int) — the tnode peek above sees only the N_TNAME leaf: + // elemt nil, esz 1-sentinel, neither isarr nor isslicestr, so the + // per-iteration base walked the array words as a POINTER (SEGV). + // Chase the stamped tinfo (cstage N_FORRANGE u = type_chase_named + // (slc->type) feeds esz/alen/base classify uniformly) and + // synthesise the element node off .sub — the FC0 non-ident + // precedent below. + let rti60: *tinfo = nil; + if (slctn != nil) { + if (slctn.kind == nkind.N_TNAME) { + let st60: *tinfo = slctn.type_: *tinfo; + if (st60 != nil) { + if (st60.kind == tykind.TY_NAMED) { rti60 = tichase(st60); }; + }; + }; + }; + if (rti60 != nil && elemt == nil) { + if (rti60.sub != nil) { + let en60: *node = newnode(nkind.N_TNAME, slctn.file, slctn.line, slctn.col); + en60.str = rti60.sub.name; + en60.type_ = rti60.sub: *void; + elemt = en60; + }; + }; // esz: raw elem byte size. For tuple-element slices `[](T0, T1)`, // C cgen reads the resolved tuple's size (sum of raw param sizes, // no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8. @@ -3575,6 +3648,12 @@ fn cgforrange(c: *cgen, n: *node) void = { // cs≠ww). elemsizeofc recovers the width from the stamped tinfo // (the #8 named-narrow precedent). let esz: i32 = elemsizeofc(c, slctn); + // #60: alias-NAMED scrutinee — stride off the chased stamped + // element (cstage esz = u->sub->size). + if (rti60 != nil) { + let es60: *tinfo = tichase(rti60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; if (elemt != nil) { if (elemt.kind == nkind.N_TTUPLE) { let total: i32 = 0; @@ -3745,6 +3824,13 @@ fn cgforrange(c: *cgen, n: *node) void = { if (streq(slctn.str, "str")) { isslicestr = true; }; }; }; + // #60: alias-NAMED scrutinee — classify off the chased stamped + // kind (cstage gates on u->kind TY_SLICE/TY_STR vs TY_ARRAY). + if (rti60 != nil) { + if (rti60.kind == tykind.TY_ARRAY) { isarr = true; }; + if (rti60.kind == tykind.TY_SLICE) { isslicestr = true; }; + if (rti60.kind == tykind.TY_STR) { isslicestr = true; }; + }; if (isslicestr) { if (slc.kind == nkind.N_IDENT) { if (slclocal != nil) { @@ -3761,6 +3847,9 @@ fn cgforrange(c: *cgen, n: *node) void = { if (slctn.rhs != nil) { if (slctn.rhs.kind == nkind.N_INTLIT) { alen = slctn.rhs.uval: i64; }; }; + // #60: alias-NAMED scrutinee has no length tnode — bound off + // the chased tinfo (cstage aimm(u->alen)). + if (rti60 != nil) { alen = rti60.alen: i64; }; emitline("\tMOVQ\t$"); emitint(alen); emitline(", "); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 9fe0ccd7..dc853c19 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -470,6 +470,20 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { dotbu = dotbu.under; }; };}; + // #60 (alias arc #5): alias-NAMED N_IDENT base — the tnode + // reads below see only the N_TNAME leaf (esz 1-sentinel, + // MOVQ base). Re-key off the chased stamped tinfo, the + // pusharg twin of the cgslice fix (cstage pushargs N_SLICE + // reads bu = type_chase_named(base->type) uniformly). + let basealias: bool = false; + let bu60: *tinfo = nil; + if (base != nil) { if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + basealias = true; + bu60 = tichase(bt60); + };}; + };}; // esz from the type table for an N_IDENT base (#76; mirrors // the cgindex idiom) or an N_DOT array/slice-field base (#257: // scale by the field's element width, not esz=1 -> silently @@ -484,26 +498,34 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { } else { if (dotbu != nil && dotbu.sub != nil) { esz = dotbu.sub.size: i32; };};}; + if (bu60 != nil) { + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; // base address → push if (baselocal != nil) { let tn: *node = baselocal.tnode; + let isarr60: bool = false; if (tn != nil) { - if (tn.kind == nkind.N_TARRAY) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), AX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), AX\n"); - }; + if (tn.kind == nkind.N_TARRAY) { isarr60 = true; }; + }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (bu60 != nil) { isarr60 = bu60.kind == tykind.TY_ARRAY; }; + if (isarr60) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), AX\n"); }; } else { if (globaltn != nil) { - if (globaltn.kind == nkind.N_TARRAY) { + let gisarr60: bool = globaltn.kind == nkind.N_TARRAY; + // #60: alias-NAMED base — chased kind; runtime- + // unreachable until #77/#78 global DATA. + if (bu60 != nil) { gisarr60 = bu60.kind == tykind.TY_ARRAY; }; + if (gisarr60) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), AX\n"); @@ -548,6 +570,20 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { }; };};}; }; + // #60: alias-NAMED base default-hi — chased kind + // (see the cgslice twin). + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + };}; + }; } else { if (globaltn != nil) { if (globaltn.kind == nkind.N_TARRAY) { let lenn: *node = globaltn.rhs; @@ -564,6 +600,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { emitline("(SB), CX\n"); emitline("\tMOVQ\t8(CX), AX\n"); };}; + // #60: alias-NAMED global base default-hi — chased + // kind; runtime-unreachable until #77/#78 global DATA. + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), CX\n"); + emitline("\tMOVQ\t8(CX), AX\n"); + };}; + }; } else { emitline("\tMOVQ\t$0, AX\n"); };};}; @@ -1252,6 +1303,19 @@ fn elemisarrayc(c: *cgen, t: *node) bool = { return elem.kind == nkind.N_TARRAY; }; +// tichase — transitive TY_NAMED peel, nil-passthrough. Exact wwstage +// twin of cstage type_chase_named (cmd/wcc/type.c:160-162, alias arc +// #5): chain-of-aliases stacks TY_NAMED layers, so any single peel +// leaves a kind-gated consumer staring at TY_NAMED and falling to a +// scalar shape (#60's esz=1/pointer-base SEGV family). One chased +// accessor is the only spelled way to dealias; raw `.under` reads +// outside it are the lint target (rob F2 ruling). +fn tichase(t0: *tinfo) *tinfo = { + let t: *tinfo = t0; + for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; }; + return t; +}; + // tinfoisarray — TY_ARRAY (NAMED-aware), the tinfo-keyed companion to // elemisarrayc for cgindex's N_DOT/N_INDEX base branches, where the // element type comes from n.type_ (stamped tinfo) not a tnode. Same diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 24ff7a73..b31069ca 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -16542,6 +16542,20 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { dotbu = dotbu.under; }; };}; + // #60 (alias arc #5): alias-NAMED N_IDENT base — the tnode + // reads below see only the N_TNAME leaf (esz 1-sentinel, + // MOVQ base). Re-key off the chased stamped tinfo, the + // pusharg twin of the cgslice fix (cstage pushargs N_SLICE + // reads bu = type_chase_named(base->type) uniformly). + let basealias: bool = false; + let bu60: *tinfo = nil; + if (base != nil) { if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + basealias = true; + bu60 = tichase(bt60); + };}; + };}; // esz from the type table for an N_IDENT base (#76; mirrors // the cgindex idiom) or an N_DOT array/slice-field base (#257: // scale by the field's element width, not esz=1 -> silently @@ -16556,26 +16570,34 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { } else { if (dotbu != nil && dotbu.sub != nil) { esz = dotbu.sub.size: i32; };};}; + if (bu60 != nil) { + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; // base address → push if (baselocal != nil) { let tn: *node = baselocal.tnode; + let isarr60: bool = false; if (tn != nil) { - if (tn.kind == nkind.N_TARRAY) { - emitline("\tLEAQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), AX\n"); - } else { - emitline("\tMOVQ\t"); - emitoff(baselocal.off: i64); - emitline("(BP), AX\n"); - }; + if (tn.kind == nkind.N_TARRAY) { isarr60 = true; }; + }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (bu60 != nil) { isarr60 = bu60.kind == tykind.TY_ARRAY; }; + if (isarr60) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), AX\n"); } else { emitline("\tMOVQ\t"); emitoff(baselocal.off: i64); emitline("(BP), AX\n"); }; } else { if (globaltn != nil) { - if (globaltn.kind == nkind.N_TARRAY) { + let gisarr60: bool = globaltn.kind == nkind.N_TARRAY; + // #60: alias-NAMED base — chased kind; runtime- + // unreachable until #77/#78 global DATA. + if (bu60 != nil) { gisarr60 = bu60.kind == tykind.TY_ARRAY; }; + if (gisarr60) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), AX\n"); @@ -16620,6 +16642,20 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { }; };};}; }; + // #60: alias-NAMED base default-hi — chased kind + // (see the cgslice twin). + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + };}; + }; } else { if (globaltn != nil) { if (globaltn.kind == nkind.N_TARRAY) { let lenn: *node = globaltn.rhs; @@ -16636,6 +16672,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { emitline("(SB), CX\n"); emitline("\tMOVQ\t8(CX), AX\n"); };}; + // #60: alias-NAMED global base default-hi — chased + // kind; runtime-unreachable until #77/#78 global DATA. + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), CX\n"); + emitline("\tMOVQ\t8(CX), AX\n"); + };}; + }; } else { emitline("\tMOVQ\t$0, AX\n"); };};}; @@ -17324,6 +17375,19 @@ fn elemisarrayc(c: *cgen, t: *node) bool = { return elem.kind == nkind.N_TARRAY; }; +// tichase — transitive TY_NAMED peel, nil-passthrough. Exact wwstage +// twin of cstage type_chase_named (cmd/wcc/type.c:160-162, alias arc +// #5): chain-of-aliases stacks TY_NAMED layers, so any single peel +// leaves a kind-gated consumer staring at TY_NAMED and falling to a +// scalar shape (#60's esz=1/pointer-base SEGV family). One chased +// accessor is the only spelled way to dealias; raw `.under` reads +// outside it are the lint target (rob F2 ruling). +fn tichase(t0: *tinfo) *tinfo = { + let t: *tinfo = t0; + for (t != nil && t.kind == tykind.TY_NAMED) { t = t.under; }; + return t; +}; + // tinfoisarray — TY_ARRAY (NAMED-aware), the tinfo-keyed companion to // elemisarrayc for cgindex's N_DOT/N_INDEX base branches, where the // element type comes from n.type_ (stamped tinfo) not a tnode. Same @@ -22633,6 +22697,13 @@ fn cgindex(c: *cgen, n: *node) void = { // class; sentinel 754). let elemisstr: bool = false; let elemisslice: bool = false; + // #60 (alias arc #5): the base's declared type is an N_IDENT alias + // (`type arr = [4]int`). The tnode walks below see only the N_TNAME + // leaf — esz fell to the 1-sentinel and the base classified as a + // POINTER (MOVQ + no IMULQ → SEGV / prefix-luck reads). Set once + // here, consumed by the elem-fact override, the etn fallback and + // the LEAQ-vs-MOVQ base classify. + let basealias: bool = false; let baselocal: *local = nil; // Global `[N]T` array or `*T` pointer used as an index base. // The local-ident lookup above misses it; we need LEAQ name(SB) @@ -22693,6 +22764,42 @@ fn cgindex(c: *cgen, n: *node) void = { }; }; }; + // #60: element facts off the chased checker-stamped + // tinfos — cstage reads them via type_chase_named/ + // idx_eff uniformly (cmd/w6c/cgen.c N_INDEX). + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(n.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + signed_elem = typeissigned(et60); + float_elem = typeisfloat(et60); + f32_elem = typeisf32(et60); + }; + // global base: array-vs-pointer re-keyed off the + // chased BASE kind (cstage isglobal && u->kind == + // TY_ARRAY → LEAQ). Runtime-unreachable until the + // alias-typed global DATA emit lands (#77/#78); + // kept so the read leg is already cs-aligned. + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + // Alias-typed ELEMENT under an ident base (`[2]row`, + // row = [3]int): elemisarrayc's node walk can't see + // through the element's N_TNAME — the chased stamped + // element tinfo is the authority (cstage gates on + // esubu = type_chase_named(esub) == TY_ARRAY). + if (!elem_isarray) { + elem_isarray = tinfoisarray(n.type_: *tinfo); + }; } else { if (base.kind == nkind.N_DOT || (base.kind == nkind.N_UN && base.op == tkind.TK_STAR)) { // `s.ptr[i]` / struct-field index: stride is the @@ -22746,6 +22853,11 @@ fn cgindex(c: *cgen, n: *node) void = { } else { etn = idxelemtn(letvartnode(c, base.str)); }; + // #60: an alias-NAMED base has no element tnode + // (idxelemtn sees the N_TNAME leaf, nil) — classify + // off the stamped index-result n.type_, the same + // source the N_DOT/N_INDEX arms read. + if (basealias) { etn = n; }; if (istaggedtype(c, etn)) { if (!isnullabletype(etn)) { elem_tagged = true; @@ -22852,6 +22964,12 @@ fn cgindex(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — LEAQ-vs-MOVQ off the chased stamped + // kind (cstage u->kind == TY_ARRAY, cmd/w6c/cgen.c N_INDEX). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -23022,6 +23140,31 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { return true; }; }; + // #60: alias-NAMED base — chased kind (cstage cg_base_cap + // receives bu pre-chased by the N_SLICE arm, cgen.c:1888). + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 16): i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + };}; return false; }; let gt: *node = letvartnode(c, base.str); @@ -23050,6 +23193,36 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { emitline("\n"); return true; }; + // #60: alias-NAMED global base — chased kind; global STR keeps + // the #73 cap=len carve-out (cstage isglobal && TY_STR → 0). + // Runtime-unreachable until #77/#78 global DATA. + let gbt60: *tinfo = base.type_: *tinfo; + if (gbt60 != nil) { if (gbt60.kind == tykind.TY_NAMED) { + let gbu60: *tinfo = tichase(gbt60); + if (gbu60 != nil) { + if (gbu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(gbu60.alen: i64); + emitline(", "); + emitline(dst); + emitline("\n"); + return true; + }; + if (gbu60.kind == tykind.TY_SLICE) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + emitline("\tMOVQ\t16("); + emitline(dst); + emitline("), "); + emitline(dst); + emitline("\n"); + return true; + }; + }; + };}; return false; }; @@ -23101,6 +23274,20 @@ fn cgslice(c: *cgen, n: *node) void = { if (base != nil) { if (base.kind == nkind.N_ARRLIT) { arrlittn = base.lhs; };}; + // #60 (alias arc #5): alias-NAMED N_IDENT base — the tnode reads + // below see only the N_TNAME leaf (esz 1-sentinel, MOVQ base, + // $0 default-hi, cap=len). All four reads re-key off the chased + // stamped tinfo, mirroring cstage N_SLICE's single + // bu = type_chase_named(base->type) source (cmd/w6c/cgen.c). + let basealias: bool = false; + let bu60: *tinfo = nil; + if (base != nil) { if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { if (bt60.kind == tykind.TY_NAMED) { + basealias = true; + bu60 = tichase(bt60); + };}; + };}; // esz from the type table for an N_IDENT base (#76; mirrors the // cgindex idiom) or an N_DOT array/slice-field base (#252: scale by // the field's element width, not esz=1 — silently wrong for non-u8). @@ -23115,6 +23302,10 @@ fn cgslice(c: *cgen, n: *node) void = { } else { if (arrlittn != nil) { esz = elemsizeofc(c, arrlittn); };};};}; + if (bu60 != nil) { + let es60: *tinfo = tichase(bu60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; // base address if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -23122,6 +23313,8 @@ fn cgslice(c: *cgen, n: *node) void = { if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -23135,7 +23328,11 @@ fn cgslice(c: *cgen, n: *node) void = { // Top-level let: [N]T → LEAQ name(SB); pointer/slice/str // → MOVQ name(SB) (the symbol holds the {ptr,len,cap} or // {ptr,len} or pointer value). - if (globaltn.kind == nkind.N_TARRAY) { + let gisarr: bool = globaltn.kind == nkind.N_TARRAY; + // #60: alias-NAMED base — chased kind; runtime-unreachable + // until #77/#78 global DATA (see cgindex twin). + if (bu60 != nil) { gisarr = bu60.kind == tykind.TY_ARRAY; }; + if (gisarr) { emitline("\tLEAQ\t"); emitsymname(c, globalname); emitline("(SB), AX\n"); @@ -23214,6 +23411,23 @@ fn cgslice(c: *cgen, n: *node) void = { }; };};}; }; + // #60: alias-NAMED base default-hi — chased kind (cstage + // N_SLICE hi-default reads bu uniformly: TY_ARRAY → $alen, + // TY_SLICE/TY_STR → len word at +8). + if (!handled && bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + handled = true; + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tMOVQ\t"); + emitoff((baselocal.off + 8): i64); + emitline("(BP), AX\n"); + handled = true; + };}; + }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { if (globaltn != nil) { let handled: bool = false; @@ -23234,6 +23448,24 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tMOVQ\t8(CX), AX\n"); handled = true; };}; + // #60: alias-NAMED global base default-hi — chased kind; + // runtime-unreachable until #77/#78 global DATA (cstage + // emits LEAQ+8 for global SLICE/STR alike). + if (!handled && bu60 != nil) { + if (bu60.kind == tykind.TY_ARRAY) { + emitline("\tMOVQ\t$"); + emitint(bu60.alen: i64); + emitline(", AX\n"); + handled = true; + } else { if (bu60.kind == tykind.TY_SLICE + || bu60.kind == tykind.TY_STR) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), CX\n"); + emitline("\tMOVQ\t8(CX), AX\n"); + handled = true; + };}; + }; if (!handled) { emitline("\tMOVQ\t$0, AX\n"); }; } else { if (dotbu != nil && dotbu.kind == tykind.TY_ARRAY) { // #252: default-hi `s.obuf[lo:]` on a struct array-field → @@ -28553,6 +28785,7 @@ fn cgassign(c: *cgen, n: *node) void = { let globalname: str; globalname.ptr = nil; globalname.len = 0; let elemtn: *node = nil; + let basealias: bool = false; if (base != nil) { if (base.kind == nkind.N_IDENT) { let bn: str = base.str; @@ -28626,6 +28859,37 @@ fn cgassign(c: *cgen, n: *node) void = { }; };};}; }; + // #60 (write spine): alias-NAMED N_IDENT base — the + // tnode walk above is blind (esz 1-sentinel, elemtn + // nil → pointer-treated base, wrong-stride store). + // Adopt the stamped-element idiom of the N_DOT/ + // N_INDEX arms (lhs.type_ IS the element tinfo); + // cstage N_INDEX store reads idx_eff(base->type)->sub + // uniformly (cmd/w6c/cgen.c:3517-3518). + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(lhs.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + elemtn = lhs; + }; + // see cgindex twin: cs-aligned, runtime- + // unreachable until #77/#78 global DATA. + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + }; + }; // Tagged-union element: materialize source in a shared // scratch slot via cgwidentaggedstore (handles struct / // str / scalar / subset / nullable variants uniformly), @@ -28671,6 +28935,12 @@ fn cgassign(c: *cgen, n: *node) void = { isarr = true; }; }; + // #60: alias-NAMED base — chased kind + // (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarr = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarr) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -28793,6 +29063,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn2: *node = baselocal.tnode; let isarr2: bool = false; if (tn2 != nil) { if (tn2.kind == nkind.N_TARRAY) { isarr2 = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarr2 = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarr2) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -28920,6 +29195,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -29061,6 +29341,31 @@ fn cgassign(c: *cgen, n: *node) void = { }; };};}; }; + // #60 (compound spine): alias-NAMED N_IDENT base — + // same stamped-element adoption as the store arm. + let basealias: bool = false; + if (base != nil) { + if (base.kind == nkind.N_IDENT) { + let bt60: *tinfo = base.type_: *tinfo; + if (bt60 != nil) { + if (bt60.kind == tykind.TY_NAMED) { basealias = true; }; + }; + if (basealias) { + let et60: *tinfo = tichase(lhs.type_: *tinfo); + if (et60 != nil) { + esz = et60.size: i32; + elemtn = lhs; + }; + if (isglobalarr || isglobalptr) { + let bu60: *tinfo = tichase(bt60); + if (bu60 != nil) { + isglobalarr = bu60.kind == tykind.TY_ARRAY; + isglobalptr = !isglobalarr; + }; + }; + }; + }; + }; // #133-expanded: hard-error unwired payload kinds // LOUD (rule-7) — replaces prior silent skip. if (elemtn != nil) { @@ -29107,6 +29412,11 @@ fn cgassign(c: *cgen, n: *node) void = { let tn: *node = baselocal.tnode; let isarray: bool = false; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isarray = true; }; }; + // #60: alias-NAMED base — chased kind (see cgindex twin). + if (basealias) { + let bu60: *tinfo = tichase(base.type_: *tinfo); + if (bu60 != nil) { isarray = bu60.kind == tykind.TY_ARRAY; }; + }; if (isarray) { emitline("\tLEAQ\t"); emitoff(baselocal.off: i64); @@ -33830,6 +34140,30 @@ fn cgexprstmt(c: *cgen, n: *node) void = { // #25); `rhs` the literal. Twin of cstage cg_arrlit_fill_bp. fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = { let elemn: *node = arrtn.lhs; + // #79 (#60 rider): alias-NAMED [count]T (`type A = [4]u32; let + // a: A = [...]`) — arrtn is the N_TNAME leaf: elemn nil, esz + // stayed the 8 sentinel and the per-element store strode MOVQ + // over a stride-4 slot (saved-BP/RIP smash; masked when esz==8). + // This is the STORE half of the #8 pair (the elemsizeofc READ + // half chases the ELEMENT via idxeffti; alias-typed INDEXABLES + // are chased at its call sites). Synthesise the element node off + // the chased stamped sub — the cgforrange FC0 precedent — so the + // prim/agg/slice/tagged/narrow dispatch below works unchanged; + // stash alen for the `...` repeat bound (cstage cg_arrlit_fill_bp + // receives the pre-chased bu and reads bu->alen). + let aliasalen: i32 = -1; + let ati79: *tinfo = arrtn.type_: *tinfo; + if (ati79 != nil) { if (ati79.kind == tykind.TY_NAMED) { + let au79: *tinfo = tichase(ati79); + if (au79 != nil) { if (au79.kind == tykind.TY_ARRAY + && au79.sub != nil) { + let en79: *node = newnode(nkind.N_TNAME, arrtn.file, arrtn.line, arrtn.col); + en79.str = au79.sub.name; + en79.type_ = au79.sub: *void; + elemn = en79; + aliasalen = au79.alen: i32; + };}; + };}; let esz: i32 = 8; let isstrel: bool = false; if (elemn != nil) { @@ -34024,6 +34358,9 @@ fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = { }; }; }; + // #79: alias arrtn has no length tnode — bound off the + // chased tinfo (see the synthesis block at fn top). + if (aliasalen >= 0) { total = aliasalen; }; for (idx < total) { if (isstrel || isslicel) { emitline("\tMOVQ\tAX, "); @@ -34933,6 +35270,27 @@ fn cglet(c: *cgen, n: *node) void = { if (n.lhs != nil) { if (n.lhs.kind == nkind.N_TARRAY) { isarr = true; }; }; + // #79 rider (#60 family): alias-NAMED bare let — the array + // classify must see through the N_TNAME leaf or an alias- + // to-array takes the composite zero-fill cstage doesn't + // emit (cstage keys on the chased lu->kind: arrays keep the + // per-index-write contract). An 8B alias-array stays + // isarr=false so it falls to the zsz==8 MOVQ $0 arm, + // matching cstage's 8B single-store shape. + if (!isarr && n.lhs != nil) { + if (n.lhs.kind == nkind.N_TNAME) { + let ati79: *tinfo = n.lhs.type_: *tinfo; + if (ati79 != nil) { if (ati79.kind == tykind.TY_NAMED) { + let au79: *tinfo = tichase(ati79); + if (au79 != nil) { + if (au79.kind == tykind.TY_ARRAY + && au79.size: i32 != 8) { + isarr = true; + }; + }; + };}; + }; + }; // Zero-fill extent. cstage sizes the run on `lu->size` // (the natural ABI size from the type table, cgen.c:8397); // wwstage's `sz` from letslotsize is slot-padded (round-to-8), @@ -35531,6 +35889,31 @@ fn cgforrange(c: *cgen, n: *node) void = { }; }; }; + // #60 (alias arc #5): alias-NAMED scrutinee (`let a: arr`, arr = + // [4]int) — the tnode peek above sees only the N_TNAME leaf: + // elemt nil, esz 1-sentinel, neither isarr nor isslicestr, so the + // per-iteration base walked the array words as a POINTER (SEGV). + // Chase the stamped tinfo (cstage N_FORRANGE u = type_chase_named + // (slc->type) feeds esz/alen/base classify uniformly) and + // synthesise the element node off .sub — the FC0 non-ident + // precedent below. + let rti60: *tinfo = nil; + if (slctn != nil) { + if (slctn.kind == nkind.N_TNAME) { + let st60: *tinfo = slctn.type_: *tinfo; + if (st60 != nil) { + if (st60.kind == tykind.TY_NAMED) { rti60 = tichase(st60); }; + }; + }; + }; + if (rti60 != nil && elemt == nil) { + if (rti60.sub != nil) { + let en60: *node = newnode(nkind.N_TNAME, slctn.file, slctn.line, slctn.col); + en60.str = rti60.sub.name; + en60.type_ = rti60.sub: *void; + elemt = en60; + }; + }; // esz: raw elem byte size. For tuple-element slices `[](T0, T1)`, // C cgen reads the resolved tuple's size (sum of raw param sizes, // no slot-padding) so e.g. `(i64, i64)` is 16, `(i32, i32)` is 8. @@ -35542,6 +35925,12 @@ fn cgforrange(c: *cgen, n: *node) void = { // cs≠ww). elemsizeofc recovers the width from the stamped tinfo // (the #8 named-narrow precedent). let esz: i32 = elemsizeofc(c, slctn); + // #60: alias-NAMED scrutinee — stride off the chased stamped + // element (cstage esz = u->sub->size). + if (rti60 != nil) { + let es60: *tinfo = tichase(rti60.sub); + if (es60 != nil) { esz = es60.size: i32; }; + }; if (elemt != nil) { if (elemt.kind == nkind.N_TTUPLE) { let total: i32 = 0; @@ -35712,6 +36101,13 @@ fn cgforrange(c: *cgen, n: *node) void = { if (streq(slctn.str, "str")) { isslicestr = true; }; }; }; + // #60: alias-NAMED scrutinee — classify off the chased stamped + // kind (cstage gates on u->kind TY_SLICE/TY_STR vs TY_ARRAY). + if (rti60 != nil) { + if (rti60.kind == tykind.TY_ARRAY) { isarr = true; }; + if (rti60.kind == tykind.TY_SLICE) { isslicestr = true; }; + if (rti60.kind == tykind.TY_STR) { isslicestr = true; }; + }; if (isslicestr) { if (slc.kind == nkind.N_IDENT) { if (slclocal != nil) { @@ -35728,6 +36124,9 @@ fn cgforrange(c: *cgen, n: *node) void = { if (slctn.rhs != nil) { if (slctn.rhs.kind == nkind.N_INTLIT) { alen = slctn.rhs.uval: i64; }; }; + // #60: alias-NAMED scrutinee has no length tnode — bound off + // the chased tinfo (cstage aimm(u->alen)). + if (rti60 != nil) { alen = rti60.alen: i64; }; emitline("\tMOVQ\t$"); emitint(alen); emitline(", "); diff --git a/test/wcc/944_alias_accept_run.c b/test/wcc/944_alias_accept_run.c index 1de3d89c..3f440adc 100644 --- a/test/wcc/944_alias_accept_run.c +++ b/test/wcc/944_alias_accept_run.c @@ -28,17 +28,15 @@ * idx/slice/range_ | index/slice/for-range over 2-level | * 2level, slice_arg | alias bases (F0 8b) — cs cgen index | * | family chased (idx_eff + base | - * | classify); CS-ONLY rows: the wwstage | - * | half is the task-#60 esz family (F2 | - * | batch 1) — flip to K_RUN when it | - * | lands | 0(cs) + * | classify); ww half landed (#60, F2a | + * | batch 1 tichase) — graduated K_RUN | 0 * 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 + * slice_alias_param | (slice leg graduated, #60 F2a b1) | 0 * slicefield_* / | reviewer-F1 leg: 2-level alias of | * strfield_store_2lvl | slice/str as a STRUCT FIELD type — | * | cgen field gates chased (stores, | @@ -424,11 +422,11 @@ static const struct row rows[] = { " return 0;\n" "};\n", 0, K_RUN, NULL }, /* whole-field read into a local: cs runtime-correct post-chase - * (the clobbered-register r1b probe), but the `let w = b.s` - * receive spine diverges cs vs ww at ONE level already (byte-id - * NO at pristine 738d7f4 = pre-existing, #71-style divergence, - * filed under #73's census) and ww's `w[i]` read is the task-#60 - * esz family — cs-only until both land. */ + * (the clobbered-register r1b probe). The `let w = b.s` receive + * spine divergence noted at pristine 738d7f4 no longer + * reproduces at the F1-merged base, and ww's `w[i]` read (the + * task-#60 esz half) landed in F2a batch 1 — byte-id + 0/0 + * verified, graduated K_RUN. */ { "slicefield_wholeread_2lvl", "package main;\n" "type sl = []int;\n" @@ -447,7 +445,7 @@ static const struct row rows[] = { " if (w.len != 3) { return 1; };\n" " if (w[2] != 900) { return 2; };\n" " return 0;\n" - "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + "};\n", 0, K_RUN, NULL }, /* graduated: #60 (F2a batch 1) */ /* #73 tripwire pin: the UNPROBED single-peel field gates * (indexed-elem store/read, ptr-chain read, heap fill, tuple-elem * read, static emit) hard-error on a 2+-level alias over an @@ -479,7 +477,7 @@ static const struct row rows[] = { " a[1] = 9999;\n" " if (a[1] != 9999) { return 2; };\n" " return 0;\n" - "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + "};\n", 0, K_RUN, NULL }, /* graduated: #60 (F2a batch 1) */ { "slice_2level", "package main;\n" "type sl = []int;\n" @@ -492,7 +490,7 @@ static const struct row rows[] = { " s[1] = 7777;\n" " if (a[2] != 7777) { return 3; };\n" " return 0;\n" - "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + "};\n", 0, K_RUN, NULL }, /* graduated: #60 (F2a batch 1) */ { "range_2level", "package main;\n" "type arr = [4]int;\n" @@ -505,7 +503,7 @@ static const struct row rows[] = { " };\n" " if (sum != 10) { return 1; };\n" " return 0;\n" - "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + "};\n", 0, K_RUN, NULL }, /* graduated: #60 (F2a batch 1) */ { "slice_of_alias_arg", "package main;\n" "type arr = [4]int;\n" @@ -522,7 +520,7 @@ static const struct row rows[] = { " if (s[1] != 3000) { return 2; };\n" " if (sum(a[0:4]) != 10000) { return 3; };\n" " return 0;\n" - "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + "};\n", 0, K_RUN, NULL }, /* graduated: #60 (F2a batch 1) */ /* ---- ken-v3 leg: the acceptance align above opens 2-level * float/str/slice aliases to the cgen KIND classifiers * (cg_isfloat/type_isf32/fld_isfloat/type_isstr/type_isslice), @@ -570,7 +568,7 @@ static const struct row rows[] = { " return 0;\n" "};\n", 0, K_RUN, NULL }, /* slice leg: cs classify fixed; the ww half of the indexed read - * is the task-#60 esz family — cs-only until F2. Values >255 + * (task-#60 esz family) landed in F2a batch 1. Values >255 * break the esz=1 prefix-luck. */ { "slice_alias_param", "package main;\n" @@ -584,7 +582,7 @@ static const struct row rows[] = { " let s: b2 = a[0:3];\n" " if (first(s) != 1000) { return 1; };\n" " return 0;\n" - "};\n", 0, K_RUN_CS, NULL }, /* ww: task #60 (F2 batch 1) */ + "};\n", 0, K_RUN, NULL }, /* graduated: #60 (F2a 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/ diff --git a/test/wcc/944_alias_idx_family_run.c b/test/wcc/944_alias_idx_family_run.c new file mode 100644 index 00000000..cfb76c61 --- /dev/null +++ b/test/wcc/944_alias_idx_family_run.c @@ -0,0 +1,475 @@ +/* + * 944_alias_idx_family_run — #5 alias arc F2a batch 1 (task #60 + + * the #79 init-store rider): wwstage INDEX / SLICE / FOR-RANGE / + * LITERAL-INIT over an alias-NAMED base type. The tnode-keyed cgen + * walks saw only the N_TNAME leaf — esz fell to the 1-sentinel (8 on + * the init-store side) and the base classified as a POINTER (MOVQ of + * array words + no IMULQ): m8b_idx1/range1 SEGV 139, m8b_slice1 + * silent-wrong past prefix-luck (m8c_slice1big exit 2), alias-[4]u32 + * literal-init MOVQ stride-8 over a stride-4 slot = saved-BP/RIP + * smash (#79, masked when esz==8). cstage reads everything off the + * chased stamped type (type_chase_named/idx_eff, post-F1), so it is + * the runtime-correct reference; the fix re-keys the ww sites off + * tichase'd stamped tinfos (cgindex/cgassign-N_INDEX/cgslice/ + * cgbasecap in cgenexpr.ww, cgforrange + cgarrlitfillbp + bare-let + * classify in cgenstmt.ww, pusharg N_SLICE in cgenutil.ww) and every + * row graduates to 0/0 byte-id. + * + * row | shape | want + * ------------------+------------------------------------------+----- + * idx0_ctl | named [4]int base read/store (control) | 0 + * idx1_alias | m8b_idx1: r/w via 1-level alias arr | 0 + * idx2_2level | write+read via arr2 = arr (944 pin) | 0 + * idx_fwdref | decl-order permuted chain + compound += | 0 + * init_u32_alias | #79: type A=[4]u32 literal-init, LAST | + * | elem readback (pre-fix BP/RIP smash) | 0 + * initrep_u32_alias | #79: alias `[v...]` repeat fill — bound | + * | off the chased alen, LAST elem | 0 + * loopfill_1024 | uninit alias [1024]int + loop-fill, LAST | + * | elem (pins the bare-let array classify: | + * | no composite zero-fill, cs-identical) | 0 + * slice0_ctl | named base slice (control) | 0 + * slice1_alias | m8b_slice1 + 1-level alias ARRAY base: | + * | sl dest + store + readback | 0 + * slice2_2level | sl2 = sl, len/read/store + RE-SLICE of | + * | the alias slice | 0 + * slice1big | m8c: 1000 elems, values > 255, LAST-elem | + * | readback + default-hi + .cap + range cnt | 0 + * slice_fwdref | decl-after-use chain over 2-level alias | + * | array base, s[1] *= 3 compound | 0 + * range0_ctl | named [4]int range (control) | 0 + * range1_alias | m8b_range1: sum over alias arr | 0 + * range2_2level | sum over arr2 = arr | 0 + * range_slicealias | for-range over an alias SLICE | 0 + * m7c_global2d | global [2]row (row = [3]int): g[1][2] | 0 + * gslice_alias | GLOBAL alias-slice: .len + indexed read | 0 + * slicearg_alias | slice-of-alias as CALL ARG (pusharg leg) | 0 + * + * Values exceed 255 so an esz=1 byte-load cannot pass by little-endian + * prefix-luck; readbacks assert the LAST element. Every row runs BOTH + * drivers and asserts cstage/wwstage asm byte-identity (alias rows are + * graduations: ww aligned UP to the cs runtime-correct side). NNN<950, + * self-contained /tmp sources, no imports (944 precedent). + * + * Deliberately NOT pinned (g-fold territory, tasks #77/#78): any + * DIRECT alias-typed global ARRAY row. Expected state at this commit, + * probe-verified UNCHANGED by the fix: `let g: arr = [...]` — ww + * link-ERR (no DATA emitted for the alias-typed global), cs 1-level + * runs 0, cs 2-level runs WRONG (silent). + */ +#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; +} + +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), cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + return rc; +} + +struct row { const char *label; const char *src; int want; }; + +static const struct row rows[] = { + { "idx0_ctl", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " if (a[3] != 4000) { return 1; };\n" + " a[3] = 9999;\n" + " if (a[3] != 9999) { return 2; };\n" + " return 0;\n" + "};\n", 0 }, + { "idx1_alias", + "package main;\n" + "type arr = [4]int;\n" + "export fn main() i32 = {\n" + " let a: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " if (a[2] != 3000) { return 1; };\n" + " if (a[3] != 4000) { return 2; };\n" + " a[3] = 8888;\n" + " if (a[3] != 8888) { return 3; };\n" + " return 0;\n" + "};\n", 0 }, + { "idx2_2level", + "package main;\n" + "type arr = [4]int;\n" + "type arr2 = arr;\n" + "export fn main() i32 = {\n" + " let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " if (a[2] != 3000) { return 1; };\n" + " a[1] = 9999;\n" + " if (a[1] != 9999) { return 2; };\n" + " if (a[3] != 4000) { return 3; };\n" + " return 0;\n" + "};\n", 0 }, + { "idx_fwdref", + "package main;\n" + "type arr2 = arr;\n" + "type arr = [4]int;\n" + "export fn main() i32 = {\n" + " let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " if (a[3] != 4000) { return 1; };\n" + " a[3] += 500;\n" + " if (a[3] != 4500) { return 2; };\n" + " return 0;\n" + "};\n", 0 }, + /* #79: narrow-elem alias literal-init. Pre-fix the per-element + * store rode the esz=8 sentinel + MOVQ over a stride-4 slot — + * elements 2/3 landed at 0(BP)/+8(BP) = saved-BP/RIP smash + * (masked when esz==8). LAST element read back. */ + { "init_u32_alias", + "package main;\n" + "type A = [4]u32;\n" + "export fn main() i32 = {\n" + " let a: A = [1: u32, 2: u32, 3: u32, 4: u32];\n" + " if (a[3] != 4) { return 1; };\n" + " if (a[0] != 1) { return 2; };\n" + " return 0;\n" + "};\n", 0 }, + /* #79 repeat leg: the `[v...]` fill bound comes off the chased + * tinfo's alen (an alias arrtn has no length tnode). */ + { "initrep_u32_alias", + "package main;\n" + "type A = [6]u32;\n" + "export fn main() i32 = {\n" + " let a: A = [7: u32...];\n" + " if (a[5] != 7) { return 1; };\n" + " if (a[0] != 7) { return 2; };\n" + " return 0;\n" + "};\n", 0 }, + /* Uninit alias array + per-index loop fill. Also pins the + * bare-let classify: an alias-to-array keeps the per-index-write + * contract (no composite zero-fill — cstage-identical asm). */ + { "loopfill_1024", + "package main;\n" + "type arrk = [1024]int;\n" + "export fn main() i32 = {\n" + " let a: arrk;\n" + " let i: int = 0;\n" + " for (i < 1024) {\n" + " a[i] = i * 3 + 1000;\n" + " i += 1;\n" + " };\n" + " if (a[1023] != 1023 * 3 + 1000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + { "slice0_ctl", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let s: []int = a[1:3];\n" + " if (s.len != 2) { return 1; };\n" + " if (s[1] != 3000) { return 2; };\n" + " return 0;\n" + "};\n", 0 }, + /* alias slice DEST over a 1-level alias ARRAY base (slice-expr + * over alias base, 1-level leg). */ + { "slice1_alias", + "package main;\n" + "type arr = [4]int;\n" + "type sl = []int;\n" + "export fn main() i32 = {\n" + " let a: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let s: sl = a[1:3];\n" + " if (s.len != 2) { return 1; };\n" + " if (s[0] != 2000) { return 2; };\n" + " s[1] = 7777;\n" + " if (a[2] != 7777) { return 3; };\n" + " return 0;\n" + "};\n", 0 }, + /* 2-level alias slice dest + RE-SLICE of the alias slice (the + * cgslice alias-SLICE-base leg). */ + { "slice2_2level", + "package main;\n" + "type sl = []int;\n" + "type sl2 = sl;\n" + "export fn main() i32 = {\n" + " let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let s: sl2 = a[1:3];\n" + " if (s.len != 2) { return 1; };\n" + " if (s[0] != 2000) { return 2; };\n" + " s[1] = 7777;\n" + " if (a[2] != 7777) { return 3; };\n" + " if (s[1] != 7777) { return 4; };\n" + " let r: sl = s[0:2];\n" + " if (r.len != 2) { return 5; };\n" + " if (r[1] != 7777) { return 6; };\n" + " return 0;\n" + "};\n", 0 }, + /* 1000 elements, values 300..7293: an esz=1 stride/byte load + * cannot land on the right word, and the LAST-element readback + * breaks prefix-luck (m8c). Also pins alias default-hi (a[2:]), + * .cap via the chased cgbasecap leg, and range over the + * 2-level-alias sub-slice. Base array stays plain [1000]int so + * this row pins the slice legs in isolation — the alias bare-let + * + loop-fill shape is loopfill_1024's pin (the #79-rider + * bare-let array classify). */ + { "slice1big", + "package main;\n" + "type slk = []int;\n" + "type slk2 = slk;\n" + "export fn main() i32 = {\n" + " let a: [1000]int;\n" + " let i: int = 0;\n" + " for (i < 1000) {\n" + " a[i] = i * 7 + 300;\n" + " i += 1;\n" + " };\n" + " let s: slk = a[1:1000];\n" + " if (s.len != 999) { return 1; };\n" + " if (s[998] != 999 * 7 + 300) { return 2; };\n" + " s[998] = 123456;\n" + " if (a[999] != 123456) { return 3; };\n" + " let t: slk2 = a[2:];\n" + " if (t.len != 998) { return 4; };\n" + " if (t.cap != 998) { return 5; };\n" + " if (t[997] != 123456) { return 6; };\n" + " let cnt: int = 0;\n" + " for (let x .. t) { cnt = cnt + 1; if (x < 0) { return 8; }; };\n" + " if (cnt != 998) { return 7; };\n" + " return 0;\n" + "};\n", 0 }, + { "slice_fwdref", + "package main;\n" + "export fn main() i32 = {\n" + " let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let s: sl2 = a[1:3];\n" + " if (s[1] != 3000) { return 1; };\n" + " s[1] *= 3;\n" + " if (a[2] != 9000) { return 2; };\n" + " return 0;\n" + "};\n" + "type sl2 = sl;\n" + "type sl = []int;\n" + "type arr2 = arr;\n" + "type arr = [4]int;\n", 0 }, + { "range0_ctl", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [4]int = [1: int, 2: int, 3: int, 4: int];\n" + " let sum: int = 0;\n" + " for (let x .. a) { sum = sum + x; };\n" + " if (sum != 10) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + { "range1_alias", + "package main;\n" + "type arr = [4]int;\n" + "export fn main() i32 = {\n" + " let a: arr = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let sum: int = 0;\n" + " for (let x .. a) { sum = sum + x; };\n" + " if (sum != 10000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + { "range2_2level", + "package main;\n" + "type arr = [4]int;\n" + "type arr2 = arr;\n" + "export fn main() i32 = {\n" + " let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let sum: int = 0;\n" + " for (let x .. a) { sum = sum + x; };\n" + " if (sum != 10000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + { "range_slicealias", + "package main;\n" + "type sl = []int;\n" + "export fn main() i32 = {\n" + " let a: [4]int = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let t: sl = a[0:4];\n" + " let sum: int = 0;\n" + " for (let x .. t) { sum = sum + x; };\n" + " if (sum != 10000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + /* m7c: the BASE is a plain [2]row global — the alias sits on the + * ELEMENT (row = [3]int). elemisarrayc's node walk can't see + * through the element's N_TNAME, so g[0] loaded a VALUE where the + * sub-array ADDRESS was needed (ww SEGV on the read; DATA emit + * was already correct both stages, F0). LAST element checked. */ + { "m7c_global2d", + "package main;\n" + "type row = [3]int;\n" + "let g: [2]row = [[10: int, 20: int, 30: int], [40: int, 50: int, 60: int]];\n" + "export fn main() i32 = {\n" + " if (g[0][0] != 10) { return 1; };\n" + " if (g[1][2] != 60) { return 2; };\n" + " return 0;\n" + "};\n", 0 }, + /* GLOBAL alias-typed SLICE: DATA emit + .len + indexed read all + * resolve (the global ARRAY twin does NOT — see the header note; + * g-fold #77/#78). */ + { "gslice_alias", + "package main;\n" + "type sl = []int;\n" + "let g: sl = [1000: int, 2000: int, 3000: int];\n" + "export fn main() i32 = {\n" + " if (g.len != 3) { return 1; };\n" + " if (g[2] != 3000) { return 2; };\n" + " return 0;\n" + "};\n", 0 }, + /* slice-of-alias-array as a CALL ARG exercises the pusharg + * N_SLICE leg (cgenutil.ww), a distinct lowering from cgslice; + * plus the inferred-let receive of an alias-base slice. */ + { "slicearg_alias", + "package main;\n" + "type arr = [4]int;\n" + "type arr2 = arr;\n" + "fn sum(s: []int) int = {\n" + " let t: int = 0;\n" + " for (let x .. s) { t = t + x; };\n" + " return t;\n" + "};\n" + "export fn main() i32 = {\n" + " let a: arr2 = [1000: int, 2000: int, 3000: int, 4000: int];\n" + " let s = a[1:3];\n" + " if (s.len != 2) { return 1; };\n" + " if (s[1] != 3000) { return 2; };\n" + " if (sum(a[0:4]) != 10000) { return 3; };\n" + " return 0;\n" + "};\n", 0 }, +}; + +static int +run_driver(const char *driver, const struct row *r, int i) +{ + char src[96], tmpdir[96], errf[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/aif_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/aif_%d_d_%d", getpid(), i); + snprintf(errf, sizeof errf, "/tmp/aif_%d_e_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, + "cd %s && timeout 20 %s build %s >/dev/null 2>%s", + tmpdir, driver, src, errf); + int brc = runwait(cmd); + if (brc != 0) { + fprintf(stderr, "row[%s]: build via %s failed\n", + r->label, driver); + unlink(src); unlink(errf); rmdir(tmpdir); + return -1; + } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[256]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + int got = runwait(outbin); + + unlink(src); unlink(outbin); unlink(errf); rmdir(tmpdir); + if (got != r->want) { + fprintf(stderr, "row[%s]: %s exit %d, want %d\n", + r->label, driver, got, r->want); + return 1; + } + return 0; +} + +static int +asm_byte_identical(const char *bin, const struct row *r, int i) +{ + char src[96], cs[96], ws[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/aif_asm_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/aif_asm_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/aif_asm_%d_%d_w.s", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c errored\n", r->label); + unlink(src); + return -1; + } + snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", + bin, ws, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); + unlink(src); unlink(cs); + return -1; + } + int rc = slurp_eq(cs, ws); + if (rc != 0) + fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", + r->label); + unlink(src); unlink(cs); unlink(ws); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[2080]; + 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 cdrv[2120], wdrv[2120]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + + for (int i = 0; i < n; i++) { + total++; + if (run_driver(cdrv, &rows[i], i) != 0) fail++; + } + if (access(wdrv, X_OK) == 0) { + for (int i = 0; i < n; i++) { + total++; + if (run_driver(wdrv, &rows[i], i) != 0) fail++; + } + for (int i = 0; i < n; i++) { + total++; + if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; + } + } + + if (fail) { + fprintf(stderr, "alias_idx_family: %d/%d checks failed\n", + fail, total); + return 1; + } + printf("alias_idx_family: %d/%d ok\n", total, total); + return 0; +}