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:
@@ -15796,6 +15796,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).
|
||||
@@ -16342,6 +16363,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"); };
|
||||
@@ -17095,6 +17125,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 =
|
||||
@@ -18653,6 +18695,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);
|
||||
@@ -18716,6 +18769,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);
|
||||
@@ -18904,7 +18966,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).
|
||||
@@ -19970,6 +20032,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");
|
||||
@@ -20088,6 +20169,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");
|
||||
@@ -20157,6 +20248,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) {
|
||||
@@ -20245,6 +20348,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) {
|
||||
@@ -21680,6 +21795,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);
|
||||
@@ -21704,6 +21833,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
@@ -25041,12 +25171,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);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -28934,6 +29120,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
|
||||
@@ -29709,6 +29952,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");
|
||||
@@ -30126,10 +30393,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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -15796,6 +15796,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).
|
||||
@@ -16342,6 +16363,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"); };
|
||||
@@ -17095,6 +17125,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 =
|
||||
@@ -18653,6 +18695,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);
|
||||
@@ -18716,6 +18769,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);
|
||||
@@ -18904,7 +18966,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).
|
||||
@@ -19970,6 +20032,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");
|
||||
@@ -20088,6 +20169,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");
|
||||
@@ -20157,6 +20248,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) {
|
||||
@@ -20245,6 +20348,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) {
|
||||
@@ -21680,6 +21795,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);
|
||||
@@ -21704,6 +21833,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let endl: str = mklabel(c, "match_end");
|
||||
@@ -25041,12 +25171,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);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -28934,6 +29120,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
|
||||
@@ -29709,6 +29952,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");
|
||||
@@ -30126,10 +30393,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