w6c+w6c_ww: tagged sret for slot>32B returns (fix #38)
A tagged-union RETURN rides a fixed AX(tag)+DX/CX/R8 cursor (TUPLE_GPCAP eightbytes = 32B slot); wider slots were silently truncated at the return crossing — payload word 4+ built in the callee frame and died there, byte-identical on both stages (gate-blind). Blocks regex fold-2a ((regex | error | nomem) = 64B slot). Classifier: cg_sret_retsize / sretretsize gain a TY_TAGGED arm (<= TUPLE_GPCAP*8 stays register-ABI — the (str|nomem)/(s3|bool) 32B boundary class is pinned unchanged byte-for-byte vs master). Callee: cgreturn writes the slot through *(@sretarg) via the existing widener non-BP base (bare return stores the void tag); exact-type 'return f();' rides the #9 sret-forward. Receive: let/assign/discard reuse the generic #23/#10 sret protocol; the match scrutinee passes its spill slot as the sret dest (tagged-specific, no tuple precedent). This could NOT land as a gate-first interim loud-stop (the planned #38a): lib/errors/errors.ww errno() already returns a 40B (errors.error) slot in-tree — the cgenstmt.ww-documented #222 latent — so a bare gate breaks the build. errno graduates to sret here instead; errnotest pins it at runtime (its cstage run; the wwstage run was already failing at master via an unrelated pre-existing indirect-call arg-classification divergence, reported separately) and test/926's errno-shaped row reads the previously-dropped tail word on both stages. The unwired cursor consumers of an sret-class call result loud-stop (rule 7) rather than read a cursor the callee no longer fills: widening forward/receive ((A|B)->(A|B|C) mem-to-mem tag-remap, filed #40), ?/!/is/as operands, argument position, and the >48B tagged-arg class both stages previously mishandled silently. One-class-one-commit per the #133 carve-out: post-flip those consumers would read AX (now the dest pointer) as the tag — a gates-trailing commit would leave a silently-wrong bisect point, so the flip and its gates are not separable. test/926: 15 rows — 56B regex-shaped round-trips (literal/local/ assign/match-scrutinee/forward/str-variant/multi-call), 40B repro + bare-return-void, the errno-shaped tail-read graduation row, 32B boundary rows pinned register-ABI by asm sentinel, and 3 loud-stop rows pinned as build failures on both stages.
This commit is contained in:
@@ -104,6 +104,27 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
let nextparam: *node = nil;
|
||||
if (param != nil) { nextparam = param.next; };
|
||||
let rest: i32 = pushargsrev(c, arg.next, nextparam);
|
||||
// #38b residual (rule 7): a tagged arg slot past the 6-reg arg
|
||||
// capacity has no push shape — cstage tagged_arg_size returns 0
|
||||
// ("too large") and both stages fell to divergent silent pushes
|
||||
// (cs one word, ww slot words). Mirrors cstage cgcall's gate.
|
||||
{
|
||||
let a48: *tinfo = arg.type_: *tinfo;
|
||||
for (a48 != nil && a48.kind == tykind.TY_NAMED) {
|
||||
a48 = a48.under;
|
||||
};
|
||||
if (a48 != nil) {
|
||||
if (a48.kind == tykind.TY_TAGGED && a48.nullable == 0) {
|
||||
// sizelint-ok: 6 SysV int arg regs (DI..R9) x
|
||||
// 8B words = the cstage tagged_arg_size cap.
|
||||
if (a48.size: i32 > 6 * 8) {
|
||||
let m48: str = "#38b: tagged arg exceeds the register arg capacity (>48B slot) — unwired\n";
|
||||
os.write(2, m48.ptr, m48.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Implicit widening from a concrete variant to a tagged-union
|
||||
// parameter slot. Skips when the arg is already a tagged local
|
||||
// (line 121's slice-or-tagged shortcut handles that).
|
||||
@@ -650,6 +671,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
// tag first. Mirrors cstage at cmd/w6c/cgen.c:4373-4387.
|
||||
let tcs: i32 = taggedcallslot(c, arg);
|
||||
if (tcs > 0) {
|
||||
// #38b residual (rule 7): an sret-class call result is in
|
||||
// memory, not the cursor — the @aggargscr-style receive-then-
|
||||
// push is the #40-family follow-up. Mirrors cstage cgen.c
|
||||
// cgcall tagged arg-push gate.
|
||||
if (callsretsize(c, arg) > 0) {
|
||||
let m38r: str = "#38b: >32B tagged call result as a call argument unwired (#40-family follow-up)\n";
|
||||
os.write(2, m38r.ptr, m38r.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (tcs > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||
if (tcs > 16) { emitline("\tPUSHQ\tCX\n"); };
|
||||
if (tcs > 8) { emitline("\tPUSHQ\tDX\n"); };
|
||||
@@ -1403,6 +1433,18 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
||||
r = r.lhs;
|
||||
if (r == nil) { return 0; };
|
||||
};
|
||||
// #38: a tagged union rides AX(tag)+DX/CX/R8 = TUPLE_GPCAP
|
||||
// eightbytes; a wider slot was silently truncated (payload word
|
||||
// 4+ died in the callee frame). The ≤cap boundary is load-bearing:
|
||||
// (str|nomem)-shaped 32B slots MUST stay register-ABI or every
|
||||
// such consumer in the tree flips. Nullable folds to one word.
|
||||
// Mirrors cstage cg_sret_retsize TY_TAGGED arm.
|
||||
if (istaggedtype(c, r)) {
|
||||
if (isnullabletype(r)) { return 0; };
|
||||
let tsz38: i32 = slotsize(c, r);
|
||||
if (tsz38 <= TUPLE_GPCAP * 8) { return 0; };
|
||||
return tsz38;
|
||||
};
|
||||
if (r.kind == nkind.N_TTUPLE) {
|
||||
// #10: over-cap tuple → sret. Walk the element TYPE nodes
|
||||
// (pt.lhs) over the SAME caps the SEND/receive use; a float =
|
||||
@@ -2961,6 +3003,17 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
ck += 8;
|
||||
};
|
||||
} else {
|
||||
// #38b: an sret-classified call result is in
|
||||
// memory (AX = dest pointer), not the cursor —
|
||||
// the spill below would store the pointer as
|
||||
// the payload. Mem-to-mem widen is #40.
|
||||
if (src.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, src) > 0) {
|
||||
let m40a: str = "#40: sret-class call result cannot be cursor-widened into a tagged slot (mem-to-mem widen unwired)\n";
|
||||
os.write(2, m40a.ptr, m40a.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
cgexpr(c, src);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8): i64);
|
||||
@@ -3024,6 +3077,15 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
// of tagged element). R8 carries the 4th word for slice-payload
|
||||
// variants (slot 32B).
|
||||
if (rhstaggedabicall(c, src)) {
|
||||
// #38b: an sret-classified call result is in memory (AX =
|
||||
// dest pointer), not the cursor. #40.
|
||||
if (src.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, src) > 0) {
|
||||
let m40b: str = "#40: sret-class call result cannot be cursor-widened into a tagged slot (mem-to-mem widen unwired)\n";
|
||||
os.write(2, m40b.ptr, m40b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
cgexpr(c, src);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(slot_off: i64);
|
||||
@@ -3212,7 +3274,7 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
// str IS []u8 and a slice is the
|
||||
// same 3-word {ptr,len,cap} header
|
||||
// from cgexpr's AX/BX/CX (#1/Phase
|
||||
// 3). Slice arm rides #38's regex-
|
||||
// 3). Slice arm rides #38b's regex-
|
||||
// shaped consumer — the prior str-
|
||||
// only gate dropped .len/.cap (#24
|
||||
// gap's widener twin).
|
||||
|
||||
Reference in New Issue
Block a user