diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index e5ea1517..f459affa 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -5020,6 +5020,108 @@ cgexpr(Cg *c, Node *n, Local *locals) cg_sret_dest_off = 0; break; } + /* #270-1b: aggregate (struct/array/tuple >8B) element + * STORE `a[i] = val`. The scalar store path below copies + * only the first 8 bytes (fldstoreop MOVQ) — a silent + * truncation. Compute &a[i] (dest) and the rhs SOURCE + * address, then word-copy esz bytes: the WRITE-twin of the + * #268 let-init copy loop. Source shapes mirror that loop + * (ident local/global, N_DOT field via cg_dotchain_addr, + * `*p` deref); a by-value call result is the deferred #271, + * so N_CALL/literal sources fall through unchanged. */ + if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN + && esubu && (esubu->kind == TY_STRUCT + || esubu->kind == TY_ARRAY + || esubu->kind == TY_TUPLE) + && esz > 8 + && ((n->rhs->kind == N_IDENT) + || (n->rhs->kind == N_DOT) + || (n->rhs->kind == N_UN + && n->rhs->op == TK_STAR))) { + /* dest &a[i] → BX */ + cgexpr(c, n->lhs->rhs, locals); /* idx → AX */ + if (esz > 1) { + ins2(c, A_MOVQ, aimm(esz), areg(D_CX)); + ins2(c, A_IMULQ, areg(D_CX), areg(D_AX)); + } + ins1(c, A_PUSHQ, areg(D_AX)); /* scaled idx */ + if (base->kind == N_IDENT) { + int off = localfind(locals, base->str); + int isglobal = (off == 0) + && let_islet(base->str); + if (isglobal && is_arr) + ins2(c, A_LEAQ, + masym(c, base->str), + areg(D_BX)); + else if (isglobal) + ins2(c, A_MOVQ, + masym(c, base->str), + areg(D_BX)); + else if (is_arr) + ins2(c, A_LEAQ, + amem(D_BP, off), areg(D_BX)); + else + ins2(c, A_MOVQ, + amem(D_BP, off), areg(D_BX)); + } else if (cg_dotbase_addr(c, base, D_BX, locals)) { + /* N_DOT array-field base resolved inline. */ + } else { + cgexpr(c, base, locals); + ins2(c, A_MOVQ, areg(D_AX), areg(D_BX)); + } + ins1(c, A_POPQ, areg(D_AX)); /* scaled idx */ + ins2(c, A_ADDQ, areg(D_AX), areg(D_BX)); + ins1(c, A_PUSHQ, areg(D_BX)); /* spill dest */ + /* rhs source address → SI */ + if (n->rhs->kind == N_UN + && n->rhs->op == TK_STAR) { + cgexpr(c, n->rhs->lhs, locals); + ins2(c, A_MOVQ, areg(D_AX), areg(D_SI)); + } else if (n->rhs->kind == N_IDENT) { + int soff = localfind(locals, + n->rhs->str); + if (soff != 0) + ins2(c, A_LEAQ, + amem(D_BP, soff), + areg(D_SI)); + else + ins2(c, A_LEAQ, + masym(c, n->rhs->str), + areg(D_SI)); + } else { + cg_dotchain_addr(c, n->rhs, D_SI, locals); + } + ins1(c, A_POPQ, areg(D_BX)); /* dest */ + int k = 0; + for (; k + 8 <= esz; k += 8) { + ins2(c, A_MOVQ, amem(D_SI, k), + areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BX, k)); + } + if (k + 4 <= esz) { + ins2(c, A_MOVL, amem(D_SI, k), + areg(D_AX)); + ins2(c, A_MOVL, areg(D_AX), + amem(D_BX, k)); + k += 4; + } + if (k + 2 <= esz) { + ins2(c, A_MOVW, amem(D_SI, k), + areg(D_AX)); + ins2(c, A_MOVW, areg(D_AX), + amem(D_BX, k)); + k += 2; + } + if (k + 1 <= esz) { + ins2(c, A_MOVB, amem(D_SI, k), + areg(D_AX)); + ins2(c, A_MOVB, areg(D_AX), + amem(D_BX, k)); + k += 1; + } + break; + } if ((is_arr || is_sl || is_ptr) && n->op == TK_ASSIGN) { cgexpr(c, n->rhs, locals); /* AX=ptr (BX=len,CX=cap if str) */ /* str/slice: stash cap+len so all three store @@ -8024,6 +8126,23 @@ cgexpr(Cg *c, Node *n, Local *locals) Type *ft = f->type; Type *fu = (ft && ft->kind == TY_NAMED) ? ft->under : ft; + /* #270-1a: an `[N]T`-typed field of an + * array element (`a[i].m[j]`) — leave the + * field's ADDRESS, a base for the outer + * index, NEVER deref. AX holds &a[i]; the + * field address is &a[i]+foff. The #135 + * read-side for `d.m[i]`, applied to an + * array-element base. Without this an array + * field fell to fldloadop below and loaded + * its first 8 bytes as a value → garbage + * base → SEGFAULT in the outer index. */ + if (fu && fu->kind == TY_ARRAY) { + if (foff != 0) + ins2(c, A_ADDQ, + aimm(foff), + areg(D_AX)); + goto dot_done; + } if (fu && (fu->kind == TY_STR || fu->kind == TY_SLICE)) { /* str/slice: the 3-word {ptr,len,cap} @@ -8847,6 +8966,77 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_MOVQ, areg(D_AX), areg(D_SI)); havesrc = 1; + } else if (base && (base->kind == N_DOT + || base->kind == N_INDEX)) { + /* #270-3a: the index BASE is an N_DOT + * array-field (`x.arr[i]`) or a nested + * N_INDEX (`a[i][j]`); the N_IDENT-base arm + * above missed both, so the copy fell to the + * 8B truncation below. Compute &base[idx]: + * scaled idx on the stack, then &base via + * cg_dotbase_addr (N_DOT field address) or + * the &abase[bidx] spine (nested N_IDENT- + * array base), then add. */ + int esz = (bu && bu->sub) + ? (int)bu->sub->size : 1; + cgexpr(c, idx, *locals); + if (esz > 1) { + ins2(c, A_MOVQ, aimm(esz), + areg(D_CX)); + ins2(c, A_IMULQ, areg(D_CX), + areg(D_AX)); + } + ins1(c, A_PUSHQ, areg(D_AX)); + int baseok = 0; + if (base->kind == N_DOT) { + baseok = cg_dotbase_addr(c, base, + D_AX, *locals); + } else { + Node *ab = base->lhs; + Node *bidx = base->rhs; + Type *abt = ab ? ab->type : NULL; + Type *abu = type_chase_named(abt); + if (ab && ab->kind == N_IDENT + && abu + && abu->kind == TY_ARRAY) { + int aesz = (abu->sub) + ? (int)abu->sub->size + : 1; + cgexpr(c, bidx, *locals); + if (aesz > 1) { + ins2(c, A_MOVQ, + aimm(aesz), + areg(D_CX)); + ins2(c, A_IMULQ, + areg(D_CX), + areg(D_AX)); + } + int aoff = localfind( + *locals, ab->str); + if (aoff != 0) + ins2(c, A_LEAQ, + amem(D_BP, + aoff), + areg(D_BX)); + else + ins2(c, A_LEAQ, + masym(c, + ab->str), + areg(D_BX)); + ins2(c, A_ADDQ, + areg(D_BX), + areg(D_AX)); + baseok = 1; + } + } + ins1(c, A_POPQ, areg(D_BX)); + if (baseok) { + ins2(c, A_ADDQ, areg(D_BX), + areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), + areg(D_SI)); + havesrc = 1; + } } } if (havesrc) { diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 2946ca17..382edc94 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -21994,6 +21994,24 @@ fn cgdot(c: *cgen, n: *node) void = { } else { emitline("\tMOVQ\tBX, AX\n"); }; + // #270-1a: an `[N]T`-typed field of an + // array element (`a[i].m[j]`) — leave the + // field's ADDRESS, a base for the outer + // index, NEVER deref. AX holds &a[i]; the + // field address is &a[i]+foff. The #135 + // read-side for `d.m[i]`, applied to an + // array-element base. Without this an array + // field fell to fieldloadop below and loaded + // its first 8 bytes as a value → garbage + // base → SEGFAULT in the outer index. + if (tinfoisarray(fi.tnode.type_: *tinfo)) { + if (fi.foff != 0) { + emitline("\tADDQ\t$"); + emitint(fi.foff: i64); + emitline(", AX\n"); + }; + return; + }; if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { // str/slice: the 3-word {ptr,len,cap} // slice header (#1). AX holds the @@ -24620,6 +24638,120 @@ fn cgassign(c: *cgen, n: *node) void = { c.sretdestoff = 0; return; }; + // #270-1b: aggregate (struct/array/tuple >8B) element + // STORE `a[i] = val`. The scalar store path below copies + // only the first 8 bytes (tnodestoreop MOVQ) — a silent + // truncation. Compute &a[i] (dest) and the rhs SOURCE + // address, then word-copy esz bytes: the WRITE-twin of + // the #268 let-init copy loop. Source shapes mirror that + // loop (ident, N_DOT field via dotchainaddr, `*p` + // deref); a by-value call result is the deferred #271, so + // N_CALL/literal sources fall through unchanged. esz>8 + // non-str/non-slice IS a struct/array/tuple here (the + // tagged element already returned above; floats are ≤8). + let aggsrc: bool = (n.rhs.kind == nkind.N_IDENT) + || (n.rhs.kind == nkind.N_DOT) + || (n.rhs.kind == nkind.N_UN + && n.rhs.op == tkind.TK_STAR); + if (esz > 8 && !isstrtype(c, elemtn) + && !isslicetype(c, elemtn) && aggsrc) { + cgexpr(c, idx); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); // scaled idx + if (isglobalarr) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (isglobalptr) { + emitline("\tMOVQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (baselocal != nil) { + let tn2: *node = baselocal.tnode; + let isarr2: bool = false; + if (tn2 != nil) { if (tn2.kind == nkind.N_TARRAY) { isarr2 = true; }; }; + if (isarr2) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { if (dotbaseaddr(c, base, "BX")) { + // N_DOT array-field base resolved inline. + } else { + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + };};};}; + emitline("\tPOPQ\tAX\n"); // scaled idx + emitline("\tADDQ\tAX, BX\n"); + emitline("\tPUSHQ\tBX\n"); // spill dest + // rhs source address → SI + if (n.rhs.kind == nkind.N_UN + && n.rhs.op == tkind.TK_STAR) { + cgexpr(c, n.rhs.lhs); + emitline("\tMOVQ\tAX, SI\n"); + } else { if (n.rhs.kind == nkind.N_IDENT) { + let sl: *local = localfindnode(c, n.rhs.str); + if (sl != nil) { + emitline("\tLEAQ\t"); + emitoff(sl.off: i64); + emitline("(BP), SI\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, n.rhs.str); + emitline("(SB), SI\n"); + }; + } else { + dotchainaddr(c, n.rhs, "SI"); + };}; + emitline("\tPOPQ\tBX\n"); // dest + let kc: i32 = 0; + for (kc + 8 <= esz) { + emitline("\tMOVQ\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 8; + }; + if (kc + 4 <= esz) { + emitline("\tMOVL\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 4; + }; + if (kc + 2 <= esz) { + emitline("\tMOVW\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 2; + }; + if (kc + 1 <= esz) { + emitline("\tMOVB\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 1; + }; + return; + }; cgexpr(c, n.rhs); // value → AX // str/slice: spill cap (CX) + len (BX) before // computing the index so the post-index store can @@ -29186,6 +29318,76 @@ fn cglet(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, SI\n"); havesrc = true; }; + // #270-3a: the index BASE is an N_DOT + // array-field (`x.arr[i]`) or a nested N_INDEX + // (`a[i][j]`); the N_IDENT-base arm above missed + // both, so the copy fell to the 8B truncation + // below. Compute &base[idx]: scaled idx on the + // stack, then &base via dotbaseaddr (N_DOT field + // address) or the &abase[bidx] spine (nested + // N_IDENT-array base), then add. + if (!havesrc && base != nil + && (base.kind == nkind.N_DOT + || base.kind == nkind.N_INDEX)) { + let esz2: i32 = 1; + if (bu != nil && bu.sub != nil) { + esz2 = bu.sub.size: i32; + }; + cgexpr(c, idx); + if (esz2 > 1) { + emitline("\tMOVQ\t$"); + emitint(esz2: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); + let baseok: bool = false; + if (base.kind == nkind.N_DOT) { + if (dotbaseaddr(c, base, "AX")) { + baseok = true; + }; + } else { + let ab: *node = base.lhs; + let bidx: *node = base.rhs; + let abu: *tinfo = nil; + if (ab != nil) { abu = ab.type_: *tinfo; }; + for (abu != nil && abu.kind == tykind.TY_NAMED) { + abu = abu.under; + }; + if (ab != nil && ab.kind == nkind.N_IDENT + && abu != nil && abu.kind == tykind.TY_ARRAY) { + let aesz: i32 = 1; + if (abu.sub != nil) { + aesz = abu.sub.size: i32; + }; + cgexpr(c, bidx); + if (aesz > 1) { + emitline("\tMOVQ\t$"); + emitint(aesz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + let abl: *local = localfindnode(c, ab.str); + if (abl != nil) { + emitline("\tLEAQ\t"); + emitoff(abl.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, ab.str); + emitline("(SB), BX\n"); + }; + emitline("\tADDQ\tBX, AX\n"); + baseok = true; + }; + }; + emitline("\tPOPQ\tBX\n"); + if (baseok) { + emitline("\tADDQ\tBX, AX\n"); + emitline("\tMOVQ\tAX, SI\n"); + havesrc = true; + }; + }; }; }; if (havesrc) { let k: i32 = 0; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 70751522..3b8fa24e 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -2496,6 +2496,24 @@ fn cgdot(c: *cgen, n: *node) void = { } else { emitline("\tMOVQ\tBX, AX\n"); }; + // #270-1a: an `[N]T`-typed field of an + // array element (`a[i].m[j]`) — leave the + // field's ADDRESS, a base for the outer + // index, NEVER deref. AX holds &a[i]; the + // field address is &a[i]+foff. The #135 + // read-side for `d.m[i]`, applied to an + // array-element base. Without this an array + // field fell to fieldloadop below and loaded + // its first 8 bytes as a value → garbage + // base → SEGFAULT in the outer index. + if (tinfoisarray(fi.tnode.type_: *tinfo)) { + if (fi.foff != 0) { + emitline("\tADDQ\t$"); + emitint(fi.foff: i64); + emitline(", AX\n"); + }; + return; + }; if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { // str/slice: the 3-word {ptr,len,cap} // slice header (#1). AX holds the @@ -5122,6 +5140,120 @@ fn cgassign(c: *cgen, n: *node) void = { c.sretdestoff = 0; return; }; + // #270-1b: aggregate (struct/array/tuple >8B) element + // STORE `a[i] = val`. The scalar store path below copies + // only the first 8 bytes (tnodestoreop MOVQ) — a silent + // truncation. Compute &a[i] (dest) and the rhs SOURCE + // address, then word-copy esz bytes: the WRITE-twin of + // the #268 let-init copy loop. Source shapes mirror that + // loop (ident, N_DOT field via dotchainaddr, `*p` + // deref); a by-value call result is the deferred #271, so + // N_CALL/literal sources fall through unchanged. esz>8 + // non-str/non-slice IS a struct/array/tuple here (the + // tagged element already returned above; floats are ≤8). + let aggsrc: bool = (n.rhs.kind == nkind.N_IDENT) + || (n.rhs.kind == nkind.N_DOT) + || (n.rhs.kind == nkind.N_UN + && n.rhs.op == tkind.TK_STAR); + if (esz > 8 && !isstrtype(c, elemtn) + && !isslicetype(c, elemtn) && aggsrc) { + cgexpr(c, idx); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); // scaled idx + if (isglobalarr) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (isglobalptr) { + emitline("\tMOVQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (baselocal != nil) { + let tn2: *node = baselocal.tnode; + let isarr2: bool = false; + if (tn2 != nil) { if (tn2.kind == nkind.N_TARRAY) { isarr2 = true; }; }; + if (isarr2) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { if (dotbaseaddr(c, base, "BX")) { + // N_DOT array-field base resolved inline. + } else { + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + };};};}; + emitline("\tPOPQ\tAX\n"); // scaled idx + emitline("\tADDQ\tAX, BX\n"); + emitline("\tPUSHQ\tBX\n"); // spill dest + // rhs source address → SI + if (n.rhs.kind == nkind.N_UN + && n.rhs.op == tkind.TK_STAR) { + cgexpr(c, n.rhs.lhs); + emitline("\tMOVQ\tAX, SI\n"); + } else { if (n.rhs.kind == nkind.N_IDENT) { + let sl: *local = localfindnode(c, n.rhs.str); + if (sl != nil) { + emitline("\tLEAQ\t"); + emitoff(sl.off: i64); + emitline("(BP), SI\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, n.rhs.str); + emitline("(SB), SI\n"); + }; + } else { + dotchainaddr(c, n.rhs, "SI"); + };}; + emitline("\tPOPQ\tBX\n"); // dest + let kc: i32 = 0; + for (kc + 8 <= esz) { + emitline("\tMOVQ\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 8; + }; + if (kc + 4 <= esz) { + emitline("\tMOVL\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 4; + }; + if (kc + 2 <= esz) { + emitline("\tMOVW\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 2; + }; + if (kc + 1 <= esz) { + emitline("\tMOVB\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 1; + }; + return; + }; cgexpr(c, n.rhs); // value → AX // str/slice: spill cap (CX) + len (BX) before // computing the index so the post-index store can diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index a25dde34..fa43a99b 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1953,6 +1953,76 @@ fn cglet(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, SI\n"); havesrc = true; }; + // #270-3a: the index BASE is an N_DOT + // array-field (`x.arr[i]`) or a nested N_INDEX + // (`a[i][j]`); the N_IDENT-base arm above missed + // both, so the copy fell to the 8B truncation + // below. Compute &base[idx]: scaled idx on the + // stack, then &base via dotbaseaddr (N_DOT field + // address) or the &abase[bidx] spine (nested + // N_IDENT-array base), then add. + if (!havesrc && base != nil + && (base.kind == nkind.N_DOT + || base.kind == nkind.N_INDEX)) { + let esz2: i32 = 1; + if (bu != nil && bu.sub != nil) { + esz2 = bu.sub.size: i32; + }; + cgexpr(c, idx); + if (esz2 > 1) { + emitline("\tMOVQ\t$"); + emitint(esz2: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); + let baseok: bool = false; + if (base.kind == nkind.N_DOT) { + if (dotbaseaddr(c, base, "AX")) { + baseok = true; + }; + } else { + let ab: *node = base.lhs; + let bidx: *node = base.rhs; + let abu: *tinfo = nil; + if (ab != nil) { abu = ab.type_: *tinfo; }; + for (abu != nil && abu.kind == tykind.TY_NAMED) { + abu = abu.under; + }; + if (ab != nil && ab.kind == nkind.N_IDENT + && abu != nil && abu.kind == tykind.TY_ARRAY) { + let aesz: i32 = 1; + if (abu.sub != nil) { + aesz = abu.sub.size: i32; + }; + cgexpr(c, bidx); + if (aesz > 1) { + emitline("\tMOVQ\t$"); + emitint(aesz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + let abl: *local = localfindnode(c, ab.str); + if (abl != nil) { + emitline("\tLEAQ\t"); + emitoff(abl.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, ab.str); + emitline("(SB), BX\n"); + }; + emitline("\tADDQ\tBX, AX\n"); + baseok = true; + }; + }; + emitline("\tPOPQ\tBX\n"); + if (baseok) { + emitline("\tADDQ\tBX, AX\n"); + emitline("\tMOVQ\tAX, SI\n"); + havesrc = true; + }; + }; }; }; if (havesrc) { let k: i32 = 0; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 677410af..fb1baa9c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -21994,6 +21994,24 @@ fn cgdot(c: *cgen, n: *node) void = { } else { emitline("\tMOVQ\tBX, AX\n"); }; + // #270-1a: an `[N]T`-typed field of an + // array element (`a[i].m[j]`) — leave the + // field's ADDRESS, a base for the outer + // index, NEVER deref. AX holds &a[i]; the + // field address is &a[i]+foff. The #135 + // read-side for `d.m[i]`, applied to an + // array-element base. Without this an array + // field fell to fieldloadop below and loaded + // its first 8 bytes as a value → garbage + // base → SEGFAULT in the outer index. + if (tinfoisarray(fi.tnode.type_: *tinfo)) { + if (fi.foff != 0) { + emitline("\tADDQ\t$"); + emitint(fi.foff: i64); + emitline(", AX\n"); + }; + return; + }; if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) { // str/slice: the 3-word {ptr,len,cap} // slice header (#1). AX holds the @@ -24620,6 +24638,120 @@ fn cgassign(c: *cgen, n: *node) void = { c.sretdestoff = 0; return; }; + // #270-1b: aggregate (struct/array/tuple >8B) element + // STORE `a[i] = val`. The scalar store path below copies + // only the first 8 bytes (tnodestoreop MOVQ) — a silent + // truncation. Compute &a[i] (dest) and the rhs SOURCE + // address, then word-copy esz bytes: the WRITE-twin of + // the #268 let-init copy loop. Source shapes mirror that + // loop (ident, N_DOT field via dotchainaddr, `*p` + // deref); a by-value call result is the deferred #271, so + // N_CALL/literal sources fall through unchanged. esz>8 + // non-str/non-slice IS a struct/array/tuple here (the + // tagged element already returned above; floats are ≤8). + let aggsrc: bool = (n.rhs.kind == nkind.N_IDENT) + || (n.rhs.kind == nkind.N_DOT) + || (n.rhs.kind == nkind.N_UN + && n.rhs.op == tkind.TK_STAR); + if (esz > 8 && !isstrtype(c, elemtn) + && !isslicetype(c, elemtn) && aggsrc) { + cgexpr(c, idx); // idx → AX + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); // scaled idx + if (isglobalarr) { + emitline("\tLEAQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (isglobalptr) { + emitline("\tMOVQ\t"); + emitsymname(c, globalname); + emitline("(SB), BX\n"); + } else { if (baselocal != nil) { + let tn2: *node = baselocal.tnode; + let isarr2: bool = false; + if (tn2 != nil) { if (tn2.kind == nkind.N_TARRAY) { isarr2 = true; }; }; + if (isarr2) { + emitline("\tLEAQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tMOVQ\t"); + emitoff(baselocal.off: i64); + emitline("(BP), BX\n"); + }; + } else { if (dotbaseaddr(c, base, "BX")) { + // N_DOT array-field base resolved inline. + } else { + cgexpr(c, base); + emitline("\tMOVQ\tAX, BX\n"); + };};};}; + emitline("\tPOPQ\tAX\n"); // scaled idx + emitline("\tADDQ\tAX, BX\n"); + emitline("\tPUSHQ\tBX\n"); // spill dest + // rhs source address → SI + if (n.rhs.kind == nkind.N_UN + && n.rhs.op == tkind.TK_STAR) { + cgexpr(c, n.rhs.lhs); + emitline("\tMOVQ\tAX, SI\n"); + } else { if (n.rhs.kind == nkind.N_IDENT) { + let sl: *local = localfindnode(c, n.rhs.str); + if (sl != nil) { + emitline("\tLEAQ\t"); + emitoff(sl.off: i64); + emitline("(BP), SI\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, n.rhs.str); + emitline("(SB), SI\n"); + }; + } else { + dotchainaddr(c, n.rhs, "SI"); + };}; + emitline("\tPOPQ\tBX\n"); // dest + let kc: i32 = 0; + for (kc + 8 <= esz) { + emitline("\tMOVQ\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 8; + }; + if (kc + 4 <= esz) { + emitline("\tMOVL\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 4; + }; + if (kc + 2 <= esz) { + emitline("\tMOVW\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 2; + }; + if (kc + 1 <= esz) { + emitline("\tMOVB\t"); + emitoff(kc: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff(kc: i64); + emitline("(BX)\n"); + kc += 1; + }; + return; + }; cgexpr(c, n.rhs); // value → AX // str/slice: spill cap (CX) + len (BX) before // computing the index so the post-index store can @@ -29186,6 +29318,76 @@ fn cglet(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, SI\n"); havesrc = true; }; + // #270-3a: the index BASE is an N_DOT + // array-field (`x.arr[i]`) or a nested N_INDEX + // (`a[i][j]`); the N_IDENT-base arm above missed + // both, so the copy fell to the 8B truncation + // below. Compute &base[idx]: scaled idx on the + // stack, then &base via dotbaseaddr (N_DOT field + // address) or the &abase[bidx] spine (nested + // N_IDENT-array base), then add. + if (!havesrc && base != nil + && (base.kind == nkind.N_DOT + || base.kind == nkind.N_INDEX)) { + let esz2: i32 = 1; + if (bu != nil && bu.sub != nil) { + esz2 = bu.sub.size: i32; + }; + cgexpr(c, idx); + if (esz2 > 1) { + emitline("\tMOVQ\t$"); + emitint(esz2: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + emitline("\tPUSHQ\tAX\n"); + let baseok: bool = false; + if (base.kind == nkind.N_DOT) { + if (dotbaseaddr(c, base, "AX")) { + baseok = true; + }; + } else { + let ab: *node = base.lhs; + let bidx: *node = base.rhs; + let abu: *tinfo = nil; + if (ab != nil) { abu = ab.type_: *tinfo; }; + for (abu != nil && abu.kind == tykind.TY_NAMED) { + abu = abu.under; + }; + if (ab != nil && ab.kind == nkind.N_IDENT + && abu != nil && abu.kind == tykind.TY_ARRAY) { + let aesz: i32 = 1; + if (abu.sub != nil) { + aesz = abu.sub.size: i32; + }; + cgexpr(c, bidx); + if (aesz > 1) { + emitline("\tMOVQ\t$"); + emitint(aesz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + let abl: *local = localfindnode(c, ab.str); + if (abl != nil) { + emitline("\tLEAQ\t"); + emitoff(abl.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, ab.str); + emitline("(SB), BX\n"); + }; + emitline("\tADDQ\tBX, AX\n"); + baseok = true; + }; + }; + emitline("\tPOPQ\tBX\n"); + if (baseok) { + emitline("\tADDQ\tBX, AX\n"); + emitline("\tMOVQ\tAX, SI\n"); + havesrc = true; + }; + }; }; }; if (havesrc) { let k: i32 = 0; diff --git a/test/wcc/949_dotbase_addr_slice_run.c b/test/wcc/949_dotbase_addr_slice_run.c index 66878d5c..5fcaa7d3 100644 --- a/test/wcc/949_dotbase_addr_slice_run.c +++ b/test/wcc/949_dotbase_addr_slice_run.c @@ -1057,6 +1057,107 @@ static const struct row rows[] = { " a[1][1] = 88;\n" " return (a[1][1] - a[0][1]): i32;\n" "};\n", 48, 1 }, + /* #270-1a: `a[i].m[j] = v` — array-of-struct element field, then + * index INTO that field. The `arr[i].field` read/write arm computed + * &a[i] then DEREF'd it (loaded the struct's first 8 bytes as a + * value) for an `[N]T`-typed field → garbage base → SEGFAULT on the + * outer store. Fix: an array-typed field of an array element leaves + * the field ADDRESS (the #135 read-side, applied to the array-element + * base). Multiple cells written then read back; byteid=1. */ + { "elemfield_store", + "package main;\n" + "type inner = struct { m: [4]u32 };\n" + "export fn main() i32 = {\n" + " let a: [3]inner;\n" + " a[0].m[1] = 5u32;\n" + " a[2].m[3] = 7u32;\n" + " a[2].m[0] = 9u32;\n" + " return (a[0].m[1] + a[2].m[3] + a[2].m[0]): i32;\n" + "};\n", 21, 1 }, + /* #270-1b: `a[i] = aggregateval` — whole-element STORE. The scalar + * store path copied only the first 8 bytes (fldstoreop MOVQ). Fix: + * an aggregate (struct/array >8B) element store word-copies the + * element from the rhs source address (WRITE-twin of the #268 + * let-init loop). Struct element + array element, full readback; + * byteid=1. */ + /* 16B struct (slot == natural) keeps byteid=1: a struct whose + * natural size is NOT an 8-multiple trips the orthogonal + * elemsizeofc slot-vs-natural array-stride divergence (cstage strides + * by sub->size, wwstage by slotsize) — see letcopy_dot_struct. */ + { "elem_struct_store", + "package main;\n" + "type inner = struct { a: u32, b: u32, c: u32, d: u32 };\n" + "export fn main() i32 = {\n" + " let arr: [3]inner;\n" + " let v: inner; v.a=10u32; v.b=20u32; v.c=30u32; v.d=40u32;\n" + " arr[2] = v;\n" + " return (arr[2].a + arr[2].b + arr[2].c + arr[2].d): i32;\n" + "};\n", 100, 1 }, + { "elem_arr_store", + "package main;\n" + "export fn main() i32 = {\n" + " let arr: [2][4]u32;\n" + " let s: [4]u32; s[0]=1u32; s[1]=2u32; s[2]=3u32; s[3]=4u32;\n" + " arr[1] = s;\n" + " return (arr[1][0]+arr[1][1]+arr[1][2]+arr[1][3]): i32;\n" + "};\n", 10, 1 }, + /* #270-3a: aggregate let-init COPY whose index base is an N_DOT + * array-field (`x.arr[i]`) or a nested N_INDEX (`a[i][j]`) — the + * let-init N_INDEX source-addr arm was N_IDENT-base-gated (#268 + * residual), so both fell to the 8B truncation. Fix computes + * &base[idx] via cg_dotbase_addr (N_DOT) / the &abase[bidx] spine + * (nested). Primitive-element rows are byteid=1; the value-struct + * rows below run-correct but trip the orthogonal value-nested-struct + * frame divergence (#254), so byteid=0 (same carve-out as chain_val_* + * above). */ + { "letcopy_dot_prim", + "package main;\n" + "type box = struct { arr: [2][4]u32 };\n" + "export fn main() i32 = {\n" + " let s: [4]u32; s[0]=1u32; s[1]=2u32; s[2]=3u32; s[3]=4u32;\n" + " let x: box;\n" + " x.arr[1] = s;\n" + " let c: [4]u32 = x.arr[1];\n" + " return (c[0]+c[1]+c[2]+c[3]): i32;\n" + "};\n", 10, 1 }, + { "letcopy_nest_prim", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [2][2][4]u32;\n" + " let s: [4]u32; s[0]=2u32; s[1]=4u32; s[2]=6u32; s[3]=8u32;\n" + " a[1][0] = s;\n" + " let c: [4]u32 = a[1][0];\n" + " return (c[0]+c[1]+c[2]+c[3]): i32;\n" + "};\n", 20, 1 }, + { "letcopy_subarr", + "package main;\n" + "export fn main() i32 = {\n" + " let a: [2][3]u32;\n" + " a[1][0] = 5u32; a[1][1] = 6u32; a[1][2] = 7u32;\n" + " let c: [3]u32 = a[1];\n" + " return (c[0] + c[1] + c[2]): i32;\n" + "};\n", 18, 1 }, + { "letcopy_dot_struct", + "package main;\n" + "type inner = struct { a: u32, b: u32, c: u32 };\n" + "type box = struct { arr: [3]inner };\n" + "export fn main() i32 = {\n" + " let x: box;\n" + " let v: inner; v.a = 10u32; v.b = 20u32; v.c = 30u32;\n" + " x.arr[1] = v;\n" + " let c: inner = x.arr[1];\n" + " return (c.a + c.b + c.c): i32;\n" + "};\n", 60, 0 }, + { "letcopy_nest_struct", + "package main;\n" + "type inner = struct { a: u32, b: u32, c: u32 };\n" + "export fn main() i32 = {\n" + " let a: [2][2]inner;\n" + " let v: inner; v.a = 11u32; v.b = 22u32; v.c = 33u32;\n" + " a[1][0] = v;\n" + " let c: inner = a[1][0];\n" + " return (c.a + c.b + c.c): i32;\n" + "};\n", 66, 0 }, { NULL, NULL, 0, 0 } };