wcc+w6c_ww: >48B tagged by-value args — MEMORY-class two-phase push (#38b)
Task #19 (the #38b residual surfaced by FC2 evidence): a tagged arg whose slot exceeds the 6-reg convention (>48B) is MEMORY-class per ref/qbe/amd64/sysv.c:80-85 (inmem) / :411-426 (stack blit). Caller stages the whole slot below every register-class word (two-phase push, rightmost-first, leftmost mem arg at 16(BP)); callee registers the param in place at positive BP offsets with zero prologue bytes; the merged slot count feeds the existing caller-cleanup ADDQ. Argument-side mirror of the #38 tagged-sret fix, same classify machinery (tagged_memarg_size / taggedmemargsize beside their register-class siblings). Pre-fix, the exact-typed arg loud-stopped on both stages, but WIDENING a concrete variant into a >48B param slipped the old guard silently — cstage pushed one scalar word while wwstage emitted an uncapped greedy stitch (wrong on both AND cs≠ww, gate-blind). Widen sources now route through the @tagscr scratch for mem slots. Loud boundaries kept (rule 7), each with its own diagnostic: sret-class tagged CALL result as mem-arg source (#40-family follow-up), global tagged let (task #25, broken at any size pre-existing), >48B variadic element, and mem-arg + register- overflow mixing (caller check + callee prologue mirror). Single commit: caller staging, callee receive, and both stages are one inseparable ABI class — landing any half alone breaks byte-id or runtime correctness (the #38 flip precedent); test/929 (15 table-driven rows: 56B/64B slots, widen-slip pin, source shapes, mixed orders both ways, two-mem call, 200k-call loop, 48B-boundary absence pin byte-id'd vs master, 5 reject rows pinning the exact per-guard diagnostic on both stages) rides with it.
This commit is contained in:
@@ -5144,6 +5144,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
if (esz < 1) { esz = 1; };
|
||||
// #38b: a >48B tagged variadic ELEMENT would
|
||||
// need the memory convention inside the vararg
|
||||
// gather buffer — unwired (rule 7). cstage twin
|
||||
// guards before its v_is_tagged gather.
|
||||
if (velem != nil) {
|
||||
if (taggedmemargsize(velem.type_: *tinfo) > 0) {
|
||||
let mv: str = "#38b: >48B tagged variadic element unwired\n";
|
||||
os.write(2, mv.ptr, mv.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
let velemtagged: bool = istaggedtype(c, velem);
|
||||
let velemstr: bool = isstrtype(c, velem);
|
||||
let velemslice: bool = isslicetype(c, velem);
|
||||
@@ -5255,7 +5266,12 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
let nargs: i32 = pushargsrev(c, n.list, calleeparams);
|
||||
// #38b: two-phase push — MEMORY-class (>48B tagged) args staged
|
||||
// first so they sit BELOW every register-class word; the pop loop
|
||||
// drains a strict prefix and never touches them. memwords feeds
|
||||
// the caller-cleanup ADDQ (with the mix guard below).
|
||||
let memwords: i32 = pushargsrev(c, n.list, calleeparams, true);
|
||||
let nargs: i32 = pushargsrev(c, n.list, calleeparams, false);
|
||||
// sret call (#23): callee returns plain TY_STRUCT > 24B. The
|
||||
// dest pointer lands in RDI; start intidx at 1 to skip RDI in
|
||||
// the user-arg pop loop and emit `LEAQ off(BP), DI` AFTER all
|
||||
@@ -5291,9 +5307,31 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
if (sretcs > 0) { intidx = 1; };
|
||||
let fpidx: i32 = 0;
|
||||
let a: *node = n.list;
|
||||
let dparam: *node = calleeparams;
|
||||
let popped: i32 = 0;
|
||||
let stackslots: i32 = 0;
|
||||
for (a != nil) {
|
||||
// #38b: MEMORY-class arg — its words sit below the pop
|
||||
// region and stay on the stack for the callee; nothing to
|
||||
// drain. Same param-keyed-else-arg-keyed detection as
|
||||
// pushargsrev (a widened concrete arg is mem-class only
|
||||
// via its param).
|
||||
let dmemsz: i32 = 0;
|
||||
if (dparam != nil) { if (dparam.kind == nkind.N_PARAM) {
|
||||
if (dparam.op != tkind.TK_ELLIPSIS) {
|
||||
if (dparam.lhs != nil) {
|
||||
dmemsz = taggedmemargsize(dparam.lhs.type_: *tinfo);
|
||||
};
|
||||
};
|
||||
}; };
|
||||
if (dmemsz == 0) {
|
||||
dmemsz = taggedmemargsize(a.type_: *tinfo);
|
||||
};
|
||||
if (dmemsz > 0) {
|
||||
if (dparam != nil) { dparam = dparam.next; };
|
||||
a = a.next;
|
||||
continue;
|
||||
};
|
||||
let fk: i32 = 0;
|
||||
if (a != nil) {
|
||||
let at: *tinfo = a.type_: *tinfo;
|
||||
@@ -5443,6 +5481,7 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
if (dparam != nil) { dparam = dparam.next; };
|
||||
a = a.next;
|
||||
};
|
||||
// Drain any remaining slots that the arg-walker didn't account
|
||||
@@ -5461,6 +5500,17 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
// #38b: MEMORY-class args and register-overflow spill words cannot
|
||||
// coexist — the callee's positive-BP cursor walks params in
|
||||
// declaration order, but the residual region puts spill words
|
||||
// below every mem copy. Loud-stop (rule 7); cgfnparams holds the
|
||||
// mirror check. The merged count feeds the caller-cleanup ADDQ.
|
||||
if (memwords > 0 && stackslots > 0) {
|
||||
let mm: str = "#38b: >48B tagged arg mixed with register-overflow stack args unwired\n";
|
||||
os.write(2, mm.ptr, mm.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
stackslots += memwords;
|
||||
// `callee` is already in scope from line 2827; reuse it. Pre-#32
|
||||
// silent-redecl masked the second `let callee` here as a no-op
|
||||
// (same value, same fn-body scope post-#27).
|
||||
|
||||
Reference in New Issue
Block a user