From bec4340c44871885eaee205420acc3edcf912265 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 15:02:15 +0900 Subject: [PATCH] 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. --- Makefile | 3 +- test/asm/memarg_test.ww | 455 ++++++++++++++++++ test/wcc/929_tagged_memarg_run.c | 784 ------------------------------- 3 files changed, 457 insertions(+), 785 deletions(-) create mode 100644 test/asm/memarg_test.ww delete mode 100644 test/wcc/929_tagged_memarg_run.c diff --git a/Makefile b/Makefile index 379ddecb..5dc836fc 100644 --- a/Makefile +++ b/Makefile @@ -387,7 +387,8 @@ XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%) 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/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/%) # Ww-native assembler/linker OBJECT-level observers: single-file ww diff --git a/test/asm/memarg_test.ww b/test/asm/memarg_test.ww new file mode 100644 index 00000000..bdf3aa19 --- /dev/null +++ b/test/asm/memarg_test.ww @@ -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"), "", ""); +}; diff --git a/test/wcc/929_tagged_memarg_run.c b/test/wcc/929_tagged_memarg_run.c deleted file mode 100644 index 588101de..00000000 --- a/test/wcc/929_tagged_memarg_run.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include - -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; -}