w6c+wwstage: receive over-cap tuple sret returns at the call site (#10 Fold B)

Fold A made the CALLEE emit an over-capacity tuple return (> 4 GP or > 2
SSE eightbytes) via sret, but every receive site stayed loud-stopped, so
such a fn was not yet usefully callable. Fold B wires the call/receive end
by aligning every receive gate UP to the shared cg_sret_retsize() /
callsretsize() > 0 predicate (never a kind), per Rob's (B) ruling:

  - single-var-let  `let t = f();`      cstage gate generalised from
        TY_STRUCT&&>24 to cg_sret_retsize(lt)>0; the let's slot IS the
        sret dest, the callee writes the whole tuple there, t.0/t.1 read
        by offset. wwstage already keyed callsretsize (verified).
  - N_ASSIGN-ident  `t = f();`          same generalisation; global arm
        kept TY_STRUCT-only (a tuple-global has no sret-to-symbol path in
        either stage). wwstage grows a tuple-local arm (rettupleof gates
        it apart from the >24B-struct recv, which keeps its own path).
  - destructure     `let (a,b) = f();` and `a,b = f();` — the genuinely
        new wiring: the callee sret's into the @sretscr discard slot, then
        a copy-out loop moves each element to its binding at the SAME
        packed offset the SEND wrote (foff += element size), each at its
        natural width (#169); a `_` binding skips its store but advances
        foff. Both stages, byte-identical.
  - return-forward  `return f();`        cstage forward gate generalised
        to the predicate, reusing cg_sret_forward verbatim. wwstage
        already keyed sretretsize (verified).

The escape boundary stays loud: arg-pass `g(f())` fatals identically in
both stages (tuple arg exceeds return-cursor ABI capacity).

Test 799 is the runtime net Fold A deferred (byte-id is blind to a
SEND/RECEIVE layout mismatch): the bytes.cut-shaped ([]u8,[]u8) round-trip
over destructure / single-var-let / reassign / return-forward, each both
RUN under cstage and asserted cs==ww byte-identical. Tests 945 (row F)
and 956 (f64x3) flip from asserting the old over-cap loud-stop to
asserting the now-working sret round-trip. combined.ww amalgams (w6c +
wwdump embed the wcc cgen) regenerated. Unblocks #4 bytes.cut/rcut.
This commit is contained in:
2026-06-01 13:06:33 +09:00
parent 19e6b68d03
commit a937d67377
9 changed files with 885 additions and 45 deletions

View File

@@ -11,8 +11,9 @@
* float to @tupfscr as it walks (X0 is clobbered by later elements) and
* reloads X0/X1 by SSE index after the integer pops; every receive site
* (single-var 16B/32B, destructure, reassign) reads the float from its
* SSE-cursor reg. SSE caps at 2 (X0,X1) — (f64,f64,f64) loud-stops at
* compile (f64x3_loudstop row asserts the compiler ERRORS, both stages).
* SSE-cursor reg. SSE caps at 2 (X0,X1) — (f64,f64,f64) is over-cap, so
* it returns via sret (#10 Fold A SEND + Fold B destructure RECEIVE); the
* f64x3_recv row asserts that round-trip works in both stages.
*
* #105 (original): a tuple-from-call receive corrupts the f64 word when
* the callee is BRANCHED. Covers ALL THREE receive forms, which share the
@@ -355,13 +356,12 @@ static const struct row rows[] = {
"\tif (t.0.len != 5) { return 2; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* #164 LOUD-STOP — three f64 = 0 GP / 3 SSE exceeds the SSE return
* cap (X0,X1 only). Must FAIL TO COMPILE in BOTH stages (rule-7:
* surface, never silently collide). Master has no SSE cap and
* miscompiles (build succeeds), so want_compile_fail discriminates:
* pre-fix the build succeeds (test fails), post-fix both stages
* error (test passes). */
{ "f64x3_loudstop",
/* #10 OVER-CAP RECV — three f64 = 0 GP / 3 SSE exceeds the SSE return
* cap (X0,X1 only). #164 loud-stopped this at the SEND; #10 Fold A
* sret's it (callee stores X0/X1 → @sretarg at foff 0/8/16) and Fold B
* destructures it (each element MOVSD'd out of @sretscr into its
* binding), so it is now a working round-trip in BOTH stages. */
{ "f64x3_recv",
"package main;\n"
"fn tri(a: f64, b: f64, c: f64) (f64, f64, f64) = {\n"
"\treturn (a, b, c);\n"
@@ -372,7 +372,7 @@ static const struct row rows[] = {
"\tif (y != 2.0) { return 2; };\n"
"\tif (z != 3.0) { return 3; };\n"
"\treturn 0;\n"
"};\n", 0, 0, 1 },
"};\n", 0, 0, 0 },
/* CONTROL — all-integer branched 2-tuple. The integer-cursor MOVQ
* path is untouched by the fix (e0/e1 not float), so this is correct
* pre- and post-fix and byte-id both ways. a=5, b=7 -> 12. */