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.
This commit is contained in:
2026-08-08 15:02:02 +09:00
parent 49464efdc5
commit f2539bd4d9
3 changed files with 296 additions and 620 deletions

295
test/asm/tagsret_test.ww Normal file
View File

@@ -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);
};