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:
@@ -172,6 +172,25 @@ fn cgtrytupleshift(c: *cgen, n: *node) bool = {
|
||||
// Success tag = 0 (#216 tracks the legacy/flag-aware success-tag
|
||||
// divergence — out of scope here, success check stays `CMPQ $0`).
|
||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
// #38b residuals (rule 7): the cursor read below cannot see an
|
||||
// sret-classified call result (AX = dest pointer), and the
|
||||
// propagate-RET cannot speak an sret-classified enclosing return
|
||||
// (the caller reads memory, not the cursor). #40-family follow-ups.
|
||||
// Mirrors cstage cgen.c N_TRYPROP gates.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, n.lhs) > 0) {
|
||||
let m38p: str = "#38b: `?` on an sret-class call result unwired (mem-based unwrap is a #40-family follow-up)\n";
|
||||
os.write(2, m38p.ptr, m38p.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
if (sretretsize(c, c.fnret) > 0) {
|
||||
let m38q: str = "#38b: `?` propagation into a >32B tagged return unwired (sret error-propagate is a #40-family follow-up)\n";
|
||||
os.write(2, m38q.ptr, m38q.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgexpr(c, n.lhs);
|
||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||
let cl: str = mklabel(c, "tryprop_ok");
|
||||
@@ -290,6 +309,16 @@ fn cgtryprop(c: *cgen, n: *node) void = {
|
||||
// cgtryunw — `e!` aborts on the error variant via exit(1). Legacy
|
||||
// semantics (success tag = 0).
|
||||
fn cgtryunw(c: *cgen, n: *node) void = {
|
||||
// #38b residual (rule 7): see the cgtryprop twin.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, n.lhs) > 0) {
|
||||
let m38u: str = "#38b: `!` on an sret-class call result unwired (mem-based unwrap is a #40-family follow-up)\n";
|
||||
os.write(2, m38u.ptr, m38u.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.lhs);
|
||||
let cl: str = mklabel(c, "tryunw_ok");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
@@ -359,6 +388,18 @@ fn cgtypetest(c: *cgen, n: *node) void = {
|
||||
// stored via *i32 in this context — direct assignment of the
|
||||
// local works, indirection through &scrutoff drops sign bits.
|
||||
let lhs: *node = n.lhs;
|
||||
// #38b residual (rule 7): an sret-class call result leaves AX =
|
||||
// dest pointer, not the tag — mem-based test is a #40-family
|
||||
// follow-up. Mirrors cstage cgen.c N_TYPETEST gate.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, lhs) > 0) {
|
||||
let m38t: str = "#38b: `is` on an sret-class call result unwired (#40-family follow-up)\n";
|
||||
os.write(2, m38t.ptr, m38t.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
let scrutoff: i32 = 0;
|
||||
let scrutt: *node = nil;
|
||||
if (lhs != nil) {
|
||||
@@ -447,6 +488,18 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
||||
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||
// Slot resolution inlined; see cgtypetest comment.
|
||||
let lhs: *node = n.lhs;
|
||||
// #38b residual (rule 7): the spill below reads the cursor, which
|
||||
// an sret-class call result never fills. Mirrors cstage cgen.c
|
||||
// N_TYPEASSERT gate.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_CALL) {
|
||||
if (callsretsize(c, lhs) > 0) {
|
||||
let m38a: str = "#38b: `as` on an sret-class call result unwired (#40-family follow-up)\n";
|
||||
os.write(2, m38a.ptr, m38a.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
let scrutoff: i32 = 0;
|
||||
let scrutt: *node = nil;
|
||||
if (lhs != nil) {
|
||||
@@ -1882,6 +1935,20 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
scrutt = matchscrutt(c, scrut);
|
||||
let spillsz: i32 = matchspillsz(c, scrutt);
|
||||
scrutoff = localalloc(c, "@match_spill", spillsz, nil);
|
||||
// #38b: sret-classified tagged call scrutinee — pass
|
||||
// the scrut slot itself as the sret dest and skip the
|
||||
// cursor spill; downstream tag dispatch / case-let
|
||||
// binds already read the slot from memory. Mirrors
|
||||
// cstage cgen.c N_MATCH.
|
||||
let msret: i32 = 0;
|
||||
if (scrut.kind == nkind.N_CALL) {
|
||||
msret = callsretsize(c, scrut);
|
||||
};
|
||||
if (msret > 0) {
|
||||
c.sretdestoff = scrutoff;
|
||||
cgexpr(c, scrut);
|
||||
c.sretdestoff = 0;
|
||||
} else {
|
||||
cgexpr(c, scrut);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(scrutoff: i64);
|
||||
@@ -1906,6 +1973,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
@@ -5243,12 +5311,68 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, lhs.str);
|
||||
if (lc != nil) {
|
||||
if (istaggedtype(c, lc.tnode)) {
|
||||
// #38b: an sret-classified tagged CALL
|
||||
// result is in memory, not the cursor —
|
||||
// an exact-type reassign sret's into the
|
||||
// local's own slot; a widening receive
|
||||
// needs mem-to-mem tag-remap (#40).
|
||||
// Mirrors cstage cgen.c N_ASSIGN tagged
|
||||
// arm + the generic sret receive.
|
||||
let asret: i32 = 0;
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_CALL) {
|
||||
asret = callsretsize(c, n.rhs);
|
||||
};
|
||||
};
|
||||
if (asret > 0) {
|
||||
let aru: *tinfo = n.rhs.type_: *tinfo;
|
||||
for (aru != nil && aru.kind == tykind.TY_NAMED) {
|
||||
aru = aru.under;
|
||||
};
|
||||
let alu: *tinfo = lc.tnode.type_: *tinfo;
|
||||
for (alu != nil && alu.kind == tykind.TY_NAMED) {
|
||||
alu = alu.under;
|
||||
};
|
||||
let aexact: bool = false;
|
||||
if (aru != nil && aru == alu) { aexact = true; }
|
||||
else {
|
||||
if (typeeq(n.rhs.type_: *tinfo,
|
||||
lc.tnode.type_: *tinfo)) {
|
||||
aexact = true;
|
||||
};
|
||||
};
|
||||
if (!aexact) {
|
||||
let m40d: str = "#40: sret-class call result cannot be widened into a tagged slot (mem-to-mem widen unwired)\n";
|
||||
os.write(2, m40d.ptr, m40d.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
c.sretdestoff = lc.off;
|
||||
cgexpr(c, n.rhs);
|
||||
c.sretdestoff = 0;
|
||||
return;
|
||||
};
|
||||
let lsz: i32 = slotsize(c, lc.tnode);
|
||||
cgwidentaggedstore(c, lc.tnode.type_: *tinfo,
|
||||
n.rhs, "BP", lc.off, lsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
// #38b: sret receive into a tagged GLOBAL
|
||||
// lvalue unwired (rule 7; cstage twin fatals).
|
||||
if (lc == nil && n.rhs != nil) {
|
||||
if (n.rhs.kind == nkind.N_CALL) {
|
||||
let gru: *tinfo = lhs.type_: *tinfo;
|
||||
for (gru != nil && gru.kind == tykind.TY_NAMED) {
|
||||
gru = gru.under;
|
||||
};
|
||||
if (gru != nil && gru.kind == tykind.TY_TAGGED
|
||||
&& callsretsize(c, n.rhs) > 0) {
|
||||
let m38g: str = "#38b: sret receive into a tagged GLOBAL lvalue unwired\n";
|
||||
os.write(2, m38g.ptr, m38g.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user