w6c+w6c_ww: append() stores the full element width per element kind (fix #34)

Both stages lowered the append element store as one sized mov from AX —
correct only for scalars <= 8B. A str/slice element kept only .ptr
(byte-id-blind), a tagged element got its raw payload written into the
tag slot (the #12 pathology, no boxing), a struct element kept only its
first qword. wwstage additionally fed rt_ensure membsz from bare
elemsizeof, whose 8-sentinel under-allocated and mis-strided named
tagged/struct elements (the #8 family; cs!=ww on the SI imm + stride).

Fix, keyed on the DECLARED slice local's element type (cstage
su->sub->size as before; wwstage elemsizeofc off the stamped tnode —
never the value node, the #25/#31 esz=0 trap), applied to both the
single-value and spread bodies (2 arms x 2 stages):

- scalar 1/2/4/8: untouched (u8 asm byte-identical to pre-fix).
- str/slice: AX/BX/CX pushed across rt_ensure, dst in DX (BX holds the
  element .len after the pops — the #24 register discipline), 3-word
  store.
- tagged: grow first, dst -> BX, box via the #12 widen choke-point
  (cg_widen_tagged_store / cgwidentaggedstore via_outer).
- struct: grow first; literal -> dst spilled to per-fn @appendscr
  (cached on cstage to mirror wwstage's @-prefix localadd dedup) +
  structlit fill DST_PTR_LOCAL; local ident -> word-copy; any other
  source shape is a rule-7 loud-stop, never a silent scalar
  fall-through. struct-from-call deferred.
- spread: the source element is already a fully-formed T (tag
  included), so the wide arm grows first and whole-width word-copies
  &items[i] -> dst, recomputing both addresses from the slice headers
  after the possibly-reallocating rt_ensure.

The elemsizeofc swap also corrects the named-scalar-alias membsz
(wwstage fed SI=$8 where cstage fed $4); no in-tree consumer appended
to such a slice, so nothing was riding the wrong 8 (lib/selfhost append
sites are all u8).

Test 800_append_wide_elem: 13 rows (runtime readback per kind, 2-append
realloc survival, spread str+tagged, @appendscr dedup, enum-alias esz,
loud-stop build-fail) + per-row cs==ww byte-id, which subsumes the
frame canary.
This commit is contained in:
2026-06-04 02:14:14 +09:00
parent bf1037d8c4
commit faade48513
6 changed files with 1382 additions and 3 deletions

View File

@@ -87,6 +87,12 @@ static int cg_tagbase;
static int cg_tagbase_sz;
static int cg_tagscr;
static int cg_tagscr_sz;
/* #34: per-fn @appendscr — 8B dst-pointer spill for the append()
* struct-literal element fill (cg_structlit_fill DST_PTR_LOCAL needs
* a BP-rooted slot to reload BX from across its internal cgexprs).
* Cached per name per fn to mirror wwstage's localadd `@`-prefix
* dedup, else two struct appends in one fn diverge the frame. */
static int cg_appendscr;
/* System V AMD64 sret discipline (task #23). Plain TY_STRUCT returns
* with size > 24B are passed via a hidden first-arg pointer (RDI) to
* a caller-prealloc dest; the callee writes through that pointer and
@@ -6335,6 +6341,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
int sn_off = (sn->kind == N_IDENT)
? localfind(locals, sn->str) : 0;
int store_op = fldstoreop(esub, esz);
/* #34 element-kind store dispatch: the scalar 1-word
* store below silently gutted every wide element
* (str/slice 24B header, tagged box, struct body).
* Mirrors the #270/#12/#20 array-literal element
* dispatch (cg_arrlit_fill_bp). */
Type *esubu = type_chase_named(esub);
int el_str = type_isstr(esub);
int el_slice = type_isslice(esub);
int el_tagged = esubu && esubu->kind == TY_TAGGED;
int el_struct = esubu && esubu->kind == TY_STRUCT;
int el_wide = el_str || el_slice || el_tagged ||
el_struct;
if (!el_wide && esz > 8)
fatal("#34: append() element kind "
"unsupported (rule-7)");
for (Node *vn = sn->next; vn; vn = vn->next) {
if (vn->kind == N_SPREAD &&
vn->lhs && vn->lhs->kind == N_IDENT) {
@@ -6350,6 +6371,61 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, amem(D_BP, it_off + 8), areg(D_DX));
ins2(c, A_CMPQ, areg(D_DX), areg(D_CX));
ins1(c, A_JGE, abranch(le));
if (el_wide) {
/* #34: a spread element is already a
* fully-formed T in the source slice
* (tag included), so a whole-width
* word-copy is the store — no boxing.
* Grow FIRST: rt_ensure may realloc,
* so both addresses are recomputed
* from the slice headers after the
* call (i reloads from the counter
* slot; CX was clobbered). */
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
ins1(c, A_CALL, masym(c, "rt_ensure"));
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX));
if (esz > 1) {
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
}
ins2(c, A_MOVQ, amem(D_BP, it_off), areg(D_BX));
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
if (esz > 1) {
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
}
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_DX));
ins2(c, A_ADDQ, areg(D_CX), areg(D_DX));
int k = 0;
for (; k + 8 <= esz; k += 8) {
ins2(c, A_MOVQ, amem(D_BX, k), areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, k));
}
if (k + 4 <= esz) {
ins2(c, A_MOVL, amem(D_BX, k), areg(D_AX));
ins2(c, A_MOVL, areg(D_AX), amem(D_DX, k));
k += 4;
}
if (k + 2 <= esz) {
ins2(c, A_MOVW, amem(D_BX, k), areg(D_AX));
ins2(c, A_MOVW, areg(D_AX), amem(D_DX, k));
k += 2;
}
if (k + 1 <= esz) {
ins2(c, A_MOVB, amem(D_BX, k), areg(D_AX));
ins2(c, A_MOVB, areg(D_AX), amem(D_DX, k));
k += 1;
}
ins2(c, A_ADDQ, aimm(1), amem(D_SP, 0));
ins1(c, A_JMP, abranch(ll));
label(c, le);
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
continue;
}
/* AX = items.ptr[i] */
ins2(c, A_MOVQ, amem(D_BP, it_off), areg(D_BX));
if (esz > 1) {
@@ -6381,6 +6457,100 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
continue;
}
if (el_str || el_slice) {
/* #34: 24B {ptr,len,cap} header. cgexpr
* leaves AX/BX/CX; all three must survive
* rt_ensure. dst lands in DX, NOT BX — the
* pops put the element .len back in BX
* (the #24 register discipline). */
cgexpr(c, vn, locals);
ins1(c, A_PUSHQ, areg(D_AX));
ins1(c, A_PUSHQ, areg(D_BX));
ins1(c, A_PUSHQ, areg(D_CX));
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
ins1(c, A_CALL, masym(c, "rt_ensure"));
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_DX));
ins2(c, A_ADDQ, areg(D_CX), areg(D_DX));
ins1(c, A_POPQ, areg(D_CX));
ins1(c, A_POPQ, areg(D_BX));
ins1(c, A_POPQ, areg(D_AX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, 0));
ins2(c, A_MOVQ, areg(D_BX), amem(D_DX, 8));
ins2(c, A_MOVQ, areg(D_CX), amem(D_DX, 16));
continue;
}
if (el_tagged || el_struct) {
/* #34: no register form survives rt_ensure
* for these — grow FIRST, then fill through
* the dst pointer (tagged: the #12 widen
* choke-point cgexprs the value internally;
* struct: literal fill / ident word-copy). */
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
ins2(c, A_LEAQ, amem(D_BP, sn_off), areg(D_DI));
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
ins1(c, A_CALL, masym(c, "rt_ensure"));
ins2(c, A_MOVQ, amem(D_BP, sn_off + 8), areg(D_CX));
ins2(c, A_SUBQ, aimm(1), areg(D_CX));
if (esz > 1) {
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
ins2(c, A_IMULQ, areg(D_AX), areg(D_CX));
}
ins2(c, A_MOVQ, amem(D_BP, sn_off), areg(D_BX));
ins2(c, A_ADDQ, areg(D_CX), areg(D_BX));
if (el_tagged) {
cg_widen_tagged_store(c, &locals,
esub, vn, D_BX, 0, esz);
continue;
}
if (vn->kind == N_STRUCTLIT) {
if (cg_appendscr == 0)
cg_appendscr = local_alloc(c,
&locals, "@appendscr", 8,
cg_frame);
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, cg_appendscr));
cg_structlit_fill(c, &locals, esubu,
vn, DST_PTR_LOCAL, cg_appendscr,
NULL, 0);
continue;
}
if (vn->kind == N_IDENT) {
int soff = localfind(locals, vn->str);
if (soff == 0)
fatal("#34: append() struct "
"element source ident is "
"not a local (rule-7)");
int k = 0;
for (; k + 8 <= esz; k += 8) {
ins2(c, A_MOVQ, amem(D_BP, soff + 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_BP, soff + 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_BP, soff + 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_BP, soff + k), areg(D_AX));
ins2(c, A_MOVB, areg(D_AX), amem(D_BX, k));
k += 1;
}
continue;
}
fatal("#34: append() struct element source "
"shape unsupported (rule-7)");
}
cgexpr(c, vn, locals); /* val → AX */
ins1(c, A_PUSHQ, areg(D_AX));
ins2(c, A_ADDQ, aimm(1), amem(D_BP, sn_off + 8));
@@ -10839,6 +11009,7 @@ cgfn(Cg *c, FILE *out, Node *fn)
cg_tagbase_sz = 0;
cg_tagscr = 0;
cg_tagscr_sz = 0;
cg_appendscr = 0;
cg_sret_arg_off = 0;
cg_sret_dest_off = 0;
cg_sret_dest_sym = NULL;