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:
@@ -71,9 +71,9 @@ fn callee_variadic_param(c: *cgen, callee: *node, nfixed_out: *i32) *node = {
|
||||
|
||||
// mkvarargname — fresh local-slot name "<prefix><seq>". Used for
|
||||
// the per-variadic-call scratch buffers (`@vararg_d_N` for the
|
||||
// element-data buffer, `@vararg_sl_N` for the 24B slice descriptor)
|
||||
// where N is recorded on the N_CALL node at scanlocals time so both
|
||||
// the prologue reservation and the call-site emission agree.
|
||||
// element-data buffer, `@vararg_sl_N` for the 24B slice descriptor).
|
||||
// N is recorded on the N_CALL node at first emit so re-entry into
|
||||
// cgcall picks the same names regardless of walk order.
|
||||
fn mkvarargname(c: *cgen, prefix: str, seq: i32) str = {
|
||||
let buf: [128]u8;
|
||||
let i: i32 = 0;
|
||||
@@ -179,7 +179,7 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
let pname: str = rhsstructpayload(c, arg);
|
||||
if (pname.len > 0) {
|
||||
let ptype: *node = param.lhs;
|
||||
let scroff: i32 = localadd(c, "@tagscr", c.tagscrsz, nil);
|
||||
let scroff: i32 = localadd(c, "@tagscr", widensz, nil);
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zz: i32 = 0;
|
||||
for (zz < widensz) {
|
||||
@@ -1747,8 +1747,8 @@ fn inferletcalltype(c: *cgen, rhs: *node) *node = {
|
||||
// letslotsize — slot size for a `let` binding. Like slotsize, but
|
||||
// detects `[_]T = arrlit;` (the type-AST has rhs == nil as the
|
||||
// length-inferred sentinel) and computes count × element-size from
|
||||
// the initialiser. Used by both scanlocals (prologue sizing) and
|
||||
// cglet (slot alloc) so they agree on the frame layout.
|
||||
// the initialiser. Called from cglet at emit time so the frame
|
||||
// grows monotonically per first-use (#15).
|
||||
//
|
||||
// `let x = f();` (no annotation): infer from `f`'s declared return
|
||||
// type so a 24B tagged-union return reserves all three spill slots,
|
||||
@@ -2119,9 +2119,8 @@ export fn resolvetagged(c: *cgen, t: *node) *node = {
|
||||
};
|
||||
|
||||
// matchscrutt — resolve a non-ident match scrutinee node to its tagged
|
||||
// type (or nil if unresolvable). Mirrors cgmatch's inline scrutinee
|
||||
// type resolution; factored so cgmatch (emit) and scanlocals (count)
|
||||
// agree on the spill slot's size per the scan+emit lockstep invariant.
|
||||
// type (or nil if unresolvable). Used by cgmatch to size the
|
||||
// @match_spill slot at first use (#15 first-use+fail-loud convergence).
|
||||
// IDENT scrutinees use a different lookup path (read off the local
|
||||
// directly, no spill) so this returns nil for them too.
|
||||
fn matchscrutt(c: *cgen, scrut: *node) *node = {
|
||||
@@ -2184,8 +2183,8 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = {
|
||||
// scrutinee lands in. Mirrors cstage's `slot_size = (su->kind ==
|
||||
// TY_TAGGED) ? su->size : 16` (cmd/w6c/cgen.c cgmatch). 16 default
|
||||
// when the scrutinee type can't be resolved keeps the historical
|
||||
// alloc for non-tagged / unresolved cases. Used by both scanlocals
|
||||
// (counting) and cgmatch (emitting) per rule-10 align-to-cstage.
|
||||
// alloc for non-tagged / unresolved cases. Called by cgmatch at first
|
||||
// use; #15 first-use+fail-loud pins this size per fn.
|
||||
fn matchspillsz(c: *cgen, scrutt: *node) i32 = {
|
||||
if (scrutt == nil) { return 16; };
|
||||
let sz: i32 = slotsize(c, scrutt);
|
||||
@@ -2972,11 +2971,11 @@ fn cgwidentaggedstore(c: *cgen, dst: *node, src: *node,
|
||||
emitline(", ");
|
||||
emitoff(bspill: i64);
|
||||
emitline("(BP)\n");
|
||||
// Same shared scratch — c.tagscrsz is the per-fn max across every
|
||||
// reservation site (scanlocals); pinning to slot_sz here would
|
||||
// undersize the slot if a sibling site (cgreturn, pushargsrev,
|
||||
// cgindex) needed a larger one and fired second.
|
||||
let scr: i32 = localadd(c, "@tagscr", c.tagscrsz, nil);
|
||||
// Shared scratch sized at first use per #15/#26c. A sibling
|
||||
// site (cgreturn, pushargsrev, cgindex) hitting @tagscr later
|
||||
// with a larger size fatals (rule 7) — pinned offset can't
|
||||
// grow in place.
|
||||
let scr: i32 = localadd(c, "@tagscr", slot_sz, nil);
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let z: i32 = 0;
|
||||
for (z < slot_sz) {
|
||||
|
||||
Reference in New Issue
Block a user