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:
7
Makefile
7
Makefile
@@ -279,6 +279,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_nested_union_widen_run \
|
$(BIN)/test_nested_union_widen_run \
|
||||||
$(BIN)/test_sret_struct_return \
|
$(BIN)/test_sret_struct_return \
|
||||||
$(BIN)/test_sret_struct_return_run \
|
$(BIN)/test_sret_struct_return_run \
|
||||||
|
$(BIN)/test_tagged_sret_run \
|
||||||
$(BIN)/test_global_sret_run \
|
$(BIN)/test_global_sret_run \
|
||||||
$(BIN)/test_sret_narrow_field \
|
$(BIN)/test_sret_narrow_field \
|
||||||
$(BIN)/test_sret_narrow_field_run \
|
$(BIN)/test_sret_narrow_field_run \
|
||||||
@@ -1164,6 +1165,12 @@ $(BIN)/test_sret_struct_return_run: test/wcc/925_sret_struct_return_run.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_tagged_sret_run: test/wcc/926_tagged_sret_run.c \
|
||||||
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
|
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||||
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_global_sret_run: test/wcc/940_global_sret_run.c \
|
$(BIN)/test_global_sret_run: test/wcc/940_global_sret_run.c \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||||
|
|||||||
232
cmd/w6c/cgen.c
232
cmd/w6c/cgen.c
@@ -322,6 +322,15 @@ cg_sret_retsize(Type *rt)
|
|||||||
if (rt == NULL) return 0;
|
if (rt == NULL) return 0;
|
||||||
if (rt->kind == TY_STRUCT)
|
if (rt->kind == TY_STRUCT)
|
||||||
return (int)rt->size <= 24 ? 0 : (int)rt->size;
|
return (int)rt->size <= 24 ? 0 : (int)rt->size;
|
||||||
|
/* #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. */
|
||||||
|
if (rt->kind == TY_TAGGED) {
|
||||||
|
if (rt->nullable) return 0;
|
||||||
|
return (int)rt->size <= TUPLE_GPCAP * 8 ? 0 : (int)rt->size;
|
||||||
|
}
|
||||||
/* #267: arrays ride the struct-return ABI — same ≤24 reg / >24 sret
|
/* #267: arrays ride the struct-return ABI — same ≤24 reg / >24 sret
|
||||||
* split. Pure-int element arrays only; no float-array-return
|
* split. Pure-int element arrays only; no float-array-return
|
||||||
* consumer exists, so struct_float_class stays struct-only. */
|
* consumer exists, so struct_float_class stays struct-only. */
|
||||||
@@ -2163,6 +2172,16 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
|||||||
amem(D_BP, write_off + 8 + k));
|
amem(D_BP, write_off + 8 + k));
|
||||||
}
|
}
|
||||||
} else {
|
} 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 == N_CALL
|
||||||
|
&& cg_sret_retsize(st) > 0)
|
||||||
|
fatal("#40: sret-class call result "
|
||||||
|
"cannot be cursor-widened into a "
|
||||||
|
"tagged slot (mem-to-mem widen "
|
||||||
|
"unwired)");
|
||||||
cgexpr(c, src, *locals_p);
|
cgexpr(c, src, *locals_p);
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
amem(D_BP, write_off + 8));
|
amem(D_BP, write_off + 8));
|
||||||
@@ -2196,6 +2215,12 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
|||||||
* variant-widen) so the unconditional store here is
|
* variant-widen) so the unconditional store here is
|
||||||
* safe even when the source variant has fewer payload
|
* safe even when the source variant has fewer payload
|
||||||
* words than the dst slot. */
|
* words than the dst slot. */
|
||||||
|
/* #38b: an sret-classified call result is in memory
|
||||||
|
* (AX = dest pointer), not the cursor. #40. */
|
||||||
|
if (src->kind == N_CALL && cg_sret_retsize(st) > 0)
|
||||||
|
fatal("#40: sret-class call result cannot be "
|
||||||
|
"cursor-widened into a tagged slot "
|
||||||
|
"(mem-to-mem widen unwired)");
|
||||||
cgexpr(c, src, *locals_p);
|
cgexpr(c, src, *locals_p);
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
amem(D_BP, write_off + 0));
|
amem(D_BP, write_off + 0));
|
||||||
@@ -2345,7 +2370,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
|||||||
? ftype->under : ftype;
|
? ftype->under : ftype;
|
||||||
/* str IS []u8 and a slice is the same 3-word
|
/* str IS []u8 and a slice is the same 3-word
|
||||||
* {ptr,len,cap} header from cgexpr's AX/BX/CX
|
* {ptr,len,cap} header from cgexpr's AX/BX/CX
|
||||||
* (#1/Phase 3). The slice arm rides #38's
|
* (#1/Phase 3). The slice arm rides #38b's
|
||||||
* regex-shaped consumer (slice fields inside a
|
* regex-shaped consumer (slice fields inside a
|
||||||
* union-payload struct literal); the prior
|
* union-payload struct literal); the prior
|
||||||
* str-only gate dropped .len/.cap via the
|
* str-only gate dropped .len/.cap via the
|
||||||
@@ -5626,17 +5651,36 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
/* Plain `r = expr;` where r is a tagged-union local.
|
/* Plain `r = expr;` where r is a tagged-union local.
|
||||||
* Delegates to cg_widen_tagged_store: covers nullable fold,
|
* Delegates to cg_widen_tagged_store: covers nullable fold,
|
||||||
* tagged→tagged (with tag remap), struct payload (ident or
|
* tagged→tagged (with tag remap), struct payload (ident or
|
||||||
* literal), str payload, and scalar payload. */
|
* literal), str payload, and scalar payload.
|
||||||
|
*
|
||||||
|
* #38b: an sret-classified tagged CALL result is in memory,
|
||||||
|
* not the cursor — an exact-type reassign falls through to
|
||||||
|
* the generic sret receive below; a widening receive needs
|
||||||
|
* mem-to-mem tag-remap (#40, unwired). */
|
||||||
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
||||||
&& n->lhs->type) {
|
&& n->lhs->type) {
|
||||||
Type *lt = n->lhs->type;
|
Type *lt = n->lhs->type;
|
||||||
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
|
||||||
if (lu && lu->kind == TY_TAGGED) {
|
if (lu && lu->kind == TY_TAGGED) {
|
||||||
int off = localfind(locals, n->lhs->str);
|
int rhs_sret_call = n->rhs
|
||||||
if (off == 0) break;
|
&& n->rhs->kind == N_CALL
|
||||||
cg_widen_tagged_store(c, &locals, lu, n->rhs,
|
&& cg_sret_retsize(n->rhs->type) > 0;
|
||||||
D_BP, off, (int)lu->size);
|
if (!rhs_sret_call) {
|
||||||
break;
|
int off = localfind(locals,
|
||||||
|
n->lhs->str);
|
||||||
|
if (off == 0) break;
|
||||||
|
cg_widen_tagged_store(c, &locals, lu,
|
||||||
|
n->rhs, D_BP, off, (int)lu->size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Type *ru = type_chase_named(n->rhs->type);
|
||||||
|
if (!(ru == lu || type_eq(n->rhs->type, lt)))
|
||||||
|
fatal("#40: sret-class call result "
|
||||||
|
"cannot be widened into a tagged "
|
||||||
|
"slot (mem-to-mem widen unwired)");
|
||||||
|
if (localfind(locals, n->lhs->str) == 0)
|
||||||
|
fatal("#38b: sret receive into a "
|
||||||
|
"tagged GLOBAL lvalue unwired");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Deref-target assignment `*p = v;`. The size of the store is
|
/* Deref-target assignment `*p = v;`. The size of the store is
|
||||||
@@ -7020,6 +7064,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
args[i], widen_sz[i]);
|
args[i], widen_sz[i]);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
/* #38b residual (rule 7): a tagged arg slot past the
|
||||||
|
* 6-reg arg capacity has no push shape —
|
||||||
|
* tagged_arg_size returns 0 ("too large") and the
|
||||||
|
* scalar default silently pushed ONE word. Loud-stop;
|
||||||
|
* symmetric ww gate in pushargsrev. */
|
||||||
|
{
|
||||||
|
Type *au = type_chase_named(args[i]->type);
|
||||||
|
if (au && au->kind == TY_TAGGED
|
||||||
|
&& !au->nullable
|
||||||
|
&& tagged_arg_size(args[i]->type) == 0)
|
||||||
|
fatal("#38b: tagged arg exceeds the "
|
||||||
|
"register arg capacity (>48B "
|
||||||
|
"slot) — unwired");
|
||||||
|
}
|
||||||
cgexpr(c, args[i], locals);
|
cgexpr(c, args[i], locals);
|
||||||
Type *tuparg_push = node_tuplearg(args[i]);
|
Type *tuparg_push = node_tuplearg(args[i]);
|
||||||
if (node_isfloat(args[i])) {
|
if (node_isfloat(args[i])) {
|
||||||
@@ -7052,6 +7110,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* values into arg-reg[1..]. Nullable (sz=8):
|
* values into arg-reg[1..]. Nullable (sz=8):
|
||||||
* AX holds the pointer, no value-word
|
* AX holds the pointer, no value-word
|
||||||
* registers — push just AX. */
|
* registers — push just AX. */
|
||||||
|
/* #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. */
|
||||||
|
if (args[i]->kind == N_CALL
|
||||||
|
&& cg_sret_retsize(args[i]->type) > 0)
|
||||||
|
fatal("#38b: >32B tagged call result "
|
||||||
|
"as a call argument unwired "
|
||||||
|
"(#40-family follow-up)");
|
||||||
int sz = tagged_arg_size(args[i]->type);
|
int sz = tagged_arg_size(args[i]->type);
|
||||||
if (sz > 24)
|
if (sz > 24)
|
||||||
ins1(c, A_PUSHQ, areg(D_R8));
|
ins1(c, A_PUSHQ, areg(D_R8));
|
||||||
@@ -7517,6 +7584,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* spill only that. */
|
* spill only that. */
|
||||||
sl_off = localoff(c, &locals, "@match_spill", slot_size,
|
sl_off = localoff(c, &locals, "@match_spill", slot_size,
|
||||||
cg_frame);
|
cg_frame);
|
||||||
|
if (s->kind == N_CALL && cg_sret_retsize(st) > 0) {
|
||||||
|
/* #38b: sret-classified tagged call — 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. */
|
||||||
|
cg_sret_dest_off = sl_off;
|
||||||
|
cgexpr(c, s, locals);
|
||||||
|
cg_sret_dest_off = 0;
|
||||||
|
} else {
|
||||||
cgexpr(c, s, locals);
|
cgexpr(c, s, locals);
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||||
if (!is_nullable) {
|
if (!is_nullable) {
|
||||||
@@ -7529,6 +7606,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins2(c, A_MOVQ, areg(D_R8),
|
ins2(c, A_MOVQ, areg(D_R8),
|
||||||
amem(D_BP, sl_off + 24));
|
amem(D_BP, sl_off + 24));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
char *end = mklabel(c, "match_end");
|
char *end = mklabel(c, "match_end");
|
||||||
/* Push the end label as the yield target for arm bodies. */
|
/* Push the end label as the yield target for arm bodies. */
|
||||||
@@ -7648,6 +7726,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* (any non-null), void variant is the error (null). The
|
* (any non-null), void variant is the error (null). The
|
||||||
* enclosing fn's null encoding is the same — RET with AX=0
|
* enclosing fn's null encoding is the same — RET with AX=0
|
||||||
* if propagating; otherwise leave AX as-is on success. */
|
* if propagating; otherwise leave AX as-is on success. */
|
||||||
|
/* #38b residuals (rule 7): the cursor read below cannot see
|
||||||
|
* an sret-classified call result (AX = dest pointer), and the
|
||||||
|
* propagate-RET below cannot speak an sret-classified
|
||||||
|
* enclosing return (the caller reads memory, not the
|
||||||
|
* cursor). Both are unwired follow-ups of #40's family. */
|
||||||
|
if (n->lhs && n->lhs->kind == N_CALL
|
||||||
|
&& cg_sret_retsize(n->lhs->type) > 0)
|
||||||
|
fatal("#38b: `?` on an sret-class call result "
|
||||||
|
"unwired (mem-based unwrap is a #40-family "
|
||||||
|
"follow-up)");
|
||||||
|
if (cg_sret_retsize(cg_ret_type) > 0)
|
||||||
|
fatal("#38b: `?` propagation into a >32B tagged "
|
||||||
|
"return unwired (sret error-propagate is a "
|
||||||
|
"#40-family follow-up)");
|
||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
if (u && u->kind == TY_NAMED) u = u->under;
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
@@ -7728,6 +7820,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
/* On error variant: exit(1) directly via the syscall.
|
/* On error variant: exit(1) directly via the syscall.
|
||||||
* Nullable: null = error; non-null = success (AX is the
|
* Nullable: null = error; non-null = success (AX is the
|
||||||
* pointer, ready to use). */
|
* pointer, ready to use). */
|
||||||
|
/* #38b residual (rule 7): see the N_TRYPROP twin. */
|
||||||
|
if (n->lhs && n->lhs->kind == N_CALL
|
||||||
|
&& cg_sret_retsize(n->lhs->type) > 0)
|
||||||
|
fatal("#38b: `!` on an sret-class call result "
|
||||||
|
"unwired (mem-based unwrap is a #40-family "
|
||||||
|
"follow-up)");
|
||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
if (u && u->kind == TY_NAMED) u = u->under;
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
@@ -7779,6 +7877,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
/* `e is T` — Compare scrutinee tag against T's variant index.
|
/* `e is T` — Compare scrutinee tag against T's variant index.
|
||||||
* Result is bool (0/1) in AX. Nullable: discriminator is
|
* Result is bool (0/1) in AX. Nullable: discriminator is
|
||||||
* pointer-vs-null, not a tag. */
|
* pointer-vs-null, not a tag. */
|
||||||
|
/* #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. */
|
||||||
|
if (n->lhs && n->lhs->kind == N_CALL
|
||||||
|
&& cg_sret_retsize(n->lhs->type) > 0)
|
||||||
|
fatal("#38b: `is` on an sret-class call result "
|
||||||
|
"unwired (#40-family follow-up)");
|
||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||||
if (u && u->kind == TY_NAMED) u = u->under;
|
if (u && u->kind == TY_NAMED) u = u->under;
|
||||||
@@ -7835,6 +7940,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
int slot_size = (u && u->kind == TY_TAGGED) ? (int)u->size : 16;
|
int slot_size = (u && u->kind == TY_TAGGED) ? (int)u->size : 16;
|
||||||
|
/* #38b residual (rule 7): the @asrt_spill below reads the
|
||||||
|
* cursor, which an sret-class call result never fills. */
|
||||||
|
if (s && s->kind == N_CALL && cg_sret_retsize(st) > 0)
|
||||||
|
fatal("#38b: `as` on an sret-class call result "
|
||||||
|
"unwired (#40-family follow-up)");
|
||||||
int sl_off = 0;
|
int sl_off = 0;
|
||||||
if (s && s->kind == N_IDENT && s->str) {
|
if (s && s->kind == N_IDENT && s->str) {
|
||||||
sl_off = localfind(locals, s->str);
|
sl_off = localfind(locals, s->str);
|
||||||
@@ -9327,14 +9437,43 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
/* #38b residual (rule 7): `let w: T = f()?;` / `f()!` where f
|
||||||
|
* returns an sret-classified tagged union — the unwrap would
|
||||||
|
* need a mem-based read of the sret slot. The N_LET arms
|
||||||
|
* below have no TRYUNW/TRYPROP shape for a >8B lt, so the
|
||||||
|
* rhs was SILENTLY dropped (no CALL emitted; wwstage's cglet
|
||||||
|
* default does cgexpr and hits the cgtryunw/cgtryprop gates —
|
||||||
|
* this keeps acceptance symmetric, rule 10). */
|
||||||
|
if (n->rhs
|
||||||
|
&& (n->rhs->kind == N_TRYUNW || n->rhs->kind == N_TRYPROP)
|
||||||
|
&& n->rhs->lhs && n->rhs->lhs->kind == N_CALL
|
||||||
|
&& cg_sret_retsize(n->rhs->lhs->type) > 0)
|
||||||
|
fatal("#38b: `?`/`!` on an sret-class call result "
|
||||||
|
"unwired (mem-based unwrap is a #40-family "
|
||||||
|
"follow-up)");
|
||||||
/* Tagged-union initialiser. Delegates to cg_widen_tagged_store,
|
/* Tagged-union initialiser. Delegates to cg_widen_tagged_store,
|
||||||
* which handles nullable fold, tagged→tagged (with tag remap
|
* which handles nullable fold, tagged→tagged (with tag remap
|
||||||
* when variant indices differ), struct payload (ident or
|
* when variant indices differ), struct payload (ident or
|
||||||
* literal — field-by-field at slot+8+field_off), str payload,
|
* literal — field-by-field at slot+8+field_off), str payload,
|
||||||
* and scalar payload (with zero-pad to the slot size). */
|
* and scalar payload (with zero-pad to the slot size).
|
||||||
|
*
|
||||||
|
* #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). */
|
||||||
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
||||||
cg_widen_tagged_store(c, locals, lu, n->rhs, D_BP, off, sz);
|
int rhs_sret_call = n->rhs->kind == N_CALL
|
||||||
break;
|
&& cg_sret_retsize(n->rhs->type) > 0;
|
||||||
|
if (!rhs_sret_call) {
|
||||||
|
cg_widen_tagged_store(c, locals, lu, n->rhs,
|
||||||
|
D_BP, off, sz);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Type *ru = type_chase_named(n->rhs->type);
|
||||||
|
if (!(ru == lu || type_eq(n->rhs->type, lt)))
|
||||||
|
fatal("#40: sret-class call result cannot be "
|
||||||
|
"widened into a tagged slot (mem-to-mem "
|
||||||
|
"widen unwired)");
|
||||||
}
|
}
|
||||||
/* Every slice initialiser routes here — fn-return, slice
|
/* Every slice initialiser routes here — fn-return, slice
|
||||||
* ident, slice param, and sub-slice `buf[lo:hi]`. cgexpr
|
* ident, slice param, and sub-slice `buf[lo:hi]`. cgexpr
|
||||||
@@ -9749,6 +9888,27 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
Type *rt = cg_ret_type;
|
Type *rt = cg_ret_type;
|
||||||
if (rt->kind == TY_NAMED) rt = rt->under;
|
if (rt->kind == TY_NAMED) rt = rt->under;
|
||||||
if (rt && rt->kind == TY_TAGGED) {
|
if (rt && rt->kind == TY_TAGGED) {
|
||||||
|
/* #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. */
|
||||||
|
if (cg_sret_retsize(rt) > 0) {
|
||||||
|
int tag = cg_tag_for_variant(rt, ty_void);
|
||||||
|
if (tag < 0) tag = 0;
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, cg_sret_arg_off),
|
||||||
|
areg(D_BX));
|
||||||
|
ins2(c, A_MOVQ, aimm(tag),
|
||||||
|
amem(D_BX, 0));
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, cg_sret_arg_off),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||||
|
ins1(c, A_POPQ, areg(D_BP));
|
||||||
|
ins0(c, A_RET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (rt->nullable) {
|
if (rt->nullable) {
|
||||||
/* bare `return;` is the void/null
|
/* bare `return;` is the void/null
|
||||||
* variant: emit AX = 0. */
|
* variant: emit AX = 0. */
|
||||||
@@ -9796,6 +9956,58 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
* arm below zeroed the whole value (never packed the
|
* arm below zeroed the whole value (never packed the
|
||||||
* operands). */
|
* operands). */
|
||||||
int istuple = vu && vu->kind == TY_TUPLE;
|
int istuple = vu && vu->kind == TY_TUPLE;
|
||||||
|
/* #38b: sret-classified tagged return (slot >
|
||||||
|
* the AX/DX/CX/R8 cursor). Three shapes:
|
||||||
|
* - exact-type N_CALL forward: inner sret's
|
||||||
|
* straight into outer's caller dest (#9
|
||||||
|
* shape, cg_sret_forward).
|
||||||
|
* - widening from an sret-class tagged source
|
||||||
|
* ((A|B)→(A|B|C) mem-to-mem tag-remap):
|
||||||
|
* unwired, loud-stop — #40.
|
||||||
|
* - everything else: cg_widen_tagged_store
|
||||||
|
* through *(@sretarg) (the widener already
|
||||||
|
* speaks non-BP bases, the #34 precedent),
|
||||||
|
* then return the dest pointer. */
|
||||||
|
if (cg_sret_retsize(rt) > 0) {
|
||||||
|
int sz = (int)rt->size;
|
||||||
|
if (passthrough) {
|
||||||
|
/* exact type but a cursor source
|
||||||
|
* (N_INDEX/N_DOT) can't carry
|
||||||
|
* >32B — loud-stop (rule 7,
|
||||||
|
* #38b residual). */
|
||||||
|
if (n->lhs->kind != N_CALL)
|
||||||
|
fatal("#38b: >32B tagged "
|
||||||
|
"return from a cursor "
|
||||||
|
"source (kind %d) "
|
||||||
|
"unsupported",
|
||||||
|
n->lhs->kind);
|
||||||
|
cg_sret_forward = 1;
|
||||||
|
cgexpr(c, n->lhs, *locals);
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, cg_sret_arg_off),
|
||||||
|
areg(D_AX));
|
||||||
|
} else if (istagged
|
||||||
|
&& n->lhs->kind != N_IDENT
|
||||||
|
&& (int)vu->size > TUPLE_GPCAP * 8) {
|
||||||
|
fatal("#40: widening tagged "
|
||||||
|
"return-forward of a >32B "
|
||||||
|
"source needs mem-to-mem "
|
||||||
|
"tag-remap (unwired)");
|
||||||
|
} else {
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, cg_sret_arg_off),
|
||||||
|
areg(D_BX));
|
||||||
|
cg_widen_tagged_store(c, locals,
|
||||||
|
rt, n->lhs, D_BX, 0, sz);
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, cg_sret_arg_off),
|
||||||
|
areg(D_AX));
|
||||||
|
}
|
||||||
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||||
|
ins1(c, A_POPQ, areg(D_BP));
|
||||||
|
ins0(c, A_RET);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (rt->nullable) {
|
if (rt->nullable) {
|
||||||
cgexpr(c, n->lhs, *locals);
|
cgexpr(c, n->lhs, *locals);
|
||||||
} else if (passthrough) {
|
} else if (passthrough) {
|
||||||
|
|||||||
@@ -15796,6 +15796,27 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
let nextparam: *node = nil;
|
let nextparam: *node = nil;
|
||||||
if (param != nil) { nextparam = param.next; };
|
if (param != nil) { nextparam = param.next; };
|
||||||
let rest: i32 = pushargsrev(c, arg.next, nextparam);
|
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
|
// Implicit widening from a concrete variant to a tagged-union
|
||||||
// parameter slot. Skips when the arg is already a tagged local
|
// parameter slot. Skips when the arg is already a tagged local
|
||||||
// (line 121's slice-or-tagged shortcut handles that).
|
// (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.
|
// tag first. Mirrors cstage at cmd/w6c/cgen.c:4373-4387.
|
||||||
let tcs: i32 = taggedcallslot(c, arg);
|
let tcs: i32 = taggedcallslot(c, arg);
|
||||||
if (tcs > 0) {
|
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 > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||||
if (tcs > 16) { emitline("\tPUSHQ\tCX\n"); };
|
if (tcs > 16) { emitline("\tPUSHQ\tCX\n"); };
|
||||||
if (tcs > 8) { emitline("\tPUSHQ\tDX\n"); };
|
if (tcs > 8) { emitline("\tPUSHQ\tDX\n"); };
|
||||||
@@ -17095,6 +17125,18 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
|||||||
r = r.lhs;
|
r = r.lhs;
|
||||||
if (r == nil) { return 0; };
|
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) {
|
if (r.kind == nkind.N_TTUPLE) {
|
||||||
// #10: over-cap tuple → sret. Walk the element TYPE nodes
|
// #10: over-cap tuple → sret. Walk the element TYPE nodes
|
||||||
// (pt.lhs) over the SAME caps the SEND/receive use; a float =
|
// (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;
|
ck += 8;
|
||||||
};
|
};
|
||||||
} else {
|
} 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);
|
cgexpr(c, src);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff((slot_off + 8): i64);
|
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
|
// of tagged element). R8 carries the 4th word for slice-payload
|
||||||
// variants (slot 32B).
|
// variants (slot 32B).
|
||||||
if (rhstaggedabicall(c, src)) {
|
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);
|
cgexpr(c, src);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(slot_off: i64);
|
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
|
// str IS []u8 and a slice is the
|
||||||
// same 3-word {ptr,len,cap} header
|
// same 3-word {ptr,len,cap} header
|
||||||
// from cgexpr's AX/BX/CX (#1/Phase
|
// 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-
|
// shaped consumer — the prior str-
|
||||||
// only gate dropped .len/.cap (#24
|
// only gate dropped .len/.cap (#24
|
||||||
// gap's widener twin).
|
// 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
|
// Success tag = 0 (#216 tracks the legacy/flag-aware success-tag
|
||||||
// divergence — out of scope here, success check stays `CMPQ $0`).
|
// divergence — out of scope here, success check stays `CMPQ $0`).
|
||||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
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);
|
cgexpr(c, n.lhs);
|
||||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||||
let cl: str = mklabel(c, "tryprop_ok");
|
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
|
// cgtryunw — `e!` aborts on the error variant via exit(1). Legacy
|
||||||
// semantics (success tag = 0).
|
// semantics (success tag = 0).
|
||||||
fn cgtryunw(c: *cgen, n: *node) void = {
|
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);
|
cgexpr(c, n.lhs);
|
||||||
let cl: str = mklabel(c, "tryunw_ok");
|
let cl: str = mklabel(c, "tryunw_ok");
|
||||||
emitline("\tCMPQ\t$0, AX\n");
|
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
|
// stored via *i32 in this context — direct assignment of the
|
||||||
// local works, indirection through &scrutoff drops sign bits.
|
// local works, indirection through &scrutoff drops sign bits.
|
||||||
let lhs: *node = n.lhs;
|
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 scrutoff: i32 = 0;
|
||||||
let scrutt: *node = nil;
|
let scrutt: *node = nil;
|
||||||
if (lhs != 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.
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
// Slot resolution inlined; see cgtypetest comment.
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
let lhs: *node = n.lhs;
|
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 scrutoff: i32 = 0;
|
||||||
let scrutt: *node = nil;
|
let scrutt: *node = nil;
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
@@ -21680,6 +21795,20 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
scrutt = matchscrutt(c, scrut);
|
scrutt = matchscrutt(c, scrut);
|
||||||
let spillsz: i32 = matchspillsz(c, scrutt);
|
let spillsz: i32 = matchspillsz(c, scrutt);
|
||||||
scrutoff = localalloc(c, "@match_spill", spillsz, nil);
|
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);
|
cgexpr(c, scrut);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(scrutoff: i64);
|
emitoff(scrutoff: i64);
|
||||||
@@ -21704,6 +21833,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
let endl: str = mklabel(c, "match_end");
|
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);
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
if (lc != nil) {
|
if (lc != nil) {
|
||||||
if (istaggedtype(c, lc.tnode)) {
|
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);
|
let lsz: i32 = slotsize(c, lc.tnode);
|
||||||
cgwidentaggedstore(c, lc.tnode.type_: *tinfo,
|
cgwidentaggedstore(c, lc.tnode.type_: *tinfo,
|
||||||
n.rhs, "BP", lc.off, lsz);
|
n.rhs, "BP", lc.off, lsz);
|
||||||
return;
|
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
|
// Struct payload or tagged-subset return — materialise
|
||||||
// the widened value in scratch via cgwidentaggedstore
|
// the widened value in scratch via cgwidentaggedstore
|
||||||
// (handles tag remap and zero pad), then load AX/DX/CX
|
// (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
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// (void has size 0). Otherwise zero AX for determinism.
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
if (istaggedtype(c, c.fnret)) {
|
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)) {
|
if (isnullabletype(c.fnret)) {
|
||||||
// null = void variant; AX = 0.
|
// null = void variant; AX = 0.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
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
|
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||||
// ABI call), struct payload (literal/ident), str payload,
|
// ABI call), struct payload (literal/ident), str payload,
|
||||||
// scalar payload — with tag remap for tagged-subset widening.
|
// 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)) {
|
if (istaggedtype(c, tn)) {
|
||||||
cgwidentaggedstore(c, tn.type_: *tinfo, rhs, "BP", off, sz);
|
let letsret: i32 = 0;
|
||||||
c.lastwasreturn = 0;
|
if (rhs.kind == nkind.N_CALL) {
|
||||||
return;
|
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()` /
|
// 32B tuple init for `let t: (scalar, str) = call()` /
|
||||||
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
|
// `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
|
// Success tag = 0 (#216 tracks the legacy/flag-aware success-tag
|
||||||
// divergence — out of scope here, success check stays `CMPQ $0`).
|
// divergence — out of scope here, success check stays `CMPQ $0`).
|
||||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
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);
|
cgexpr(c, n.lhs);
|
||||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||||
let cl: str = mklabel(c, "tryprop_ok");
|
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
|
// cgtryunw — `e!` aborts on the error variant via exit(1). Legacy
|
||||||
// semantics (success tag = 0).
|
// semantics (success tag = 0).
|
||||||
fn cgtryunw(c: *cgen, n: *node) void = {
|
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);
|
cgexpr(c, n.lhs);
|
||||||
let cl: str = mklabel(c, "tryunw_ok");
|
let cl: str = mklabel(c, "tryunw_ok");
|
||||||
emitline("\tCMPQ\t$0, AX\n");
|
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
|
// stored via *i32 in this context — direct assignment of the
|
||||||
// local works, indirection through &scrutoff drops sign bits.
|
// local works, indirection through &scrutoff drops sign bits.
|
||||||
let lhs: *node = n.lhs;
|
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 scrutoff: i32 = 0;
|
||||||
let scrutt: *node = nil;
|
let scrutt: *node = nil;
|
||||||
if (lhs != 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.
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
// Slot resolution inlined; see cgtypetest comment.
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
let lhs: *node = n.lhs;
|
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 scrutoff: i32 = 0;
|
||||||
let scrutt: *node = nil;
|
let scrutt: *node = nil;
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
@@ -1882,6 +1935,20 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
scrutt = matchscrutt(c, scrut);
|
scrutt = matchscrutt(c, scrut);
|
||||||
let spillsz: i32 = matchspillsz(c, scrutt);
|
let spillsz: i32 = matchspillsz(c, scrutt);
|
||||||
scrutoff = localalloc(c, "@match_spill", spillsz, nil);
|
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);
|
cgexpr(c, scrut);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(scrutoff: i64);
|
emitoff(scrutoff: i64);
|
||||||
@@ -1906,6 +1973,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
let endl: str = mklabel(c, "match_end");
|
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);
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
if (lc != nil) {
|
if (lc != nil) {
|
||||||
if (istaggedtype(c, lc.tnode)) {
|
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);
|
let lsz: i32 = slotsize(c, lc.tnode);
|
||||||
cgwidentaggedstore(c, lc.tnode.type_: *tinfo,
|
cgwidentaggedstore(c, lc.tnode.type_: *tinfo,
|
||||||
n.rhs, "BP", lc.off, lsz);
|
n.rhs, "BP", lc.off, lsz);
|
||||||
return;
|
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
|
// Struct payload or tagged-subset return — materialise
|
||||||
// the widened value in scratch via cgwidentaggedstore
|
// the widened value in scratch via cgwidentaggedstore
|
||||||
// (handles tag remap and zero pad), then load AX/DX/CX
|
// (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
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// (void has size 0). Otherwise zero AX for determinism.
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
if (istaggedtype(c, c.fnret)) {
|
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)) {
|
if (isnullabletype(c.fnret)) {
|
||||||
// null = void variant; AX = 0.
|
// null = void variant; AX = 0.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
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
|
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||||
// ABI call), struct payload (literal/ident), str payload,
|
// ABI call), struct payload (literal/ident), str payload,
|
||||||
// scalar payload — with tag remap for tagged-subset widening.
|
// 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)) {
|
if (istaggedtype(c, tn)) {
|
||||||
cgwidentaggedstore(c, tn.type_: *tinfo, rhs, "BP", off, sz);
|
let letsret: i32 = 0;
|
||||||
c.lastwasreturn = 0;
|
if (rhs.kind == nkind.N_CALL) {
|
||||||
return;
|
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()` /
|
// 32B tuple init for `let t: (scalar, str) = call()` /
|
||||||
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
|
// `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;
|
let nextparam: *node = nil;
|
||||||
if (param != nil) { nextparam = param.next; };
|
if (param != nil) { nextparam = param.next; };
|
||||||
let rest: i32 = pushargsrev(c, arg.next, nextparam);
|
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
|
// Implicit widening from a concrete variant to a tagged-union
|
||||||
// parameter slot. Skips when the arg is already a tagged local
|
// parameter slot. Skips when the arg is already a tagged local
|
||||||
// (line 121's slice-or-tagged shortcut handles that).
|
// (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.
|
// tag first. Mirrors cstage at cmd/w6c/cgen.c:4373-4387.
|
||||||
let tcs: i32 = taggedcallslot(c, arg);
|
let tcs: i32 = taggedcallslot(c, arg);
|
||||||
if (tcs > 0) {
|
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 > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||||
if (tcs > 16) { emitline("\tPUSHQ\tCX\n"); };
|
if (tcs > 16) { emitline("\tPUSHQ\tCX\n"); };
|
||||||
if (tcs > 8) { emitline("\tPUSHQ\tDX\n"); };
|
if (tcs > 8) { emitline("\tPUSHQ\tDX\n"); };
|
||||||
@@ -1403,6 +1433,18 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
|||||||
r = r.lhs;
|
r = r.lhs;
|
||||||
if (r == nil) { return 0; };
|
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) {
|
if (r.kind == nkind.N_TTUPLE) {
|
||||||
// #10: over-cap tuple → sret. Walk the element TYPE nodes
|
// #10: over-cap tuple → sret. Walk the element TYPE nodes
|
||||||
// (pt.lhs) over the SAME caps the SEND/receive use; a float =
|
// (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;
|
ck += 8;
|
||||||
};
|
};
|
||||||
} else {
|
} 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);
|
cgexpr(c, src);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff((slot_off + 8): i64);
|
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
|
// of tagged element). R8 carries the 4th word for slice-payload
|
||||||
// variants (slot 32B).
|
// variants (slot 32B).
|
||||||
if (rhstaggedabicall(c, src)) {
|
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);
|
cgexpr(c, src);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(slot_off: i64);
|
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
|
// str IS []u8 and a slice is the
|
||||||
// same 3-word {ptr,len,cap} header
|
// same 3-word {ptr,len,cap} header
|
||||||
// from cgexpr's AX/BX/CX (#1/Phase
|
// 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-
|
// shaped consumer — the prior str-
|
||||||
// only gate dropped .len/.cap (#24
|
// only gate dropped .len/.cap (#24
|
||||||
// gap's widener twin).
|
// gap's widener twin).
|
||||||
|
|||||||
@@ -15796,6 +15796,27 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
let nextparam: *node = nil;
|
let nextparam: *node = nil;
|
||||||
if (param != nil) { nextparam = param.next; };
|
if (param != nil) { nextparam = param.next; };
|
||||||
let rest: i32 = pushargsrev(c, arg.next, nextparam);
|
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
|
// Implicit widening from a concrete variant to a tagged-union
|
||||||
// parameter slot. Skips when the arg is already a tagged local
|
// parameter slot. Skips when the arg is already a tagged local
|
||||||
// (line 121's slice-or-tagged shortcut handles that).
|
// (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.
|
// tag first. Mirrors cstage at cmd/w6c/cgen.c:4373-4387.
|
||||||
let tcs: i32 = taggedcallslot(c, arg);
|
let tcs: i32 = taggedcallslot(c, arg);
|
||||||
if (tcs > 0) {
|
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 > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||||
if (tcs > 16) { emitline("\tPUSHQ\tCX\n"); };
|
if (tcs > 16) { emitline("\tPUSHQ\tCX\n"); };
|
||||||
if (tcs > 8) { emitline("\tPUSHQ\tDX\n"); };
|
if (tcs > 8) { emitline("\tPUSHQ\tDX\n"); };
|
||||||
@@ -17095,6 +17125,18 @@ export fn sretretsize(c: *cgen, t: *node) i32 = {
|
|||||||
r = r.lhs;
|
r = r.lhs;
|
||||||
if (r == nil) { return 0; };
|
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) {
|
if (r.kind == nkind.N_TTUPLE) {
|
||||||
// #10: over-cap tuple → sret. Walk the element TYPE nodes
|
// #10: over-cap tuple → sret. Walk the element TYPE nodes
|
||||||
// (pt.lhs) over the SAME caps the SEND/receive use; a float =
|
// (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;
|
ck += 8;
|
||||||
};
|
};
|
||||||
} else {
|
} 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);
|
cgexpr(c, src);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff((slot_off + 8): i64);
|
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
|
// of tagged element). R8 carries the 4th word for slice-payload
|
||||||
// variants (slot 32B).
|
// variants (slot 32B).
|
||||||
if (rhstaggedabicall(c, src)) {
|
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);
|
cgexpr(c, src);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(slot_off: i64);
|
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
|
// str IS []u8 and a slice is the
|
||||||
// same 3-word {ptr,len,cap} header
|
// same 3-word {ptr,len,cap} header
|
||||||
// from cgexpr's AX/BX/CX (#1/Phase
|
// 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-
|
// shaped consumer — the prior str-
|
||||||
// only gate dropped .len/.cap (#24
|
// only gate dropped .len/.cap (#24
|
||||||
// gap's widener twin).
|
// 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
|
// Success tag = 0 (#216 tracks the legacy/flag-aware success-tag
|
||||||
// divergence — out of scope here, success check stays `CMPQ $0`).
|
// divergence — out of scope here, success check stays `CMPQ $0`).
|
||||||
fn cgtryprop(c: *cgen, n: *node) void = {
|
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);
|
cgexpr(c, n.lhs);
|
||||||
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
// AX = tag. If non-zero, this is an error; pop frame and RET.
|
||||||
let cl: str = mklabel(c, "tryprop_ok");
|
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
|
// cgtryunw — `e!` aborts on the error variant via exit(1). Legacy
|
||||||
// semantics (success tag = 0).
|
// semantics (success tag = 0).
|
||||||
fn cgtryunw(c: *cgen, n: *node) void = {
|
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);
|
cgexpr(c, n.lhs);
|
||||||
let cl: str = mklabel(c, "tryunw_ok");
|
let cl: str = mklabel(c, "tryunw_ok");
|
||||||
emitline("\tCMPQ\t$0, AX\n");
|
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
|
// stored via *i32 in this context — direct assignment of the
|
||||||
// local works, indirection through &scrutoff drops sign bits.
|
// local works, indirection through &scrutoff drops sign bits.
|
||||||
let lhs: *node = n.lhs;
|
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 scrutoff: i32 = 0;
|
||||||
let scrutt: *node = nil;
|
let scrutt: *node = nil;
|
||||||
if (lhs != 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.
|
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
|
||||||
// Slot resolution inlined; see cgtypetest comment.
|
// Slot resolution inlined; see cgtypetest comment.
|
||||||
let lhs: *node = n.lhs;
|
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 scrutoff: i32 = 0;
|
||||||
let scrutt: *node = nil;
|
let scrutt: *node = nil;
|
||||||
if (lhs != nil) {
|
if (lhs != nil) {
|
||||||
@@ -21680,6 +21795,20 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
scrutt = matchscrutt(c, scrut);
|
scrutt = matchscrutt(c, scrut);
|
||||||
let spillsz: i32 = matchspillsz(c, scrutt);
|
let spillsz: i32 = matchspillsz(c, scrutt);
|
||||||
scrutoff = localalloc(c, "@match_spill", spillsz, nil);
|
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);
|
cgexpr(c, scrut);
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(scrutoff: i64);
|
emitoff(scrutoff: i64);
|
||||||
@@ -21704,6 +21833,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
|||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
let endl: str = mklabel(c, "match_end");
|
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);
|
let lc: *local = localfindnode(c, lhs.str);
|
||||||
if (lc != nil) {
|
if (lc != nil) {
|
||||||
if (istaggedtype(c, lc.tnode)) {
|
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);
|
let lsz: i32 = slotsize(c, lc.tnode);
|
||||||
cgwidentaggedstore(c, lc.tnode.type_: *tinfo,
|
cgwidentaggedstore(c, lc.tnode.type_: *tinfo,
|
||||||
n.rhs, "BP", lc.off, lsz);
|
n.rhs, "BP", lc.off, lsz);
|
||||||
return;
|
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
|
// Struct payload or tagged-subset return — materialise
|
||||||
// the widened value in scratch via cgwidentaggedstore
|
// the widened value in scratch via cgwidentaggedstore
|
||||||
// (handles tag remap and zero pad), then load AX/DX/CX
|
// (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
|
// the void variant: emit its tag. Payload is undefined
|
||||||
// (void has size 0). Otherwise zero AX for determinism.
|
// (void has size 0). Otherwise zero AX for determinism.
|
||||||
if (istaggedtype(c, c.fnret)) {
|
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)) {
|
if (isnullabletype(c.fnret)) {
|
||||||
// null = void variant; AX = 0.
|
// null = void variant; AX = 0.
|
||||||
emitline("\tMOVQ\t$0, AX\n");
|
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
|
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||||
// ABI call), struct payload (literal/ident), str payload,
|
// ABI call), struct payload (literal/ident), str payload,
|
||||||
// scalar payload — with tag remap for tagged-subset widening.
|
// 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)) {
|
if (istaggedtype(c, tn)) {
|
||||||
cgwidentaggedstore(c, tn.type_: *tinfo, rhs, "BP", off, sz);
|
let letsret: i32 = 0;
|
||||||
c.lastwasreturn = 0;
|
if (rhs.kind == nkind.N_CALL) {
|
||||||
return;
|
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()` /
|
// 32B tuple init for `let t: (scalar, str) = call()` /
|
||||||
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
|
// `let t: (str, scalar) = call()` (#105 / #164/#107). Each
|
||||||
|
|||||||
516
test/wcc/926_tagged_sret_run.c
Normal file
516
test/wcc/926_tagged_sret_run.c
Normal file
@@ -0,0 +1,516 @@
|
|||||||
|
/*
|
||||||
|
* 926_tagged_sret_run — tagged-union sret returns (#38b): a tagged
|
||||||
|
* RETURN whose slot exceeds the AX/DX/CX/R8 register cursor
|
||||||
|
* (TUPLE_GPCAP eightbytes = 32B; tag + 3 payload words) routes
|
||||||
|
* through the SysV sret discipline instead of silently truncating
|
||||||
|
* payload word 4+ in the callee frame.
|
||||||
|
*
|
||||||
|
* Pre-#38 both stages emitted the SAME truncating cursor loads
|
||||||
|
* (gate-blind byte-id): the regex-shaped 56B payload (64B slot)
|
||||||
|
* died at the return crossing while locals, args, and the widener's
|
||||||
|
* memory stores were all correct. errors.errno's 40B (errors.error)
|
||||||
|
* slot was the latent in-tree instance — benign only because no
|
||||||
|
* consumer read opaque_data word 2.
|
||||||
|
*
|
||||||
|
* Three checks per row:
|
||||||
|
* - byte-id: w6c vs w6c_ww .s must be identical (rule 10).
|
||||||
|
* - sret-presence: rows flagged `sret` must emit the hidden-RDI
|
||||||
|
* `LEAQ <K>(BP), DI` caller dest; boundary rows (slot EXACTLY
|
||||||
|
* 32B — the (str|nomem)/(s3|bool) class) must NOT — an
|
||||||
|
* off-by-one in the classifier flips every (T|nomem) consumer
|
||||||
|
* in the tree (ken's top #38 risk).
|
||||||
|
* - runtime: build via the ww / ww_ww drivers and run; the exit
|
||||||
|
* code pins every payload word INCLUDING the last (the
|
||||||
|
* truncation signature).
|
||||||
|
* Rows flagged `buildfail` must be REJECTED by both stages (the
|
||||||
|
* rule-7 loud-stops over the unwired #40-family shapes: widening
|
||||||
|
* sret forward, `?`/arg-position consumption of an sret-class call
|
||||||
|
* result).
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct row {
|
||||||
|
const char *label;
|
||||||
|
const char *src;
|
||||||
|
int want; /* expected exit code (run rows) */
|
||||||
|
int sret; /* 1: .s must contain LEAQ..DI; 0: must not */
|
||||||
|
int buildfail; /* 1: both stages must reject (loud-stop) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Shared regex-shaped prelude: 56B payload (two slices + i64) inside
|
||||||
|
* a 3-variant union = 64B slot, the lib/regex compile() return shape
|
||||||
|
* that surfaced #38. */
|
||||||
|
#define WIDE_TYPES \
|
||||||
|
"type oops = !str;\n" \
|
||||||
|
"type nomem = !void;\n" \
|
||||||
|
"type wide = struct { xs: []u8, ys: []u8, n: i64 };\n"
|
||||||
|
|
||||||
|
#define WIDE_MAIN_CHECK \
|
||||||
|
" case let e: oops => return 13;\n" \
|
||||||
|
" case nomem => return 14;\n" \
|
||||||
|
" };\n" \
|
||||||
|
" return 0;\n" \
|
||||||
|
"};\n"
|
||||||
|
|
||||||
|
static const struct row rows[] = {
|
||||||
|
/* Literal source; every field checked, n (the LAST payload
|
||||||
|
* word, dead pre-#38) checked FIRST so its loss is exit 1. */
|
||||||
|
{ "wide_lit_roundtrip",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n"
|
||||||
|
" return wide { xs = b, ys = b, n = n };\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [9u8, 8u8, 7u8, 6u8];\n"
|
||||||
|
" match (mk(buf[0:4], 42)) {\n"
|
||||||
|
" case let w: wide => {\n"
|
||||||
|
" if (w.n != 42) { return 1; };\n"
|
||||||
|
" if (w.xs.len != 4) { return 2; };\n"
|
||||||
|
" if (w.ys.len != 4) { return 3; };\n"
|
||||||
|
" if (w.xs[3] != 6u8) { return 4; };\n"
|
||||||
|
" };\n"
|
||||||
|
WIDE_MAIN_CHECK,
|
||||||
|
0, 1, 0 },
|
||||||
|
/* Local-ident source (the widener's mem-to-mem struct arm). */
|
||||||
|
{ "wide_local_roundtrip",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n"
|
||||||
|
" let w: wide = wide { xs = b, ys = b, n = n };\n"
|
||||||
|
" return w;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" let r: (wide | oops | nomem) = mk(buf[0:4], 7);\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let w: wide => {\n"
|
||||||
|
" if (w.n != 7) { return 1; };\n"
|
||||||
|
" if (w.ys[0] != 1u8) { return 2; };\n"
|
||||||
|
" };\n"
|
||||||
|
WIDE_MAIN_CHECK,
|
||||||
|
0, 1, 0 },
|
||||||
|
/* let-receive then ASSIGN-receive into the same slot, with
|
||||||
|
* frame canaries (locals around the receives must survive). */
|
||||||
|
{ "wide_assign_receive",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n"
|
||||||
|
" return wide { xs = b, ys = b, n = n };\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let canary1: i64 = 111;\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" let r: (wide | oops | nomem) = mk(buf[0:4], 5);\n"
|
||||||
|
" let canary2: i64 = 222;\n"
|
||||||
|
" r = mk(buf[0:4], 6);\n"
|
||||||
|
" if (canary1 != 111) { return 21; };\n"
|
||||||
|
" if (canary2 != 222) { return 22; };\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let w: wide => { if (w.n != 6) { return 1; }; };\n"
|
||||||
|
WIDE_MAIN_CHECK,
|
||||||
|
0, 1, 0 },
|
||||||
|
/* match-scrutinee receive — the tagged-specific arm with no
|
||||||
|
* tuple precedent: the scrut slot itself is the sret dest. */
|
||||||
|
{ "wide_match_scrutinee",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n"
|
||||||
|
" return wide { xs = b, ys = b, n = n };\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" match (mk(buf[0:4], 9)) {\n"
|
||||||
|
" case let w: wide => { if (w.n != 9) { return 1; }; };\n"
|
||||||
|
WIDE_MAIN_CHECK,
|
||||||
|
0, 1, 0 },
|
||||||
|
/* Exact-type return-forward: inner sret's straight into
|
||||||
|
* outer's caller dest (cg_sret_forward / c.sretforward). */
|
||||||
|
{ "wide_forward_exact",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn inner(b: []u8, n: i64) (wide | oops | nomem) = {\n"
|
||||||
|
" return wide { xs = b, ys = b, n = n };\n"
|
||||||
|
"};\n"
|
||||||
|
"fn outer(b: []u8, n: i64) (wide | oops | nomem) = {\n"
|
||||||
|
" return inner(b, n);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" match (outer(buf[0:4], 11)) {\n"
|
||||||
|
" case let w: wide => { if (w.n != 11) { return 1; }; };\n"
|
||||||
|
WIDE_MAIN_CHECK,
|
||||||
|
0, 1, 0 },
|
||||||
|
/* str-payload error variant through the 64B slot (the widener
|
||||||
|
* str arm writing *(@sretarg)). */
|
||||||
|
{ "wide_str_error_variant",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n"
|
||||||
|
" if (n < 0) { return \"neg\": oops; };\n"
|
||||||
|
" return wide { xs = b, ys = b, n = n };\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" match (mk(buf[0:4], -1)) {\n"
|
||||||
|
" case let w: wide => return 1;\n"
|
||||||
|
" case let e: oops => {\n"
|
||||||
|
" if ((e: str).len != 3) { return 2; };\n"
|
||||||
|
" };\n"
|
||||||
|
" case nomem => return 3;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 1, 0 },
|
||||||
|
/* Two wide results live at once: distinct @match-independent
|
||||||
|
* receive slots, no shared-scratch clobber. */
|
||||||
|
{ "wide_multicall",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n"
|
||||||
|
" return wide { xs = b, ys = b, n = n };\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" let r1: (wide | oops | nomem) = mk(buf[0:4], 100);\n"
|
||||||
|
" let r2: (wide | oops | nomem) = mk(buf[0:4], 200);\n"
|
||||||
|
" match (r1) {\n"
|
||||||
|
" case let w: wide => { if (w.n != 100) { return 1; }; };\n"
|
||||||
|
" case let e: oops => return 13;\n"
|
||||||
|
" case nomem => return 14;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (r2) {\n"
|
||||||
|
" case let w: wide => { if (w.n != 200) { return 2; }; };\n"
|
||||||
|
WIDE_MAIN_CHECK,
|
||||||
|
0, 1, 0 },
|
||||||
|
/* Bare `return;` (void variant) through a 40B slot: the tag
|
||||||
|
* must land in *(@sretarg), not AX. */
|
||||||
|
{ "wide_bare_return_void",
|
||||||
|
"type s4 = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
||||||
|
"fn maybe(n: i64) (s4 | void) = {\n"
|
||||||
|
" if (n == 0) { return; };\n"
|
||||||
|
" return s4 { a = 1, b = 2, c = 3, d = n };\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" match (maybe(0)) {\n"
|
||||||
|
" case let v: s4 => return 1;\n"
|
||||||
|
" case void => { };\n"
|
||||||
|
" };\n"
|
||||||
|
" match (maybe(5)) {\n"
|
||||||
|
" case let v: s4 => { if (v.d != 5) { return 2; }; };\n"
|
||||||
|
" case void => return 3;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 1, 0 },
|
||||||
|
/* The #38 repro shape: 32B struct payload = 40B slot, literal
|
||||||
|
* AND local sources (was exit 2 / silent d-drop at master). */
|
||||||
|
{ "wide_s4_repro",
|
||||||
|
"type s4 = struct { a: i64, b: i64, c: i64, d: i64 };\n"
|
||||||
|
"fn taglit4() (s4 | bool) = {\n"
|
||||||
|
" return s4 { a = 1, b = 2, c = 3, d = 4 };\n"
|
||||||
|
"};\n"
|
||||||
|
"fn taglocal4() (s4 | bool) = {\n"
|
||||||
|
" let v: s4 = s4 { a = 1, b = 2, c = 3, d = 4 };\n"
|
||||||
|
" return v;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" match (taglit4()) {\n"
|
||||||
|
" case let r: s4 => { if (r.d != 4) { return 2; }; };\n"
|
||||||
|
" case let b: bool => return 3;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (taglocal4()) {\n"
|
||||||
|
" case let r: s4 => { if (r.d != 4) { return 4; }; };\n"
|
||||||
|
" case let b: bool => return 5;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 1, 0 },
|
||||||
|
/* errno-shaped graduation row: errors.error's opaque_ variant is
|
||||||
|
* struct { strerror *fn (8B), data [3]u64 (24B) } = 32B payload =
|
||||||
|
* 40B slot — the LIVE in-tree #38 instance that truncated
|
||||||
|
* benignly-by-luck at master (data[2] was never read). This row
|
||||||
|
* READS the previously-dropped tail word first, pinning the
|
||||||
|
* errors.errno sret graduation. Local-ident return source, like
|
||||||
|
* errno's `return err;`. */
|
||||||
|
{ "errno_shaped_tail_read",
|
||||||
|
"type opq = struct { h: i64, data: [3]u64 };\n"
|
||||||
|
"fn wrap(e: i64) (opq | bool) = {\n"
|
||||||
|
" let o: opq;\n"
|
||||||
|
" o.h = 7;\n"
|
||||||
|
" o.data[0] = e: u64;\n"
|
||||||
|
" o.data[1] = 1111u64;\n"
|
||||||
|
" o.data[2] = 2222u64;\n"
|
||||||
|
" return o;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" match (wrap(5)) {\n"
|
||||||
|
" case let o: opq => {\n"
|
||||||
|
" if (o.data[2] != 2222u64) { return 1; };\n"
|
||||||
|
" if (o.data[1] != 1111u64) { return 2; };\n"
|
||||||
|
" if (o.data[0] != 5u64) { return 3; };\n"
|
||||||
|
" if (o.h != 7) { return 4; };\n"
|
||||||
|
" };\n"
|
||||||
|
" case let b: bool => return 5;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 1, 0 },
|
||||||
|
/* BOUNDARY: 24B struct payload = EXACTLY 32B slot — must stay
|
||||||
|
* register-ABI (sret==0 pins no hidden-RDI LEAQ in the .s). */
|
||||||
|
{ "boundary_s3_register",
|
||||||
|
"type s3 = struct { a: i64, b: i64, c: i64 };\n"
|
||||||
|
"fn mk(ok: bool) (s3 | bool) = {\n"
|
||||||
|
" if (!ok) { return false; };\n"
|
||||||
|
" return s3 { a = 7, b = 8, c = 9 };\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" match (mk(true)) {\n"
|
||||||
|
" case let v: s3 => { if (v.c != 9) { return 1; }; };\n"
|
||||||
|
" case let b: bool => return 2;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (mk(false)) {\n"
|
||||||
|
" case let v: s3 => return 3;\n"
|
||||||
|
" case let b: bool => { if (b) { return 4; }; };\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 0, 0 },
|
||||||
|
/* BOUNDARY: (str|nomem) — the 32B-slot class fmt/io/strconv
|
||||||
|
* return everywhere; flipping it to sret breaks the tree. */
|
||||||
|
{ "boundary_str_nomem_register",
|
||||||
|
"type nomem = !void;\n"
|
||||||
|
"fn pick(ok: bool) (str | nomem) = {\n"
|
||||||
|
" if (ok) { return \"hello\"; };\n"
|
||||||
|
" let nm: nomem;\n"
|
||||||
|
" return nm;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" match (pick(true)) {\n"
|
||||||
|
" case let s: str => { if (s.len != 5) { return 1; }; };\n"
|
||||||
|
" case nomem => return 2;\n"
|
||||||
|
" };\n"
|
||||||
|
" match (pick(false)) {\n"
|
||||||
|
" case let s: str => return 3;\n"
|
||||||
|
" case nomem => { };\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 0, 0 },
|
||||||
|
/* LOUD-STOP: widening return-forward of an sret-class source
|
||||||
|
* ((wide|oops) -> (wide|oops|nomem)) needs the mem-to-mem
|
||||||
|
* tag-remap — unwired, filed #40. Must NOT compile. */
|
||||||
|
{ "fail_widening_forward",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn inner(b: []u8) (wide | oops) = {\n"
|
||||||
|
" return wide { xs = b, ys = b, n = 1 };\n"
|
||||||
|
"};\n"
|
||||||
|
"fn outer(b: []u8) (wide | oops | nomem) = {\n"
|
||||||
|
" return inner(b);\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" match (outer(buf[0:4])) {\n"
|
||||||
|
" case let w: wide => return 0;\n"
|
||||||
|
WIDE_MAIN_CHECK,
|
||||||
|
0, 0, 1 },
|
||||||
|
/* LOUD-STOP: `!` consuming an sret-class call result reads the
|
||||||
|
* cursor the callee never filled — #40-family follow-up. */
|
||||||
|
{ "fail_tryunw_wide",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn mk(b: []u8) (wide | oops | nomem) = {\n"
|
||||||
|
" return wide { xs = b, ys = b, n = 1 };\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" let w: wide = mk(buf[0:4])!;\n"
|
||||||
|
" if (w.n != 1) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 0, 1 },
|
||||||
|
/* LOUD-STOP: an sret-class tagged call result in argument
|
||||||
|
* position (cursor push of a memory result). */
|
||||||
|
{ "fail_wide_call_arg",
|
||||||
|
WIDE_TYPES
|
||||||
|
"fn mk(b: []u8) (wide | oops | nomem) = {\n"
|
||||||
|
" return wide { xs = b, ys = b, n = 1 };\n"
|
||||||
|
"};\n"
|
||||||
|
"fn use(r: (wide | oops | nomem)) i64 = {\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let w: wide => return w.n;\n"
|
||||||
|
" case let e: oops => return -1;\n"
|
||||||
|
" case nomem => return -2;\n"
|
||||||
|
" };\n"
|
||||||
|
" return -3;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n"
|
||||||
|
" if (use(mk(buf[0:4])) != 1) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 0, 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *g_bin;
|
||||||
|
|
||||||
|
/* compile one row with `tool` (w6c or w6c_ww) into outpath; returns
|
||||||
|
* the tool's exit code. */
|
||||||
|
static int
|
||||||
|
compile_s(const char *tool, const char *src, const char *outpath)
|
||||||
|
{
|
||||||
|
char cmd[1024];
|
||||||
|
snprintf(cmd, sizeof cmd, "%s/%s %s > %s 2>/dev/null",
|
||||||
|
g_bin, tool, src, outpath);
|
||||||
|
return runwait(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
file_eq(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
char cmd[1024];
|
||||||
|
snprintf(cmd, sizeof cmd, "cmp -s %s %s", a, b);
|
||||||
|
return runwait(cmd) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
file_has(const char *path, const char *needle)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return 0;
|
||||||
|
static char buf[1 << 20];
|
||||||
|
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[n] = '\0';
|
||||||
|
return strstr(buf, needle) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_driver(const char *driver, const char *src, const char *label)
|
||||||
|
{
|
||||||
|
char tmpdir[128], cmd[1024];
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/tsret_%d_d", getpid());
|
||||||
|
mkdir(tmpdir, 0755);
|
||||||
|
snprintf(cmd, sizeof cmd, "cd %s && %s/%s build %s >/dev/null 2>&1",
|
||||||
|
tmpdir, g_bin, driver, src);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||||
|
label, driver);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const char *base = strrchr(src, '/');
|
||||||
|
base = base ? base + 1 : src;
|
||||||
|
char outbin[256];
|
||||||
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||||
|
char *dot = strrchr(outbin, '.');
|
||||||
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
|
int got = runwait(outbin);
|
||||||
|
unlink(outbin);
|
||||||
|
rmdir(tmpdir);
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = getenv("BIN");
|
||||||
|
if (!bin) bin = "out/bin";
|
||||||
|
static char absbin[512];
|
||||||
|
if (bin[0] != '/') {
|
||||||
|
char cwd[256];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||||
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||||
|
bin = absbin;
|
||||||
|
}
|
||||||
|
g_bin = bin;
|
||||||
|
|
||||||
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||||
|
int total = 0, fail = 0;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
const struct row *r = &rows[i];
|
||||||
|
char src[128], cs_s[128], ww_s[128];
|
||||||
|
snprintf(src, sizeof src, "/tmp/tsret_%d_%d.ww",
|
||||||
|
getpid(), i);
|
||||||
|
snprintf(cs_s, sizeof cs_s, "/tmp/tsret_%d_%d_cs.s",
|
||||||
|
getpid(), i);
|
||||||
|
snprintf(ww_s, sizeof ww_s, "/tmp/tsret_%d_%d_ww.s",
|
||||||
|
getpid(), i);
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
if (!f) return 1;
|
||||||
|
fputs("package main;\n\n", f);
|
||||||
|
fputs(r->src, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
int cs_rc = compile_s("w6c", src, cs_s);
|
||||||
|
int ww_rc = compile_s("w6c_ww", src, ww_s);
|
||||||
|
if (r->buildfail) {
|
||||||
|
total++;
|
||||||
|
if (cs_rc == 0 || ww_rc == 0) {
|
||||||
|
fprintf(stderr, "FAIL row[%s]: loud-stop "
|
||||||
|
"expected, cstage rc=%d wwstage rc=%d\n",
|
||||||
|
r->label, cs_rc, ww_rc);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
unlink(src); unlink(cs_s); unlink(ww_s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
total++;
|
||||||
|
if (cs_rc != 0 || ww_rc != 0) {
|
||||||
|
fprintf(stderr, "FAIL row[%s]: compile rc cs=%d "
|
||||||
|
"ww=%d\n", r->label, cs_rc, ww_rc);
|
||||||
|
fail++;
|
||||||
|
unlink(src); unlink(cs_s); unlink(ww_s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!file_eq(cs_s, ww_s)) {
|
||||||
|
fprintf(stderr, "FAIL row[%s]: cs != ww .s\n",
|
||||||
|
r->label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
/* hidden-RDI dest: present iff the row is sret-class.
|
||||||
|
* The boundary rows pin the 32B class stays register. */
|
||||||
|
int has_di = file_has(cs_s, "(BP), DI\n");
|
||||||
|
if (r->sret && !has_di) {
|
||||||
|
fprintf(stderr, "FAIL row[%s]: expected sret "
|
||||||
|
"hidden-RDI LEAQ, none emitted\n", r->label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
if (!r->sret && has_di) {
|
||||||
|
fprintf(stderr, "FAIL row[%s]: 32B-slot row "
|
||||||
|
"flipped to sret (classifier boundary "
|
||||||
|
"off-by-one)\n", r->label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
int got_cs = run_driver("ww", src, r->label);
|
||||||
|
if (got_cs != r->want) {
|
||||||
|
fprintf(stderr, "FAIL row[%s] cstage: want %d "
|
||||||
|
"got %d\n", r->label, r->want, got_cs);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
char wwdrv[600];
|
||||||
|
snprintf(wwdrv, sizeof wwdrv, "%s/ww_ww", g_bin);
|
||||||
|
if (access(wwdrv, X_OK) == 0) {
|
||||||
|
int got_ww = run_driver("ww_ww", src, r->label);
|
||||||
|
if (got_ww != r->want) {
|
||||||
|
fprintf(stderr, "FAIL row[%s] wwstage: "
|
||||||
|
"want %d got %d\n",
|
||||||
|
r->label, r->want, got_ww);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unlink(src); unlink(cs_s); unlink(ww_s);
|
||||||
|
}
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "tagged_sret_run: %d/%d rows failed\n",
|
||||||
|
fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("tagged_sret_run: %d rows ok\n", total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user