selfhost+cstage+test: graduate frame growth to first-use+fail-loud (#15)

Subsumes #36. Drop wwstage scanlocals pre-pass; both stages converge on
first-use+fail-loud frame growth, rule-10 polarity DOWN to leaner side.
#36's surfaces (frame-total divergence on match-arm case-let; sibling
offset divergence in variadic+iter+match-prev compositions) close
naturally — running-max c.frame includes every first-use binding.

selfhost/cmd/wcc: add atlocals persistent @-prefix registry surviving
cgblock save/restore; add cgoutbuf/cgoutmode/cgout_enable/disable/flush
for deferred prologue (emit body to buffer, finalise c.frame, then
TEXT/SUBQ + flush); localadd @-prefix dedups against atlocals +
fail-louds on size-grow (rule 7 — no silent truncate); cgreturn-tagged
routes through @retscr (was colliding with @tagscr on arg-widen sizes);
variadic gather esz uses raw primsize (rune->4) not slotsize (rune->8)
— matches cstage and fixes the #36 sibling runtime miscompile in
non-leaf variadic+iter+match-prev callees.

cmd/w6c/cgen.c: drop the over-allocation hack ("for byte-id with
wwstage scanlocals reservation") since wwstage no longer over-reserves;
add fail-loud on @sretscr size-grow; @tagscr sites pass actual slot_sz
instead of stale c.tagscrsz.

748_size_strategy_convergence: table-driven 4 rows x 2 stages
(tag_variadic_runearm, trim_iter_match_prev, variadic_gather_rune_stride,
leaf_baseline). Each exercises a #36 surface shape; 8/8 ok.

Net -1565 lines. Sister latents filed as cosmetic (cs/ws frame size
drift on multiple-variadic-call fns): labelseq drift + varargseq
stuck at 0 — both bootstrap-byte-id safe (ww2==ww3==ww4 holds since
both ww2 and ww3 are wwstage outputs).

make test 122/122; ww2==ww3==ww4 byte-id holds via 995_self_rebuild.
This commit is contained in:
2026-05-19 02:13:58 +09:00
parent 7a278c1a2d
commit 5609d0456f
10 changed files with 1197 additions and 2469 deletions

View File

@@ -51,17 +51,15 @@ static int cg_retscr;
* cg_tagscr — sized scratch shared across THREE sites: cg_widen_
* tagged_store via_outer write target, cg_widen_tagged_
* push struct/tagged-source widen, N_INDEX tagged-element
* assign. Mirrors wwstage @tagscr (cgenutil.ww:180 +
* :2918, cgenexpr.ww:3524). Wwstage shares the slot via
* localadd `@`-prefix dedup, sized to `c.tagscrsz`
* (per-fn max computed by scanlocals pre-pass).
* assign. Mirrors wwstage @tagscr — wwstage shares the
* slot via localadd `@`-prefix dedup against c.atlocals.
*
* Cstage has no pre-pass: first call across the 3 sites sizes the
* slot; subsequent calls (any of the 3 sites) reuse if sz ≤ cached,
* fatal() if larger. Per rule 7: surface, don't silently corrupt the
* frame. The size-strategy convergence (wwstage DOWN to first-use+
* fail-loud on BOTH stages, or cstage UP to a scanlocals pre-pass) is
* filed as #26c — separate concern. _sz tracks cached allocation size. */
* Both stages now size at first use and fatal() if a later site asks
* for more (rule 7: surface, don't silently corrupt the frame —
* pinned offset can't grow in place once neighbours are allocated).
* Per-fn convergence completed by #15 (#26c follow-up): wwstage
* dropped its scanlocals pre-pass and aligned DOWN to cstage's
* first-use shape. _sz tracks cached allocation size. */
static int cg_tagbase;
static int cg_tagbase_sz;
static int cg_tagscr;
@@ -1239,9 +1237,8 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
if (cg_tagscr != 0) {
if (sz > cg_tagscr_sz)
fatal("cg_widen_tagged_store: @tagscr "
"cached sz %d, need %d (per-fn slot "
"growth needs scanlocals pre-pass — "
"STATUS-4 #26c follow-up)",
"cached sz %d, need %d (pinned offset "
"can't grow in place; rule 7 — #15/#26c)",
cg_tagscr_sz, sz);
write_off = cg_tagscr;
} else {
@@ -1532,8 +1529,8 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
if (cg_tagscr != 0) {
if (sz > cg_tagscr_sz)
fatal("cg_widen_tagged_push: @tagscr cached sz %d, "
"need %d (per-fn slot growth needs scanlocals "
"pre-pass — STATUS-4 #26c follow-up)",
"need %d (pinned offset can't grow in place; "
"rule 7 — #15/#26c)",
cg_tagscr_sz, sz);
scr = cg_tagscr;
} else {
@@ -3518,10 +3515,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (ssz > cg_tagscr_sz)
fatal("N_INDEX tagged: "
"@tagscr cached sz %d, "
"need %d (per-fn slot "
"growth needs scanlocals "
"pre-pass — STATUS-4 #26c "
"follow-up)",
"need %d (pinned offset "
"can't grow in place; "
"rule 7 — #15/#26c)",
cg_tagscr_sz, ssz);
scr = cg_tagscr;
} else {
@@ -4318,8 +4314,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
? (int)velem->size : 8;
/* Allocate dname BEFORE sname so the
* descriptor lives below the element
* buffer, matching the wwstage scanlocals
* reservation order (rule 10). */
* buffer, matching wwstage's emit-time
* order (rule 10). */
int doff = 0;
if (nvar > 0) {
const char *dname = mklabel(c, "vararg_d");
@@ -4612,21 +4608,31 @@ cgexpr(Cg *c, Node *n, Local *locals)
sret_call_sz = cg_sret_retsize(ret);
}
if (sret_call_sz > 0) {
/* Always pre-allocate @sretscr at the first sret CALL
* regardless of whether cg_sret_dest_off is set — keeps
* cstage's frame in lockstep with wwstage's
* scanlocals-based reservation. Single-slot SSoT (cg_
* sretscr_off) mirrors @retscr / @tagscr conventions. */
if (cg_sretscr_off == 0) {
cg_sretscr_off = local_alloc(c,
&locals, "@sretscr",
sret_call_sz, cg_frame);
cg_sretscr_sz = sret_call_sz;
}
/* @sretscr is only needed when the result is dropped
* (no `let x = f();` receiver wired the call's dest into
* cg_sret_dest_off). Allocate first-use per #15/#26c
* size-strategy convergence — wwstage's scanlocals pre-
* pass that used to reserve this slot unconditionally is
* gone; cstage matches by skipping the allocation when a
* dest is already wired. fatal() on a later sret CALL
* needing a bigger slot (rule 7 — pinned offset can't
* grow in place). */
if (cg_sret_dest_off != 0) {
sret_call_off = cg_sret_dest_off;
cg_sret_dest_off = 0;
} else {
if (cg_sretscr_off == 0) {
cg_sretscr_off = local_alloc(c,
&locals, "@sretscr",
sret_call_sz, cg_frame);
cg_sretscr_sz = sret_call_sz;
} else if (sret_call_sz > cg_sretscr_sz) {
fatal("cgcall: @sretscr cached sz "
"%d, need %d (per-fn slot growth "
"post-#15 — pinned offset can't "
"grow in place)",
cg_sretscr_sz, sret_call_sz);
}
sret_call_off = cg_sretscr_off;
}
}
@@ -4708,9 +4714,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
* forwards through an sret callee, source RDI from outer's
* saved @sretarg — inner writes directly into outer's
* caller-prealloc dest. No temporary in outer's frame.
* The @sretscr slot was still allocated above for byte-id
* lockstep with wwstage's scanlocals reservation; it goes
* unused on the forwarding branch. */
* Post-#15 @sretscr is skipped entirely on the forwarding
* branch (no allocation, no frame growth) — earlier scan-
* lockstep reservation is gone. */
if (sret_call_sz > 0) {
if (cg_sret_forward) {
ins2(c, A_MOVQ,
@@ -6768,19 +6774,19 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
if (cg_retscr != 0) {
scr = cg_retscr;
} else {
/* STATUS-3 #15: use the fixed
* "@retscr" SSoT name (mirrors
* wwstage's c.retscroff pattern in
* selfhost/cmd/wcc/cgen.ww). Pre-
* fix mklabel(c, "retscr") consumed
/* Fixed "@retscr" SSoT name —
* mirrors wwstage's localadd
* @-prefix dedup. Pre-fix
* mklabel(c, "retscr") consumed
* one labelseq counter slot per
* function with a tagged return,
* pushing every subsequent ct/ce/
* else/end label 1 ahead of wwstage.
* Site 1 sentinel masked by latent
* struct-widen offset divergence
* (STATUS-3 #20/#21); fix is
* preventive symmetry per rule 10. */
* else/end label 1 ahead of
* wwstage. Site 1 sentinel
* masked by latent struct-widen
* offset divergence (#20/#21);
* fix is preventive symmetry per
* rule 10. */
scr = local_alloc(c, locals,
"@retscr", sz, cg_frame);
cg_retscr = scr;
@@ -6936,9 +6942,9 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|| n->lhs->kind == N_STRUCTLIT)) {
int sz = (int)rt->size;
/* Single-slot @retscr (#14): see tagged arm
* above for rationale. STATUS-3 #15: fixed
* "@retscr" name avoids bumping labelseq;
* mirrors wwstage's c.retscroff SSoT. */
* above for rationale. Fixed "@retscr" name
* avoids bumping labelseq; mirrors wwstage's
* localadd @-prefix dedup. */
int scr;
if (cg_retscr != 0) {
scr = cg_retscr;