From f2539bd4d984e8944a5cfe21294891121f41210c Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 15:02:02 +0900 Subject: [PATCH] test: port the tagged-sret marker windows to ww; retire 926_tagged_sret_run The 13 active rows keep compile + cs==ws byte-id and the hidden-RDI "(BP), DI" presence/absence polarity (present iff sret-class; the two exactly-32B boundary rows must stay register). The per-row driver build+run legs move to the r926 run fixture corpus per the residual audit port split; the seven buildfail rows were already dead here, owned by the r926_sret_reject_* fixtures. --- Makefile | 2 +- test/asm/tagsret_test.ww | 295 ++++++++++++++++ test/wcc/926_tagged_sret_run.c | 619 --------------------------------- 3 files changed, 296 insertions(+), 620 deletions(-) create mode 100644 test/asm/tagsret_test.ww delete mode 100644 test/wcc/926_tagged_sret_run.c diff --git a/Makefile b/Makefile index 159e2c38..379ddecb 100644 --- a/Makefile +++ b/Makefile @@ -387,7 +387,7 @@ 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/ampfn_test.ww test/asm/tagsret_test.ww ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%) # Ww-native assembler/linker OBJECT-level observers: single-file ww diff --git a/test/asm/tagsret_test.ww b/test/asm/tagsret_test.ww new file mode 100644 index 00000000..faebf1be --- /dev/null +++ b/test/asm/tagsret_test.ww @@ -0,0 +1,295 @@ +package tagsret_test; + +// Direct-w6c asm-window gate over tagged-union sret returns (#38b). +// Port of the retired native carrier test/wcc/926_tagged_sret_run.c. +// A tagged RETURN whose slot exceeds the 32B register cursor routes +// through the SysV sret discipline; pre-#38 both stages emitted the +// SAME truncating cursor loads, so byte-id alone was gate-blind — the +// hidden-RDI marker is the discriminator no fixture can own. +// +// Per row: w6c and w6c_ww both compile (rc 0, .s on stdout exactly as +// the carrier drove them), the two .s are byte-identical (rule 10), +// and the .s contains "(BP), DI\n" (the hidden-RDI LEAQ caller dest) +// IFF the row is sret-class. The two boundary rows (slot EXACTLY 32B: +// (s3|bool) and (str|nomem)) must NOT contain it — an off-by-one in +// the classifier flips every (T|nomem) consumer in the tree. +// +// The carrier's per-row driver build+run legs move to the r926 run +// fixture corpus per the residual audit port split; the seven +// buildfail rows were already dead there — their loud-reject +// diagnostics are owned by the r926_sret_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("tagsret 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; +}; + +// .s arrives on stdout — the carrier's `w6c src > out.s` invocation. +fn emitstdout(td: str, label: str, stage: str, drv: str) str = { + let av: []str = [drv, strings.concat(td, "/input.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; +}; + +fn sretrow(label: str, src: str, sret: bool) void = { + let td: str = testenv.fresh(); + testenv.writefile(strings.concat(td, "/input.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"); + }; + let hasdi: bool = testenv.has(cs, "(BP), DI\n"); + if (sret && !hasdi) { + fail(label, "expected sret hidden-RDI LEAQ, none emitted"); + }; + if (!sret && hasdi) { + fail(label, + "32B-slot row flipped to sret (classifier off-by-one)"); + }; + testenv.clean(td); +}; + +// 56B payload (two slices + i64) inside a 3-variant union = 64B slot, +// the lib/regex compile() return shape that surfaced #38. +fn widetypes() str = { + return strings.concat( + "type oops = !str;\n", + "type nomem = !void;\n", + "type wide = struct { xs: []u8, ys: []u8, n: i64 };\n"); +}; + +fn widemaincheck() str = { + return strings.concat( + " case let e: oops => return 13;\n", + " case nomem => return 14;\n", + " };\n", + " return 0;\n", + "};\n"); +}; + +fn widemk() str = { + return strings.concat( + "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n", + " return wide { xs = b, ys = b, n = n };\n", + "};\n"); +}; + +// The last payload word n (dead pre-#38) is checked FIRST so its loss +// is the visible failure in the fixture twin; the same sources here +// feed the marker window. +@test fn widerows() void = { + sretrow("wide_lit_roundtrip", strings.concat(widetypes(), widemk(), + "export fn main() i32 = {\n", + " let buf: [4]u8 = [9u8, 8u8, 7u8, 6u8];\n", + " match (mk(buf[0:4], 42)) {\n", + " case let w: wide => {\n", + " if (w.n != 42) { return 1; };\n", + " if (w.xs.len != 4) { return 2; };\n", + " if (w.ys.len != 4) { return 3; };\n", + " if (w.xs[3] != 6u8) { return 4; };\n", + " };\n", widemaincheck()), true); + sretrow("wide_local_roundtrip", strings.concat(widetypes(), + "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n", + " let w: wide = wide { xs = b, ys = b, n = n };\n", + " return w;\n", + "};\n", + "export fn main() i32 = {\n", + " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n", + " let r: (wide | oops | nomem) = mk(buf[0:4], 7);\n", + " match (r) {\n", + " case let w: wide => {\n", + " if (w.n != 7) { return 1; };\n", + " if (w.ys[0] != 1u8) { return 2; };\n", + " };\n", widemaincheck()), true); + sretrow("wide_assign_receive", strings.concat(widetypes(), widemk(), + "export fn main() i32 = {\n", + " let canary1: i64 = 111;\n", + " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n", + " let r: (wide | oops | nomem) = mk(buf[0:4], 5);\n", + " let canary2: i64 = 222;\n", + " r = mk(buf[0:4], 6);\n", + " if (canary1 != 111) { return 21; };\n", + " if (canary2 != 222) { return 22; };\n", + " match (r) {\n", + " case let w: wide => { if (w.n != 6) { return 1; }; };\n", + widemaincheck()), true); + sretrow("wide_match_scrutinee", strings.concat(widetypes(), widemk(), + "export fn main() i32 = {\n", + " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n", + " match (mk(buf[0:4], 9)) {\n", + " case let w: wide => { if (w.n != 9) { return 1; }; };\n", + widemaincheck()), true); + sretrow("wide_forward_exact", strings.concat(widetypes(), + "fn inner(b: []u8, n: i64) (wide | oops | nomem) = {\n", + " return wide { xs = b, ys = b, n = n };\n", + "};\n", + "fn outer(b: []u8, n: i64) (wide | oops | nomem) = {\n", + " return inner(b, n);\n", + "};\n", + "export fn main() i32 = {\n", + " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n", + " match (outer(buf[0:4], 11)) {\n", + " case let w: wide => { if (w.n != 11) { return 1; }; };\n", + widemaincheck()), true); + sretrow("wide_str_error_variant", strings.concat(widetypes(), + "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n", + " if (n < 0) { return \"neg\": oops; };\n", + " return wide { xs = b, ys = b, n = n };\n", + "};\n", + "export fn main() i32 = {\n", + " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n", + " match (mk(buf[0:4], -1)) {\n", + " case let w: wide => return 1;\n", + " case let e: oops => {\n", + " if ((e: str).len != 3) { return 2; };\n", + " };\n", + " case nomem => return 3;\n", + " };\n", + " return 0;\n", + "};\n"), true); + sretrow("wide_multicall", strings.concat(widetypes(), widemk(), + "export fn main() i32 = {\n", + " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n", + " let r1: (wide | oops | nomem) = mk(buf[0:4], 100);\n", + " let r2: (wide | oops | nomem) = mk(buf[0:4], 200);\n", + " match (r1) {\n", + " case let w: wide => { if (w.n != 100) { return 1; }; };\n", + " case let e: oops => return 13;\n", + " case nomem => return 14;\n", + " };\n", + " match (r2) {\n", + " case let w: wide => { if (w.n != 200) { return 2; }; };\n", + widemaincheck()), true); + sretrow("wide_bare_return_void", strings.concat( + "type s4 = struct { a: i64, b: i64, c: i64, d: i64 };\n", + "fn maybe(n: i64) (s4 | void) = {\n", + " if (n == 0) { return; };\n", + " return s4 { a = 1, b = 2, c = 3, d = n };\n", + "};\n", + "export fn main() i32 = {\n", + " match (maybe(0)) {\n", + " case let v: s4 => return 1;\n", + " case void => { };\n", + " };\n", + " match (maybe(5)) {\n", + " case let v: s4 => { if (v.d != 5) { return 2; }; };\n", + " case void => return 3;\n", + " };\n", + " return 0;\n", + "};\n"), true); + sretrow("wide_s4_repro", strings.concat( + "type s4 = struct { a: i64, b: i64, c: i64, d: i64 };\n", + "fn taglit4() (s4 | bool) = {\n", + " return s4 { a = 1, b = 2, c = 3, d = 4 };\n", + "};\n", + "fn taglocal4() (s4 | bool) = {\n", + " let v: s4 = s4 { a = 1, b = 2, c = 3, d = 4 };\n", + " return v;\n", + "};\n", + "export fn main() i32 = {\n", + " match (taglit4()) {\n", + " case let r: s4 => { if (r.d != 4) { return 2; }; };\n", + " case let b: bool => return 3;\n", + " };\n", + " match (taglocal4()) {\n", + " case let r: s4 => { if (r.d != 4) { return 4; }; };\n", + " case let b: bool => return 5;\n", + " };\n", + " return 0;\n", + "};\n"), true); + // errors.error's opaque_ variant shape: the LIVE in-tree #38 + // instance that truncated benignly-by-luck (data[2] never read). + sretrow("errno_shaped_tail_read", strings.concat( + "type opq = struct { h: i64, data: [3]u64 };\n", + "fn wrap(e: i64) (opq | bool) = {\n", + " let o: opq;\n", + " o.h = 7;\n", + " o.data[0] = e: u64;\n", + " o.data[1] = 1111u64;\n", + " o.data[2] = 2222u64;\n", + " return o;\n", + "};\n", + "export fn main() i32 = {\n", + " match (wrap(5)) {\n", + " case let o: opq => {\n", + " if (o.data[2] != 2222u64) { return 1; };\n", + " if (o.data[1] != 1111u64) { return 2; };\n", + " if (o.data[0] != 5u64) { return 3; };\n", + " if (o.h != 7) { return 4; };\n", + " };\n", + " case let b: bool => return 5;\n", + " };\n", + " return 0;\n", + "};\n"), true); + sretrow("wide_discard_stmt", strings.concat(widetypes(), + "fn mk(b: []u8) (wide | oops | nomem) = {\n", + " return wide { xs = b, ys = b, n = 1 };\n", + "};\n", + "export fn main() i32 = {\n", + " let canary: i64 = 777;\n", + " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n", + " mk(buf[0:4]);\n", + " mk(buf[0:4]);\n", + " if (canary != 777) { return 1; };\n", + " return 0;\n", + "};\n"), true); +}; + +// The 32B-slot class fmt/io/strconv return everywhere; flipping it to +// sret breaks the tree (ken's top #38 risk). +@test fn boundaryrows() void = { + sretrow("boundary_s3_register", strings.concat( + "type s3 = struct { a: i64, b: i64, c: i64 };\n", + "fn mk(ok: bool) (s3 | bool) = {\n", + " if (!ok) { return false; };\n", + " return s3 { a = 7, b = 8, c = 9 };\n", + "};\n", + "export fn main() i32 = {\n", + " match (mk(true)) {\n", + " case let v: s3 => { if (v.c != 9) { return 1; }; };\n", + " case let b: bool => return 2;\n", + " };\n", + " match (mk(false)) {\n", + " case let v: s3 => return 3;\n", + " case let b: bool => { if (b) { return 4; }; };\n", + " };\n", + " return 0;\n", + "};\n"), false); + sretrow("boundary_str_nomem_register", strings.concat( + "type nomem = !void;\n", + "fn pick(ok: bool) (str | nomem) = {\n", + " if (ok) { return \"hello\"; };\n", + " let nm: nomem;\n", + " return nm;\n", + "};\n", + "export fn main() i32 = {\n", + " match (pick(true)) {\n", + " case let s: str => { if (s.len != 5) { return 1; }; };\n", + " case nomem => return 2;\n", + " };\n", + " match (pick(false)) {\n", + " case let s: str => return 3;\n", + " case nomem => { };\n", + " };\n", + " return 0;\n", + "};\n"), false); +}; diff --git a/test/wcc/926_tagged_sret_run.c b/test/wcc/926_tagged_sret_run.c deleted file mode 100644 index ab9f7f11..00000000 --- a/test/wcc/926_tagged_sret_run.c +++ /dev/null @@ -1,619 +0,0 @@ -/* - * 926_tagged_sret_run — tagged-union sret returns (#38b): a tagged - * RETURN whose slot exceeds the AX/DX/CX/R8 register cursor - * (TUPLE_GPCAP eightbytes = 32B; tag + 3 payload words) routes - * through the SysV sret discipline instead of silently truncating - * payload word 4+ in the callee frame. - * - * Pre-#38 both stages emitted the SAME truncating cursor loads - * (gate-blind byte-id): the regex-shaped 56B payload (64B slot) - * died at the return crossing while locals, args, and the widener's - * memory stores were all correct. errors.errno's 40B (errors.error) - * slot was the latent in-tree instance — benign only because no - * consumer read opaque_data word 2. - * - * Three checks per row: - * - byte-id: w6c vs w6c_ww .s must be identical (rule 10). - * - sret-presence: rows flagged `sret` must emit the hidden-RDI - * `LEAQ (BP), DI` caller dest; boundary rows (slot EXACTLY - * 32B — the (str|nomem)/(s3|bool) class) must NOT — an - * off-by-one in the classifier flips every (T|nomem) consumer - * in the tree (ken's top #38 risk). - * - runtime: build via the ww / ww_ww drivers and run; the exit - * code pins every payload word INCLUDING the last (the - * truncation signature). - * Rows flagged `buildfail` are source carriers only; their symmetric - * diagnostic checks moved to r926_sret_reject_* wwfixtures. - */ -#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) */ - int sret; /* 1: .s must contain LEAQ..DI; 0: must not */ - int buildfail; /* 1: both stages must reject (loud-stop) */ -}; - -/* Shared regex-shaped prelude: 56B payload (two slices + i64) inside - * a 3-variant union = 64B slot, the lib/regex compile() return shape - * that surfaced #38. */ -#define WIDE_TYPES \ - "type oops = !str;\n" \ - "type nomem = !void;\n" \ - "type wide = struct { xs: []u8, ys: []u8, n: i64 };\n" - -#define WIDE_MAIN_CHECK \ - " case let e: oops => return 13;\n" \ - " case nomem => return 14;\n" \ - " };\n" \ - " return 0;\n" \ - "};\n" - -static const struct row rows[] = { - /* Literal source; every field checked, n (the LAST payload - * word, dead pre-#38) checked FIRST so its loss is exit 1. */ - { "wide_lit_roundtrip", - WIDE_TYPES - "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = n };\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [9u8, 8u8, 7u8, 6u8];\n" - " match (mk(buf[0:4], 42)) {\n" - " case let w: wide => {\n" - " if (w.n != 42) { return 1; };\n" - " if (w.xs.len != 4) { return 2; };\n" - " if (w.ys.len != 4) { return 3; };\n" - " if (w.xs[3] != 6u8) { return 4; };\n" - " };\n" - WIDE_MAIN_CHECK, - 0, 1, 0 }, - /* Local-ident source (the widener's mem-to-mem struct arm). */ - { "wide_local_roundtrip", - WIDE_TYPES - "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n" - " let w: wide = wide { xs = b, ys = b, n = n };\n" - " return w;\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " let r: (wide | oops | nomem) = mk(buf[0:4], 7);\n" - " match (r) {\n" - " case let w: wide => {\n" - " if (w.n != 7) { return 1; };\n" - " if (w.ys[0] != 1u8) { return 2; };\n" - " };\n" - WIDE_MAIN_CHECK, - 0, 1, 0 }, - /* let-receive then ASSIGN-receive into the same slot, with - * frame canaries (locals around the receives must survive). */ - { "wide_assign_receive", - WIDE_TYPES - "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = n };\n" - "};\n" - "export fn main() i32 = {\n" - " let canary1: i64 = 111;\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " let r: (wide | oops | nomem) = mk(buf[0:4], 5);\n" - " let canary2: i64 = 222;\n" - " r = mk(buf[0:4], 6);\n" - " if (canary1 != 111) { return 21; };\n" - " if (canary2 != 222) { return 22; };\n" - " match (r) {\n" - " case let w: wide => { if (w.n != 6) { return 1; }; };\n" - WIDE_MAIN_CHECK, - 0, 1, 0 }, - /* match-scrutinee receive — the tagged-specific arm with no - * tuple precedent: the scrut slot itself is the sret dest. */ - { "wide_match_scrutinee", - WIDE_TYPES - "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = n };\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " match (mk(buf[0:4], 9)) {\n" - " case let w: wide => { if (w.n != 9) { return 1; }; };\n" - WIDE_MAIN_CHECK, - 0, 1, 0 }, - /* Exact-type return-forward: inner sret's straight into - * outer's caller dest (cg_sret_forward / c.sretforward). */ - { "wide_forward_exact", - WIDE_TYPES - "fn inner(b: []u8, n: i64) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = n };\n" - "};\n" - "fn outer(b: []u8, n: i64) (wide | oops | nomem) = {\n" - " return inner(b, n);\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " match (outer(buf[0:4], 11)) {\n" - " case let w: wide => { if (w.n != 11) { return 1; }; };\n" - WIDE_MAIN_CHECK, - 0, 1, 0 }, - /* str-payload error variant through the 64B slot (the widener - * str arm writing *(@sretarg)). */ - { "wide_str_error_variant", - WIDE_TYPES - "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n" - " if (n < 0) { return \"neg\": oops; };\n" - " return wide { xs = b, ys = b, n = n };\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " match (mk(buf[0:4], -1)) {\n" - " case let w: wide => return 1;\n" - " case let e: oops => {\n" - " if ((e: str).len != 3) { return 2; };\n" - " };\n" - " case nomem => return 3;\n" - " };\n" - " return 0;\n" - "};\n", - 0, 1, 0 }, - /* Two wide results live at once: distinct @match-independent - * receive slots, no shared-scratch clobber. */ - { "wide_multicall", - WIDE_TYPES - "fn mk(b: []u8, n: i64) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = n };\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " let r1: (wide | oops | nomem) = mk(buf[0:4], 100);\n" - " let r2: (wide | oops | nomem) = mk(buf[0:4], 200);\n" - " match (r1) {\n" - " case let w: wide => { if (w.n != 100) { return 1; }; };\n" - " case let e: oops => return 13;\n" - " case nomem => return 14;\n" - " };\n" - " match (r2) {\n" - " case let w: wide => { if (w.n != 200) { return 2; }; };\n" - WIDE_MAIN_CHECK, - 0, 1, 0 }, - /* Bare `return;` (void variant) through a 40B slot: the tag - * must land in *(@sretarg), not AX. */ - { "wide_bare_return_void", - "type s4 = struct { a: i64, b: i64, c: i64, d: i64 };\n" - "fn maybe(n: i64) (s4 | void) = {\n" - " if (n == 0) { return; };\n" - " return s4 { a = 1, b = 2, c = 3, d = n };\n" - "};\n" - "export fn main() i32 = {\n" - " match (maybe(0)) {\n" - " case let v: s4 => return 1;\n" - " case void => { };\n" - " };\n" - " match (maybe(5)) {\n" - " case let v: s4 => { if (v.d != 5) { return 2; }; };\n" - " case void => return 3;\n" - " };\n" - " return 0;\n" - "};\n", - 0, 1, 0 }, - /* The #38 repro shape: 32B struct payload = 40B slot, literal - * AND local sources (was exit 2 / silent d-drop at master). */ - { "wide_s4_repro", - "type s4 = struct { a: i64, b: i64, c: i64, d: i64 };\n" - "fn taglit4() (s4 | bool) = {\n" - " return s4 { a = 1, b = 2, c = 3, d = 4 };\n" - "};\n" - "fn taglocal4() (s4 | bool) = {\n" - " let v: s4 = s4 { a = 1, b = 2, c = 3, d = 4 };\n" - " return v;\n" - "};\n" - "export fn main() i32 = {\n" - " match (taglit4()) {\n" - " case let r: s4 => { if (r.d != 4) { return 2; }; };\n" - " case let b: bool => return 3;\n" - " };\n" - " match (taglocal4()) {\n" - " case let r: s4 => { if (r.d != 4) { return 4; }; };\n" - " case let b: bool => return 5;\n" - " };\n" - " return 0;\n" - "};\n", - 0, 1, 0 }, - /* errno-shaped graduation row: errors.error's opaque_ variant is - * struct { strerror *fn (8B), data [3]u64 (24B) } = 32B payload = - * 40B slot — the LIVE in-tree #38 instance that truncated - * benignly-by-luck at master (data[2] was never read). This row - * READS the previously-dropped tail word first, pinning the - * errors.errno sret graduation. Local-ident return source, like - * errno's `return err;`. */ - { "errno_shaped_tail_read", - "type opq = struct { h: i64, data: [3]u64 };\n" - "fn wrap(e: i64) (opq | bool) = {\n" - " let o: opq;\n" - " o.h = 7;\n" - " o.data[0] = e: u64;\n" - " o.data[1] = 1111u64;\n" - " o.data[2] = 2222u64;\n" - " return o;\n" - "};\n" - "export fn main() i32 = {\n" - " match (wrap(5)) {\n" - " case let o: opq => {\n" - " if (o.data[2] != 2222u64) { return 1; };\n" - " if (o.data[1] != 1111u64) { return 2; };\n" - " if (o.data[0] != 5u64) { return 3; };\n" - " if (o.h != 7) { return 4; };\n" - " };\n" - " case let b: bool => return 5;\n" - " };\n" - " return 0;\n" - "};\n", - 0, 1, 0 }, - /* BOUNDARY: 24B struct payload = EXACTLY 32B slot — must stay - * register-ABI (sret==0 pins no hidden-RDI LEAQ in the .s). */ - { "boundary_s3_register", - "type s3 = struct { a: i64, b: i64, c: i64 };\n" - "fn mk(ok: bool) (s3 | bool) = {\n" - " if (!ok) { return false; };\n" - " return s3 { a = 7, b = 8, c = 9 };\n" - "};\n" - "export fn main() i32 = {\n" - " match (mk(true)) {\n" - " case let v: s3 => { if (v.c != 9) { return 1; }; };\n" - " case let b: bool => return 2;\n" - " };\n" - " match (mk(false)) {\n" - " case let v: s3 => return 3;\n" - " case let b: bool => { if (b) { return 4; }; };\n" - " };\n" - " return 0;\n" - "};\n", - 0, 0, 0 }, - /* BOUNDARY: (str|nomem) — the 32B-slot class fmt/io/strconv - * return everywhere; flipping it to sret breaks the tree. */ - { "boundary_str_nomem_register", - "type nomem = !void;\n" - "fn pick(ok: bool) (str | nomem) = {\n" - " if (ok) { return \"hello\"; };\n" - " let nm: nomem;\n" - " return nm;\n" - "};\n" - "export fn main() i32 = {\n" - " match (pick(true)) {\n" - " case let s: str => { if (s.len != 5) { return 1; }; };\n" - " case nomem => return 2;\n" - " };\n" - " match (pick(false)) {\n" - " case let s: str => return 3;\n" - " case nomem => { };\n" - " };\n" - " return 0;\n" - "};\n", - 0, 0, 0 }, - /* LOUD-STOP: widening return-forward of an sret-class source - * ((wide|oops) -> (wide|oops|nomem)) needs the mem-to-mem - * tag-remap — unwired, filed #40. Must NOT compile. */ - { "fail_widening_forward", - WIDE_TYPES - "fn inner(b: []u8) (wide | oops) = {\n" - " return wide { xs = b, ys = b, n = 1 };\n" - "};\n" - "fn outer(b: []u8) (wide | oops | nomem) = {\n" - " return inner(b);\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " match (outer(buf[0:4])) {\n" - " case let w: wide => return 0;\n" - WIDE_MAIN_CHECK, - 0, 0, 1 }, - /* LOUD-STOP: `!` consuming an sret-class call result reads the - * cursor the callee never filled — #40-family follow-up. */ - { "fail_tryunw_wide", - WIDE_TYPES - "fn mk(b: []u8) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = 1 };\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " let w: wide = mk(buf[0:4])!;\n" - " if (w.n != 1) { return 1; };\n" - " return 0;\n" - "};\n", - 0, 0, 1 }, - /* LOUD-STOP: an sret-class tagged call result in argument - * position (cursor push of a memory result). */ - { "fail_wide_call_arg", - WIDE_TYPES - "fn mk(b: []u8) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = 1 };\n" - "};\n" - "fn use(r: (wide | oops | nomem)) i64 = {\n" - " match (r) {\n" - " case let w: wide => return w.n;\n" - " case let e: oops => return -1;\n" - " case nomem => return -2;\n" - " };\n" - " return -3;\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " if (use(mk(buf[0:4])) != 1) { return 1; };\n" - " return 0;\n" - "};\n", - 0, 0, 1 }, - /* Discard statement: rides the generic @sretscr slot; the frame - * canary pins no clobber across two discarded wide results. */ - { "wide_discard_stmt", - WIDE_TYPES - "fn mk(b: []u8) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = 1 };\n" - "};\n" - "export fn main() i32 = {\n" - " let canary: i64 = 777;\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " mk(buf[0:4]);\n" - " mk(buf[0:4]);\n" - " if (canary != 777) { return 1; };\n" - " return 0;\n" - "};\n", - 0, 1, 0 }, - /* LOUD-STOP: `?` consuming an sret-class call result (cstage - * gates at the N_LET TRYUNW/TRYPROP shape, wwstage at cgtryprop - * — same acceptance, rule 10). */ - { "fail_tryprop_wide", - WIDE_TYPES - "fn mk(b: []u8) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = 1 };\n" - "};\n" - "fn go(b: []u8) (i64 | oops | nomem) = {\n" - " let w: wide = mk(b)?;\n" - " return w.n;\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " match (go(buf[0:4])) {\n" - " case let v: i64 => { if (v != 1) { return 1; }; };\n" - WIDE_MAIN_CHECK, - 0, 0, 1 }, - /* LOUD-STOP: `is` on an sret-class call result (AX = dest - * pointer, not the tag). */ - { "fail_typetest_wide", - WIDE_TYPES - "fn mk(b: []u8) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = 1 };\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " if (mk(buf[0:4]) is wide) { return 0; };\n" - " return 1;\n" - "};\n", - 0, 0, 1 }, - /* LOUD-STOP: sret receive into a tagged GLOBAL lvalue. */ - { "fail_global_receive", - WIDE_TYPES - "let g: (wide | oops | nomem);\n" - "fn mk(b: []u8) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = 1 };\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " g = mk(buf[0:4]);\n" - " match (g) {\n" - " case let w: wide => { if (w.n != 1) { return 1; }; };\n" - WIDE_MAIN_CHECK, - 0, 0, 1 }, - /* LOUD-STOP: struct-literal FIELD of tagged type from an - * sret-class call — the widener's cursor-spill #40 gate. */ - { "fail_structfield_widen", - WIDE_TYPES - "type holder = struct { r: (wide | oops | nomem), k: i64 };\n" - "fn mk(b: []u8) (wide | oops | nomem) = {\n" - " return wide { xs = b, ys = b, n = 1 };\n" - "};\n" - "export fn main() i32 = {\n" - " let buf: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" - " let h: holder = holder { r = mk(buf[0:4]), k = 5 };\n" - " match (h.r) {\n" - " case let w: wide => { if (w.n != 1) { return 1; }; };\n" - WIDE_MAIN_CHECK, - 0, 0, 1 }, -}; - -static const char *g_bin; - -/* compile one row with `tool` (w6c or w6c_ww) into outpath; stderr - * goes to errpath so buildfail rows can pin the loud-stop marker. - * Returns the tool's exit code. */ -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, - const char *work) -{ - char outbin[256], cmd[1024]; - snprintf(outbin, sizeof outbin, "%s/%s.run", work, driver); - 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]; - /* Loud-reject behavior is owned by r926_sret_reject_* fixtures. */ - if (r->buildfail) continue; - char work[] = "/tmp/tsret_XXXXXX"; - char src[256], cs_s[256], ww_s[256]; - char cs_e[256], ww_e[256], cbin[256], wbin[256]; - char cscratch[288], wscratch[288], cmd[384]; - int rowfail = 0; - if (mkdtemp(work) == NULL) { - fprintf(stderr, "FAIL row[%s]: workspace acquisition failed\n", - r->label); - fail++; - continue; - } - snprintf(src, sizeof src, "%s/input.ww", work); - snprintf(cs_s, sizeof cs_s, "%s/c.s", work); - snprintf(ww_s, sizeof ww_s, "%s/w.s", work); - snprintf(cs_e, sizeof cs_e, "%s/c.err", work); - snprintf(ww_e, sizeof ww_e, "%s/w.err", work); - snprintf(cbin, sizeof cbin, "%s/ww.run", work); - snprintf(wbin, sizeof wbin, "%s/ww_ww.run", work); - snprintf(cscratch, sizeof cscratch, "%s.sepwork", cbin); - snprintf(wscratch, sizeof wscratch, "%s.sepwork", wbin); - FILE *f = fopen(src, "wb"); - if (!f) { - fprintf(stderr, "FAIL row[%s]: source create failed\n", r->label); - rowfail = 1; - goto row_done; - } - fputs("package main;\n\n", f); - fputs(r->src, f); - int werr = ferror(f); - if (fclose(f) != 0) werr = 1; - if (werr) { - fprintf(stderr, "FAIL row[%s]: source write failed\n", r->label); - rowfail = 1; - goto row_done; - } - - int cs_rc = compile_s("w6c", src, cs_s, cs_e); - int ww_rc = compile_s("w6c_ww", src, ww_s, ww_e); - 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); - rowfail = 1; - goto row_done; - } - if (!file_eq(cs_s, ww_s)) { - fprintf(stderr, "FAIL row[%s]: cs != ww .s\n", - r->label); - rowfail = 1; - } - /* hidden-RDI dest: present iff the row is sret-class. - * The boundary rows pin the 32B class stays register. */ - int has_di = file_has(cs_s, "(BP), DI\n"); - if (r->sret && !has_di) { - fprintf(stderr, "FAIL row[%s]: expected sret " - "hidden-RDI LEAQ, none emitted\n", r->label); - rowfail = 1; - } - if (!r->sret && has_di) { - fprintf(stderr, "FAIL row[%s]: 32B-slot row " - "flipped to sret (classifier boundary " - "off-by-one)\n", r->label); - rowfail = 1; - } - int got_cs = run_driver("ww", src, r->label, work); - if (got_cs != r->want) { - fprintf(stderr, "FAIL row[%s] cstage: want %d " - "got %d\n", r->label, r->want, got_cs); - rowfail = 1; - } - 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, work); - if (got_ww != r->want) { - fprintf(stderr, "FAIL row[%s] wwstage: " - "want %d got %d\n", - r->label, r->want, got_ww); - rowfail = 1; - } - } - - row_done: - { - int cleanfail = 0; - if (unlink(src) != 0 && errno != ENOENT) cleanfail = 1; - if (unlink(cs_s) != 0 && errno != ENOENT) cleanfail = 1; - if (unlink(ww_s) != 0 && errno != ENOENT) cleanfail = 1; - if (unlink(cs_e) != 0 && errno != ENOENT) cleanfail = 1; - if (unlink(ww_e) != 0 && errno != ENOENT) cleanfail = 1; - if (unlink(cbin) != 0 && errno != ENOENT) cleanfail = 1; - if (unlink(wbin) != 0 && errno != ENOENT) cleanfail = 1; - snprintf(cmd, sizeof cmd, "rm -rf %s", cscratch); - if (runwait(cmd) != 0) cleanfail = 1; - snprintf(cmd, sizeof cmd, "rm -rf %s", wscratch); - if (runwait(cmd) != 0) cleanfail = 1; - if (rmdir(work) != 0) cleanfail = 1; - if (cleanfail) { - fprintf(stderr, "FAIL row[%s]: workspace cleanup failed\n", - r->label); - rowfail = 1; - } - } - if (rowfail) fail++; - } - if (fail) { - fprintf(stderr, "tagged_sret_run: %d/%d rows failed\n", - fail, total); - return 1; - } - printf("tagged_sret_run: %d rows ok\n", total); - return 0; -}