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:
@@ -663,6 +663,63 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #38b: sret-classified tagged return (slot > the
|
||||
// AX/DX/CX/R8 cursor) — write through *(@sretarg) and
|
||||
// return the dest pointer. Three shapes mirror cstage
|
||||
// cgen.c N_RETURN #38b: exact-type N_CALL forward
|
||||
// (c.sretforward), widening from a >32B tagged source
|
||||
// (#40 loud-stop), everything else through
|
||||
// cgwidentaggedstore's non-BP base.
|
||||
if (sretretsize(c, c.fnret) > 0) {
|
||||
let sa38v: i32 = localfind(c, "@sretarg");
|
||||
if (forwardtagged) {
|
||||
// exact type, but only an N_CALL source
|
||||
// sret's into outer's dest; a cursor
|
||||
// source (N_INDEX/N_DOT) can't carry
|
||||
// >32B (rule 7, #38b residual).
|
||||
if (rhs.kind != nkind.N_CALL) {
|
||||
let m38d: str = "#38b: >32B tagged return from a cursor source (N_INDEX/N_DOT) unsupported\n";
|
||||
os.write(2, m38d.ptr, m38d.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
c.sretforward = 1;
|
||||
cgexpr(c, rhs);
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(sa38v: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
let ru38: *tinfo = rhs.type_: *tinfo;
|
||||
for (ru38 != nil && ru38.kind == tykind.TY_NAMED) {
|
||||
ru38 = ru38.under;
|
||||
};
|
||||
if (ru38 != nil) {
|
||||
if (ru38.kind == tykind.TY_TAGGED
|
||||
&& rhs.kind != nkind.N_IDENT
|
||||
&& ru38.size: i32 > TUPLE_GPCAP * 8) {
|
||||
let m38e: str = "#40: widening tagged return-forward of a >32B source needs mem-to-mem tag-remap (unwired)\n";
|
||||
os.write(2, m38e.ptr, m38e.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(sa38v: i64);
|
||||
emitline("(BP), BX\n");
|
||||
cgwidentaggedstore(c, c.fnret.type_: *tinfo, rhs,
|
||||
"BX", 0, slotsize(c, c.fnret));
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(sa38v: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
// Struct payload or tagged-subset return — materialise
|
||||
// the widened value in scratch via cgwidentaggedstore
|
||||
// (handles tag remap and zero pad), then load AX/DX/CX
|
||||
@@ -1438,6 +1495,30 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
// the void variant: emit its tag. Payload is undefined
|
||||
// (void has size 0). Otherwise zero AX for determinism.
|
||||
if (istaggedtype(c, c.fnret)) {
|
||||
// #38b: an sret-classified tagged return (slot > the
|
||||
// AX/DX/CX/R8 cursor) writes the void-variant tag
|
||||
// through *(@sretarg) and returns the dest pointer —
|
||||
// the cursor can't carry the slot and the caller reads
|
||||
// memory. Mirrors cstage cgen.c N_RETURN bare arm.
|
||||
if (sretretsize(c, c.fnret) > 0) {
|
||||
let sa38: i32 = localfind(c, "@sretarg");
|
||||
let vidx38: i32 = voidvariantindex(c.fnret);
|
||||
if (vidx38 < 0) { vidx38 = 0; };
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(sa38: i64);
|
||||
emitline("(BP), BX\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(vidx38: i64);
|
||||
emitline(", (BX)\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(sa38: i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
c.lastwasreturn = 1;
|
||||
return;
|
||||
};
|
||||
if (isnullabletype(c.fnret)) {
|
||||
// null = void variant; AX = 0.
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
@@ -1855,10 +1936,44 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||
// ABI call), struct payload (literal/ident), str payload,
|
||||
// scalar payload — with tag remap for tagged-subset widening.
|
||||
//
|
||||
// #38b: an sret-classified tagged CALL result is in memory,
|
||||
// not the cursor — an exact-type receive falls through to the
|
||||
// generic sret receive below (the let's slot IS the dest); a
|
||||
// widening receive needs mem-to-mem tag-remap (#40, unwired).
|
||||
// Mirrors cstage cgen.c N_LET tagged arm.
|
||||
if (istaggedtype(c, tn)) {
|
||||
cgwidentaggedstore(c, tn.type_: *tinfo, rhs, "BP", off, sz);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
let letsret: i32 = 0;
|
||||
if (rhs.kind == nkind.N_CALL) {
|
||||
letsret = callsretsize(c, rhs);
|
||||
};
|
||||
if (letsret == 0) {
|
||||
cgwidentaggedstore(c, tn.type_: *tinfo, rhs,
|
||||
"BP", off, sz);
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
let lru: *tinfo = rhs.type_: *tinfo;
|
||||
for (lru != nil && lru.kind == tykind.TY_NAMED) {
|
||||
lru = lru.under;
|
||||
};
|
||||
let llu: *tinfo = tn.type_: *tinfo;
|
||||
for (llu != nil && llu.kind == tykind.TY_NAMED) {
|
||||
llu = llu.under;
|
||||
};
|
||||
let exact38: bool = false;
|
||||
if (lru != nil && lru == llu) { exact38 = true; }
|
||||
else {
|
||||
if (typeeq(rhs.type_: *tinfo, tn.type_: *tinfo)) {
|
||||
exact38 = true;
|
||||
};
|
||||
};
|
||||
if (!exact38) {
|
||||
let m40c: str = "#40: sret-class call result cannot be widened into a tagged slot (mem-to-mem widen unwired)\n";
|
||||
os.write(2, m40c.ptr, m40c.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// fall through to the generic sret receive below.
|
||||
};
|
||||
// 32B tuple init for `let t: (scalar, str) = call()` /
|
||||
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
|
||||
|
||||
Reference in New Issue
Block a user