cgen+test: copy struct >8B local-ident rhs in N_LET (#32)

let p2: T = p1; where T is a struct >8B and rhs is a local ident
silently dropped most of the copy. Cstage's N_LET fell past every
specialized rhs branch (str/tuple/tagged/structlit/call) without
matching the bare-ident case, then past the sz==8 fallback (false)
to the no-rhs zero-init (false: rhs present), emitting zero
instructions — the dest slot read fresh-stack zeros. Wwstage's
cglet fell to cgexpr+MOVQ AX which loads only the first qword
(cgident shape for struct ident), and for sz==16 slots the
str-init tail then stored a stale BX into +8. Reads after the
let saw whatever the stack held: silent partial copy.

Both stages now byte-copy src slot → dst slot per qword with
a sized tail (MOVL/MOVB) for natural sizes not 8-aligned.
Mirrors cg_widen_tagged_store's struct-ident payload copy.

744_letcopy_struct pins the four struct shapes (3×i32, i32+str,
i32+[]u8, i32+tagged) on asm-presence in both stages, cmp -s
byte-id, and runtime exit code via both drivers.

Scope: only N_IDENT rhs at the local-ident-found path. Filed as
siblings (no in-tree consumer today, bootstrap byte-id proves it):
  - N_DOT / N_INDEX / N_UN(deref) struct rhs.
  - Top-level (non-local) struct ident rhs.
  - TY_TUPLE same-shape ident-copy bug.

Row (a) uses tri{a=11, b=22, c=33} structlit init for p1 to
isolate this fix from STATUS-3 #15/#26c (no-rhs zero-init sz=12
vs sz=16 slot-padded divergence between stages, separate task).
Row (d) runtime check uses only p2.a to isolate from match-on-
tagged-field scrutinee spill divergence (same task).

118/118 ok. ww2 == ww3 == ww4 byte-id holds.
This commit is contained in:
2026-05-18 23:01:09 +09:00
parent aa8d15182a
commit d84704e389
6 changed files with 594 additions and 0 deletions

View File

@@ -6532,6 +6532,45 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
}
break;
}
/* Struct ident copy: `let p2: T = p1;` where T is a struct
* >8B and rhs is a local ident. Pre-fix the path fell
* through to the `sz == 8` test (false) and emitted
* nothing — the dst slot read whatever the stack held,
* presenting as a silent zero copy on a fresh frame.
* Per-qword MOVQ from src slot to dst slot, with a sized
* tail (MOVL/MOVB) for natural sizes that aren't
* 8-aligned (e.g. `struct { i32, i32, i32 }` is 12B).
* Mirrors the slot-to-slot copy in cg_widen_tagged_store
* for a TY_STRUCT payload (Task #32). */
if (n->rhs && n->rhs->kind == N_IDENT && lu
&& lu->kind == TY_STRUCT && sz > 8) {
Local *src_l = NULL;
for (Local *l = *locals; l; l = l->next)
if (strcmp(l->name, n->rhs->str) == 0) {
src_l = l; break;
}
if (src_l) {
int soff = src_l->off;
int k = 0;
while (k + 8 <= sz) {
ins2(c, A_MOVQ,
amem(D_BP, soff + k), areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, off + k));
k += 8;
}
if (k < sz) {
int tail = sz - k;
int lop = (tail == 4) ? A_MOVL :
(tail == 1) ? A_MOVB : A_MOVQ;
ins2(c, lop,
amem(D_BP, soff + k), areg(D_AX));
ins2(c, lop, areg(D_AX),
amem(D_BP, off + k));
}
break;
}
}
if (n->rhs && sz == 8) {
cgexpr(c, n->rhs, *locals);
if (isf) {