node_tuplearg was N_CALL-scoped and its comment claimed non-call forms "loud-stop" — they did NOT: a tuple ident/literal/unwrap arg fell to the scalar single-PUSHQ default, skewing every later arg register so the callee read garbage word 2 (byte-id both stages, the gate-blind both-wrong class; packed shapes SIGSEGV'd pre-C-t0). The receive side (cgfn #163 walk) was already correct. cgexpr already fills the return-ABI cursor for every supported producer (#241: ident via slot-to-cursor, literal via lit-to-cursor, unwrap via payload shift; call via the return ABI) — the send now admits exactly those into the existing @tupargscr restage + per-class drain (node_tuplearg widened; wwstage gains nodetuplearg, mirroring it over the local tnode / inferletcalltype; rettupleof stays N_CALL-scoped for the destructure receives). Any OTHER tuple-typed source shape loud-stops at the push site — the false comment's claim, now true (rule 7). Literal tuple elements are stamped expr types, so the restage/drain wide test goes type_isstr/type_isslice (TY_UNTYPED_STR- aware) with the ty_str->size header stride; the wwstage twin walks a literal's VALUE exprs the way cgtuplelittocursor classifies them. Ken review demands folded in: (1) a NESTED composite element (tuple/struct/array/tagged inside the tuple) occupies more than the one GP word the restage walk counts — the checker accepted it and it ran WRONG (inner words skewed, wwstage SIGSEGV); both stages' restage walks now loud-stop the element kind (wiring is the filed follow-up, task #65). (2) the variadic interaction probed: a tuple arg ahead of a variadic tail rides the restage correctly (positive row); variadic-of-tuples stays bounded-loud via the tuple-in-slice read surface. 941 grows the t2 matrix: packed/16B params with branched callees, mixed arg orders both ways, literal arg, (f64,i64) param, unwrap arg, ken's >6-GP-pressure stress (4 leading scalars + tuple + a 7th stack-class word), variadic-after-tuple, plus rule-7 reject rows (chain-source arg, nested-element arg, variadic-of-tuples, over-cap ident arg) and the fold-4 charset substrate pin ([](u32,u32) append stays LOUD). At the C-t1 parent 18/73 checks fail: every runtime arg row except the (f64,i64) anchor on BOTH stages (byte-identically — the gate-blind both-wrong class) and the chain/nested args silently accepted.
This commit is contained in:
@@ -74,7 +74,25 @@ slurp_eq(const char *a, const char *b)
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
|
||||
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
|
||||
|
||||
struct row { const char *label; const char *src; int want;
|
||||
int kind; const char *experr; };
|
||||
|
||||
/* errlog_has — a BUILDERR row must fail WITH its diagnostic; any other
|
||||
* failure (parse error, crash) is a vacuous reject (940 precedent). */
|
||||
static int
|
||||
errlog_has(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[8192];
|
||||
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[got] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "letcall_packed",
|
||||
@@ -87,7 +105,7 @@ static const struct row rows[] = {
|
||||
" if (t.0 != 3) { return 1; };\n"
|
||||
" if (t.1 != 4) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "letcall_float_packed",
|
||||
"package main;\n"
|
||||
"fn f() (u32, f64) = {\n"
|
||||
@@ -100,7 +118,7 @@ static const struct row rows[] = {
|
||||
" if (t.0 != 9) { return 1; };\n"
|
||||
" if (t.1 != 2.5) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "letcall_signed_packed",
|
||||
"package main;\n"
|
||||
"fn f() (i32, i32) = {\n"
|
||||
@@ -114,7 +132,7 @@ static const struct row rows[] = {
|
||||
" if (t.1 != -6) { return 2; };\n"
|
||||
" if (t.0 + t.1 != -11) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "letcall_mixed_packed",
|
||||
"package main;\n"
|
||||
"fn f() (u32, str) = {\n"
|
||||
@@ -127,7 +145,7 @@ static const struct row rows[] = {
|
||||
" if (t.0 != 7) { return 1; };\n"
|
||||
" if (len(t.1) != 5) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "letcall_16_neutral",
|
||||
"package main;\n"
|
||||
"fn f() (i64, i64) = {\n"
|
||||
@@ -138,7 +156,7 @@ static const struct row rows[] = {
|
||||
" if (t.0 != 41) { return 1; };\n"
|
||||
" if (t.1 != 17) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "letcall_32_neutral",
|
||||
"package main;\n"
|
||||
"fn f() (i64, str) = {\n"
|
||||
@@ -151,7 +169,7 @@ static const struct row rows[] = {
|
||||
" if (t.0 != 12) { return 1; };\n"
|
||||
" if (len(t.1) != 4) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "mlet_packed_neutral",
|
||||
"package main;\n"
|
||||
"fn f() (u32, u32) = {\n"
|
||||
@@ -162,7 +180,7 @@ static const struct row rows[] = {
|
||||
" if (a != 3) { return 1; };\n"
|
||||
" if (b != 4) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
|
||||
/* ---- C-t1 (#33): the let RECEIVE re-keyed onto the declared
|
||||
* type's register classify. Pre-C-t1 wwstage keyed on producer
|
||||
@@ -180,7 +198,7 @@ static const struct row rows[] = {
|
||||
"export fn main() i32 = {\n"
|
||||
" if (second() != 4) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t1_lit_16",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
@@ -188,7 +206,7 @@ static const struct row rows[] = {
|
||||
" if (t.0 != 3) { return 1; };\n"
|
||||
" if (t.1 != 4) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t1_lit_mixed",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
@@ -196,7 +214,7 @@ static const struct row rows[] = {
|
||||
" if (t.0 != 12) { return 1; };\n"
|
||||
" if (len(t.1) != 4) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t1_lit_3scalar",
|
||||
"package main;\n"
|
||||
"fn second() i64 = {\n"
|
||||
@@ -208,7 +226,7 @@ static const struct row rows[] = {
|
||||
"export fn main() i32 = {\n"
|
||||
" if (second() != 5) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t1_call_3scalar",
|
||||
"package main;\n"
|
||||
"fn f() (i64, i64, i64) = {\n"
|
||||
@@ -220,7 +238,7 @@ static const struct row rows[] = {
|
||||
" if (t.1 != 8) { return 2; };\n"
|
||||
" if (t.2 != 9) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t1_call_noannot_neutral",
|
||||
"package main;\n"
|
||||
"fn f() (i64, i64) = {\n"
|
||||
@@ -231,7 +249,193 @@ static const struct row rows[] = {
|
||||
" if (t.0 != 3) { return 1; };\n"
|
||||
" if (t.1 != 4) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
|
||||
/* ---- C-t2 (#32): the by-value tuple ARG send. node_tuplearg
|
||||
* was N_CALL-scoped (and its comment FALSELY claimed the rest
|
||||
* loud-stop): a tuple ident/literal/unwrap arg fell to the
|
||||
* scalar single-PUSHQ default, skewing every later arg register
|
||||
* — the callee read garbage word 2 (packed shapes SIGSEGV'd
|
||||
* pre-C-t0). Now every cursor-filling producer rides the #163
|
||||
* @tupargscr restage; any other tuple-typed source loud-stops. ---- */
|
||||
{ "t2_param_packed",
|
||||
"package main;\n"
|
||||
"fn pick(t: (u32, u32), k: i32) u32 = {\n"
|
||||
" if (k == 0) { return t.0; };\n"
|
||||
" return t.1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (u32, u32) = (41, 17);\n"
|
||||
" if (pick(t, 0) != 41) { return 1; };\n"
|
||||
" if (pick(t, 1) != 17) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t2_param_16",
|
||||
"package main;\n"
|
||||
"fn pick(t: (i64, i64), k: i32) i64 = {\n"
|
||||
" if (k == 0) { return t.0; };\n"
|
||||
" return t.1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (i64, i64) = (41, 17);\n"
|
||||
" if (pick(t, 0) != 41) { return 1; };\n"
|
||||
" if (pick(t, 1) != 17) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t2_mixed_order",
|
||||
"package main;\n"
|
||||
"fn g(a: i64, t: (u32, u32), b: i64) i64 = {\n"
|
||||
" return a*1000 + (t.0: i64)*100 + (t.1: i64)*10 + b;\n"
|
||||
"};\n"
|
||||
"fn h(t: (i64, i64), s: str, k: i64) i64 = {\n"
|
||||
" return t.0 + t.1 + (len(s): i64) + k;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (u32, u32) = (3, 4);\n"
|
||||
" if (g(1, t, 2) != 1342) { return 1; };\n"
|
||||
" let u: (i64, i64) = (10, 20);\n"
|
||||
" if (h(u, \"abc\", 7) != 40) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t2_lit_arg",
|
||||
"package main;\n"
|
||||
"fn pick(t: (i64, i64), k: i32) i64 = {\n"
|
||||
" if (k == 0) { return t.0; };\n"
|
||||
" return t.1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (pick((41, 17), 0) != 41) { return 1; };\n"
|
||||
" if (pick((41, 17), 1) != 17) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t2_float_param",
|
||||
"package main;\n"
|
||||
"fn fsum(t: (f64, i64)) f64 = {\n"
|
||||
" return t.0 + (t.1: f64);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (f64, i64) = (2.5, 4);\n"
|
||||
" if (fsum(t) != 6.5) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
{ "t2_unwrap_arg",
|
||||
"package main;\n"
|
||||
"fn mk() ((i64, i64) | nomem) = {\n"
|
||||
" let a: i64 = 5;\n"
|
||||
" let b: i64 = 6;\n"
|
||||
" return (a, b);\n"
|
||||
"};\n"
|
||||
"fn sum(t: (i64, i64)) i64 = {\n"
|
||||
" return t.0 + t.1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (sum(mk()!) != 11) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* ken's >6-GP-pressure stress: 4 leading scalars + the 2-word
|
||||
* tuple + a 7th GP word — the restage drain fills the INTEGER
|
||||
* cursor past the tuple and the 7th word must still land in the
|
||||
* stack arg class, not be eaten or shifted by the tuple's pops. */
|
||||
{ "t2_gp_pressure",
|
||||
"package main;\n"
|
||||
"fn f(a: i64, b: i64, c: i64, d: i64, t: (i64, i64), e: i64) i64 = {\n"
|
||||
" if (e != 60) { return -1; };\n"
|
||||
" if (a != 1 || b != 2 || c != 3 || d != 4) { return -2; };\n"
|
||||
" return t.0 * 100 + t.1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (i64, i64) = (7, 9);\n"
|
||||
" if (f(1, 2, 3, 4, t, 60) != 709) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* a tuple arg mixed with a VARIADIC tail rides the same restage —
|
||||
* positive pin of the variadic interaction (ken demand 2). */
|
||||
{ "t2_variadic_after_tuple",
|
||||
"package main;\n"
|
||||
"fn g(t: (i64, i64), xs: i64...) i64 = {\n"
|
||||
" let s: i64 = t.0 + t.1;\n"
|
||||
" for (let x .. xs) { s += x; };\n"
|
||||
" return s;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (i64, i64) = (3, 4);\n"
|
||||
" if (g(t, 1, 2) != 10) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* rule-7 (ken demand 1): a NESTED composite element (tuple-in-
|
||||
* tuple here) occupies more than the one GP word the restage walk
|
||||
* counts — pre-guard the checker accepted it and it ran WRONG
|
||||
* (inner words skewed; wwstage SIGSEGV'd). Loud until a consumer
|
||||
* motivates the wiring. */
|
||||
{ "t2_reject_nested_elem_arg",
|
||||
"package main;\n"
|
||||
"fn f(t: ((i64, i64), i64)) i64 = {\n"
|
||||
" return t.1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let inner: (i64, i64) = (1, 2);\n"
|
||||
" let t: ((i64, i64), i64) = (inner, 9);\n"
|
||||
" if (f(t) != 9) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "tuple arg element kind unsupported" },
|
||||
/* variadic-of-tuples ((i64,i64)...) stays LOUD via the
|
||||
* tuple-in-slice read surface — bounded, not silent (demand 2). */
|
||||
{ "t2_reject_variadic_of_tuples",
|
||||
"package main;\n"
|
||||
"fn first(ts: (i64, i64)...) i64 = {\n"
|
||||
" if (len(ts) == 0) { return -1; };\n"
|
||||
" return ts[0].0;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (i64, i64) = (3, 4);\n"
|
||||
" if (first(t) != 3) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "unsupported field-read shape" },
|
||||
/* rule-7: a tuple arg from a source whose cgexpr does NOT fill
|
||||
* the cursor (here a struct-field chain) dies loud — pre-C-t2
|
||||
* it fell to the scalar single-PUSHQ default silently. */
|
||||
{ "t2_reject_chain_arg",
|
||||
"package main;\n"
|
||||
"type holder = struct { t: (i64, i64) };\n"
|
||||
"fn sum(t: (i64, i64)) i64 = {\n"
|
||||
" return t.0 + t.1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let h: holder = holder{ t = (1, 2) };\n"
|
||||
" if (sum(h.t) != 3) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "tuple arg from unsupported source shape" },
|
||||
/* rule-7: an over-cap tuple ident arg louds at the slot-to-cursor
|
||||
* cap (the #10 sret follow-up), never a partial push. */
|
||||
{ "t2_reject_overcap_ident_arg",
|
||||
"package main;\n"
|
||||
"fn f(t: (str, str)) i64 = {\n"
|
||||
" return (len(t.0) + len(t.1)): i64;\n"
|
||||
"};\n"
|
||||
"fn g() (str, str) = {\n"
|
||||
" return (\"ab\", \"cde\");\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (str, str) = g();\n"
|
||||
" if (f(t) != 5) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "tuple ident exceeds register-return ABI capacity" },
|
||||
/* fold-4 substrate pin (charset_range_item = [](u32,u32)): the
|
||||
* tuple-in-slice consumer shape is LOUD today, both stages —
|
||||
* wiring it is fold-4's prerequisite work, not a silent gap. */
|
||||
{ "charset_slice_append_reject",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: [](u32, u32) = [];\n"
|
||||
" append(s, (1, 2));\n"
|
||||
" if (len(s) != 1) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
K_BUILDERR, "element kind unsupported" },
|
||||
};
|
||||
|
||||
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
|
||||
@@ -252,6 +456,16 @@ run_driver(const char *driver, const struct row *r, int i)
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>%s",
|
||||
tmpdir, driver, src, errf);
|
||||
int brc = runwait(cmd);
|
||||
if (r->kind == K_BUILDERR) {
|
||||
int ok = (brc != 0)
|
||||
&& (r->experr == NULL || errlog_has(errf, r->experr));
|
||||
if (!ok)
|
||||
fprintf(stderr, "row[%s]: %s expected loud builderr "
|
||||
"\"%s\" (brc=%d)\n", r->label, driver,
|
||||
r->experr ? r->experr : "", brc);
|
||||
unlink(src); unlink(errf); rmdir(tmpdir);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
if (brc != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
@@ -341,6 +555,8 @@ main(void)
|
||||
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].kind == K_BUILDERR)
|
||||
continue;
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user