wcc+w6c_ww: append through pointer-to-slice place via cgplaceaddr (FA1)
Re-key the append() lowering from BP-displacement assumptions onto a resolver-provided header PLACE (task #15, the add_thread hard-blocker; cgplaceaddr's third consumer after C1/C1.25). One mirrored choke-point, two failure modes: cstage 0-defaulted sn_off for any non-ident target, so 0(BP)/8(BP) became the "slice header" and rt_ensure corrupted the CALLER frame (SIGSEGV); wwstage cgappend silently emitted nothing (gate-blind cs!=ww). cg_append_grow/cg_append_slot (mirror cgappendgrow/cgappendslot) factor the 5 grow + 5 slot header-access sites. Ident-local targets keep the legacy BP-disp emission byte-identical (probed across all 9 existing source shapes, before/after .s). Non-ident targets resolve once through cgplaceaddr and spill the header address to an @apphdrscr slot: rt_ensure may realloc .ptr but never moves the header, so the slot stays valid; every access reloads from it. The slot is allocated fresh per append SITE, not cached per fn: a nested append-through-pointer inside a value expression (match-yield arm) spills its own resolve, and a shared slot would hand the outer grow/slot reloads the inner target's header — silent cross-slice corruption (pinned by the reentrant_value row). Indirect mode keys esz/element-kind/load-op off the checker-stamped target tinfo (no declared tnode behind `*p`; the #209/#211 discipline). Unwired target places die LOUD "#15: append() target place unsupported (rule-7)" on BOTH stages — the silent-corruption class is closed by construction. The FA4/#35 boundary is unchanged: non-ident spread SOURCES stay loud (pinned by a reject row). Surfaced pre-existing checker divergence filed as task #34 (wwstage rejects global slice-lit let). test/wcc/806: 14 runtime rows (element kinds x target shapes, spread, narrow-signed spread load, cap-crossing realloc loop with branched callee + caller-frame sentinels, deref-spine target, nested-append reentrancy, direct-arm neutrality pin) + 2 exact-text reject rows, both drivers + per-row cs==ww asm byte-id.
This commit is contained in:
154
cmd/w6c/cgen.c
154
cmd/w6c/cgen.c
@@ -102,6 +102,16 @@ static int cg_ntagscr;
|
||||
* 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;
|
||||
/* FA1 (#15) @apphdrscr — 8B spill of the resolver-derived slice-header
|
||||
* ADDRESS for append() through a non-ident-local target (`append(*p,
|
||||
* v)`). rt_ensure may realloc .ptr but never moves the header, so the
|
||||
* spilled address stays valid across the call; every access reloads
|
||||
* from the slot because registers don't survive it. Allocated fresh
|
||||
* per append SITE (no per-fn cache, no decl here): a nested
|
||||
* append-through-pointer inside a value expression (match-yield arm)
|
||||
* spills its own resolve, and a shared slot would feed the outer
|
||||
* grow/slot reloads the INNER target's header — silent cross-slice
|
||||
* corruption (806 reentrant_value row). */
|
||||
/* 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
|
||||
@@ -2088,6 +2098,50 @@ cgplaceaddr(Cg *c, Node *n, int dst_reg, Local *locals)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FA1 (#15): append() header-place access, cgplaceaddr's append
|
||||
* consumer. direct = ident-local header in the frame (BP-disp — the
|
||||
* legacy emission, kept byte-identical); indirect = header address
|
||||
* pre-spilled to @apphdrscr by the resolver. grow = len+=1, &hdr→DI,
|
||||
* esz→SI, CALL rt_ensure. In indirect mode the len bump goes through
|
||||
* DI so the loaded address doubles as the call argument. */
|
||||
static void
|
||||
cg_append_grow(Cg *c, int direct, int off, int scr, int esz)
|
||||
{
|
||||
if (direct) {
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_BP, off + 8));
|
||||
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_DI));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr), areg(D_DI));
|
||||
ins2(c, A_ADDQ, aimm(1), amem(D_DI, 8));
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_SI));
|
||||
ins1(c, A_CALL, masym(c, "rt_ensure"));
|
||||
}
|
||||
|
||||
/* Post-rt_ensure slot address: CX = (len-1)*esz, dst = .ptr + CX.
|
||||
* Clobbers AX (the IMUL immediate) and CX, like the emission it
|
||||
* replaces; dst must not be AX or CX. */
|
||||
static void
|
||||
cg_append_slot(Cg *c, int direct, int off, int scr, int esz, int dst)
|
||||
{
|
||||
if (direct) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_CX));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr), areg(dst));
|
||||
ins2(c, A_MOVQ, amem(dst, 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));
|
||||
}
|
||||
if (direct)
|
||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(dst));
|
||||
else
|
||||
ins2(c, A_MOVQ, amem(dst, 0), areg(dst));
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(dst));
|
||||
}
|
||||
|
||||
/* cg_structlit_fill modes — see helper docstring. */
|
||||
enum {
|
||||
DST_BP = 0,
|
||||
@@ -6842,6 +6896,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
Type *esub = su ? su->sub : NULL;
|
||||
int sn_off = (sn->kind == N_IDENT)
|
||||
? localfind(locals, sn->str) : 0;
|
||||
/* FA1 (#15): a non-ident-local target used to silently
|
||||
* 0-default sn_off, making 0(BP)/8(BP) the "header" —
|
||||
* rt_ensure then corrupted the caller frame. Non-direct
|
||||
* targets resolve through cgplaceaddr below; a shape it
|
||||
* can't address is loud. */
|
||||
int sn_direct = sn_off != 0;
|
||||
int store_op = fldstoreop(esub, esz);
|
||||
/* #34 element-kind store dispatch: the scalar 1-word
|
||||
* store below silently gutted every wide element
|
||||
@@ -6858,6 +6918,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (!el_wide && esz > 8)
|
||||
fatal("#34: append() element kind "
|
||||
"unsupported (rule-7)");
|
||||
int sn_scr = 0;
|
||||
if (!sn_direct) {
|
||||
if (!cgplaceaddr(c, sn, D_BX, locals))
|
||||
fatal("#15: append() target place "
|
||||
"unsupported (rule-7)");
|
||||
/* Fresh slot per SITE, not a per-fn cache: a
|
||||
* nested append-through-pointer inside a value
|
||||
* expression (match-yield arm) spills its own
|
||||
* resolve; a shared slot would hand the outer
|
||||
* grow/slot reloads the inner target's header.
|
||||
* local_alloc never dedups by name. */
|
||||
sn_scr = local_alloc(c, &locals,
|
||||
"@apphdrscr", 8, cg_frame);
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, sn_scr));
|
||||
}
|
||||
for (Node *vn = sn->next; vn; vn = vn->next) {
|
||||
/* #34 review: a spread whose source is not a
|
||||
* local ident used to fall PAST the spread arm
|
||||
@@ -6897,10 +6973,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* 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"));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX));
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_AX));
|
||||
@@ -6908,14 +6982,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
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));
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_DX);
|
||||
int k = 0;
|
||||
for (; k + 8 <= esz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BX, k), areg(D_AX));
|
||||
@@ -6952,18 +7020,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, load_op, amem(D_BX, 0), areg(D_AX));
|
||||
/* ensure + store one element */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
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));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_BX);
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
|
||||
/* loop tail */
|
||||
@@ -6983,16 +7043,10 @@ cgexpr(Cg *c, Node *n, Local *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));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_DX);
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
@@ -7007,18 +7061,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* 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));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_BX);
|
||||
if (el_tagged) {
|
||||
cg_widen_tagged_store(c, &locals,
|
||||
esub, vn, D_BX, 0, esz);
|
||||
@@ -7069,18 +7115,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
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));
|
||||
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));
|
||||
cg_append_grow(c, sn_direct, sn_off,
|
||||
sn_scr, esz);
|
||||
cg_append_slot(c, sn_direct, sn_off,
|
||||
sn_scr, esz, D_BX);
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, store_op, areg(D_AX), amem(D_BX, 0));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user