test: port the tagged-memarg marker windows to ww; retire 929_tagged_memarg_run

The 18 active rows keep compile + cs==ws byte-id and the tab-anchored
ADDQ $N,SP cleanup markers with both polarities (mem-class rows must
contain their slot's cleanup; the exactly-48B boundary row must not).
The per-row driver build+run legs (incl. the uninit-global zero-box
exit 91) move to the r929 run fixture corpus per the residual audit
port split; the four buildfail rows were already dead here, owned by
the r929_memarg_reject_* fixtures.
This commit is contained in:
2026-08-08 15:02:15 +09:00
parent f2539bd4d9
commit bec4340c44
3 changed files with 457 additions and 785 deletions

View File

@@ -387,7 +387,8 @@ XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%)
ASM_WW_TESTS = test/asm/modshadow_test.ww test/asm/sret_test.ww \ ASM_WW_TESTS = test/asm/modshadow_test.ww test/asm/sret_test.ww \
test/asm/callarg_test.ww test/asm/chain_test.ww \ test/asm/callarg_test.ww test/asm/chain_test.ww \
test/asm/dataemit_test.ww test/asm/matchdispatch_test.ww \ test/asm/dataemit_test.ww test/asm/matchdispatch_test.ww \
test/asm/ampfn_test.ww test/asm/tagsret_test.ww test/asm/ampfn_test.ww test/asm/tagsret_test.ww \
test/asm/memarg_test.ww
ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%) ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%)
# Ww-native assembler/linker OBJECT-level observers: single-file ww # Ww-native assembler/linker OBJECT-level observers: single-file ww

455
test/asm/memarg_test.ww Normal file
View File

