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:
2
Makefile
2
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
|
||||
|
||||
295
test/asm/tagsret_test.ww
Normal file
295
test/asm/tagsret_test.ww
Normal 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);
|
||||
};
|
||||
@@ -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 <K>(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 <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) */
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user