wcc+w6c_ww: append() struct-element sources via split place-resolve (#49)

#49 (#35's single-element sibling, tranche-C pre-check PC2): the
struct-element append arm dispatched on SOURCE node kind — N_STRUCTLIT
(literal fill) and N_IDENT (local word-copy) only; every
place-resolvable chain died on the rule-7 fatal in BOTH stages,
including search()'s result-build line
`append(res, threads[best_idx].root_capture)` (regex.ha:819).

Wire those shapes with a SPLIT resolve around the grow (the #49
ruling): the chain's rvalues — deref-root pointer expr, index expr —
evaluate exactly once PRE-grow into @appendsroot/@appendsoff (an index
reading the slice header sees the pre-append len, Hare's argument
order), then only the BASE re-derives POST-grow from the live storage
and the stashed offsets land back on top, so a self-append source
re-roots in the post-realloc buffer. harec resolves an aggregate
source address wholly PRE-grow (gen.c: gen_load returns the address
for STORAGE_STRUCT, gen_store copies after rt.ensure) — a
use-after-free under a reclaiming allocator; per #263 we align to the
runtime-correct side, not the reference. A pointer ALIASING the grown
buffer keeps Hare's own stale-base hole (sound today only because
rt/malloc.ww never reclaims). Supported shapes are bounded: root
(local/global ident | deref) + at most one index + trailing direct
fields; all else stays on the #34 fatal, including CALL rvalues (the
#42-style bound, new reject row pins the text in both stages). The
N_STRUCTLIT/N_IDENT fast-paths keep their emission byte-identical.

806_append_place grows eight rows: indexed-field 56B capture (the
ha:819 shape, header readback), computed-index whole element,
deref-spine param pair, deref source, self-append ×33 crossing three
cap-doubling reallocs, the split-order semantics pin (a CALLED index
helper reading len must run once and see the PRE-grow len — the
pre-split emission failed exactly there), an element-kind ×
place-source matrix row (scalar/narrow/str/slice/tagged route via the
pre-existing arms — regression net), and the CALL-source reject. The
six fix rows verified FAILING against a pristine 796d41b build on
both drivers (loud #34 fatal, identically in cstage and w6c_ww —
there was no silent path at master); 70/70 fixtures green here
including per-row cs/ww asm byte-cmp.

Unblocks regex fold-2b tranche C (search) — PC2 was the lone
pre-check failure; PC1/PC3/PC4 passed at base.
This commit is contained in:
2026-06-04 14:06:34 +09:00
parent 796d41bb9f
commit c34a48a81f
5 changed files with 1009 additions and 0 deletions

View File

