From 47c7dbc2c8c231e37611d655ef1c9c2ae9e15a24 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 24 Jun 2026 01:00:47 +0900 Subject: [PATCH] test: migrate Fam12 opaque value tests to @test row-tables (#5-C1) fold-2 chunk C1 (drew's Fam8-13 plan): 960_opaque_decl_run.c and 962_opaque_assign_cast_run.c were value-row C drivers. Migrate their 3+3 cases to test/lang/opaque_decl_test.ww and opaque_assign_cast_test.ww as @test row-tables. The test-lang byte-id (LANGBYTEID) gate gives cs==ww automatically -- 960 was cstage-only-run before, so this strengthens it. Both byte-id clean, no cs!=ww carve. Retire the 2 C drivers (LANGBYTEID floor 57->59) and repoint a dead comment ref in lib/sort/sort.ww. 961_opaque_guards (reject/guards) stays in C -- fold-3 territory, not in the Fam12 fold-2 worklist. --- Makefile | 13 +- lib/sort/sort.ww | 2 +- test/lang/opaque_assign_cast_test.ww | 109 ++++++++++ test/lang/opaque_decl_test.ww | 58 ++++++ test/wcc/960_opaque_decl_run.c | 146 ------------- test/wcc/962_opaque_assign_cast_run.c | 282 -------------------------- 6 files changed, 169 insertions(+), 441 deletions(-) create mode 100644 test/lang/opaque_assign_cast_test.ww create mode 100644 test/lang/opaque_decl_test.ww delete mode 100644 test/wcc/960_opaque_decl_run.c delete mode 100644 test/wcc/962_opaque_assign_cast_run.c diff --git a/Makefile b/Makefile index 5c525bfa..96ab268f 100644 --- a/Makefile +++ b/Makefile @@ -586,9 +586,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_floats_run \ $(BIN)/test_types_sizelim_run \ $(BIN)/test_types_intlim_run \ - $(BIN)/test_opaque_decl_run \ $(BIN)/test_opaque_guards \ - $(BIN)/test_opaque_assign_cast_run \ $(BIN)/test_sort_run \ $(BIN)/test_bufio_run $(BIN)/test_random_run \ $(BIN)/test_asserttyped_gap \ @@ -3177,20 +3175,11 @@ $(BIN)/test_types_intlim_run: test/wcc/959_types_intlim_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_opaque_decl_run: test/wcc/960_opaque_decl_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_opaque_guards: test/wcc/961_opaque_guards.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_opaque_assign_cast_run: test/wcc/962_opaque_assign_cast_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_sort_run: test/wcc/963_sort_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< @@ -3321,7 +3310,7 @@ test-lang: all LANGBYTEID_DIR = $(OUT)/langbyteid LANGBYTEID_FILES = $(filter-out %_runonly_test.ww,$(wildcard test/lang/*_test.ww)) LANGBYTEID_VERB = test -c -LANGBYTEID_EXPECTED_MIN = 57 +LANGBYTEID_EXPECTED_MIN = 59 $(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus)) test-lang-byteid: all @set -e; \ diff --git a/lib/sort/sort.ww b/lib/sort/sort.ww index 7c48c242..afd3df97 100644 --- a/lib/sort/sort.ww +++ b/lib/sort/sort.ww @@ -19,7 +19,7 @@ // • Hare walks bytes through `in: *[*]u8` + `&ba[i*sz]`; ww has no // unbounded-array `[*]`, so the base is `*u8` reinterpreted to // `uintptr` and elements are `base + i*sz` (the stride idiom proven -// by test/wcc/962_opaque_assign_cast_run.c). +// by test/lang/opaque_assign_cast_test.ww). // • `len()` yields i32 in ww (slice length is i32 today), so the // count is cast `: size`; Hare's len() is already size. // • ww `for` is single-condition only (no `for (init; cond; post)`), diff --git a/test/lang/opaque_assign_cast_test.ww b/test/lang/opaque_assign_cast_test.ww new file mode 100644 index 00000000..6930de85 --- /dev/null +++ b/test/lang/opaque_assign_cast_test.ww @@ -0,0 +1,109 @@ +// opaque_assign_cast_test — runtime proof of #108 sub-fold (c): opaque as a +// type-erasure sink. Two assignability rules + the reinterpret casts sort's +// implementation relies on. Migrated from test/wcc/962_opaque_assign_cast_run.c. +// +// rule 1 `*T -> *opaque` IMPLICIT (no cast). Any pointer is the universal +// void-pointer. harec type_is_assignable, pointer arm: +// ref/harec/src/types.c:1053 (`case STORAGE_OPAQUE: break;` — the +// referent need not match). +// rule 2 `[]T -> []opaque` IMPLICIT (no cast). Any slice is the type-erased +// slice; the {ptr,len,cap} header is normal, the byte stride is +// supplied at runtime (itemsz). harec slice arm: types.c:1094 +// (`if (to_secondary->storage == STORAGE_OPAQUE) return true;`). +// casts `[]opaque -> *u8` (slice -> byte ptr; cgexpr leaves the ptr in AX, +// so the cast naturally takes .ptr) and `*opaque -> *u8` / +// `*opaque -> *i32` (ptr->ptr reinterpret, a no-op). drew described +// the Hare idiom as `*[*]u8`; ww has no unbounded-array `[*]`, so the +// ww-faithful reinterpret target is `*u8` + uintptr stride arithmetic. +// +// rules 1 & 2 are CSTAGE-led; the wwstage check.ww isassignable is a +// resolve-only AST approximation that returns "can't tell, stay quiet" +// (confident=false) for a ptr/slice whose element it cannot match, so it +// already ACCEPTS every form here. The casts are validation-free in BOTH stages +// (N_CAST never checks legality). The T2 byte-id floor (LANGBYTEID) carries the +// cs==ww rule-10 proof directly — opaque is inert on the selfhost corpus so the +// 990-997 gates never exercise it. +// +// Array->[]opaque (harec's array->slice decay) is deliberately EXCLUDED: ww has +// no implicit array->slice conversion for any element type (a slice is built +// only via an explicit `a[0:n]`), so there is no array->slice-header cgen. +// +// Call results are bound to locals before each assert (the test/lang idiom), +// not compared inline. The C original avoided an inline `if (f(x) != k)` shape +// (arg from a prior uintptr-arith call) that a pre-#116-era cgen bug once +// mis-compiled; that form now compiles correct + cs==ww on HEAD, so the binding +// is style, not a live workaround. The opaque feature is exercised in full. + +package opaque_assign_cast_test; + +fn readi32(p: *opaque) i32 = { let pi: *i32 = p: *i32; return *pi; }; +fn slen(o: []opaque) i32 = { return o.len: i32; }; +fn first(o: []opaque) i32 = { let p: *opaque = o.ptr; let pi: *i32 = p: *i32; return *pi; }; + +fn elemptr(items: []opaque, i: size, itemsz: size) *opaque = { + let base: *u8 = items: *u8; // []opaque -> *u8 (takes .ptr) + let off: uintptr = (i * itemsz): uintptr; + return ((base: uintptr) + off): *opaque; +}; + +fn swap(items: []opaque, x: size, y: size, itemsz: size) void = { + let pa: *u8 = elemptr(items, x, itemsz): *u8; + let pb: *u8 = elemptr(items, y, itemsz): *u8; + let k: size = 0; + for (k < itemsz) { + let qa: *u8 = ((pa: uintptr) + k: uintptr): *u8; + let qb: *u8 = ((pb: uintptr) + k: uintptr): *u8; + let t: u8 = *qa; + *qa = *qb; + *qb = t; + k = k + 1; + }; +}; + +@test fn rule1_implicit_ptr() void = { + // rule 1: `*T -> *opaque` IMPLICIT (no cast) at a let-init AND a call-arg. + // Round-trip a real *i32 through *opaque and back, deref. + let n: i32 = 42; + let po: *opaque = &n; // let-init *i32 -> *opaque + let v: i32 = readi32(po); + let v2: i32 = readi32(&n); // call-arg *i32 -> *opaque + assert(v == 42); + assert(v2 == 42); +}; + +@test fn rule2_implicit_slice() void = { + // rule 2: `[]T -> []opaque` IMPLICIT (no cast) at a let-init AND a call-arg. + // Read .len, round-trip .ptr (a *opaque) back to *i32. + let a: [4]i32 = [11, 22, 33, 44]; + let s: []i32 = a[0:4]; + let o: []opaque = s; // let-init []i32 -> []opaque + let n: i32 = slen(o); + let n2: i32 = slen(s); // call-arg []i32 -> []opaque + assert(n == 4); + assert(n2 == 4); + let f: i32 = first(s); + assert(f == 11); +}; + +@test fn sort_pattern() void = { + // sort's actual usage end-to-end: a `fn(items: []opaque, itemsz: size)` + // called with a []i32; inside, reinterpret the slice as a byte base + // (`items: *u8`), uintptr-arith two element addresses, byte-swap them by + // itemsz. Then read back through the *opaque element path and the original + // []i32 view — the type erasure round-trips iff both agree. + let a: [4]i32 = [10, 20, 30, 40]; + let s: []i32 = a[0:4]; + swap(s, 0: size, 3: size, size(i32)); // []i32 -> []opaque call-arg + assert(a[0] == 40); // erased swap round-trips + assert(a[3] == 10); + let p0: *opaque = elemptr(s, 0: size, size(i32)); + let v0: i32 = readi32(p0); + assert(v0 == 40); + let nn: i32 = 77; + let pn: *opaque = &nn; // *i32 -> *opaque let-init + let vn: i32 = readi32(pn); + assert(vn == 77); + let o: []opaque = s; + let ol: i32 = o.len: i32; + assert(ol == 4); +}; diff --git a/test/lang/opaque_decl_test.ww b/test/lang/opaque_decl_test.ww new file mode 100644 index 00000000..f3c13395 --- /dev/null +++ b/test/lang/opaque_decl_test.ww @@ -0,0 +1,58 @@ +// opaque_decl_test — runtime proof that the abstract `opaque` type (#108 +// sub-fold a) EXISTS and is usable behind indirection: `*opaque` is a usable +// 8-byte pointer and `[]opaque` is a usable 24-byte slice header (.len/.ptr). +// Migrated from test/wcc/960_opaque_decl_run.c. The name resolves via the +// type-name resolver (lookup_builtin / tinfofornode's N_TNAME chain), +// mirroring uintptr/size; `opaque` itself is unsized (size=align= +// SIZE_UNDEFINED), so it is only ever materialised through a pointer or slice, +// never as a bare value. A green @test IS the proof the name binds: without the +// resolver arm every `*opaque` / `[]opaque` use fails to compile ("unknown type +// 'opaque'"). +// +// This fold (a) deliberately does NOT test bare `let x: opaque`, `size(opaque)`, +// an `opaque` (bare) struct field, `[N]opaque`, or `[]opaque` INDEXING: those +// are the use-restriction GUARDS of sub-fold (b) (test/wcc/961_opaque_guards.c, +// reject rows — out of this fold-2 chunk). Every row here stays behind +// indirection. + +package opaque_decl_test; + +type handle = struct { p: *opaque, tag: i32 }; + +@test fn opaque_ptr_roundtrip() void = { + // `*opaque` is a usable 8-byte pointer: round-trip a real *i32 through + // *opaque and back, then deref. Proves the type binds and carries pointer + // width. + let n: i32 = 42; + let pn: *i32 = &n; + let po: *opaque = pn: *opaque; + let back: *i32 = po: *i32; + assert(*back == 42); +}; + +@test fn opaque_slice_header() void = { + // `[]opaque` is a usable 24-byte slice header: reinterpret a real []i32 as + // []opaque, read .len, and round-trip .ptr (a *opaque) back to *i32 + deref. + // The []opaque is never indexed (guard b). The .ptr deref is bound to a var + // before the assert (test/lang idiom; the inline `*so.ptr`-in-comparison + // form the C original avoided now compiles correct + cs==ww on HEAD). + let a: [4]i32; + a[0] = 11; a[1] = 22; a[2] = 33; a[3] = 44; + let s: []i32 = a[0:4]; + let so: []opaque = s: []opaque; + let n: i32 = so.len: i32; + assert(n == 4); + let pp: *opaque = so.ptr; + let pi: *i32 = pp: *i32; + let v: i32 = *pi; + assert(v == 11); +}; + +@test fn opaque_struct_field() void = { + // `*opaque` as a struct field (behind indirection): set from a cast pointer, + // read back, deref + add a sibling scalar field. + let n: i32 = 99; + let h: handle = handle{ p = (&n): *opaque, tag = 7 }; + let pi: *i32 = h.p: *i32; + assert(*pi + h.tag == 106); +}; diff --git a/test/wcc/960_opaque_decl_run.c b/test/wcc/960_opaque_decl_run.c deleted file mode 100644 index 0f2c161e..00000000 --- a/test/wcc/960_opaque_decl_run.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 960_opaque_decl_run — runtime proof that the abstract `opaque` type - * (#108 sub-fold a) EXISTS and is usable behind indirection: `*opaque` - * is a usable 8-byte pointer and `[]opaque` is a usable 24-byte slice - * header (.len/.ptr). The name resolves via the type-name resolver - * (lookup_builtin / tinfofornode's N_TNAME chain), mirroring uintptr/ - * size; `opaque` itself is unsized (size=align=SIZE_UNDEFINED), so it is - * only ever materialised through a pointer or slice, never as a bare - * value. A green row IS the proof the name binds: without the resolver - * arm every `*opaque` / `[]opaque` row fails to compile ("unknown type - * 'opaque'"). - * - * NOTE — this fold (a) deliberately does NOT test bare `let x: opaque`, - * `size(opaque)`, an `opaque` (bare) struct field, `[N]opaque`, or - * `[]opaque` INDEXING: those are the use-restriction GUARDS of sub-fold - * (b). Every row here stays behind indirection. - * - * Table-driven like 957_size_type_run / 952_floats_run: each row is a - * self-contained ww program; the C-side cstage `ww build -I lib` - * compiles it, we run the binary and assert the exit code. cstage-only - * by design (mirrors 951/952/957/969): per-program wwstage byte-id is - * the 990-997 gates' job, and the wwstage resolver arm is twinned for - * rule-10 symmetry but stays dead on the opaque-free selfhost corpus. - */ -#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 *src; int want_exit; }; - -static const struct row rows[] = { - /* `*opaque` is a usable 8-byte pointer: round-trip a real *i32 - * through *opaque and back, then deref. Proves the type binds and - * carries pointer width. */ - { "package main;\n" - "export fn main() i32 = {\n" - " let n: i32 = 42;\n" - " let pn: *i32 = &n;\n" - " let po: *opaque = pn: *opaque;\n" - " let back: *i32 = po: *i32;\n" - " return *back;\n" - "};\n", 42 }, - /* `[]opaque` is a usable 24-byte slice header: reinterpret a real - * []i32 as []opaque, read .len, and round-trip .ptr (a *opaque) - * back to *i32 + deref. The []opaque is never indexed (guard b). - * (Uses a[0:4] + a store-to-var deref to steer clear of an - * unrelated, pre-existing cgen bug — inline deref of a sub-slice - * .ptr inside a comparison; see report. The opaque path itself is - * exercised in full.) */ - { "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]i32;\n" - " a[0] = 11; a[1] = 22; a[2] = 33; a[3] = 44;\n" - " let s: []i32 = a[0:4];\n" - " let so: []opaque = s: []opaque;\n" - " let n: i32 = so.len: i32;\n" - " if (n != 4) { return 1; };\n" - " let pp: *opaque = so.ptr;\n" - " let pi: *i32 = pp: *i32;\n" - " let v: i32 = *pi;\n" - " if (v != 11) { return 2; };\n" - " return n - 1;\n" - "};\n", 3 }, - /* `*opaque` as a struct field (behind indirection): set from a cast - * pointer, read back, deref + add a sibling scalar field. */ - { "package main;\n" - "type handle = struct { p: *opaque, tag: i32 };\n" - "export fn main() i32 = {\n" - " let n: i32 = 99;\n" - " let h: handle = handle{ p = (&n): *opaque, tag = 7 };\n" - " let pi: *i32 = h.p: *i32;\n" - " return *pi + h.tag;\n" - "};\n", 106 }, - { NULL, 0 } -}; - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - char absbin[1024]; - if (bin[0] != '/') { - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwopq_%d_%d", getpid(), i); - mkdir(tmpdir, 0755); - - char rmcmd[128]; - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - char src[128]; - snprintf(src, sizeof src, "%s/wwopq_%d_%d.ww", tmpdir, getpid(), i); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; runwait(rmcmd); continue; } - fputs(rows[i].src, f); - fclose(f); - - char outbin[128]; - snprintf(outbin, sizeof outbin, "%s/out", tmpdir); - - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -I %s/lib -o %s %s", - bin, cwd, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row %d: build failed\n src: %s\n", - i, rows[i].src); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n", - i, got, rows[i].want_exit, rows[i].src); - fail++; - } - runwait(rmcmd); - } - if (fail) { - fprintf(stderr, "%d/%d opaque tests failed\n", fail, n); - return 1; - } - printf("opaque: %d/%d ok\n", n, n); - return 0; -} diff --git a/test/wcc/962_opaque_assign_cast_run.c b/test/wcc/962_opaque_assign_cast_run.c deleted file mode 100644 index e6b6d8cd..00000000 --- a/test/wcc/962_opaque_assign_cast_run.c +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 962_opaque_assign_cast_run — runtime + byte-id proof of #108 sub-fold - * (c): opaque as a type-erasure sink. Two assignability rules + the - * reinterpret casts sort's implementation relies on. - * - * rule 1 `*T -> *opaque` IMPLICIT (no cast). Any pointer is the - * universal void-pointer. harec type_is_assignable, pointer - * arm: ref/harec/src/types.c:1053 (`case STORAGE_OPAQUE: - * break;` — the referent need not match). - * rule 2 `[]T -> []opaque` IMPLICIT (no cast). Any slice is the - * type-erased slice; the {ptr,len,cap} header is normal, - * the byte stride is supplied at runtime (itemsz). harec - * slice arm: types.c:1094 (`if (to_secondary->storage == - * STORAGE_OPAQUE) return true;`). - * casts `[]opaque -> *u8` (slice -> byte ptr; cgexpr leaves the - * ptr in AX, so the cast naturally takes .ptr) and - * `*opaque -> *u8` / `*opaque -> *i32` (ptr->ptr reinterpret, - * a no-op). drew described the Hare idiom as `*[*]u8`; ww has - * no unbounded-array `[*]`, so the ww-faithful reinterpret - * target is `*u8` + uintptr stride arithmetic. - * - * Rule-10 placement (per-rule, empirical): - * rules 1 & 2 are CSTAGE-ONLY. cstage type_assignable (cmd/wcc/ - * type.c) gained the opaque sink; the wwstage check.ww isassignable - * is a resolve-only AST approximation that returns "can't tell, stay - * quiet" (confident=false) for a ptr/slice whose element it cannot - * match, so it already ACCEPTS every form here (let-init AND call- - * arg). Verified empirically: w6c_ww compiles each row's source with - * exit 0, byte-identically to w6c (the cs==ww gate below). cstage - * rejected these before the type.c change. No ww twin is needed - * (same align-down precedent as 960/961's cstage-only arms). - * The casts are validation-free in BOTH stages (N_CAST never checks - * legality) and the reinterpret cgen needed no change — proven by - * the cs==ww byte-id gate. - * - * Array->[]opaque (harec's array->slice decay, types.c:1080-1099) is - * deliberately EXCLUDED: ww has no implicit array->slice conversion for - * any element type (`let s: []i32 = a` is rejected too — a slice is - * built only via an explicit `a[0:n]`), so there is no array->slice- - * header cgen. Accepting array->[]opaque alone would assign a fat - * array local into a 24-byte slot with no decay: a silent miscompile - * (rule 7). sort's caller passes a slice, so slice->[]opaque suffices. - * - * opaque is unused by the bootstrap, so the new rules fire only on - * opaque-typed operands — INERT on the selfhost corpus, 990-997 stay - * byte-identical. But that same inertness means the 990-997 gates - * never exercise opaque cs==ww; this test carries its own w6c-vs-w6c_ww - * byte-id gate (dimension (b)) to cover the rule-10 symmetry directly. - * - * Each row carries BOTH dimensions, like 953_f64crossmod_run: - * (a) cstage `ww build` + run, asserting the exit code — pins that - * the converged asm is runtime-correct (the type erasure round- - * trips: a value written/read through the opaque path reads back - * intact). - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge. - * - * NOTE — call results are bound to locals before any comparison, never - * compared inline (`if (f(x) != k)`). That inline-call-result-in- - * comparison shape is mis-compiled by a PRE-EXISTING cgen bug (#116 - * family: reproduced with zero opaque — a fn-call result compared - * inline when its pointer arg was produced by a prior call doing - * uintptr arithmetic). Binding first is the same dodge 960 uses; it is - * NOT a workaround for the opaque feature, which is exercised in full. - */ -#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; -} - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* rule 1: `*T -> *opaque` IMPLICIT (no cast) at a let-init AND a - * call-arg. Round-trip a real *i32 through *opaque and back, deref. */ - { "rule1_implicit_ptr", - "package main;\n" - "fn readi32(p: *opaque) i32 = { let pi: *i32 = p: *i32; return *pi; };\n" - "export fn main() i32 = {\n" - " let n: i32 = 42;\n" - " let po: *opaque = &n;\n" /* let-init *i32 -> *opaque */ - " let v: i32 = readi32(po);\n" - " let v2: i32 = readi32(&n);\n" /* call-arg *i32 -> *opaque */ - " if (v != 42) { return 1; };\n" - " if (v2 != 42) { return 2; };\n" - " return v;\n" - "};\n", 42 }, - /* rule 2: `[]T -> []opaque` IMPLICIT (no cast) at a let-init AND a - * call-arg. Read .len, round-trip .ptr (a *opaque) back to *i32. */ - { "rule2_implicit_slice", - "package main;\n" - "fn slen(o: []opaque) i32 = { return o.len: i32; };\n" - "fn first(o: []opaque) i32 = { let p: *opaque = o.ptr; let pi: *i32 = p: *i32; return *pi; };\n" - "export fn main() i32 = {\n" - " let a: [4]i32 = [11, 22, 33, 44];\n" - " let s: []i32 = a[0:4];\n" - " let o: []opaque = s;\n" /* let-init []i32 -> []opaque */ - " let n: i32 = slen(o);\n" - " let n2: i32 = slen(s);\n" /* call-arg []i32 -> []opaque */ - " if (n != 4) { return 1; };\n" - " if (n2 != 4) { return 2; };\n" - " let f: i32 = first(s);\n" - " if (f != 11) { return 3; };\n" - " return n;\n" - "};\n", 4 }, - /* sort's actual usage end-to-end: a `fn(items: []opaque, itemsz: - * size)` called with a []i32; inside, reinterpret the slice as a - * byte base (`items: *u8`), uintptr-arith two element addresses, - * byte-swap them by itemsz. Then read back through the *opaque - * element path and through the original []i32 view — the type - * erasure round-trips iff both agree. Also a *opaque arg straight - * from a *i32 (rule 1). exit 0 == every assertion held. */ - { "sort_pattern", - "package main;\n" - "fn elemptr(items: []opaque, i: size, itemsz: size) *opaque = {\n" - " let base: *u8 = items: *u8;\n" /* []opaque -> *u8 (takes .ptr) */ - " let off: uintptr = (i * itemsz): uintptr;\n" - " return ((base: uintptr) + off): *opaque;\n" - "};\n" - "fn swap(items: []opaque, x: size, y: size, itemsz: size) void = {\n" - " let pa: *u8 = elemptr(items, x, itemsz): *u8;\n" - " let pb: *u8 = elemptr(items, y, itemsz): *u8;\n" - " let k: size = 0;\n" - " for (k < itemsz) {\n" - " let qa: *u8 = ((pa: uintptr) + k: uintptr): *u8;\n" - " let qb: *u8 = ((pb: uintptr) + k: uintptr): *u8;\n" - " let t: u8 = *qa;\n" - " *qa = *qb;\n" - " *qb = t;\n" - " k = k + 1;\n" - " };\n" - "};\n" - "fn readi32(p: *opaque) i32 = { let pi: *i32 = p: *i32; return *pi; };\n" - "export fn main() i32 = {\n" - " let a: [4]i32 = [10, 20, 30, 40];\n" - " let s: []i32 = a[0:4];\n" - " swap(s, 0: size, 3: size, size(i32));\n" /* []i32 -> []opaque call-arg */ - " if (a[0] != 40) { return 1; };\n" /* erased swap round-trips */ - " if (a[3] != 10) { return 2; };\n" - " let p0: *opaque = elemptr(s, 0: size, size(i32));\n" - " let v0: i32 = readi32(p0);\n" - " if (v0 != 40) { return 3; };\n" - " let nn: i32 = 77;\n" - " let pn: *opaque = &nn;\n" /* *i32 -> *opaque let-init */ - " let vn: i32 = readi32(pn);\n" - " if (vn != 77) { return 4; };\n" - " let o: []opaque = s;\n" - " let ol: i32 = o.len: i32;\n" - " if (ol != 4) { return 5; };\n" - " return 0;\n" - "};\n", 0 }, - { NULL, NULL, 0 } -}; - -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 w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "opaque_assign_cast: w6c_ww missing — cannot " - "run the cs==ww byte-id gate (the rule-10 proof)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - /* src reused across (a) build+run AND (b) the byte-id phase; - * keep src, outbin, cs.s, ws.s ALL under the one tmpdir and - * defer a single rm -rf to the end so .sepwork never leaks. */ - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwopqc_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char rmcmd[160]; - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - char src[128], outbin[128]; - snprintf(src, sizeof src, "%s/wwopqc_%d_%d.ww", - tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwopqc_%d_%d", - tmpdir, getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { runwait(rmcmd); fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run. */ - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */ - char cs_s[160], ws_s[160]; - snprintf(cs_s, sizeof cs_s, "%s/wwopqc_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwopqc_%d_%d_ww.s", - tmpdir, getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; runwait(rmcmd); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; runwait(rmcmd); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - runwait(rmcmd); - } - - if (fail) { - fprintf(stderr, "%d/%d opaque assign/cast tests failed\n", - fail, n); - return 1; - } - printf("opaque_assign_cast: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -}