wcc_ww/cgen: #60+#79 alias-NAMED array/slice ELEMENT paths read the chased tinfo — tichase lands, SEGV families graduate byte-id

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).
This commit is contained in:
2026-06-05 19:38:54 +09:00
parent ac63951d54
commit da81a4c86e
8 changed files with 1727 additions and 50 deletions

View File

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