diff --git a/Makefile b/Makefile index fe2bc067..fd3cbd74 100644 --- a/Makefile +++ b/Makefile @@ -407,7 +407,7 @@ OBJECT_WW_TARGETS = $(OBJECT_WW_TESTS:%=wwtest/%) # run-stderr probes, .wwi round-trip trees, tool-dump AST proofs, # cstage-only rejects). Compiler/driver gates like test/sep: they run # under test-compiler. -MISC_WW_TESTS = test/misc/arrglob_test.ww +MISC_WW_TESTS = test/misc/arrglob_test.ww test/misc/packedwwi_test.ww MISC_WW_TARGETS = $(MISC_WW_TESTS:%=wwtest/%) BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \ test/wcc/991_w6a_ww.c \ diff --git a/test/misc/packedwwi_test.ww b/test/misc/packedwwi_test.ww new file mode 100644 index 00000000..ca333d71 --- /dev/null +++ b/test/misc/packedwwi_test.ww @@ -0,0 +1,134 @@ +package packedwwi_test; + +// `struct @packed` legs no fixture can host, ported from the retired +// native carrier test/wcc/671_struct_packed.c (#51). The 15 +// layout/runtime value rows are fixture-shaped and move to the +// declarative corpus per the residual audit port split; harec +// references live in that carrier's header (type_store.c:206-213, +// :886, :193; types.c:517,621). +// +// wwiroundtrip: a library package exports ONLY the packed type; the +// importer computes size+offset ITSELF, so the value depends on the +// .wwi producer re-emitting `struct @packed {` and the importer +// re-parsing it (unparse re-emit per ref/hare/hare/unparse/ +// type.ha:122-126). Both driver stages must build the -I tree and run +// exit 91 (9*10+1; a dropped @packed gives 168). Strengthened over +// the carrier: the re-emitted `struct @packed {` is also asserted in +// the retained sepwork pk2.wwi. A single-file two-package form +// produces NO .wwi (one __root unit), so the real -I tree is +// irreducible here. +// +// identityreject: assigning packed A to a structurally-identical +// unpacked B — packed is type identity (harec types.c:621). STAGE- +// ASYMMETRIC divergence pin: cstage must reject WITH "not assignable" +// while wwstage must build AND run exit 0 through its pre-existing +// broadly-lenient nominal-lossy struct assignability (#224/#10 — a +// documented deferred arc, NOT desired behavior and NOT a #51 +// regression). A wwstage tighten fails the row and demands graduation +// to a //ww:error two-frag fixture. + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("packedwwi FAIL: ", label, " -- ", why, + "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (240i64 * (time.second: i64)): time.duration; +}; + +fn rundir(dir: str, name: str, argv: []str, out: *testenv.commandout) i32 = { + testenv.runcommand(dir, dir, name, argv, tmo(), out); + if (out.termination != exec.termination.EXIT) { return -1; }; + return out.code; +}; + +@test fn wwiroundtrip() void = { + let drvs: []str = ["ww", "ww_ww"]; + let s: i32 = 0; + for (s < 2) { + let td: str = testenv.fresh(); + assert(os.mkdir(strings.concat(td, "/lib"), 493) == 0); + assert(os.mkdir(strings.concat(td, "/lib/pk2"), 493) == 0); + assert(os.mkdir(strings.concat(td, "/app"), 493) == 0); + testenv.writefile(strings.concat(td, "/lib/pk2/pk2.ww"), + strings.concat( + "package pk2;\n", + "export type Ev = struct @packed { tag: u8, data: u64 };\n")); + testenv.writefile(strings.concat(td, "/app/main.ww"), + strings.concat( + "package main;\n", + "import pk2;\n", + "export fn main() i32 = {\n", + "\tlet e: pk2.Ev = pk2.Ev { tag = 1u8, data = 2u64 };\n", + "\treturn size(pk2.Ev): i32 * 10 + offset(e.data): i32;\n", + "};\n")); + let app: str = strings.concat(td, "/app"); + let co: testenv.commandout; + let av: []str = [testenv.driver(drvs[s]), "build", "-I", + strings.concat(td, "/lib"), "-o", "m", "main.ww"]; + if (rundir(app, strings.concat("build_", drvs[s]), av, &co) + != 0) { + fail(drvs[s], "-I tree build failed"); + }; + let rav: []str = [strings.concat(app, "/m")]; + if (rundir(app, strings.concat("run_", drvs[s]), rav, &co) + != 91) { + fail(drvs[s], strings.concat("importer-side run-exit ", + "!= 91 (@packed dropped in the .wwi re-emit ", + "gives 168)")); + }; + let wwi: str = testenv.readfile( + strings.concat(app, "/m.sepwork/pk2.wwi")); + if (!testenv.has(wwi, "struct @packed {")) { + fail(drvs[s], + "pk2.wwi lost the `struct @packed {` re-emit"); + }; + testenv.clean(td); + s += 1; + }; +}; + +@test fn identityreject() void = { + let src: str = strings.concat( + "package main;\n", + "type A = struct @packed { x: u8, y: u64 };\n", + "type B = struct { x: u8, y: u64 };\n", + "export fn main() i32 = {\n", + "\tlet a: A = A { x = 1u8, y = 2u64 };\n", + "\tlet b: B = a;\n", + "\treturn 0;\n", + "};\n"); + let td: str = testenv.fresh(); + testenv.writefile(strings.concat(td, "/id.ww"), src); + let co: testenv.commandout; + let cav: []str = [testenv.driver("ww"), "build", "-o", "idc", + "id.ww"]; + if (rundir(td, "build_cstage", cav, &co) == 0) { + fail("identity_reject", + "cstage accepted packed->unpacked assignment"); + }; + if (!testenv.has(co.stderr, "not assignable")) { + fail("identity_reject", strings.concat("cstage reject lost ", + "its `not assignable` diagnostic (crash is vacuous)")); + }; + let wav: []str = [testenv.driver("ww_ww"), "build", "-o", "idw", + "id.ww"]; + if (rundir(td, "build_wwstage", wav, &co) != 0) { + fail("identity_reject", strings.concat("wwstage rejected -- ", + "#224/#10 leniency closed; graduate this row to a ", + "//ww:error fixture")); + }; + let rav: []str = [strings.concat(td, "/idw")]; + if (rundir(td, "run_wwstage", rav, &co) != 0) { + fail("identity_reject", "wwstage run-exit != 0"); + }; + testenv.clean(td); +}; diff --git a/test/wcc/671_struct_packed.c b/test/wcc/671_struct_packed.c deleted file mode 100644 index dcb5842a..00000000 --- a/test/wcc/671_struct_packed.c +++ /dev/null @@ -1,504 +0,0 @@ -/* - * 671_struct_packed — cstage and wwstage agree, byte-for-byte and at - * runtime, on the `struct @packed` attribute (task #51): no inter-field - * or trailing padding, alignment UNCHANGED (= max field align, NOT forced - * to 1), packed flag part of type identity, and the flag round-trips - * through the .wwi sep-compile interface. - * - * harec reference (verified): a packed struct field offset is the running - * size with NO add_padding (ref/harec/src/type_store.c:206-213); the tail - * pad-to-align is skipped (type_store.c:886 `!packed`); but `type->align` - * is still the max field alignment (type_store.c:193 runs unconditionally) - * — so packed `{u8, u64}` is size 9, align 8. Packedness is hashed - * (types.c:517) and type-equated (types.c:621), and unparse re-emits - * `struct @packed {` (ref/hare/hare/unparse/type.ha:122-126). - * - * row | shape | want - * -----------------+-------------------------------------+------------- - * unpacked_size | size(struct{u8,u64}) | 16 + byte-id - * packed_size | size(struct @packed{u8,u64}) | 9 + byte-id - * packed_off_b | offset(p.b), p: packed{u8,u64} | 1 + byte-id - * unpacked_off_b | offset(u.b), u: {u8,u64} | 8 + byte-id - * packed_align | align(packed{u8,u64}) — UNCHANGED | 8 + byte-id - * packed5_size | size(packed{u8,u32}) | 5 + byte-id - * packed5_off_b | offset(.b), packed{u8,u32} | 1 + byte-id - * p3_packed_size | size(packed{u8,u16,u8}) | 4 + byte-id - * p3_packed_off_c | offset(.c), packed{u8,u16,u8} | 3 + byte-id - * p3_unpacked_size | size({u8,u16,u8}) — pads to align 2 | 6 + byte-id - * field_roundtrip | packed{u8,u16,u8}, set+sum fields | 6 + byte-id - * arr_stride_size | size([3]packed{u8,u32}) | 15 + byte-id - * arr_stride_run | [3]packed{u8,u32}, write+read t[2].b| 33 + byte-id - * - * The runtime rows pin that CGEN reads the packed field offsets (a - * regression to padded offsets corrupts the sum / strides into the wrong - * element). The arr_stride rows pin the 5-byte (not padded-8) element - * stride. The byte-id rows pin rule-10: every layout/offset source is the - * type table, so cstage's single `size`/offset and wwstage's tinfo + - * astsize/astoffset folds emit identical asm. - * - * IDENTITY (identity_reject, stage-asymmetric): assigning a packed struct - * value to a structurally-identical UNPACKED twin is a type error — proves - * packed is part of type identity (harec types.c:621). cstage rejects it - * (structural type_eq honours packed). wwstage's struct-vs-struct - * assignability is the pre-existing broadly-lenient nominal-lossy path - * (#224/#10: a bare cross-module same-leaf type name can mis-resolve, so - * wwstage cannot confidently reject ANY same-kind struct mismatch — it - * already accepts `struct{a:u8}` into `struct{x:u64,y:u64}`); the packed - * twin rides that same deferred arc. The row explicitly requires cstage to - * reject and wwstage to accept; the latter is the documented #224/#10 - * divergence, not a #51 regression. - * - * WWI ROUND-TRIP (wwi_roundtrip): a library package exports - * `type Ev = struct @packed {...}`; an importing package computes size(Ev) - * and offset of a field ON THE IMPORTER SIDE. The .wwi producer re-emits - * `struct @packed {`, the importer re-parses it, so the importer lays Ev - * out packed. Both stages must build+run with the packed importer-side - * size/offset (9*10+1 = 91; an unpacked Ev would give 16*10+8 = 168). - */ -#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; -} - -/* want sentinels (outside any real exit code 0..255). */ -#define REJECT_CSTAGE (-2147483647 - 1) /* cstage fails; wwstage runs 0 */ - -/* The packed/unpacked twins shared by the layout rows. Each row's `expr` - * is spliced into a fn body that has `p` (packed) and `u` (unpacked) of - * each shape in scope, so offset()/field reads have a receiver. */ -struct row { const char *label; const char *src; int want; }; - -static const struct row rows[] = { - { "unpacked_size", - "package main;\n" - "type U = struct { a: u8, b: u64 };\n" - "export fn main() i32 = { return size(U): i32; };\n", 16 }, - - { "packed_size", - "package main;\n" - "type P = struct @packed { a: u8, b: u64 };\n" - "export fn main() i32 = { return size(P): i32; };\n", 9 }, - - { "packed_off_b", - "package main;\n" - "type P = struct @packed { a: u8, b: u64 };\n" - "export fn main() i32 = {\n" - "\tlet p: P = P { a = 1u8, b = 2u64 };\n" - "\treturn offset(p.b): i32;\n" - "};\n", 1 }, - - { "unpacked_off_b", - "package main;\n" - "type U = struct { a: u8, b: u64 };\n" - "export fn main() i32 = {\n" - "\tlet u: U = U { a = 1u8, b = 2u64 };\n" - "\treturn offset(u.b): i32;\n" - "};\n", 8 }, - - /* harec-critical: packed removes PADDING, not the alignment VALUE. */ - { "packed_align", - "package main;\n" - "type P = struct @packed { a: u8, b: u64 };\n" - "export fn main() i32 = { return align(P): i32; };\n", 8 }, - - { "packed5_size", - "package main;\n" - "type P = struct @packed { a: u8, b: u32 };\n" - "export fn main() i32 = { return size(P): i32; };\n", 5 }, - - { "packed5_off_b", - "package main;\n" - "type P = struct @packed { a: u8, b: u32 };\n" - "export fn main() i32 = {\n" - "\tlet p: P = P { a = 1u8, b = 2u32 };\n" - "\treturn offset(p.b): i32;\n" - "};\n", 1 }, - - { "p3_packed_size", - "package main;\n" - "type P = struct @packed { a: u8, b: u16, c: u8 };\n" - "export fn main() i32 = { return size(P): i32; };\n", 4 }, - - { "p3_packed_off_c", - "package main;\n" - "type P = struct @packed { a: u8, b: u16, c: u8 };\n" - "export fn main() i32 = {\n" - "\tlet p: P = P { a = 1u8, b = 2u16, c = 3u8 };\n" - "\treturn offset(p.c): i32;\n" - "};\n", 3 }, - - /* unpacked twin pads b to align 2 (@2), then trailing to align 2 → 6. */ - { "p3_unpacked_size", - "package main;\n" - "type U = struct { a: u8, b: u16, c: u8 };\n" - "export fn main() i32 = { return size(U): i32; };\n", 6 }, - - /* cgen reads packed offsets at runtime: a=1,b=2,c=3 → 6. */ - { "field_roundtrip", - "package main;\n" - "type P = struct @packed { a: u8, b: u16, c: u8 };\n" - "export fn main() i32 = {\n" - "\tlet p: P = P { a = 1u8, b = 2u16, c = 3u8 };\n" - "\treturn (p.a: i32) + (p.b: i32) + (p.c: i32);\n" - "};\n", 6 }, - - { "arr_stride_size", - "package main;\n" - "type P = struct @packed { a: u8, b: u32 };\n" - "export fn main() i32 = { return size([3]P): i32; };\n", 15 }, - - /* 5-byte stride: t[2].b lands at byte 10+1, not overlapping t[1]. */ - { "arr_stride_run", - "package main;\n" - "type P = struct @packed { a: u8, b: u32 };\n" - "export fn main() i32 = {\n" - "\tlet t: [3]P;\n" - "\tt[0].b = 11u32; t[1].b = 22u32; t[2].b = 33u32;\n" - "\treturn t[2].b: i32;\n" - "};\n", 33 }, - - /* by-value ABI (ken): a narrow packed struct (size 5) passed BY - * VALUE exercises the sub-8 slotsize through the struct-arg - * classify+copy. slotsize == size (5) for packed, so the eightbyte - * count and copy width match cstage's size-driven ABI byte-for-byte. - * (By-value RETURN of a size∉{8,16} struct is the pre-existing - * cstage CALL-drop, task #107 — reproduces UNPACKED too — so the - * return half is omitted here; the arg path is the sound oracle.) */ - { "byval_arg", - "package main;\n" - "type Q = struct @packed { a: u8, b: u32 };\n" - "fn useq(q: Q) i32 = { return (q.a: i32) + (q.b: i32); };\n" - "export fn main() i32 = {\n" - "\tlet q: Q = Q { a = 7u8, b = 100u32 };\n" - "\treturn useq(q);\n" - "};\n", 107 }, - - /* frame-offset proof (ken): a size-5 packed local FOLLOWED by another - * local. localreserve rounds the frame cursor (frame+sz+7)&~7 with no - * sub-8 floor (byte-id to cstage localslot), so `after` is not - * misaligned by the packed slot. p.b write+read confirms the packed - * field offset survives the slot. */ - { "frame_offset", - "package main;\n" - "type Q = struct @packed { a: u8, b: u32 };\n" - "export fn main() i32 = {\n" - "\tlet p: Q = Q { a = 1u8, b = 5u32 };\n" - "\tlet after: i32 = 77;\n" - "\tp.b = 200u32;\n" - "\treturn (p.b: i32) + after - (p.a: i32);\n" - "};\n", 20 }, - - /* identity: cstage rejects the packed/unpacked assignment; wwstage - * accepts it through the documented #224/#10 leniency (see header). */ - { "identity_reject", - "package main;\n" - "type A = struct @packed { x: u8, y: u64 };\n" - "type B = struct { x: u8, y: u64 };\n" - "export fn main() i32 = {\n" - "\tlet a: A = A { x = 1u8, y = 2u64 };\n" - "\tlet b: B = a;\n" - "\treturn 0;\n" - "};\n", REJECT_CSTAGE }, -}; - -static int -run_driver(const char *driver, const struct row *r, int i) -{ - char tmpdir[64], src[128], outbin[128], scratch[160], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/pk51_%d_d_%d", getpid(), i); - snprintf(src, sizeof src, "%s/pk51_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/pk51_%d_%d", tmpdir, getpid(), i); - snprintf(scratch, sizeof scratch, "%s.sepwork", outbin); - if (mkdir(tmpdir, 0755) != 0) { - perror(tmpdir); - return -2; - } - int result = -2; - - FILE *f = fopen(src, "wb"); - if (!f) { perror(src); goto cleanup; } - fputs(r->src, f); - if (fclose(f) != 0) { perror(src); goto cleanup; } - - snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null", - driver, outbin, src); - int built = runwait(cmd); - if (built != 0) { - /* build failed (a reject, or a real error). Caller decides. */ - result = -1; - goto cleanup; - } - - result = runwait(outbin); - -cleanup: - /* `ww build -o` retains this exact caller-owned scratch tree. */ - snprintf(cmd, sizeof cmd, "rm -rf %s", scratch); - int cleanfail = runwait(cmd) != 0; - if (unlink(outbin) != 0 && errno != ENOENT) { - perror(outbin); cleanfail = 1; - } - if (unlink(src) != 0 && errno != ENOENT) { - perror(src); cleanfail = 1; - } - if (rmdir(tmpdir) != 0) { - perror(tmpdir); cleanfail = 1; - } - if (cleanfail) { - fprintf(stderr, "row[%s]: cleanup failed after exit=%d\n", - r->label, result); - return -2; - } - return result; -} - -/* asm_byte_identical — w6c (cstage) vs w6c_ww (wwstage) .s diff. */ -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char tmpdir[64], src[128], cs[128], ws[128], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/pk51a_%d_%d", getpid(), i); - snprintf(src, sizeof src, "%s/input.ww", tmpdir); - snprintf(cs, sizeof cs, "%s/c.s", tmpdir); - snprintf(ws, sizeof ws, "%s/w.s", tmpdir); - if (mkdir(tmpdir, 0755) != 0) { - perror(tmpdir); - return -1; - } - int rc = -1; - - FILE *f = fopen(src, "wb"); - if (!f) { perror(src); goto cleanup; } - fputs(r->src, f); - if (fclose(f) != 0) { perror(src); goto cleanup; } - - snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c errored\n", r->label); - goto cleanup; - } - snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", bin, ws, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); - goto cleanup; - } - - FILE *fc = fopen(cs, "rb"); - FILE *fw = fopen(ws, "rb"); - rc = 0; - if (!fc || !fw) { - rc = -1; - } else { - for (;;) { - int a = fgetc(fc); - int b = fgetc(fw); - if (a != b) { rc = -1; break; } - if (a == EOF) break; - } - } - if (fc) fclose(fc); - if (fw) fclose(fw); - if (rc != 0) - fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", - r->label); - -cleanup: - if (unlink(src) != 0 && errno != ENOENT) { - perror(src); rc = -1; - } - if (unlink(cs) != 0 && errno != ENOENT) { - perror(cs); rc = -1; - } - if (unlink(ws) != 0 && errno != ENOENT) { - perror(ws); rc = -1; - } - if (rmdir(tmpdir) != 0) { - perror(tmpdir); rc = -1; - } - return rc; -} - -/* wwi_roundtrip — a library pkg exports a packed type; the importing pkg - * computes size+offset on it directly. The .wwi producer must re-emit - * `struct @packed {` so the importer re-parses + lays Ev out packed. - * Returns the app's exit code (importer-side size(Ev)*10 + offset(.data) - * = 9*10 + 1 = 91; a dropped @packed would give 168), or -1 on build fail. */ -static int -wwi_roundtrip(const char *driver, int idx) -{ - char root[80], libroot[112], libdir[128], appdir[112]; - char pk2[160], appww[160], outbin[200], scratch[220], cmd[1024]; - snprintf(root, sizeof root, "/tmp/pk51w_%d_%d", getpid(), idx); - snprintf(libroot, sizeof libroot, "%s/lib", root); - snprintf(libdir, sizeof libdir, "%s/lib/pk2", root); - snprintf(appdir, sizeof appdir, "%s/app", root); - snprintf(pk2, sizeof pk2, "%s/pk2.ww", libdir); - snprintf(appww, sizeof appww, "%s/app/main.ww", root); - snprintf(outbin, sizeof outbin, "%s/app/m", root); - snprintf(scratch, sizeof scratch, "%s.sepwork", outbin); - if (mkdir(root, 0755) != 0) { - perror(root); - return -1; - } - int result = -1; - int cleanfail; - int libroot_owned = 0, libdir_owned = 0, app_owned = 0; - if (mkdir(libroot, 0755) != 0) { perror(libroot); goto cleanup; } - libroot_owned = 1; - if (mkdir(libdir, 0755) != 0) { perror(libdir); goto cleanup; } - libdir_owned = 1; - if (mkdir(appdir, 0755) != 0) { perror(appdir); goto cleanup; } - app_owned = 1; - - FILE *f = fopen(pk2, "wb"); - if (!f) { perror(pk2); goto cleanup; } - /* pk2 exports ONLY the packed type — no helper fns. The size/offset - * are computed on the IMPORTER side (below), so the value depends on - * the .wwi re-parse laying Ev out packed. (If evsize()/evoff() lived - * here the layout would be computed definer-side and the importer's - * @packed re-parse would never be exercised.) */ - fputs("package pk2;\n" - "export type Ev = struct @packed { tag: u8, data: u64 };\n", f); - if (fclose(f) != 0) { perror(pk2); goto cleanup; } - - f = fopen(appww, "wb"); - if (!f) { perror(appww); goto cleanup; } - /* importer computes layout from the re-parsed .wwi: packed → 9*10+1 - * = 91; a dropped @packed (unpacked Ev) would give 16*10+8 = 168. */ - fputs("package main;\n" - "import pk2;\n" - "export fn main() i32 = {\n" - "\tlet e: pk2.Ev = pk2.Ev { tag = 1u8, data = 2u64 };\n" - "\treturn size(pk2.Ev): i32 * 10 + offset(e.data): i32;\n" - "};\n", f); - if (fclose(f) != 0) { perror(appww); goto cleanup; } - - snprintf(cmd, sizeof cmd, - "cd %s/app && %s build -I %s/lib -o m main.ww 2>/dev/null", - root, driver, root); - if (runwait(cmd) != 0) { - goto cleanup; - } - result = runwait(outbin); - -cleanup: - cleanfail = 0; - if (app_owned) { - snprintf(cmd, sizeof cmd, "rm -rf %s", scratch); - if (runwait(cmd) != 0) cleanfail = 1; - if (unlink(outbin) != 0 && errno != ENOENT) { - perror(outbin); cleanfail = 1; - } - if (unlink(appww) != 0 && errno != ENOENT) { - perror(appww); cleanfail = 1; - } - } - if (libdir_owned && unlink(pk2) != 0 && errno != ENOENT) { - perror(pk2); cleanfail = 1; - } - if (app_owned && rmdir(appdir) != 0) { - perror(appdir); cleanfail = 1; - } - if (libdir_owned && rmdir(libdir) != 0) { - perror(libdir); cleanfail = 1; - } - if (libroot_owned && rmdir(libroot) != 0) { - perror(libroot); cleanfail = 1; - } - if (rmdir(root) != 0) { - perror(root); cleanfail = 1; - } - if (cleanfail && result == 91) result = -1; - return result; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char cdrv[1024], wdrv[1024]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - struct { const char *name; const char *path; int gated; } - drivers[] = { - { "cstage", cdrv, 0 }, - { "wwstage", wdrv, 1 }, - { NULL, NULL, 0 }, - }; - - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int d = 0; drivers[d].name; d++) { - int is_cstage = strcmp(drivers[d].name, "cstage") == 0; - if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) { - fprintf(stderr, "struct_packed: skip %s (no %s)\n", - drivers[d].name, drivers[d].path); - continue; - } - for (int i = 0; i < n; i++) { - int got = run_driver(drivers[d].path, &rows[i], i); - total++; - int want = rows[i].want; - if (want == REJECT_CSTAGE) want = is_cstage ? -1 : 0; - int bad = (got != want); - if (bad) { - fprintf(stderr, - "struct_packed[%s][%s]: exit=%d want=%d\n", - drivers[d].name, rows[i].label, - got, want); - fail++; - } - } - /* wwi cross-module round-trip per driver. */ - total++; - int rt = wwi_roundtrip(drivers[d].path, d); - if (rt != 91) { - fprintf(stderr, - "struct_packed[%s][wwi_roundtrip]: exit=%d want=91\n", - drivers[d].name, rt); - fail++; - } - } - - /* asm byte-id: only the value rows (REJECT_CSTAGE rows have no - * cstage asm). Gated on ww_ww presence. */ - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - if (rows[i].want == REJECT_CSTAGE) continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) - fail++; - } - } - - if (fail) { - fprintf(stderr, "struct_packed: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("struct_packed: %d/%d ok\n", total, total); - return 0; -}