w6c+wwstage: aggregate let-init copy for ident-array/N_DOT/N_INDEX rhs (#268 fold-1b) — close addressable-rhs copy family

#265 fold-1 landed the deref-rhs aggregate copy as one slot→slot memcpy
loop fed from a source address in SI. fold-1b adds the remaining
addressable-rhs source-address setups, all routed into that SAME loop:

  - array IDENT `let c: [N]T = s`  — LEAQ the source slot into SI.
    Pre-fix both stages truncated to the 8B scalar tail.
  - N_DOT field `let c: A = o.i`   — cg_dotchain_addr / dotchainaddr
    (#253) lands &(o.i) in SI. Pre-fix truncated to 8B.
  - N_INDEX element `let c: A = a[i]` — the &base[i] spine (#252:
    scaled index + LEAQ base) lands the element address in SI. Pre-fix
    scalar-loaded the element address as a value → segfault.

Size (the #254 non-slot-padded ABI extent) comes from the declared let
type for every shape (lu->size / structabisize|tinfo.size), independent
of the rhs; only the per-rhs address setup differs. The deref arm
becomes one branch of the unified arm. Struct-IDENT keeps its own #32
slot-copy arm above (unchanged). With those, the whole addressable-rhs
let-init-copy family is closed by construction: struct-ident / array-
ident / deref / N_DOT / N_INDEX all full-copy, both stages byte-identical
(rule-10).

949 gains 9 full-readback rows (every member written distinct + summed,
so a partial copy fails): array-ident 16B/32B + 12B(MOVL)/11B(MOVW+MOVB)
tails; N_DOT struct-field 16B + array-field 32B + 11B-tail struct field;
N_INDEX struct element 16B/32B. The N_INDEX source array is populated
through a `*inner` to `&a[i]` (the #135/#252 store path) because the
array-of-struct element direct store (`a[i].m[j]=v` / `a[i]=s` / struct-
array literal) segfaults on a SEPARATE pre-existing bug, reported
alongside this fold. w6c+wwdump combined.ww regen (#110). 70/70 949,
test-unit 241, sizelint, smoke green.
This commit is contained in:
2026-06-02 11:22:01 +09:00
parent dfa9771f42
commit bb2f4e1dfe
5 changed files with 517 additions and 140 deletions

View File

@@ -28858,39 +28858,108 @@ fn cglet(c: *cgen, n: *node) void = {
};
};
};
// #265 fold-1: aggregate deref-rhs let-init `let c: T = *p`
// (T a struct or array, >8B). cgexpr(rhs.lhs) leaves the
// SOURCE ADDRESS in AX (a `*p` ident loads the pointer value;
// `*(&s)` LEAQs the slot); memcpy N bytes slot→slot via SI — a
// MOVQ run plus a sized MOVL/MOVW/MOVB tail. N is the #254
// non-slot-padded ABI extent: structabisize for a struct (=
// cstage lu->size), tinfo.size for an array. Pre-fix wwstage
// copied only the first 8B (scalar tail below) and cstage
// dropped the copy entirely — both wrong; converge on the full
// copy (rule-10). Mirror of cstage cgen.c N_LET deref arm; the
// by-value RETURN ABI is fold-2 (#267).
if (rhs.kind == nkind.N_UN) { if (rhs.op == tkind.TK_STAR) {
let ncopy: i32 = 0;
let dsi: *structinfo = structlookupchain(c, tn);
if (dsi != nil) {
ncopy = structabisize(dsi);
} else {
let dti: *tinfo = nil;
if (tn != nil) { dti = tn.type_: *tinfo; };
for (dti != nil && dti.kind == tykind.TY_NAMED) {
dti = dti.under;
};
if (dti != nil) {
if (dti.kind == tykind.TY_ARRAY) {
ncopy = dti.size: i32;
};
// #265 fold-1/1b (#268): aggregate let-init copy from an
// ADDRESSABLE rhs — `*p` (deref), an array ident `= s`
// (struct-ident is the arm above), an N_DOT field `= o.i`, an
// N_INDEX element `= a[i]`, T a struct/array >8B. ONE memcpy
// loop fed by a per-rhs source-address setup landing the SOURCE
// ADDRESS in SI; copy N bytes (the #254 non-slot-padded ABI
// extent: structabisize for a struct, tinfo.size for an array)
// slot→slot — a MOVQ run plus a sized MOVL/MOVW/MOVB tail. Pre-
// fix array-ident/N_DOT truncated to the 8B scalar tail below
// and N_INDEX scalar-loaded the element address (segfault).
// Mirror of cstage cgen.c N_LET arm (rule-10); the by-value
// RETURN ABI is fold-2 (#267). Source-addr setups reuse closed
// machinery: LEAQ-slot (ident), the deref operand (cgexpr),
// dotchainaddr (#253, N_DOT), the &base[i] spine (#252,
// N_INDEX).
let aggn: i32 = 0;
let aggsi: *structinfo = structlookupchain(c, tn);
if (aggsi != nil) {
aggn = structabisize(aggsi);
} else {
let aggti: *tinfo = nil;
if (tn != nil) { aggti = tn.type_: *tinfo; };
for (aggti != nil && aggti.kind == tykind.TY_NAMED) {
aggti = aggti.under;
};
if (aggti != nil) {
if (aggti.kind == tykind.TY_ARRAY) {
aggn = aggti.size: i32;
};
};
if (ncopy > 8) {
cgexpr(c, rhs.lhs);
emitline("\tMOVQ\tAX, SI\n");
};
if (aggn > 8) {
let havesrc: bool = false;
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) {
cgexpr(c, rhs.lhs);
emitline("\tMOVQ\tAX, SI\n");
havesrc = true;
};
};
if (!havesrc) { if (rhs.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, rhs.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), SI\n");
havesrc = true;
} else {
if (isletvar(c, rhs.str)
|| deflookup(c, rhs.str)) {
emitline("\tLEAQ\t");
emitsymname(c, rhs.str);
emitline("(SB), SI\n");
havesrc = true;
};
};
}; };
if (!havesrc) { if (rhs.kind == nkind.N_DOT) {
if (dotchainaddr(c, rhs, "SI")) {
havesrc = true;
};
}; };
if (!havesrc) { if (rhs.kind == nkind.N_INDEX) {
let base: *node = rhs.lhs;
let idx: *node = rhs.rhs;
let bu: *tinfo = nil;
if (base != nil) { bu = base.type_: *tinfo; };
for (bu != nil && bu.kind == tykind.TY_NAMED) {
bu = bu.under;
};
if (base != nil && base.kind == nkind.N_IDENT
&& bu != nil && bu.kind == tykind.TY_ARRAY) {
let esz: i32 = 1;
if (bu.sub != nil) {
esz = bu.sub.size: i32;
};
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
let bl: *local = localfindnode(c,
base.str);
if (bl != nil) {
emitline("\tLEAQ\t");
emitoff(bl.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), BX\n");
};
emitline("\tADDQ\tBX, AX\n");
emitline("\tMOVQ\tAX, SI\n");
havesrc = true;
};
}; };
if (havesrc) {
let k: i32 = 0;
for (k + 8 <= ncopy) {
for (k + 8 <= aggn) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -28899,7 +28968,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= ncopy) {
if (k + 4 <= aggn) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -28908,7 +28977,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= ncopy) {
if (k + 2 <= aggn) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -28917,7 +28986,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= ncopy) {
if (k + 1 <= aggn) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -28929,7 +28998,7 @@ fn cglet(c: *cgen, n: *node) void = {
c.lastwasreturn = 0;
return;
};
}; };
};
cgexpr(c, rhs);
// Float local: cgexpr leaves the value in X0. Spill via
// MOVSS (f32, 4B) or MOVSD (f64, 8B).

View File

@@ -1718,39 +1718,108 @@ fn cglet(c: *cgen, n: *node) void = {
};
};
};
// #265 fold-1: aggregate deref-rhs let-init `let c: T = *p`
// (T a struct or array, >8B). cgexpr(rhs.lhs) leaves the
// SOURCE ADDRESS in AX (a `*p` ident loads the pointer value;
// `*(&s)` LEAQs the slot); memcpy N bytes slot→slot via SI — a
// MOVQ run plus a sized MOVL/MOVW/MOVB tail. N is the #254
// non-slot-padded ABI extent: structabisize for a struct (=
// cstage lu->size), tinfo.size for an array. Pre-fix wwstage
// copied only the first 8B (scalar tail below) and cstage
// dropped the copy entirely — both wrong; converge on the full
// copy (rule-10). Mirror of cstage cgen.c N_LET deref arm; the
// by-value RETURN ABI is fold-2 (#267).
if (rhs.kind == nkind.N_UN) { if (rhs.op == tkind.TK_STAR) {
let ncopy: i32 = 0;
let dsi: *structinfo = structlookupchain(c, tn);
if (dsi != nil) {
ncopy = structabisize(dsi);
} else {
let dti: *tinfo = nil;
if (tn != nil) { dti = tn.type_: *tinfo; };
for (dti != nil && dti.kind == tykind.TY_NAMED) {
dti = dti.under;
};
if (dti != nil) {
if (dti.kind == tykind.TY_ARRAY) {
ncopy = dti.size: i32;
};
// #265 fold-1/1b (#268): aggregate let-init copy from an
// ADDRESSABLE rhs — `*p` (deref), an array ident `= s`
// (struct-ident is the arm above), an N_DOT field `= o.i`, an
// N_INDEX element `= a[i]`, T a struct/array >8B. ONE memcpy
// loop fed by a per-rhs source-address setup landing the SOURCE
// ADDRESS in SI; copy N bytes (the #254 non-slot-padded ABI
// extent: structabisize for a struct, tinfo.size for an array)
// slot→slot — a MOVQ run plus a sized MOVL/MOVW/MOVB tail. Pre-
// fix array-ident/N_DOT truncated to the 8B scalar tail below
// and N_INDEX scalar-loaded the element address (segfault).
// Mirror of cstage cgen.c N_LET arm (rule-10); the by-value
// RETURN ABI is fold-2 (#267). Source-addr setups reuse closed
// machinery: LEAQ-slot (ident), the deref operand (cgexpr),
// dotchainaddr (#253, N_DOT), the &base[i] spine (#252,
// N_INDEX).
let aggn: i32 = 0;
let aggsi: *structinfo = structlookupchain(c, tn);
if (aggsi != nil) {
aggn = structabisize(aggsi);
} else {
let aggti: *tinfo = nil;
if (tn != nil) { aggti = tn.type_: *tinfo; };
for (aggti != nil && aggti.kind == tykind.TY_NAMED) {
aggti = aggti.under;
};
if (aggti != nil) {
if (aggti.kind == tykind.TY_ARRAY) {
aggn = aggti.size: i32;
};
};
if (ncopy > 8) {
cgexpr(c, rhs.lhs);
emitline("\tMOVQ\tAX, SI\n");
};
if (aggn > 8) {
let havesrc: bool = false;
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) {
cgexpr(c, rhs.lhs);
emitline("\tMOVQ\tAX, SI\n");
havesrc = true;
};
};
if (!havesrc) { if (rhs.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, rhs.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), SI\n");
havesrc = true;
} else {
if (isletvar(c, rhs.str)
|| deflookup(c, rhs.str)) {
emitline("\tLEAQ\t");
emitsymname(c, rhs.str);
emitline("(SB), SI\n");
havesrc = true;
};
};
}; };
if (!havesrc) { if (rhs.kind == nkind.N_DOT) {
if (dotchainaddr(c, rhs, "SI")) {
havesrc = true;
};
}; };
if (!havesrc) { if (rhs.kind == nkind.N_INDEX) {
let base: *node = rhs.lhs;
let idx: *node = rhs.rhs;
let bu: *tinfo = nil;
if (base != nil) { bu = base.type_: *tinfo; };
for (bu != nil && bu.kind == tykind.TY_NAMED) {
bu = bu.under;
};
if (base != nil && base.kind == nkind.N_IDENT
&& bu != nil && bu.kind == tykind.TY_ARRAY) {
let esz: i32 = 1;
if (bu.sub != nil) {
esz = bu.sub.size: i32;
};
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
let bl: *local = localfindnode(c,
base.str);
if (bl != nil) {
emitline("\tLEAQ\t");
emitoff(bl.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), BX\n");
};
emitline("\tADDQ\tBX, AX\n");
emitline("\tMOVQ\tAX, SI\n");
havesrc = true;
};
}; };
if (havesrc) {
let k: i32 = 0;
for (k + 8 <= ncopy) {
for (k + 8 <= aggn) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -1759,7 +1828,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= ncopy) {
if (k + 4 <= aggn) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -1768,7 +1837,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= ncopy) {
if (k + 2 <= aggn) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -1777,7 +1846,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= ncopy) {
if (k + 1 <= aggn) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -1789,7 +1858,7 @@ fn cglet(c: *cgen, n: *node) void = {
c.lastwasreturn = 0;
return;
};
}; };
};
cgexpr(c, rhs);
// Float local: cgexpr leaves the value in X0. Spill via
// MOVSS (f32, 4B) or MOVSD (f64, 8B).

View File

@@ -28858,39 +28858,108 @@ fn cglet(c: *cgen, n: *node) void = {
};
};
};
// #265 fold-1: aggregate deref-rhs let-init `let c: T = *p`
// (T a struct or array, >8B). cgexpr(rhs.lhs) leaves the
// SOURCE ADDRESS in AX (a `*p` ident loads the pointer value;
// `*(&s)` LEAQs the slot); memcpy N bytes slot→slot via SI — a
// MOVQ run plus a sized MOVL/MOVW/MOVB tail. N is the #254
// non-slot-padded ABI extent: structabisize for a struct (=
// cstage lu->size), tinfo.size for an array. Pre-fix wwstage
// copied only the first 8B (scalar tail below) and cstage
// dropped the copy entirely — both wrong; converge on the full
// copy (rule-10). Mirror of cstage cgen.c N_LET deref arm; the
// by-value RETURN ABI is fold-2 (#267).
if (rhs.kind == nkind.N_UN) { if (rhs.op == tkind.TK_STAR) {
let ncopy: i32 = 0;
let dsi: *structinfo = structlookupchain(c, tn);
if (dsi != nil) {
ncopy = structabisize(dsi);
} else {
let dti: *tinfo = nil;
if (tn != nil) { dti = tn.type_: *tinfo; };
for (dti != nil && dti.kind == tykind.TY_NAMED) {
dti = dti.under;
};
if (dti != nil) {
if (dti.kind == tykind.TY_ARRAY) {
ncopy = dti.size: i32;
};
// #265 fold-1/1b (#268): aggregate let-init copy from an
// ADDRESSABLE rhs — `*p` (deref), an array ident `= s`
// (struct-ident is the arm above), an N_DOT field `= o.i`, an
// N_INDEX element `= a[i]`, T a struct/array >8B. ONE memcpy
// loop fed by a per-rhs source-address setup landing the SOURCE
// ADDRESS in SI; copy N bytes (the #254 non-slot-padded ABI
// extent: structabisize for a struct, tinfo.size for an array)
// slot→slot — a MOVQ run plus a sized MOVL/MOVW/MOVB tail. Pre-
// fix array-ident/N_DOT truncated to the 8B scalar tail below
// and N_INDEX scalar-loaded the element address (segfault).
// Mirror of cstage cgen.c N_LET arm (rule-10); the by-value
// RETURN ABI is fold-2 (#267). Source-addr setups reuse closed
// machinery: LEAQ-slot (ident), the deref operand (cgexpr),
// dotchainaddr (#253, N_DOT), the &base[i] spine (#252,
// N_INDEX).
let aggn: i32 = 0;
let aggsi: *structinfo = structlookupchain(c, tn);
if (aggsi != nil) {
aggn = structabisize(aggsi);
} else {
let aggti: *tinfo = nil;
if (tn != nil) { aggti = tn.type_: *tinfo; };
for (aggti != nil && aggti.kind == tykind.TY_NAMED) {
aggti = aggti.under;
};
if (aggti != nil) {
if (aggti.kind == tykind.TY_ARRAY) {
aggn = aggti.size: i32;
};
};
if (ncopy > 8) {
cgexpr(c, rhs.lhs);
emitline("\tMOVQ\tAX, SI\n");
};
if (aggn > 8) {
let havesrc: bool = false;
if (rhs.kind == nkind.N_UN) {
if (rhs.op == tkind.TK_STAR) {
cgexpr(c, rhs.lhs);
emitline("\tMOVQ\tAX, SI\n");
havesrc = true;
};
};
if (!havesrc) { if (rhs.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, rhs.str);
if (lc != nil) {
emitline("\tLEAQ\t");
emitoff(lc.off: i64);
emitline("(BP), SI\n");
havesrc = true;
} else {
if (isletvar(c, rhs.str)
|| deflookup(c, rhs.str)) {
emitline("\tLEAQ\t");
emitsymname(c, rhs.str);
emitline("(SB), SI\n");
havesrc = true;
};
};
}; };
if (!havesrc) { if (rhs.kind == nkind.N_DOT) {
if (dotchainaddr(c, rhs, "SI")) {
havesrc = true;
};
}; };
if (!havesrc) { if (rhs.kind == nkind.N_INDEX) {
let base: *node = rhs.lhs;
let idx: *node = rhs.rhs;
let bu: *tinfo = nil;
if (base != nil) { bu = base.type_: *tinfo; };
for (bu != nil && bu.kind == tykind.TY_NAMED) {
bu = bu.under;
};
if (base != nil && base.kind == nkind.N_IDENT
&& bu != nil && bu.kind == tykind.TY_ARRAY) {
let esz: i32 = 1;
if (bu.sub != nil) {
esz = bu.sub.size: i32;
};
cgexpr(c, idx);
if (esz > 1) {
emitline("\tMOVQ\t$");
emitint(esz: i64);
emitline(", CX\n");
emitline("\tIMULQ\tCX, AX\n");
};
let bl: *local = localfindnode(c,
base.str);
if (bl != nil) {
emitline("\tLEAQ\t");
emitoff(bl.off: i64);
emitline("(BP), BX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, base.str);
emitline("(SB), BX\n");
};
emitline("\tADDQ\tBX, AX\n");
emitline("\tMOVQ\tAX, SI\n");
havesrc = true;
};
}; };
if (havesrc) {
let k: i32 = 0;
for (k + 8 <= ncopy) {
for (k + 8 <= aggn) {
emitline("\tMOVQ\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -28899,7 +28968,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 8;
};
if (k + 4 <= ncopy) {
if (k + 4 <= aggn) {
emitline("\tMOVL\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -28908,7 +28977,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 4;
};
if (k + 2 <= ncopy) {
if (k + 2 <= aggn) {
emitline("\tMOVW\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -28917,7 +28986,7 @@ fn cglet(c: *cgen, n: *node) void = {
emitline("(BP)\n");
k += 2;
};
if (k + 1 <= ncopy) {
if (k + 1 <= aggn) {
emitline("\tMOVB\t");
emitoff(k: i64);
emitline("(SI), AX\n");
@@ -28929,7 +28998,7 @@ fn cglet(c: *cgen, n: *node) void = {
c.lastwasreturn = 0;
return;
};
}; };
};
cgexpr(c, rhs);
// Float local: cgexpr leaves the value in X0. Spill via
// MOVSS (f32, 4B) or MOVSD (f64, 8B).