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:
@@ -497,7 +497,7 @@ fn cgstrlit(c: *cgen, n: *node) void = {
|
||||
let nstr: str = n.str;
|
||||
let lab: str = internstrlit(c, nstr);
|
||||
emitline("\tLEAQ\t");
|
||||
os.write(1, lab.ptr, lab.len: u64);
|
||||
emitbytes( lab.ptr, lab.len: u64);
|
||||
emitline("(SB), AX\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(nstr.len: i64);
|
||||
@@ -564,7 +564,7 @@ fn cgident(c: *cgen, n: *node) void = {
|
||||
let bytes: str = drhs.str;
|
||||
let lab: str = internstrlit(c, bytes);
|
||||
emitline("\tLEAQ\t");
|
||||
os.write(1, lab.ptr, lab.len: u64);
|
||||
emitbytes( lab.ptr, lab.len: u64);
|
||||
emitline("(SB), AX\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(bytes.len: i64);
|
||||
@@ -1008,9 +1008,9 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
// (N_INDEX) and tagged-field loads (N_DOT, fixed by
|
||||
// #28) produce the same triple. Nullable returns are
|
||||
// single-word (AX = ptr); only +0 is read.
|
||||
// Scrutinee type + spill size factored into matchscrutt
|
||||
// / matchspillsz so scanlocals stays lockstep — see
|
||||
// cgenutil.ww (task #9 align-down to cstage).
|
||||
// Scrutinee type + spill size resolved through matchscrutt
|
||||
// / matchspillsz at first use (#15) — see cgenutil.ww
|
||||
// (task #9 align-down to cstage).
|
||||
scrutt = matchscrutt(c, scrut);
|
||||
let spillsz: i32 = matchspillsz(c, scrutt);
|
||||
scrutoff = localalloc(c, "@match_spill", spillsz, nil);
|
||||
@@ -1520,7 +1520,7 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
if (streq(fld, "ptr")) {
|
||||
let lab: str = internstrlit(c, bytes);
|
||||
emitline("\tLEAQ\t");
|
||||
os.write(1, lab.ptr, lab.len: u64);
|
||||
emitbytes( lab.ptr, lab.len: u64);
|
||||
emitline("(SB), AX\n");
|
||||
return;
|
||||
};
|
||||
@@ -1754,7 +1754,7 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
let bytes: str = drhs.str;
|
||||
let lab: str = internstrlit(c, bytes);
|
||||
emitline("\tLEAQ\t");
|
||||
os.write(1, lab.ptr, lab.len: u64);
|
||||
emitbytes( lab.ptr, lab.len: u64);
|
||||
emitline("(SB), AX\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(bytes.len: i64);
|
||||
@@ -2941,7 +2941,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
// Forwarding shape (`xs...`) skips the gather: the spread's
|
||||
// inner slice expression replaces the wrapper in place. Empty
|
||||
// (no trailing args) writes a {nil, 0, 0} descriptor. The seq
|
||||
// matches the one scanlocals stamped on n.uval.
|
||||
// matches the one cgcall stamped on n.uval at first emit.
|
||||
{
|
||||
let nfixed_v: i32 = 0;
|
||||
let varp: *node = callee_variadic_param(c, callee, &nfixed_v);
|
||||
@@ -2982,7 +2982,25 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
let seq: i32 = n.uval: i32;
|
||||
let dname: str = mkvarargname(c, "@vararg_d_", seq);
|
||||
let sname: str = mkvarargname(c, "@vararg_sl_", seq);
|
||||
let esz: i32 = slotsize(c, varp.lhs);
|
||||
// Use raw element size, not stack-padded
|
||||
// slotsize. cstage cmd/w6c/cgen.c cgcall
|
||||
// gathers a `T...` slice at velem->size stride
|
||||
// (MOVL for u32, MOVB for u8); the callee
|
||||
// `arg[i]` reads at the same raw stride. wwstage
|
||||
// previously sized through slotsize which pads
|
||||
// scalars to 8, mismatching the stride at the
|
||||
// callee read site — runtime miscompile in
|
||||
// `(rune...)` callees per #36.
|
||||
let esz: i32 = 8;
|
||||
if (varp.lhs != nil) {
|
||||
if (varp.lhs.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(varp.lhs.str);
|
||||
if (ps > 0) { esz = ps; }
|
||||
else { esz = slotsize(c, varp.lhs); };
|
||||
} else {
|
||||
esz = slotsize(c, varp.lhs);
|
||||
};
|
||||
};
|
||||
if (esz < 1) { esz = 1; };
|
||||
let velemtagged: bool = istaggedtype(c, varp.lhs);
|
||||
let velemstr: bool = isstrtype(c, varp.lhs);
|
||||
@@ -3079,7 +3097,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
// pops have finished (so they don't clobber RDI). The dest off
|
||||
// is either the receive site's slot (c.sretdestoff, propagated
|
||||
// from cglet / cgassign ident) or the per-fn @sretscr discard
|
||||
// slot reserved by scanlocals.
|
||||
// slot, sized at first use per #15/#26c.
|
||||
let sretcs: i32 = callsretsize(c, n);
|
||||
let sretcalloff: i32 = 0;
|
||||
if (sretcs > 0) {
|
||||
@@ -3088,7 +3106,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
c.sretdestoff = 0;
|
||||
} else {
|
||||
sretcalloff = localadd(c, "@sretscr",
|
||||
c.sretscrsz, nil);
|
||||
sretcs, nil);
|
||||
};
|
||||
};
|
||||
// Pop forward. Float args were pushed as 8 bytes from X0 via
|
||||
@@ -3242,8 +3260,9 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
// on the forwarding branch.
|
||||
if (sretcs > 0) {
|
||||
if (c.sretforward != 0) {
|
||||
let sretargoff: i32 = localfind(c, "@sretarg");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(c.sretargoff: i64);
|
||||
emitoff(sretargoff: i64);
|
||||
emitline("(BP), DI\n");
|
||||
c.sretforward = 0;
|
||||
} else {
|
||||
@@ -3608,12 +3627,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// str / scalar / subset / nullable variants uniformly),
|
||||
// then compute &arr[i] and byte-copy. The scratch
|
||||
// (@tagscr) is reused across all tagged-arr stores in
|
||||
// the function and counted once in scanlocals.
|
||||
// the function; first-use sizes the slot (#15/#26c).
|
||||
if (elemtn != nil) {
|
||||
if (istaggedtype(c, elemtn)) {
|
||||
let slot_sz: i32 = slotsize(c, elemtn);
|
||||
let scroff: i32 = localadd(c, "@tagscr",
|
||||
c.tagscrsz, nil);
|
||||
slot_sz, nil);
|
||||
// Pre-zero scratch (matches push helper).
|
||||
emitline("\tXORQ\tAX, AX\n");
|
||||
let zz: i32 = 0;
|
||||
|
||||
Reference in New Issue
Block a user