@@ -102,6 +102,13 @@ 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;
/* #49 split-resolve stashes: the source chain's PRE-grow rvalues —
* deref-root pointer value (@appendsroot) and scaled index offset
* (@appendsoff) — must survive rt_ensure so the POST-grow base
* re-derivation can add them back. Same per-fn name-cache discipline
* as @appendscr. */
static int cg_appendsroot;
static int cg_appendsoff;
/* 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
@@ -7114,6 +7121,112 @@ 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). */
/* #49 (#35's single-element sibling): a
* place-chain source (indexed field
* `threads[i].root_capture` regex.ha:819,
* deref spine, computed index) SPLITS
* around the grow per the #49 ruling:
* the chain's rvalues (deref-root
* pointer expr, index expr) evaluate
* exactly once PRE-grow — an index
* reading the slice header sees the
* pre-append len, Hare's argument
* order — and only the BASE re-derives
* POST-grow from the live storage, so
* a self-append source re-roots in the
* post-realloc buffer. harec resolves
* an aggregate source address wholly
* PRE-grow (gen.c: gen_load returns
* the address for STORAGE_STRUCT,
* gen_store copies after rt.ensure) —
* a use-after-free under a reclaiming
* allocator; per #263 we align to the
* runtime-correct side, not the
* reference. A pointer ALIASING the
* grown buffer keeps Hare's own
* stale-base hole (sound today only
* because rt/malloc.ww never
* reclaims). Spec not vendored
* (ref/hare/docs = man pages only),
* spec-silence assumed — re-verify if
* the spec is ever vendored.
* Bounded shapes: root (local/global
* ident | deref) + at most one index
* + trailing direct fields; all else
* stays on the #34 fatal (incl. CALL
* rvalues, the #42-style bound). */
int aplace = 0, afld = 0, aidx_esz = 0;
int abase_slice = 0, aroot_off = 0;
Node *aroot = NULL, *aidx = NULL;
if (el_struct && vn->kind != N_STRUCTLIT
&& vn->kind != N_IDENT) {
Node *ch = vn;
int aok = 1;
while (aok && ch->kind == N_DOT) {
Node *ab = ch->lhs;
Type *abu = ab ? type_chase_named(ab->type) : NULL;
Tfield *af = NULL;
if (abu && abu->kind == TY_STRUCT)
for (Tfield *fl = abu->fields; fl; fl = fl->next)
if (strcmp(fl->name, ch->str) == 0) { af = fl; break; }
if (af == NULL) { aok = 0; break; }
afld += (int)af->offset;
ch = ab;
}
if (aok && ch->kind == N_INDEX) {
Node *ab = ch->lhs;
Type *abu = ab ? type_chase_named(ab->type) : NULL;
Type *aet = type_chase_named(ch->type);
if (ab == NULL || abu == NULL || aet == NULL
|| (abu->kind != TY_SLICE && abu->kind != TY_ARRAY)) {
aok = 0;
} else {
abase_slice = abu->kind == TY_SLICE;
aidx_esz = (int)aet->size;
aidx = ch->rhs;
ch = ab;
}
}
if (aok) {
if (ch->kind == N_IDENT) {
aroot_off = localfind(locals, ch->str);
if (aroot_off == 0
&& !let_islet(ch->str)
&& !def_isstructdef(ch->str)
&& !def_isarraydef(ch->str))
aok = 0;
} else if (!(ch->kind == N_UN && ch->op == TK_STAR)) {
aok = 0;
}
}
if (!aok)
fatal("#34: append() struct element source "
"shape unsupported (rule-7)");
aroot = ch;
if (aroot->kind == N_UN) {
if (cg_appendsroot == 0)
cg_appendsroot = local_alloc(c,
&locals, "@appendsroot", 8,
cg_frame);
cgexpr(c, aroot->lhs, locals);
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, cg_appendsroot));
}
if (aidx != NULL) {
if (cg_appendsoff == 0)
cg_appendsoff = local_alloc(c,
&locals, "@appendsoff", 8,
cg_frame);
cgexpr(c, aidx, locals);
if (aidx_esz > 1) {
ins2(c, A_MOVQ, aimm(aidx_esz), areg(D_CX));
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
}
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, cg_appendsoff));
}
aplace = 1;
}
cg_append_grow(c, sn_direct, sn_off,
sn_scr, esz);
cg_append_slot(c, sn_direct, sn_off,
@@ -7163,6 +7276,70 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
continue;
}
if (aplace) {
/* phase 2: dst slot to
* @appendscr, base from the
* live storage, stashed
* offsets back on top. */
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));
if (aroot->kind == N_IDENT) {
if (aroot_off != 0)
ins2(c, A_LEAQ,
amem(D_BP, aroot_off),
areg(D_BX));
else
ins2(c, A_LEAQ,
masym(c, aroot->str),
areg(D_BX));
} else {
ins2(c, A_MOVQ,
amem(D_BP, cg_appendsroot),
areg(D_BX));
}
if (aidx != NULL) {
if (abase_slice)
ins2(c, A_MOVQ,
amem(D_BX, 0),
areg(D_BX));
ins2(c, A_MOVQ,
amem(D_BP, cg_appendsoff),
areg(D_AX));
ins2(c, A_ADDQ, areg(D_AX),
areg(D_BX));
}
if (afld != 0)
ins2(c, A_ADDQ, aimm(afld),
areg(D_BX));
ins2(c, A_MOVQ,
amem(D_BP, cg_appendscr),
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;
}
continue;
}
fatal("#34: append() struct element source "
"shape unsupported (rule-7)");
}
@@ -11999,6 +12176,8 @@ cgfn(Cg *c, FILE *out, Node *fn)
cg_tagbase_sz = 0;
cg_ntagscr = 0;
cg_appendscr = 0;
cg_appendsroot = 0;
cg_appendsoff = 0;
cg_sret_arg_off = 0;
cg_sret_dest_off = 0;
cg_sret_dest_sym = NULL;