A tuple return whose SysV register-return footprint exceeds the caps
(> 4 integer eightbytes or > 2 SSE eightbytes) previously LOUD-STOPPED
at the N_RETURN SEND. Fold A makes the CALLEE emit such a return through
the existing >24B-struct sret skeleton:
- classifier (cg_sret_retsize / sretretsize) grows a TY_TUPLE arm:
walk the element footprint over the SAME caps the SEND uses, and
return the tuple's natural total size (type table) when over-cap,
else 0. The gp/sse caps are factored to a single shared SSoT
(TUPLE_GPCAP / TUPLE_SSECAP — cgen.c macros in cstage, cgen.ww defs
in wwstage) consumed by the classifier AND every emit/receive site
(the SEND, the destructure guards, the cgcall arg guard) — so
classify and emit can't disagree in either stage.
- the SEND replaces the loud-stop with a write-through: cgexpr each
element, store it through *(@sretarg) at its packed layout offset
(the t.0/t.1 positional layout), each at its natural width so a
narrow tail stores MOVL/MOVB not an over-MOVQ (#169); the dest base
reloads into DX each step since a wide element clobbers AX/BX/CX.
Then the existing struct-sret epilogue (MOVQ @sretarg->AX; ret).
- the prologue already wires @sretarg when the classifier is nonzero.
The CALL/receive side is deliberately untouched: the N_MLET/N_MASSIGN
destructure loud-stops stay, so an over-cap tuple return is not yet
usefully callable. The end-to-end round-trip arrives with Fold B (#10-B).
Symmetric cstage (cmd/w6c/cgen.c) + wwstage (cgen.ww / cgenstmt.ww /
cgenutil.ww); combined.ww amalgams regenerated. Test 798 asserts the
callee now COMPILES (no loud-stop) and w6c vs w6c_ww .s byte-identical
across all-wide, str, narrow-tail, and float-over-cap shapes; no runtime
row (uncallable until Fold B). All 236 pass incl. 990-997 byte-id.
154 lines
5.2 KiB
C
154 lines
5.2 KiB
C
/*
|
|
* 798_tuple_sret_callee — project #10 Fold A (wide tuple-return / sret,
|
|
* CALLEE side). An over-capacity tuple return (> 4 integer eightbytes or
|
|
* > 2 SSE eightbytes) used to LOUD-STOP at the N_RETURN SEND site
|
|
* ("tuple return exceeds ... register-return ABI capacity"). Fold A makes
|
|
* the CALLEE emit such a return via the existing sret skeleton: the
|
|
* prologue wires @sretarg (the caller-prealloc dest in RDI), and the
|
|
* return stores each element through *(@sretarg) at its packed layout
|
|
* offset, then returns @sretarg in RAX.
|
|
*
|
|
* THIS TEST IS COMPILE + BYTE-ID ONLY (no runtime row):
|
|
* (a) w6c (cstage) AND w6c_ww (wwstage) must now COMPILE the over-cap
|
|
* tuple-returning fn — no loud-stop. The pre-Fold-A behavior was a
|
|
* hard error; a green compile here proves the SEND emits sret.
|
|
* (b) the two .s outputs must be BYTE-IDENTICAL (rule-10). The classifier
|
|
* (cg_sret_retsize / sretretsize) and the SEND emitter share a single
|
|
* cap SSoT, so both stages classify + lay out the tuple the same way.
|
|
*
|
|
* WHY NO RUNTIME ROW: the CALL/receive side stays deliberately loud-stopped
|
|
* — an N_MLET/N_MASSIGN destructure of an over-cap tuple still aborts
|
|
* ("tuple destructure exceeds ... capacity"), so the over-cap callee is not
|
|
* yet usefully callable. The end-to-end round-trip (allocate dest, call,
|
|
* read the elements back) arrives with Fold B (#10-B), which wires the
|
|
* receive. So this fn is compiled but never called/destructured here.
|
|
*
|
|
* ROWS exercise the layout arithmetic the SEND must get right:
|
|
* - ([]u8, []u8) 6 GP eightbytes, all-wide, offsets 0 / 24.
|
|
* - (str, str) 6 GP, str IS []u8 (24B header), offsets 0 / 24.
|
|
* - (str, str, i32) 7 GP; the trailing narrow i32 must store MOVL at
|
|
* its NATURAL packed offset 48 (#169), not over-MOVQ.
|
|
* - (f64, f64, f64) 3 SSE eightbytes > the 2-wide SSE cap.
|
|
*
|
|
* GATE POLARITY: must stay GREEN. A non-zero compile means the SEND
|
|
* loud-stop regressed (Fold A reverted); a byte-id FAIL means cstage and
|
|
* wwstage diverged on the sret classification or the element layout
|
|
* (rule-10 violation / cap-SSoT drift).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
|
|
static int
|
|
runwait(const char *cmd)
|
|
{
|
|
int rc = system(cmd);
|
|
if (rc == -1) return -1;
|
|
return rc;
|
|
}
|
|
|
|
struct row { const char *label; const char *src; };
|
|
|
|
static const struct row rows[] = {
|
|
{ "two_slices",
|
|
"package main;\n"
|
|
"export fn f(a: []u8, b: []u8) ([]u8, []u8) = { return (a, b); };\n" },
|
|
{ "two_str",
|
|
"package main;\n"
|
|
"export fn f(a: str, b: str) (str, str) = { return (a, b); };\n" },
|
|
{ "str_str_i32",
|
|
"package main;\n"
|
|
"export fn f(a: str, b: str, n: i32) (str, str, i32) = {\n"
|
|
" return (a, b, n);\n"
|
|
"};\n" },
|
|
{ "three_f64",
|
|
"package main;\n"
|
|
"export fn f(x: f64, y: f64, z: f64) (f64, f64, f64) = {\n"
|
|
" return (x, y, z);\n"
|
|
"};\n" },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static int
|
|
slurp_eq(const char *a, const char *b)
|
|
{
|
|
FILE *fa = fopen(a, "rb");
|
|
FILE *fb = fopen(b, "rb");
|
|
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
|
int rc = 0;
|
|
for (;;) {
|
|
int ca = fgetc(fa);
|
|
int cb = fgetc(fb);
|
|
if (ca != cb) { rc = -1; break; }
|
|
if (ca == EOF) break;
|
|
}
|
|
fclose(fa); fclose(fb);
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
const char *bin = getenv("BIN");
|
|
if (!bin) bin = "out/bin";
|
|
char absbin[1024];
|
|
if (bin[0] != '/') {
|
|
char cwd[1024];
|
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
bin = absbin;
|
|
}
|
|
|
|
char w6c[1100], w6c_ww[1100];
|
|
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
|
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
|
if (access(w6c_ww, X_OK) != 0) {
|
|
fprintf(stderr, "tuple_sret_callee: w6c_ww missing — cannot run "
|
|
"the cs==ww byte-id gate\n");
|
|
return 1;
|
|
}
|
|
|
|
int n = 0, fail = 0;
|
|
for (int i = 0; rows[i].src; i++, n++) {
|
|
char src[64];
|
|
snprintf(src, sizeof src, "/tmp/wwtsc_%d_%d.ww", getpid(), i);
|
|
FILE *f = fopen(src, "wb");
|
|
if (f == NULL) { fail++; continue; }
|
|
fputs(rows[i].src, f);
|
|
fclose(f);
|
|
|
|
char cs_s[64], ws_s[64], cmd[2048];
|
|
snprintf(cs_s, sizeof cs_s, "/tmp/wwtsc_%d_%d_cs.s", getpid(), i);
|
|
snprintf(ws_s, sizeof ws_s, "/tmp/wwtsc_%d_%d_ww.s", getpid(), i);
|
|
|
|
/* (a) cstage must COMPILE the over-cap tuple callee (no loud-stop). */
|
|
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "row[%s]: w6c loud-stopped (Fold A regressed)\n",
|
|
rows[i].label);
|
|
fail++; unlink(src); continue;
|
|
}
|
|
/* (a') wwstage must compile it too. */
|
|
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, src);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "row[%s]: w6c_ww loud-stopped (Fold A regressed)\n",
|
|
rows[i].label);
|
|
fail++; unlink(src); unlink(cs_s); continue;
|
|
}
|
|
/* (b) cs==ww byte-id gate. */
|
|
if (slurp_eq(cs_s, ws_s) != 0) {
|
|
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
|
|
"(rule-10 byte-id violation)\n", rows[i].label);
|
|
fail++;
|
|
}
|
|
unlink(src); unlink(cs_s); unlink(ws_s);
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "%d/%d tuple-sret-callee tests failed\n", fail, n);
|
|
return 1;
|
|
}
|
|
printf("tuple_sret_callee: %d/%d ok (compile + cs==ww byte-id)\n", n, n);
|
|
return 0;
|
|
}
|