diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 48c00cdc..f26c8cc5 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -6977,6 +6977,18 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX)); } ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); + /* #156 (PREREQ-1 read-half): element is itself an array + * ([N][M]T → element [M]T). This index yields the sub- + * array's ADDRESS, not a loaded value — the outer index + * adds its own offset and only the final scalar element + * dereferences. Sister of #135 (N_DOT-base-on-[N]T-field + * needs ADDRESS). BX holds base+idx*esz; move it to AX (the + * value-result reg). Gated on TY_ARRAY element so 1D arrays + * are byte-identical (no 2D consumer pre-#156). */ + if (esubu && esubu->kind == TY_ARRAY) { + ins2(c, A_MOVQ, areg(D_BX), areg(D_AX)); + break; + } /* str/slice element: load the full (ptr, len, cap) header * into (AX, BX, CX) — both are 24B since #1, so the cap * word must survive. Kind-gate on type_isstr||type_isslice, @@ -7045,6 +7057,11 @@ cgexpr(Cg *c, Node *n, Local *locals) cgexpr(c, n->lhs, locals); ins1(c, A_POPQ, areg(D_BX)); ins2(c, A_ADDQ, areg(D_BX), areg(D_AX)); + /* #156 (PREREQ-1 read-half): array element → AX already holds + * &elem (base+idx*esz); a nested index adds its offset and + * dereferences. See the N_IDENT arm above. */ + if (esubu && esubu->kind == TY_ARRAY) + break; /* str/slice element via fallback base: load the full (ptr, len, * cap) header into (AX, BX, CX). Kind-gate on type_isstr|| * type_isslice, never size==24 (see Site A). Base is AX. */ @@ -8914,6 +8931,46 @@ emit_array_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, int emit_phase) return 1; } + /* #129 A.3 capstone (PREREQ-1, #156): nested-array element [M]T + * inside [N][M]T. Mirror of the TY_STRUCT-element arm above and of + * the TY_ARRAY-field-in-struct arm in emit_struct_lit_bytes — recurse + * into emit_array_lit_bytes per element; recursion bottoms out at + * scalar (int/float) elements. esz = etype->size gives the per- + * element stride (rule 13, no manual stride math). The `...` repeat + * marker with nested-array elements is rejected loud (rule 7): no + * consumer needs it (powers_of_ten is fully enumerated) and the + * scalar-repeat byte-fill cannot reduce a nested N_ARRLIT. */ + if (eu && eu->kind == TY_ARRAY) { + int idx = 0; + for (Node *e = rhs->list; e && idx < alen; e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) + fatal("emit_array_lit_bytes: '...' repeat with " + "nested-array elements unsupported " + "(#129 A.3, rule 7)"); + Node *ev = e; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + if (ev == NULL || ev->kind != N_ARRLIT) return 0; + if (!emit_array_lit_bytes(out, c, etype, ev, 0)) + return 0; + idx++; + } + if (!emit_phase) return 1; + idx = 0; + for (Node *e = rhs->list; e && idx < alen; e = e->next) { + Node *ev = e; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + emit_array_lit_bytes(out, c, etype, ev, 1); + idx++; + } + while (idx < alen) { + for (int b = 0; b < esz; b++) + emit_data_byte(out, 0); + idx++; + } + return 1; + } + if (type_isfloat(etype)) { int isf32 = type_isf32(etype); /* Validate: every element must be N_FLOATLIT (after N_CAST diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 686ad188..7f0d1e21 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -12221,6 +12221,43 @@ fn elemisf32c(c: *cgen, t: *node) bool = { return typeisf32(ti.sub); }; +// elemisarrayc — given an indexable type-AST (`*T` / `[]T` / `[N]T`), is +// its element itself an array (`[N][M]T` → element `[M]T`)? cgindex then +// leaves the sub-array's ADDRESS in the result reg rather than +// dereferencing — a nested index adds its offset and only the final +// scalar element dereferences (#156, sister of #135 N_DOT-base-on- +// array-field). Node-based with the `*[N]T` drill-through, mirroring +// elemsizeof (:920-948) so elem-is-array aligns with the esz this same +// tnode feeds. cstage twin: idx_eff(bt)->sub unwrapped == TY_ARRAY +// (cmd/w6c/cgen.c). `c` kept for signature symmetry with elemisfloatc. +fn elemisarrayc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + let elem: *node = nil; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + if (k == nkind.N_TPTR) { + if (elem.kind == nkind.N_TARRAY) { + if (elem.lhs != nil) { elem = elem.lhs; }; + }; + }; + return elem.kind == nkind.N_TARRAY; +}; + +// 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 +// role as typeisslice/typeisstr in lib/ww/typ.ww; kept cgen-local to +// avoid widening the frontend surface for one #156 read-half check. +fn tinfoisarray(t: *tinfo) bool = { + let u: *tinfo = t; + for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (u == nil) { return false; }; + return u.kind == tykind.TY_ARRAY; +}; + // fieldissignedc — does this field/element type-AST need sign- // extension on a sub-word load? One-liner via typeissigned (cstage // cgen.c:240 `fld_issigned` SSoT). t.type_ is stamped at check.ww @@ -12345,6 +12382,12 @@ fn elemsizeof(t: *node) i32 = { if (elem == nil) { return 1; }; // `*[N]T`: drill through the pointer into the array's element so // indexing scales by T's width, not the whole-array byte size. + // FOOTGUN (#156): this drill ALSO fires for a bare 2D `[N][M]T` + // (elem = the inner `[M]T`), so elemsizeof of a 2D array bottoms + // out at the SCALAR T size, NOT the `[M]T` sub-array stride. 2D + // double-index (cgindex) needs the sub-array stride — call + // elemsizeofc, the 2D-correct entry, which recovers slotsize([M]T) + // when elemsizeof returns 8. Never call elemsizeof for a 2D stride. if (elem.kind == nkind.N_TARRAY) { if (elem.lhs != nil) { elem = elem.lhs; }; }; @@ -15553,6 +15596,14 @@ fn cgindex(c: *cgen, n: *node) void = { // from the SAME tinfo esz reads — never a fresh node-stamp (#121). let float_elem: bool = false; let f32_elem: bool = false; + // #156 (PREREQ-1 read-half): element is itself an array ([N][M]T → + // element [M]T) → leave the sub-array's ADDRESS in the result reg + // instead of dereferencing; the outer index adds its offset and the + // final scalar element dereferences. Sister of #135. Mirrors cstage + // esubu->kind == TY_ARRAY. Node-based (elemisarrayc) for ident bases, + // n.type_ tinfo-based for N_DOT/N_INDEX bases — same source split as + // esz above. + let elem_isarray: bool = false; // #1/Phase 3: str and slice are both 24B (and a >16B struct is // 24B+ too), so the header branches below MUST gate on KIND // (elemisstr/elemisslice, mirroring cstage's elem_is_str|| @@ -15580,6 +15631,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, baselocal.tnode); float_elem = elemisfloatc(c, baselocal.tnode); f32_elem = elemisf32c(c, baselocal.tnode); + elem_isarray = elemisarrayc(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); // #129 A.3: array-typed defs now have DATA storage; @@ -15596,6 +15648,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, tn); float_elem = elemisfloatc(c, tn); f32_elem = elemisf32c(c, tn); + elem_isarray = elemisarrayc(c, tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; @@ -15604,6 +15657,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, tn); float_elem = elemisfloatc(c, tn); f32_elem = elemisf32c(c, tn); + elem_isarray = elemisarrayc(c, tn); }; }; }; @@ -15616,6 +15670,7 @@ fn cgindex(c: *cgen, n: *node) void = { // stays unset, as before. let dt: *tinfo = n.type_: *tinfo; if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); }; + elem_isarray = tinfoisarray(dt); } else { if (base.kind == nkind.N_INDEX) { // #60: chained `names[i][k]` — n.type_ is the checker- // stamped outer element tinfo (indexresult over the inner @@ -15628,6 +15683,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = typeissigned(et); float_elem = typeisfloat(et); f32_elem = typeisf32(et); + elem_isarray = tinfoisarray(et); }; };};}; }; @@ -15688,6 +15744,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("(SB), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); + // #156: array element ([N][M]T) → leave the sub-array ADDRESS + // in AX (BX holds base+idx*esz); nested index dereferences. + if (elem_isarray) { + emitline("\tMOVQ\tBX, AX\n"); + return; + }; if (elem_tagged) { if (elem_slot_sz > 24) { emitline("\tMOVQ\t24(BX), R8\n"); @@ -15741,6 +15803,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); + // #156: array element ([N][M]T) → leave the sub-array ADDRESS + // in AX (BX holds base+idx*esz); nested index dereferences. + if (elem_isarray) { + emitline("\tMOVQ\tBX, AX\n"); + return; + }; if (elem_tagged) { if (elem_slot_sz > 24) { emitline("\tMOVQ\t24(BX), R8\n"); @@ -15785,6 +15853,11 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tPOPQ\tBX\n"); emitline("\tADDQ\tBX, AX\n"); + // #156: array element ([N][M]T) → AX already holds &elem + // (base+idx*esz); a nested index dereferences. See ident arms. + if (elem_isarray) { + return; + }; if (elem_tagged) { // AX holds the element address. Copy to BX (loading slot+0 // into AX clobbers it), then read slot words. @@ -25498,6 +25571,51 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, return true; }; + // #129 A.3 capstone (PREREQ-1, #156): nested-array element [M]T + // inside [N][M]T. Mirror of the TY_STRUCT-element arm above and of + // the TY_ARRAY-field-in-struct arm in emitstructlitbytes — recurse + // into emitarraylitbytes per element; recursion bottoms out at + // scalar (int/float) elements. esz = au.sub.size gives the per- + // element stride (rule 13). The `...` repeat marker with nested- + // array elements is rejected loud (rule 7): no consumer needs it + // (powers_of_ten is fully enumerated). + if (eu != nil && eu.kind == tykind.TY_ARRAY) { + let idx: i32 = 0; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + let m: str = "emitarraylitbytes: '...' repeat with nested-array elements unsupported (#129 A.3, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_ARRLIT) { return false; }; + if (!emitarraylitbytes(c, au.sub, ev, 0)) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + e = rhs.list; + for (e != nil && idx < alen) { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + emitarraylitbytes(c, au.sub, ev, 1); + idx += 1; + e = e.next; + }; + for (idx < alen) { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + idx += 1; + }; + return true; + }; + if (typeisfloat(au.sub)) { let isf32: bool = typeisf32(au.sub); // Validate. diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index ec053dee..c79ad340 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -1567,6 +1567,51 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, return true; }; + // #129 A.3 capstone (PREREQ-1, #156): nested-array element [M]T + // inside [N][M]T. Mirror of the TY_STRUCT-element arm above and of + // the TY_ARRAY-field-in-struct arm in emitstructlitbytes — recurse + // into emitarraylitbytes per element; recursion bottoms out at + // scalar (int/float) elements. esz = au.sub.size gives the per- + // element stride (rule 13). The `...` repeat marker with nested- + // array elements is rejected loud (rule 7): no consumer needs it + // (powers_of_ten is fully enumerated). + if (eu != nil && eu.kind == tykind.TY_ARRAY) { + let idx: i32 = 0; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + let m: str = "emitarraylitbytes: '...' repeat with nested-array elements unsupported (#129 A.3, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_ARRLIT) { return false; }; + if (!emitarraylitbytes(c, au.sub, ev, 0)) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + e = rhs.list; + for (e != nil && idx < alen) { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + emitarraylitbytes(c, au.sub, ev, 1); + idx += 1; + e = e.next; + }; + for (idx < alen) { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + idx += 1; + }; + return true; + }; + if (typeisfloat(au.sub)) { let isf32: bool = typeisf32(au.sub); // Validate. diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 0cd3aee9..9c827d29 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -867,6 +867,14 @@ fn cgindex(c: *cgen, n: *node) void = { // from the SAME tinfo esz reads — never a fresh node-stamp (#121). let float_elem: bool = false; let f32_elem: bool = false; + // #156 (PREREQ-1 read-half): element is itself an array ([N][M]T → + // element [M]T) → leave the sub-array's ADDRESS in the result reg + // instead of dereferencing; the outer index adds its offset and the + // final scalar element dereferences. Sister of #135. Mirrors cstage + // esubu->kind == TY_ARRAY. Node-based (elemisarrayc) for ident bases, + // n.type_ tinfo-based for N_DOT/N_INDEX bases — same source split as + // esz above. + let elem_isarray: bool = false; // #1/Phase 3: str and slice are both 24B (and a >16B struct is // 24B+ too), so the header branches below MUST gate on KIND // (elemisstr/elemisslice, mirroring cstage's elem_is_str|| @@ -894,6 +902,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, baselocal.tnode); float_elem = elemisfloatc(c, baselocal.tnode); f32_elem = elemisf32c(c, baselocal.tnode); + elem_isarray = elemisarrayc(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); // #129 A.3: array-typed defs now have DATA storage; @@ -910,6 +919,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, tn); float_elem = elemisfloatc(c, tn); f32_elem = elemisf32c(c, tn); + elem_isarray = elemisarrayc(c, tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; @@ -918,6 +928,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, tn); float_elem = elemisfloatc(c, tn); f32_elem = elemisf32c(c, tn); + elem_isarray = elemisarrayc(c, tn); }; }; }; @@ -930,6 +941,7 @@ fn cgindex(c: *cgen, n: *node) void = { // stays unset, as before. let dt: *tinfo = n.type_: *tinfo; if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); }; + elem_isarray = tinfoisarray(dt); } else { if (base.kind == nkind.N_INDEX) { // #60: chained `names[i][k]` — n.type_ is the checker- // stamped outer element tinfo (indexresult over the inner @@ -942,6 +954,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = typeissigned(et); float_elem = typeisfloat(et); f32_elem = typeisf32(et); + elem_isarray = tinfoisarray(et); }; };};}; }; @@ -1002,6 +1015,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("(SB), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); + // #156: array element ([N][M]T) → leave the sub-array ADDRESS + // in AX (BX holds base+idx*esz); nested index dereferences. + if (elem_isarray) { + emitline("\tMOVQ\tBX, AX\n"); + return; + }; if (elem_tagged) { if (elem_slot_sz > 24) { emitline("\tMOVQ\t24(BX), R8\n"); @@ -1055,6 +1074,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); + // #156: array element ([N][M]T) → leave the sub-array ADDRESS + // in AX (BX holds base+idx*esz); nested index dereferences. + if (elem_isarray) { + emitline("\tMOVQ\tBX, AX\n"); + return; + }; if (elem_tagged) { if (elem_slot_sz > 24) { emitline("\tMOVQ\t24(BX), R8\n"); @@ -1099,6 +1124,11 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tPOPQ\tBX\n"); emitline("\tADDQ\tBX, AX\n"); + // #156: array element ([N][M]T) → AX already holds &elem + // (base+idx*esz); a nested index dereferences. See ident arms. + if (elem_isarray) { + return; + }; if (elem_tagged) { // AX holds the element address. Copy to BX (loading slot+0 // into AX clobbers it), then read slot words. diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index bfeb1cfb..07fcd8d9 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -819,6 +819,43 @@ fn elemisf32c(c: *cgen, t: *node) bool = { return typeisf32(ti.sub); }; +// elemisarrayc — given an indexable type-AST (`*T` / `[]T` / `[N]T`), is +// its element itself an array (`[N][M]T` → element `[M]T`)? cgindex then +// leaves the sub-array's ADDRESS in the result reg rather than +// dereferencing — a nested index adds its offset and only the final +// scalar element dereferences (#156, sister of #135 N_DOT-base-on- +// array-field). Node-based with the `*[N]T` drill-through, mirroring +// elemsizeof (:920-948) so elem-is-array aligns with the esz this same +// tnode feeds. cstage twin: idx_eff(bt)->sub unwrapped == TY_ARRAY +// (cmd/w6c/cgen.c). `c` kept for signature symmetry with elemisfloatc. +fn elemisarrayc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + let elem: *node = nil; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + if (k == nkind.N_TPTR) { + if (elem.kind == nkind.N_TARRAY) { + if (elem.lhs != nil) { elem = elem.lhs; }; + }; + }; + return elem.kind == nkind.N_TARRAY; +}; + +// 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 +// role as typeisslice/typeisstr in lib/ww/typ.ww; kept cgen-local to +// avoid widening the frontend surface for one #156 read-half check. +fn tinfoisarray(t: *tinfo) bool = { + let u: *tinfo = t; + for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (u == nil) { return false; }; + return u.kind == tykind.TY_ARRAY; +}; + // fieldissignedc — does this field/element type-AST need sign- // extension on a sub-word load? One-liner via typeissigned (cstage // cgen.c:240 `fld_issigned` SSoT). t.type_ is stamped at check.ww @@ -943,6 +980,12 @@ fn elemsizeof(t: *node) i32 = { if (elem == nil) { return 1; }; // `*[N]T`: drill through the pointer into the array's element so // indexing scales by T's width, not the whole-array byte size. + // FOOTGUN (#156): this drill ALSO fires for a bare 2D `[N][M]T` + // (elem = the inner `[M]T`), so elemsizeof of a 2D array bottoms + // out at the SCALAR T size, NOT the `[M]T` sub-array stride. 2D + // double-index (cgindex) needs the sub-array stride — call + // elemsizeofc, the 2D-correct entry, which recovers slotsize([M]T) + // when elemsizeof returns 8. Never call elemsizeof for a 2D stride. if (elem.kind == nkind.N_TARRAY) { if (elem.lhs != nil) { elem = elem.lhs; }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index ebff838a..3f937d68 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -12221,6 +12221,43 @@ fn elemisf32c(c: *cgen, t: *node) bool = { return typeisf32(ti.sub); }; +// elemisarrayc — given an indexable type-AST (`*T` / `[]T` / `[N]T`), is +// its element itself an array (`[N][M]T` → element `[M]T`)? cgindex then +// leaves the sub-array's ADDRESS in the result reg rather than +// dereferencing — a nested index adds its offset and only the final +// scalar element dereferences (#156, sister of #135 N_DOT-base-on- +// array-field). Node-based with the `*[N]T` drill-through, mirroring +// elemsizeof (:920-948) so elem-is-array aligns with the esz this same +// tnode feeds. cstage twin: idx_eff(bt)->sub unwrapped == TY_ARRAY +// (cmd/w6c/cgen.c). `c` kept for signature symmetry with elemisfloatc. +fn elemisarrayc(c: *cgen, t: *node) bool = { + if (t == nil) { return false; }; + let k: nkind = t.kind; + let elem: *node = nil; + if (k == nkind.N_TPTR) { elem = t.lhs; }; + if (k == nkind.N_TSLICE) { elem = t.lhs; }; + if (k == nkind.N_TARRAY) { elem = t.lhs; }; + if (elem == nil) { return false; }; + if (k == nkind.N_TPTR) { + if (elem.kind == nkind.N_TARRAY) { + if (elem.lhs != nil) { elem = elem.lhs; }; + }; + }; + return elem.kind == nkind.N_TARRAY; +}; + +// 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 +// role as typeisslice/typeisstr in lib/ww/typ.ww; kept cgen-local to +// avoid widening the frontend surface for one #156 read-half check. +fn tinfoisarray(t: *tinfo) bool = { + let u: *tinfo = t; + for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (u == nil) { return false; }; + return u.kind == tykind.TY_ARRAY; +}; + // fieldissignedc — does this field/element type-AST need sign- // extension on a sub-word load? One-liner via typeissigned (cstage // cgen.c:240 `fld_issigned` SSoT). t.type_ is stamped at check.ww @@ -12345,6 +12382,12 @@ fn elemsizeof(t: *node) i32 = { if (elem == nil) { return 1; }; // `*[N]T`: drill through the pointer into the array's element so // indexing scales by T's width, not the whole-array byte size. + // FOOTGUN (#156): this drill ALSO fires for a bare 2D `[N][M]T` + // (elem = the inner `[M]T`), so elemsizeof of a 2D array bottoms + // out at the SCALAR T size, NOT the `[M]T` sub-array stride. 2D + // double-index (cgindex) needs the sub-array stride — call + // elemsizeofc, the 2D-correct entry, which recovers slotsize([M]T) + // when elemsizeof returns 8. Never call elemsizeof for a 2D stride. if (elem.kind == nkind.N_TARRAY) { if (elem.lhs != nil) { elem = elem.lhs; }; }; @@ -15553,6 +15596,14 @@ fn cgindex(c: *cgen, n: *node) void = { // from the SAME tinfo esz reads — never a fresh node-stamp (#121). let float_elem: bool = false; let f32_elem: bool = false; + // #156 (PREREQ-1 read-half): element is itself an array ([N][M]T → + // element [M]T) → leave the sub-array's ADDRESS in the result reg + // instead of dereferencing; the outer index adds its offset and the + // final scalar element dereferences. Sister of #135. Mirrors cstage + // esubu->kind == TY_ARRAY. Node-based (elemisarrayc) for ident bases, + // n.type_ tinfo-based for N_DOT/N_INDEX bases — same source split as + // esz above. + let elem_isarray: bool = false; // #1/Phase 3: str and slice are both 24B (and a >16B struct is // 24B+ too), so the header branches below MUST gate on KIND // (elemisstr/elemisslice, mirroring cstage's elem_is_str|| @@ -15580,6 +15631,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, baselocal.tnode); float_elem = elemisfloatc(c, baselocal.tnode); f32_elem = elemisf32c(c, baselocal.tnode); + elem_isarray = elemisarrayc(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); // #129 A.3: array-typed defs now have DATA storage; @@ -15596,6 +15648,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, tn); float_elem = elemisfloatc(c, tn); f32_elem = elemisf32c(c, tn); + elem_isarray = elemisarrayc(c, tn); }; if (tn.kind == nkind.N_TPTR) { isglobalptr = true; @@ -15604,6 +15657,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = elemissignedc(c, tn); float_elem = elemisfloatc(c, tn); f32_elem = elemisf32c(c, tn); + elem_isarray = elemisarrayc(c, tn); }; }; }; @@ -15616,6 +15670,7 @@ fn cgindex(c: *cgen, n: *node) void = { // stays unset, as before. let dt: *tinfo = n.type_: *tinfo; if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); float_elem = typeisfloat(dt); f32_elem = typeisf32(dt); }; + elem_isarray = tinfoisarray(dt); } else { if (base.kind == nkind.N_INDEX) { // #60: chained `names[i][k]` — n.type_ is the checker- // stamped outer element tinfo (indexresult over the inner @@ -15628,6 +15683,7 @@ fn cgindex(c: *cgen, n: *node) void = { signed_elem = typeissigned(et); float_elem = typeisfloat(et); f32_elem = typeisf32(et); + elem_isarray = tinfoisarray(et); }; };};}; }; @@ -15688,6 +15744,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("(SB), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); + // #156: array element ([N][M]T) → leave the sub-array ADDRESS + // in AX (BX holds base+idx*esz); nested index dereferences. + if (elem_isarray) { + emitline("\tMOVQ\tBX, AX\n"); + return; + }; if (elem_tagged) { if (elem_slot_sz > 24) { emitline("\tMOVQ\t24(BX), R8\n"); @@ -15741,6 +15803,12 @@ fn cgindex(c: *cgen, n: *node) void = { emitline("(BP), BX\n"); }; emitline("\tADDQ\tAX, BX\n"); + // #156: array element ([N][M]T) → leave the sub-array ADDRESS + // in AX (BX holds base+idx*esz); nested index dereferences. + if (elem_isarray) { + emitline("\tMOVQ\tBX, AX\n"); + return; + }; if (elem_tagged) { if (elem_slot_sz > 24) { emitline("\tMOVQ\t24(BX), R8\n"); @@ -15785,6 +15853,11 @@ fn cgindex(c: *cgen, n: *node) void = { }; emitline("\tPOPQ\tBX\n"); emitline("\tADDQ\tBX, AX\n"); + // #156: array element ([N][M]T) → AX already holds &elem + // (base+idx*esz); a nested index dereferences. See ident arms. + if (elem_isarray) { + return; + }; if (elem_tagged) { // AX holds the element address. Copy to BX (loading slot+0 // into AX clobbers it), then read slot words. @@ -25498,6 +25571,51 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, return true; }; + // #129 A.3 capstone (PREREQ-1, #156): nested-array element [M]T + // inside [N][M]T. Mirror of the TY_STRUCT-element arm above and of + // the TY_ARRAY-field-in-struct arm in emitstructlitbytes — recurse + // into emitarraylitbytes per element; recursion bottoms out at + // scalar (int/float) elements. esz = au.sub.size gives the per- + // element stride (rule 13). The `...` repeat marker with nested- + // array elements is rejected loud (rule 7): no consumer needs it + // (powers_of_ten is fully enumerated). + if (eu != nil && eu.kind == tykind.TY_ARRAY) { + let idx: i32 = 0; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + let m: str = "emitarraylitbytes: '...' repeat with nested-array elements unsupported (#129 A.3, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_ARRLIT) { return false; }; + if (!emitarraylitbytes(c, au.sub, ev, 0)) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + e = rhs.list; + for (e != nil && idx < alen) { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + emitarraylitbytes(c, au.sub, ev, 1); + idx += 1; + e = e.next; + }; + for (idx < alen) { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + idx += 1; + }; + return true; + }; + if (typeisfloat(au.sub)) { let isf32: bool = typeisf32(au.sub); // Validate. diff --git a/test/wcc/919_array_static_init_run.c b/test/wcc/919_array_static_init_run.c index 15f4f6ba..2570436a 100644 --- a/test/wcc/919_array_static_init_run.c +++ b/test/wcc/919_array_static_init_run.c @@ -62,10 +62,21 @@ * Each row: cstage `ww build` + run asserting exit code + w6c vs * w6c_ww `.s` cmp (rule-10 byte-id). * + * #156 (PREREQ-1) extends this with 2D `[N][M]T` static-init (a) + + * double-index read (b) — the A.3 shape-14 capstone, consumer-driven by + * fold-4's powers_of_ten[596][2]u64. emit_array_lit_bytes gains a + * TY_ARRAY-element arm (recurse; esz=etype->size); cgindex leaves the + * sub-array ADDRESS for an array element (sister of #135). Rows + * let_2d_x, def_2d_u64 and let_struct_2d_field + the nested-`...` + * loud-reject (rule-7) below. The D.m[i][j] global-struct-field-array + * READ stays a + * pre-existing gap (#160, 1D+2D, cs≠ww) out of scope here. + * * Deferred: * - Pointer-element arrays `[N]*T = [&G, &H]` — needs DATAR per * element (own task/fold). - * - Nested arrays `[N][M]T` — no current consumer. + * - `...` repeat with a nested-array element — loud-reject (#156 + * rule-7); no consumer (powers_of_ten is fully enumerated). * - Bare-int `[N]u8 = [1, 2, 3, 4]` — #130 (checker issue). * - Partial init `[4]u8 = [1u8]` — checker rejects (parser/checker * decision). @@ -140,6 +151,59 @@ static const struct row rows[] = { "package main;\n" "let A: [1]u8 = [7u8];\n" "export fn main() i32 = { return A[0]: i32; };\n", 7 }, + /* #156 (PREREQ-1): 2D [N][M]T static-init (a) + double-index read + * (b) — the A.3 shape-14 capstone, consumer-driven by fold-4's + * powers_of_ten[596][2]u64. emit_array_lit_bytes recurses on the + * TY_ARRAY element (esz=etype->size, rule-13); cgindex leaves the + * sub-array ADDRESS (not a value) for an array element so the outer + * index dereferences the right cell (sister of #135). */ + { "let_2d_u64", + "package main;\n" + "let A: [3][2]u64 = [[1u64,2u64],[3u64,4u64],[5u64,6u64]];\n" + "export fn main() i32 = { return A[1][0]: i32; };\n", 3 }, + { "def_2d_u64", + "package main;\n" + "def A: [3][2]u64 = [[1u64,2u64],[3u64,4u64],[5u64,6u64]];\n" + "export fn main() i32 = { return A[2][1]: i32; };\n", 6 }, + /* variable-index read — the exact fold-4 access pattern + * (powers_of_ten[i][0]/[i][1]). 21 + 30 = 51. */ + { "let_2d_varidx", + "package main;\n" + "let A: [3][2]u64 = [[10u64,11u64],[20u64,21u64],[30u64,31u64]];\n" + "fn at(i: i32, j: i32) u64 = { return A[i][j]; };\n" + "export fn main() i32 = { return (at(1, 1) + at(2, 0)): i32; };\n", + 51 }, + /* 8-byte 2D: must NOT hit the sz==8 scalar short-circuit (the + * isarr8/N_TARRAY guard, #128 lesson) — routes to the array arm. */ + { "let_2d_u32_8byte", + "package main;\n" + "let A: [2][1]u32 = [[7u32],[9u32]];\n" + "export fn main() i32 = { return A[1][0]: i32; };\n", 9 }, + /* 2D write to one cell, sum all four — verifies the lvalue address + * targets the exact cell with no neighbour clobber. 1+2+99+4=106. */ + { "let_2d_write", + "package main;\n" + "let A: [2][2]u64 = [[1u64,2u64],[3u64,4u64]];\n" + "export fn main() i32 = { A[1][0] = 99u64; return " + "(A[0][0]+A[0][1]+A[1][0]+A[1][1]): i32; };\n", 106 }, + /* struct field that is itself a 2D array — static-init emit + + * layout (read D.tag). The D.m[i][j] field-array READ exercises a + * pre-existing global-struct-field-base bug (#137/#150 family, + * 1D+2D, cs≠ww) out of PREREQ-1 scope — the array bytes are + * covered by the cs==ww byte-id gate below. */ + { "let_struct_2d_field", + "package main;\n" + "type dt = struct { tag: i32, m: [2][2]u64 };\n" + "let D: dt = dt{tag=42, m=[[1u64,2u64],[3u64,4u64]]};\n" + "export fn main() i32 = { return D.tag; };\n", 42 }, + /* 3D — locks recursion-depth>2 in both emit (nested TY_ARRAY arm + * recurses twice) and read (double-then-single index, two + * address-leaves). [[[1,2],[3,4]],[[5,6],[7,8]]]; A[1][1][0] = 7. */ + { "let_3d_u8", + "package main;\n" + "let A: [2][2][2]u8 = " + "[[[1u8,2u8],[3u8,4u8]],[[5u8,6u8],[7u8,8u8]]];\n" + "export fn main() i32 = { return A[1][1][0]: i32; };\n", 7 }, { NULL, NULL, 0 } }; @@ -250,6 +314,38 @@ main(void) unlink(src); unlink(cs_s); unlink(ws_s); } + /* #156 rule-7: a `...` repeat marker with a nested-array element is + * a loud reject in BOTH stages (no consumer needs it; powers_of_ten + * is fully enumerated). The compile must FAIL, not silently emit + * wrong bytes. Separate from the rows table (which asserts build + * success). */ + { + char src[64]; + snprintf(src, sizeof src, "/tmp/wwari_%d_rej.ww", getpid()); + FILE *f = fopen(src, "wb"); + if (f != NULL) { + fputs("package main;\n" + "let A: [4][2]u64 = [[1u64, 2u64]...];\n" + "export fn main() i32 = { return 0; };\n", f); + fclose(f); + } + char cmd[2048]; + snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null", + w6c, src); + int rc_cs = runwait(cmd); + snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null", + w6c_ww, src); + int rc_ww = runwait(cmd); + n++; + if (rc_cs == 0 || rc_ww == 0) { + fprintf(stderr, "row[nested_ellipsis_reject]: expected " + "BOTH stages to reject (cs=%d ww=%d), want nonzero " + "(#156 rule-7)\n", rc_cs, rc_ww); + fail++; + } + unlink(src); + } + if (fail) { fprintf(stderr, "%d/%d array-static-init tests failed\n", fail, n); return 1;