@@ -0,0 +1,455 @@
package memarg_test;
// Direct-w6c asm-window gate over >48B tagged by-value ARGS (#38b).
// Port of the retired native carrier test/wcc/929_tagged_memarg_run.c.
// A tagged arg whose slot exceeds the 6-reg convention (48B) is
// MEMORY-class: the caller stages the slot on the outgoing stack and
// the caller-cleanup ADDQ reclaims it after CALL (ref/qbe/amd64/
// sysv.c:80-85 inmem, :411-426 stack blit). Pre-fix the widened
// concrete source slipped the guard SILENTLY on both stages AND
// cs != ww — the exact tab-anchored cleanup markers are the
// discriminator no fixture can own.
//
// Per row: w6c and w6c_ww both compile (rc 0, .s on stdout as the
// carrier drove them), byte-identical .s (rule 10), then the marker:
// `needs` rows must contain their `\tADDQ\t$N, SP\n` cleanup line
// (N = slot bytes; $112/$120 for the two-mem-arg layouts), and the
// boundary48_register row (40B payload = EXACTLY 48B slot) must NOT
// contain `\tADDQ\t$48, SP\n` — an off-by-one in tagged_memarg_size
// flips every 48B-slot call in the tree.
//
// The carrier's per-row driver build+run legs move to the r929 run
// fixture corpus per the residual audit port split (the uninit-global
// row's zero-box exit 91 rides there); the four buildfail rows were
// already dead here — their #38b loud-stop diagnostics are owned by
// the r929_memarg_reject_* fixtures.
import os;
import os.exec;
import strings;
import testenv;
import time;
fn fail(label: str, why: str) void = {
let m: str = strings.concat("memarg FAIL: ", label, " -- ", why,
"\n");
os.write(2, m.ptr, m.len: u64);
assert(false);
};
fn tmo() time.duration = {
return (180i64 * (time.second: i64)): time.duration;
};
fn emitstdout(td: str, label: str, stage: str, drv: str) str = {
let av: []str = [drv, strings.concat(td, "/case.ww")];
let co: testenv.commandout;
testenv.runcommand(td, td, stage, av, tmo(), &co);
if (co.termination != exec.termination.EXIT || co.code != 0) {
fail(label, strings.concat(stage, " compile failed"));
};
return co.stdout;
};
// needs/rejects: "" skips that polarity, like the carrier's NULLs.
fn memargrow(label: str, src: str, needs: str, rejects: str) void = {
let td: str = testenv.fresh();
testenv.writefile(strings.concat(td, "/case.ww"),
strings.concat("package main;\n\n", src));
let cs: str = emitstdout(td, label, "cstage", testenv.driver("w6c"));
let ws: str = emitstdout(td, label, "wwstage",
testenv.driver("w6c_ww"));
if (!testenv.same(cs, ws)) {
fail(label, "cstage vs wwstage asm differs");
};
if (needs.len != 0 && !testenv.has(cs, needs)) {
fail(label, "expected mem-cleanup marker missing from .s");
};
if (rejects.len != 0 && testenv.has(cs, rejects)) {
fail(label, strings.concat("boundary row emitted the ",
"mem-cleanup marker (classifier off-by-one)"));
};
testenv.clean(td);
};
// 56B slot: 48B all-scalar payload + tag; m3 is the late word at slot
// offset +48, dead under the old 6-reg cap.
fn mem56types() str = {
return strings.concat(
"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.
fn mem56probe() str = {
return strings.concat(
"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");
};
fn replit() str = {
return strings.concat(
"t_rep { id = 7, origin = 9, m0 = 10, m1 = 11, m2 = 12,\n",
" m3 = 1234 }");
};
def NEED56: str = "\tADDQ\t$56, SP\n";
@test fn mem56rows() void = {
memargrow("mem56_ident_allvariants", strings.concat(mem56types(),
mem56probe(),
"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 = ", replit(), ";\n",
" let cc: t_u = r;\n",
" if (probe(cc) != 3) { return 3; };\n",
" return 0;\n",
"};\n"), NEED56, "");
memargrow("mem64_ident_lateword", strings.concat(
"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"), "\tADDQ\t$64, SP\n", "");
memargrow("mem56_widen_concrete", strings.concat(mem56types(),
mem56probe(),
"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 = ", replit(), ";\n",
" if (probe(r) != 3) { return 3; };\n",
" return 0;\n",
"};\n"), NEED56, "");
memargrow("mem56_subset_remap", strings.concat(mem56types(),
"type t_sub = (t_lit | t_any);\n",
mem56probe(),
"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"), NEED56, "");
memargrow("mem56_srcshapes", strings.concat(mem56types(),
"type holder = struct { k: i64, u: t_u };\n",
mem56probe(),
"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 = ", replit(), ";\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"), NEED56, "");
memargrow("mem56_mixed_orders", strings.concat(mem56types(),
"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 = ", replit(), ";\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"), NEED56, "");
memargrow("mem56_call_in_loop", strings.concat(mem56types(),
"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 = ", replit(), ";\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"), NEED56, "");
// TWO mem args (56B + 64B): $120 pins the left-to-right outgoing
// layout (leftmost mem arg at 16(BP)).
memargrow("mem56_twomem", strings.concat(mem56types(),
"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 = ", replit(), ";\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"), "\tADDQ\t$120, SP\n", "");
// The real lib/regex inst layout; outer-tag dispatch only (inner-
// field matches ride the pre-existing match-on-tagged-struct-field
// divergence class, out of scope here).
memargrow("mem56_inst_shape", strings.concat(
"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"), NEED56, "");
};
// #40/FB3 place-resolved element sources (cgplaceaddr fallback).
@test fn elemrows() void = {
memargrow("mem56_elem_source", strings.concat(mem56types(),
mem56probe(),
"export fn main() i32 = {\n",
" let xs: []t_u = [];\n",
" let a: t_u = ('x': t_lit);\n",
" append(xs, a);\n",
" let r: t_rep = ", replit(), ";\n",
" let b: t_u = r;\n",
" append(xs, b);\n",
" if (probe(xs[0]) != 1) { return 1; };\n",
" if (probe(xs[1]) != 3) { return 2; };\n",
" let i: size = 1;\n",
" if (probe(xs[i]) != 3) { return 3; };\n",
" return 0;\n",
"};\n"), NEED56, "");
memargrow("mem56_elem_deref_spine", strings.concat(mem56types(),
mem56probe(),
"export fn main() i32 = {\n",
" let xs: []t_u = [];\n",
" let r: t_rep = ", replit(), ";\n",
" let b: t_u = r;\n",
" append(xs, b);\n",
" let p: *[]t_u = &xs;\n",
" if (probe((*p)[0]) != 3) { return 1; };\n",
" return 0;\n",
"};\n"), NEED56, "");
memargrow("mem56_elem_nested_spine", strings.concat(mem56types(),
"type re_t = struct { flag: i64, insts: []t_u };\n",
"type thr = struct { pc: size, gen: i64 };\n",
mem56probe(),
"export fn main() i32 = {\n",
" let xs: []t_u = [];\n",
" let a: t_u = ('x': t_lit);\n",
" append(xs, a);\n",
" let r: t_rep = ", replit(), ";\n",
" let b: t_u = r;\n",
" append(xs, b);\n",
" let re: re_t = re_t { flag = 4, insts = xs };\n",
" let threads: []thr = [];\n",
" append(threads, thr { pc = 1, gen = 5 });\n",
" if (probe(re.insts[threads[0].pc]) != 3) { return 1; };\n",
" return 0;\n",
"};\n"), NEED56, "");
memargrow("mem56_elem_mixed_regs", strings.concat(mem56types(),
"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 xs: []t_u = [];\n",
" let r: t_rep = ", replit(), ";\n",
" let b: t_u = r;\n",
" append(xs, b);\n",
" if (before(5, \"abc\", xs[0]) != 1239) { return 1; };\n",
" if (after(xs[0], 6, \"abc\") != 1240) { return 2; };\n",
" return 0;\n",
"};\n"), NEED56, "");
// Inner CALL runs inside the mem pre-pass with the other arg's 7
// words already staged — the resolver's push-balance claim; $112
// = both 56B slots.
memargrow("mem56_elem_callidx_twomem", strings.concat(mem56types(),
mem56probe(),
"fn pick() size = {\n",
" return 1;\n",
"};\n",
"fn two(a: t_u, b: t_u) i32 = {\n",
" return probe(a) * 10 + probe(b);\n",
"};\n",
"export fn main() i32 = {\n",
" let xs: []t_u = [];\n",
" let a: t_u = ('x': t_lit);\n",
" append(xs, a);\n",
" let r: t_rep = ", replit(), ";\n",
" let b: t_u = r;\n",
" append(xs, b);\n",
" if (two(xs[0], xs[pick()]) != 13) { return 1; };\n",
" if (two(xs[pick()], xs[0]) != 31) { return 2; };\n",
" return 0;\n",
"};\n"), "\tADDQ\t$112, SP\n", "");
// Slice globals have DATA storage, so the element resolves via the
// let_islet arm; the storage-LESS global-VALUE twin stays loud
// (task #25) and is pinned by the r929 reject fixtures.
memargrow("mem56_elem_global_slice", strings.concat(mem56types(),
"let gxs: []t_u;\n",
mem56probe(),
"export fn main() i32 = {\n",
" let r: t_rep = ", replit(), ";\n",
" let b: t_u = r;\n",
" append(gxs, b);\n",
" if (probe(gxs[0]) != 3) { return 1; };\n",
" return 0;\n",
"};\n"), NEED56, "");
};
@test fn boundaryrows() void = {
memargrow("boundary48_register", strings.concat(
"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"), "", "\tADDQ\t$48, SP\n");
// GRADUATED by #87 (tagged globals emit static DATA): the uninit
// >48B tagged GLOBAL arg compiles through the cgplaceaddr arm;
// its zero-box run-exit 91 rides the r929 run fixture corpus.
memargrow("global_uninit_memarg_src", strings.concat(mem56types(),
"let g: t_u;\n",
mem56probe(),
"export fn main() i32 = {\n",
" return probe(g);\n",
"};\n"), "", "");
// #35 Family C flip: the exact-type same-type cast is peeled
// before memarg place resolution, so the IDENT place is wired.
memargrow("memarg_idcast_peeled", strings.concat(mem56types(),
mem56probe(),
"export fn main() i32 = {\n",
" let a: t_u = ('x': t_lit);\n",
" if (probe((a: t_u)) != 1) { return 1; };\n",
" return 0;\n",
"};\n"), "", "");
};

View File

@@ -1,784 +0,0 @@
/*
* 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` are source carriers only; their exact symmetric
* diagnostics moved to r929_memarg_reject_* wwfixtures. #40/FB3 wired the place-resolved
* sources (slice element, deref-spine, nested index) via the
* cgplaceaddr fallback — those are run rows now.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.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 },
/* #40/FB3: exact-type SLICE-ELEMENT source — `f(xs[i])`, the
* regex run_thread loop-condition shape
* (is_consuming_inst(re.insts[threads[i].pc]), regex.ha:602).
* Staged from a cgplaceaddr-resolved address; literal and
* computed indices, every variant, late payload word per arm. */
{ "mem56_elem_source",
MEM56_TYPES
MEM56_PROBE
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let a: t_u = ('x': t_lit);\n"
" append(xs, a);\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" if (probe(xs[0]) != 1) { return 1; };\n"
" if (probe(xs[1]) != 3) { return 2; };\n"
" let i: size = 1;\n"
" if (probe(xs[i]) != 3) { return 3; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: deref-spine element source — `f((*p)[i])`, B4's
* actual spelling (slice reached through a pointer). */
{ "mem56_elem_deref_spine",
MEM56_TYPES
MEM56_PROBE
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" let p: *[]t_u = &xs;\n"
" if (probe((*p)[0]) != 3) { return 1; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: the full nested spelling — dot-base slice indexed
* by a field of another indexed element
* (re.insts[threads[i].pc]). */
{ "mem56_elem_nested_spine",
MEM56_TYPES
"type re_t = struct { flag: i64, insts: []t_u };\n"
"type thr = struct { pc: size, gen: i64 };\n"
MEM56_PROBE
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let a: t_u = ('x': t_lit);\n"
" append(xs, a);\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" let re: re_t = re_t { flag = 4, insts = xs };\n"
" let threads: []thr = [];\n"
" append(threads, thr { pc = 1, gen = 5 });\n"
" if (probe(re.insts[threads[0].pc]) != 3) { return 1; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: element source mixed with register-class args, both
* orders — the resolver's transient AX/CX clobbers must not
* disturb the staged words or the register cursors. */
{ "mem56_elem_mixed_regs",
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 xs: []t_u = [];\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" if (before(5, \"abc\", xs[0]) != 1239) { return 1; };\n"
" if (after(xs[0], 6, \"abc\") != 1240) { return 2; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$56, SP\n", NULL, 0, NULL },
/* #40/FB3: fn-CALL index (the 2c computed-pc shape) + TWO mem
* args per call, both orders. The inner CALL runs inside the
* mem pre-pass with the other arg's 7 words already staged —
* pins the resolver's push-balance claim; $112 = both slots. */
{ "mem56_elem_callidx_twomem",
MEM56_TYPES
MEM56_PROBE
"fn pick() size = {\n"
" return 1;\n"
"};\n"
"fn two(a: t_u, b: t_u) i32 = {\n"
" return probe(a) * 10 + probe(b);\n"
"};\n"
"export fn main() i32 = {\n"
" let xs: []t_u = [];\n"
" let a: t_u = ('x': t_lit);\n"
" append(xs, a);\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(xs, b);\n"
" if (two(xs[0], xs[pick()]) != 13) { return 1; };\n"
" if (two(xs[pick()], xs[0]) != 31) { return 2; };\n"
" return 0;\n"
"};\n",
0, "\tADDQ\t$112, SP\n", NULL, 0, NULL },
/* #40/FB3: element of a GLOBAL slice let — the resolver's
* let_islet arm, sound because slice globals have DATA storage
* (the element lives behind .ptr). The storage-LESS twin (a
* global tagged VALUE, let_emit_size=0, task #25) must stay
* loud — fail_global_src pins that side of the boundary. */
{ "mem56_elem_global_slice",
MEM56_TYPES
"let gxs: []t_u;\n"
MEM56_PROBE
"export fn main() i32 = {\n"
" let r: t_rep = " MEM56_REP_LIT ";\n"
" let b: t_u = r;\n"
" append(gxs, b);\n"
" if (probe(gxs[0]) != 3) { return 1; };\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)" },
/* GRADUATED by #87: tagged-union globals now emit static DATA and
* register as letvars, so a >48B tagged GLOBAL by-value arg resolves
* through the MEMORY-class pre-pass's cgplaceaddr arm (LEAQ g(SB) +
* 56B blit) instead of the #38b loud-stop. Pre-#87 let_emit_size
* returned 0 for TY_TAGGED, so g was not a letvar and the arg path
* stopped loud. An UNINIT tagged global is a zero box = the FIRST
* variant (t_lit rune 0); probe reads tag 0 → the rune arm → 0 != 'x'
* → 91. The arg-path blit is value-agnostic (any box at g(SB) copies
* the same way); a NON-zero >48B init needs a struct-variant literal,
* which #87's int/str-only DATA emit loud-stops — that data-emit
* boundary is pinned in attest_pass.ww (910/997). The first-class-
* VALUE copy of a tagged global (`let q = g`) stays a pre-existing
* silent residual (task #111), independent of this read-into-arg
* path. */
{ "global_uninit_memarg_src",
MEM56_TYPES
"let g: t_u;\n"
MEM56_PROBE
"export fn main() i32 = {\n"
" return probe(g);\n" /* zero box → rune-0 arm → 91 */
"};\n",
91, NULL, NULL, 0, NULL },
/* 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" },
/* #35 (Family C) FLIP: the exact-type same-type cast is peeled
* (cg_tagged_castpeel / taggedcastpeel) before the memarg place
* resolution, so `probe((a: t_u))` reduces to the wired IDENT
* place and runs — the old loud pin ("rvalue and
* unresolvable-place sources unwired") covers only the shapes
* with no peeled place left. */
{ "memarg_idcast_peeled",
MEM56_TYPES
MEM56_PROBE
"export fn main() i32 = {\n"
" let a: t_u = ('x': t_lit);\n"
" if (probe((a: t_u)) != 1) { return 1; };\n"
" return 0;\n"
"};\n",
0, NULL, NULL, 0, NULL },
};
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 *outbin,
const char *label)
{
char cmd[1024];
snprintf(cmd, sizeof cmd, "%s/%s build -o %s %s >/dev/null 2>&1",
g_bin, driver, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
label, driver);
return -1;
}
return runwait(outbin);
}
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 tmpdir[128];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tmemarg_%d_%d_XXXXXX",
getpid(), i);
if (mkdtemp(tmpdir) == NULL) {
fprintf(stderr, "FAIL row[%s]: temporary directory "
"acquisition failed\n", r->label);
fail++;
continue;
}
char src[192], cs_s[192], ww_s[192], cs_e[192], ww_e[192];
char cs_bin[192], ww_bin[192], cs_work[208], ww_work[208];
snprintf(src, sizeof src, "%s/case.ww", tmpdir);
snprintf(cs_s, sizeof cs_s, "%s/cs.s", tmpdir);
snprintf(ww_s, sizeof ww_s, "%s/ww.s", tmpdir);
snprintf(cs_e, sizeof cs_e, "%s/cs.err", tmpdir);
snprintf(ww_e, sizeof ww_e, "%s/ww.err", tmpdir);
snprintf(cs_bin, sizeof cs_bin, "%s/run_c", tmpdir);
snprintf(ww_bin, sizeof ww_bin, "%s/run_w", tmpdir);
snprintf(cs_work, sizeof cs_work, "%s.sepwork", cs_bin);
snprintf(ww_work, sizeof ww_work, "%s.sepwork", ww_bin);
FILE *f = fopen(src, "wb");
if (!f) {
fprintf(stderr, "FAIL row[%s]: source setup failed\n",
r->label);
fail++;
goto row_cleanup;
}
fputs("package main;\n\n", f);
fputs(r->src, f);
fclose(f);
/* Loud-reject behavior is owned by r929_memarg_reject_* fixtures. */
if (r->buildfail) goto row_cleanup;
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++;
}
goto row_cleanup;
}
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++;
goto row_cleanup;
}
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, cs_bin, 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, ww_bin, 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++;
}
}
row_cleanup:
{
int bad = 0;
char rmcmd[256];
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", cs_work);
if (runwait(rmcmd) != 0) bad = 1;
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", ww_work);
if (runwait(rmcmd) != 0) bad = 1;
if (unlink(cs_bin) != 0 && errno != ENOENT) bad = 1;
if (unlink(ww_bin) != 0 && errno != ENOENT) bad = 1;
if (unlink(src) != 0 && errno != ENOENT) bad = 1;
if (unlink(cs_s) != 0 && errno != ENOENT) bad = 1;
if (unlink(ww_s) != 0 && errno != ENOENT) bad = 1;
if (unlink(cs_e) != 0 && errno != ENOENT) bad = 1;
if (unlink(ww_e) != 0 && errno != ENOENT) bad = 1;
if (rmdir(tmpdir) != 0) bad = 1;
if (bad) {
fprintf(stderr, "FAIL row[%s]: temporary cleanup failed\n",
r->label);
fail++;
}
}
}
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;
}