wcc+w6c_ww: >48B tagged by-value args — MEMORY-class two-phase push (#38b)
Task #19 (the #38b residual surfaced by FC2 evidence): a tagged arg whose slot exceeds the 6-reg convention (>48B) is MEMORY-class per ref/qbe/amd64/sysv.c:80-85 (inmem) / :411-426 (stack blit). Caller stages the whole slot below every register-class word (two-phase push, rightmost-first, leftmost mem arg at 16(BP)); callee registers the param in place at positive BP offsets with zero prologue bytes; the merged slot count feeds the existing caller-cleanup ADDQ. Argument-side mirror of the #38 tagged-sret fix, same classify machinery (tagged_memarg_size / taggedmemargsize beside their register-class siblings). Pre-fix, the exact-typed arg loud-stopped on both stages, but WIDENING a concrete variant into a >48B param slipped the old guard silently — cstage pushed one scalar word while wwstage emitted an uncapped greedy stitch (wrong on both AND cs≠ww, gate-blind). Widen sources now route through the @tagscr scratch for mem slots. Loud boundaries kept (rule 7), each with its own diagnostic: sret-class tagged CALL result as mem-arg source (#40-family follow-up), global tagged let (task #25, broken at any size pre-existing), >48B variadic element, and mem-arg + register- overflow mixing (caller check + callee prologue mirror). Single commit: caller staging, callee receive, and both stages are one inseparable ABI class — landing any half alone breaks byte-id or runtime correctness (the #38 flip precedent); test/929 (15 table-driven rows: 56B/64B slots, widen-slip pin, source shapes, mixed orders both ways, two-mem call, 200k-call loop, 48B-boundary absence pin byte-id'd vs master, 5 reject rows pinning the exact per-guard diagnostic on both stages) rides with it.
This commit is contained in:
609
test/wcc/929_tagged_memarg_run.c
Normal file
609
test/wcc/929_tagged_memarg_run.c
Normal file
@@ -0,0 +1,609 @@
|
||||
/*
|
||||
* 929_tagged_memarg_run — >48B tagged by-value ARGS (#38b): a tagged
|
||||
* arg whose slot exceeds the 6-reg register convention (48B) is
|
||||
* MEMORY-class — the caller stages the whole slot on the outgoing
|
||||
* stack below every register-class word, the callee reads it in
|
||||
* place at positive BP offsets, and the caller-cleanup ADDQ reclaims
|
||||
* it after CALL. ABI shape per ref/qbe/amd64/sysv.c:80-85 (inmem) /
|
||||
* :411-426 (stack blit).
|
||||
*
|
||||
* Pre-fix the exact-typed arg loud-stopped on both stages (the
|
||||
* designed #38b guard), but the WIDENED concrete source into a >48B
|
||||
* param slipped past the guard SILENTLY: cstage pushed one scalar
|
||||
* word and received a 1-word scalar param while wwstage emitted an
|
||||
* uncapped greedy stitch — silently wrong on both AND cs≠ww
|
||||
* (gate-blind: no in-tree >48B call existed).
|
||||
*
|
||||
* Three checks per row:
|
||||
* - byte-id: w6c vs w6c_ww .s identical (rule 10). Rows avoid the
|
||||
* pre-existing match-on-tagged-struct-field divergence class by
|
||||
* keeping payloads all-scalar; the inst-shaped row dispatches on
|
||||
* the outer tag only.
|
||||
* - asm markers: `needs` pins the mem cleanup ADDQ (the memory-
|
||||
* class signature); `rejects` pins the 48B boundary row stays
|
||||
* register-convention (no cleanup) — an off-by-one in
|
||||
* tagged_memarg_size would flip every 48B-slot call in the tree.
|
||||
* - runtime: build via ww / ww_ww and run; exit codes read the tag
|
||||
* AND the late payload words (slot offsets +48/+56, past the old
|
||||
* register cap) FIRST so their loss is the visible failure.
|
||||
* Rows flagged `buildfail` must be rejected by BOTH stages with that
|
||||
* row's EXACT #38b diagnostic — pinning WHICH guard fired (the
|
||||
* remaining unwired sub-shapes: sret-class call source, global-let
|
||||
* source, variadic element, register-overflow mixing caller- and
|
||||
* callee-side).
|
||||
*/
|
||||
#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) */
|
||||
const char *needs; /* .s must contain (NULL: skip) */
|
||||
const char *rejects; /* .s must NOT contain (NULL: skip) */
|
||||
int buildfail; /* 1: both stages must loud-stop */
|
||||
const char *failmark; /* exact diagnostic both stages must emit
|
||||
* — pins WHICH #38b guard fired, so one
|
||||
* guard cannot silently cover for
|
||||
* another's regression. */
|
||||
};
|
||||
|
||||
/* 56B slot: 48B all-scalar payload + tag. m3 is the late word at slot
|
||||
* offset +48 — dead under the old 6-reg cap. */
|
||||
#define MEM56_TYPES \
|
||||
"type t_lit = rune;\n" \
|
||||
"type t_any = void;\n" \
|
||||
"type t_rep = struct { id: size, origin: size, m0: size,\n" \
|
||||
" m1: size, m2: size, m3: size };\n" \
|
||||
"type t_u = (t_lit | t_any | t_rep);\n"
|
||||
|
||||
/* Branched callee: reads the tag AND the late payload words per arm,
|
||||
* late word FIRST. A single-return callee would mask wrong word
|
||||
* routing by coincidence. */
|
||||
#define MEM56_PROBE \
|
||||
"fn probe(a: t_u) i32 = {\n" \
|
||||
" match (a) {\n" \
|
||||
" case let l: t_lit => {\n" \
|
||||
" if (l == 'x') { return 1; };\n" \
|
||||
" return 91;\n" \
|
||||
" };\n" \
|
||||
" case t_any => { return 2; };\n" \
|
||||
" case let r: t_rep => {\n" \
|
||||
" if (r.m3 != 1234) { return 92; };\n" \
|
||||
" if (r.m2 != 12) { return 93; };\n" \
|
||||
" if (r.id != 7) { return 94; };\n" \
|
||||
" return 3;\n" \
|
||||
" };\n" \
|
||||
" };\n" \
|
||||
" return 99;\n" \
|
||||
"};\n"
|
||||
|
||||
#define MEM56_REP_LIT \
|
||||
"t_rep { id = 7, origin = 9, m0 = 10, m1 = 11, m2 = 12,\n" \
|
||||
" m3 = 1234 }"
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Exact-type local-ident source, every variant exercised. */
|
||||
{ "mem56_ident_allvariants",
|
||||
MEM56_TYPES
|
||||
MEM56_PROBE
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: t_u = ('x': t_lit);\n"
|
||||
" if (probe(a) != 1) { return 1; };\n"
|
||||
" let av: t_any;\n"
|
||||
" let b: t_u = av;\n"
|
||||
" if (probe(b) != 2) { return 2; };\n"
|
||||
" let r: t_rep = " MEM56_REP_LIT ";\n"
|
||||
" let cc: t_u = r;\n"
|
||||
" if (probe(cc) != 3) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
|
||||
/* 64B slot — one word wider; late word at +56. */
|
||||
{ "mem64_ident_lateword",
|
||||
"type t_lit = rune;\n"
|
||||
"type t_rep = struct { id: size, origin: size, m0: size,\n"
|
||||
" m1: size, m2: size, m3: size, m4: size };\n"
|
||||
"type t_u = (t_lit | t_rep);\n"
|
||||
"fn probe(a: t_u) i32 = {\n"
|
||||
" match (a) {\n"
|
||||
" case let l: t_lit => {\n"
|
||||
" if (l == 'y') { return 1; };\n"
|
||||
" return 91;\n"
|
||||
" };\n"
|
||||
" case let r: t_rep => {\n"
|
||||
" if (r.m4 != 7777) { return 92; };\n"
|
||||
" if (r.m3 != 5) { return 93; };\n"
|
||||
" return 2;\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: t_u = ('y': t_lit);\n"
|
||||
" if (probe(a) != 1) { return 1; };\n"
|
||||
" let r: t_rep = t_rep { id = 1, origin = 2, m0 = 3,\n"
|
||||
" m1 = 4, m2 = 5, m3 = 5, m4 = 7777 };\n"
|
||||
" let b: t_u = r;\n"
|
||||
" if (probe(b) != 2) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$64, SP\n", NULL, 0, NULL },
|
||||
/* Widened concrete sources at the call site — the sub-shape
|
||||
* that slipped the old guard SILENTLY (cs 1-word scalar vs ww
|
||||
* greedy stitch, both wrong). Scalar cast, void, struct local. */
|
||||
{ "mem56_widen_concrete",
|
||||
MEM56_TYPES
|
||||
MEM56_PROBE
|
||||
"export fn main() i32 = {\n"
|
||||
" if (probe('x': t_lit) != 1) { return 1; };\n"
|
||||
" let av: t_any;\n"
|
||||
" if (probe(av) != 2) { return 2; };\n"
|
||||
" let r: t_rep = " MEM56_REP_LIT ";\n"
|
||||
" if (probe(r) != 3) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
|
||||
/* Tagged SUBSET source widened into the 56B slot (tag remap). */
|
||||
{ "mem56_subset_remap",
|
||||
MEM56_TYPES
|
||||
"type t_sub = (t_lit | t_any);\n"
|
||||
MEM56_PROBE
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: t_sub = ('x': t_lit);\n"
|
||||
" if (probe(s) != 1) { return 1; };\n"
|
||||
" let s2: t_sub = (void: t_any);\n"
|
||||
" if (probe(s2) != 2) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
|
||||
/* Exact-type non-ident addressable sources: array element,
|
||||
* struct field, pointer deref (the aggarg_srcaddr shapes). */
|
||||
{ "mem56_srcshapes",
|
||||
MEM56_TYPES
|
||||
"type holder = struct { k: i64, u: t_u };\n"
|
||||
MEM56_PROBE
|
||||
"export fn main() i32 = {\n"
|
||||
" let arr: [2]t_u = [\n"
|
||||
" ('x': t_lit): t_u,\n"
|
||||
" ('x': t_lit): t_u,\n"
|
||||
" ];\n"
|
||||
" if (probe(arr[1]) != 1) { return 1; };\n"
|
||||
" let r: t_rep = " MEM56_REP_LIT ";\n"
|
||||
" let h: holder = holder { k = 9, u = r };\n"
|
||||
" if (probe(h.u) != 3) { return 2; };\n"
|
||||
" let x: t_u = ('x': t_lit);\n"
|
||||
" let p: *t_u = &x;\n"
|
||||
" if (probe(*p) != 1) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
|
||||
/* Mem arg mixed with register-class args, both orders — the
|
||||
* mem copy must not disturb the int/str register cursors. */
|
||||
{ "mem56_mixed_orders",
|
||||
MEM56_TYPES
|
||||
"fn before(k: i64, s: str, a: t_u) i64 = {\n"
|
||||
" if (k != 5) { return 91; };\n"
|
||||
" if (s.len != 3) { return 92; };\n"
|
||||
" match (a) {\n"
|
||||
" case let r: t_rep => { return k + (r.m3: i64); };\n"
|
||||
" case => { return 93; };\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n"
|
||||
"fn after(a: t_u, k: i64, s: str) i64 = {\n"
|
||||
" if (k != 6) { return 91; };\n"
|
||||
" if (s.len != 3) { return 92; };\n"
|
||||
" match (a) {\n"
|
||||
" case let r: t_rep => { return k + (r.m3: i64); };\n"
|
||||
" case => { return 93; };\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let r: t_rep = " MEM56_REP_LIT ";\n"
|
||||
" let a: t_u = r;\n"
|
||||
" if (before(5, \"abc\", a) != 1239) { return 1; };\n"
|
||||
" if (after(a, 6, \"abc\") != 1240) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
|
||||
/* Call in a tight loop: a leaked stack copy (missing cleanup)
|
||||
* skews SP long before 200k iterations. */
|
||||
{ "mem56_call_in_loop",
|
||||
MEM56_TYPES
|
||||
"fn grab(a: t_u) i64 = {\n"
|
||||
" match (a) {\n"
|
||||
" case let r: t_rep => { return r.m3: i64; };\n"
|
||||
" case => { return -1; };\n"
|
||||
" };\n"
|
||||
" return -2;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let r: t_rep = " MEM56_REP_LIT ";\n"
|
||||
" let a: t_u = r;\n"
|
||||
" let canary: i64 = 4242;\n"
|
||||
" let sum: i64 = 0;\n"
|
||||
" let i: i64 = 0;\n"
|
||||
" for (i < 200000) {\n"
|
||||
" sum += grab(a) - 1234;\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" if (canary != 4242) { return 2; };\n"
|
||||
" if (sum != 0) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
|
||||
/* TWO mem args in one call (56B + 64B): pins the left-to-right
|
||||
* outgoing layout (leftmost mem arg at 16(BP)). */
|
||||
{ "mem56_twomem",
|
||||
MEM56_TYPES
|
||||
"type w_rep = struct { id: size, origin: size, m0: size,\n"
|
||||
" m1: size, m2: size, m3: size, m4: size };\n"
|
||||
"type w_u = (t_lit | w_rep);\n"
|
||||
"fn both(a: t_u, b: w_u) i64 = {\n"
|
||||
" let x: i64 = 0;\n"
|
||||
" match (a) {\n"
|
||||
" case let r: t_rep => { x = r.m3: i64; };\n"
|
||||
" case => { return 91; };\n"
|
||||
" };\n"
|
||||
" match (b) {\n"
|
||||
" case let r: w_rep => { return x + (r.m4: i64); };\n"
|
||||
" case => { return 92; };\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let r: t_rep = " MEM56_REP_LIT ";\n"
|
||||
" let a: t_u = r;\n"
|
||||
" let w: w_rep = w_rep { id = 1, origin = 2, m0 = 3,\n"
|
||||
" m1 = 4, m2 = 5, m3 = 6, m4 = 2 };\n"
|
||||
" let b: w_u = w;\n"
|
||||
" if (both(a, b) != 1236) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$120, SP\n", NULL, 0, NULL },
|
||||
/* Inst-shaped row: the real lib/regex inst layout (48B
|
||||
* inst_repeat payload with nested (void|size) fields = 56B
|
||||
* slot). Outer-tag dispatch only — inner-field matches ride the
|
||||
* pre-existing match-on-tagged-struct-field divergence class,
|
||||
* out of scope here; the all-scalar rows above pin the late
|
||||
* payload words. */
|
||||
{ "mem56_inst_shape",
|
||||
"type inst_lit = rune;\n"
|
||||
"type inst_any = void;\n"
|
||||
"type inst_repeat = struct { id: size, origin: size,\n"
|
||||
" min: (void | size), max: (void | size) };\n"
|
||||
"type inst = (inst_lit | inst_any | inst_repeat);\n"
|
||||
"fn is_consuming(a: inst) bool = {\n"
|
||||
" return a is inst_lit || a is inst_any;\n"
|
||||
"};\n"
|
||||
"fn rep_id(a: inst) i64 = {\n"
|
||||
" match (a) {\n"
|
||||
" case let r: inst_repeat => { return r.origin: i64; };\n"
|
||||
" case => { return -1; };\n"
|
||||
" };\n"
|
||||
" return -2;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: inst = ('x': inst_lit);\n"
|
||||
" if (!is_consuming(a)) { return 1; };\n"
|
||||
" let r: inst_repeat = inst_repeat { id = 7, origin = 9,\n"
|
||||
" min = (11: size), max = (1234: size) };\n"
|
||||
" let b: inst = r;\n"
|
||||
" if (is_consuming(b)) { return 2; };\n"
|
||||
" if (rep_id(b) != 9) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
|
||||
/* BOUNDARY: 40B payload = EXACTLY 48B slot — must stay on the
|
||||
* register convention (no mem cleanup ADDQ). An off-by-one in
|
||||
* tagged_memarg_size flips every 48B-slot call in the tree. */
|
||||
{ "boundary48_register",
|
||||
"type t_lit = rune;\n"
|
||||
"type t_rep = struct { id: size, origin: size, m0: size,\n"
|
||||
" m1: size, m2: size };\n"
|
||||
"type t_u = (t_lit | t_rep);\n"
|
||||
"fn probe(a: t_u) i32 = {\n"
|
||||
" match (a) {\n"
|
||||
" case let l: t_lit => { return 1; };\n"
|
||||
" case let r: t_rep => {\n"
|
||||
" if (r.m2 != 12) { return 92; };\n"
|
||||
" return 2;\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: t_u = ('x': t_lit);\n"
|
||||
" if (probe(a) != 1) { return 1; };\n"
|
||||
" let r: t_rep = t_rep { id = 7, origin = 9, m0 = 10,\n"
|
||||
" m1 = 11, m2 = 12 };\n"
|
||||
" let b: t_u = r;\n"
|
||||
" if (probe(b) != 2) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, NULL, "\tADDQ\t$48, SP\n", 0, NULL },
|
||||
/* LOUD-STOP: sret-class tagged call result in >48B argument
|
||||
* position (memory result behind a dest pointer, not a cursor —
|
||||
* receive-then-push is the #40-family follow-up). */
|
||||
{ "fail_callsrc",
|
||||
MEM56_TYPES
|
||||
MEM56_PROBE
|
||||
"fn mk() t_u = {\n"
|
||||
" return ('x': t_lit);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (probe(mk()) != 1) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, NULL, NULL, 1,
|
||||
"#38b: sret-class tagged call result as a >48B by-value arg "
|
||||
"unwired (#40-family follow-up)" },
|
||||
/* LOUD-STOP: global-let source. Global tagged lets have no
|
||||
* .data emission at ANY size (let_emit_size returns 0 for
|
||||
* TY_TAGGED; the ≤48B arg path silently reads stack garbage at
|
||||
* master — independent latent, filed as its own task). The >48B
|
||||
* ARG path stops loud instead. No assign to g here — the global
|
||||
* tagged ASSIGN is the other half of that independent bug and
|
||||
* fails with its own (non-#38b) resolver diagnostic on wwstage
|
||||
* post-cgplaceaddr. */
|
||||
{ "fail_global_src",
|
||||
MEM56_TYPES
|
||||
"let g: t_u;\n"
|
||||
MEM56_PROBE
|
||||
"export fn main() i32 = {\n"
|
||||
" if (probe(g) != 1) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
/* marker stops before the source-kind number — cstage prints
|
||||
* the numeric node kind, wwstage doesn't. */
|
||||
0, NULL, NULL, 1,
|
||||
"#38b: >48B tagged arg from unsupported source kind" },
|
||||
/* LOUD-STOP: >48B tagged VARIADIC element (memory convention
|
||||
* inside the vararg gather buffer — unwired). */
|
||||
{ "fail_variadic_elem",
|
||||
MEM56_TYPES
|
||||
"fn v(xs: t_u...) i32 = {\n"
|
||||
" return len(xs): i32;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: t_u = ('x': t_lit);\n"
|
||||
" if (v(a) != 1) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, NULL, NULL, 1,
|
||||
"#38b: >48B tagged variadic element unwired" },
|
||||
/* LOUD-STOP: mem arg + register-overflow args in one call (7
|
||||
* register-class words: 2 strs + int) — the positive-BP layouts
|
||||
* collide. With the callee BEFORE main, its prologue mirror-
|
||||
* check fires first (decl-order compilation); the caller-side
|
||||
* twin is pinned by the next row. */
|
||||
{ "fail_mem_plus_overflow",
|
||||
MEM56_TYPES
|
||||
"fn f(a: t_u, s1: str, s2: str, k: i64) i64 = {\n"
|
||||
" match (a) {\n"
|
||||
" case let r: t_rep => {\n"
|
||||
" return (r.m3: i64) + (s1.len: i64) + (s2.len: i64) + k;\n"
|
||||
" };\n"
|
||||
" case => { return 91; };\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let r: t_rep = " MEM56_REP_LIT ";\n"
|
||||
" let a: t_u = r;\n"
|
||||
" if (f(a, \"abc\", \"de\", 1) != 1240) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, NULL, NULL, 1,
|
||||
"#38b: >48B tagged param mixed with stack-spilled params "
|
||||
"unwired" },
|
||||
/* LOUD-STOP: same mixing, main BEFORE the callee — the CALL
|
||||
* site compiles first, so the caller-side guard fires (the
|
||||
* row above never reaches it). */
|
||||
{ "fail_mem_plus_overflow_callsite",
|
||||
MEM56_TYPES
|
||||
"export fn main() i32 = {\n"
|
||||
" let r: t_rep = " MEM56_REP_LIT ";\n"
|
||||
" let a: t_u = r;\n"
|
||||
" if (f(a, \"abc\", \"de\", 1) != 1240) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"fn f(a: t_u, s1: str, s2: str, k: i64) i64 = {\n"
|
||||
" match (a) {\n"
|
||||
" case let r: t_rep => {\n"
|
||||
" return (r.m3: i64) + (s1.len: i64) + (s2.len: i64) + k;\n"
|
||||
" };\n"
|
||||
" case => { return 91; };\n"
|
||||
" };\n"
|
||||
" return 99;\n"
|
||||
"};\n",
|
||||
0, NULL, NULL, 1,
|
||||
"#38b: >48B tagged arg mixed with register-overflow stack "
|
||||
"args unwired" },
|
||||
};
|
||||
|
||||
static const char *g_bin;
|
||||
|
||||
static int
|
||||
compile_s(const char *tool, const char *src, const char *outpath,
|
||||
const char *errpath)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "%s/%s %s > %s 2> %s",
|
||||
g_bin, tool, src, outpath, errpath);
|
||||
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/tmemarg_%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];
|
||||
char cs_e[128], ww_e[128];
|
||||
snprintf(src, sizeof src, "/tmp/tmemarg_%d_%d.ww",
|
||||
getpid(), i);
|
||||
snprintf(cs_s, sizeof cs_s, "/tmp/tmemarg_%d_%d_cs.s",
|
||||
getpid(), i);
|
||||
snprintf(ww_s, sizeof ww_s, "/tmp/tmemarg_%d_%d_ww.s",
|
||||
getpid(), i);
|
||||
snprintf(cs_e, sizeof cs_e, "/tmp/tmemarg_%d_%d_cs.err",
|
||||
getpid(), i);
|
||||
snprintf(ww_e, sizeof ww_e, "/tmp/tmemarg_%d_%d_ww.err",
|
||||
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, cs_e);
|
||||
int ww_rc = compile_s("w6c_ww", src, ww_s, ww_e);
|
||||
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++;
|
||||
} else if (!file_has(cs_e, r->failmark)
|
||||
|| !file_has(ww_e, r->failmark)) {
|
||||
/* the rejection must be THIS row's exact
|
||||
* #38b loud-stop, not an unrelated error —
|
||||
* or another guard — masquerading as
|
||||
* coverage. */
|
||||
fprintf(stderr, "FAIL row[%s]: rejected but "
|
||||
"without the exact diagnostic \"%s\"\n",
|
||||
r->label, r->failmark);
|
||||
fail++;
|
||||
}
|
||||
unlink(src); unlink(cs_s); unlink(ww_s);
|
||||
unlink(cs_e); unlink(ww_e);
|
||||
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);
|
||||
unlink(cs_e); unlink(ww_e);
|
||||
continue;
|
||||
}
|
||||
if (!file_eq(cs_s, ww_s)) {
|
||||
fprintf(stderr, "FAIL row[%s]: cs != ww .s\n",
|
||||
r->label);
|
||||
fail++;
|
||||
}
|
||||
if (r->needs && !file_has(cs_s, r->needs)) {
|
||||
fprintf(stderr, "FAIL row[%s]: expected mem-cleanup "
|
||||
"marker missing from .s\n", r->label);
|
||||
fail++;
|
||||
}
|
||||
if (r->rejects && file_has(cs_s, r->rejects)) {
|
||||
fprintf(stderr, "FAIL row[%s]: boundary row emitted "
|
||||
"the mem-cleanup marker (classifier 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);
|
||||
unlink(cs_e); unlink(ww_e);
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "tagged_memarg_run: %d/%d rows failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("tagged_memarg_run: %d rows ok\n", total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user