/* * 989_m2wwi_run — M2 `.wwi` export-data producer gate (task #22 arc). * Both stages (rule-10): the new `w6c -I ` producer is DEAD on * the live path (combined.ww stays the compile substrate; nothing reads * `.wwi`), so this is purely a new cross-stage byte-id substrate. * * POSITIVE gate — for each package (ascii/strings/getopt, drew2-audited * leak-free), drive the target as the PRIMARY module of a driver-combined * unit, then: * 1. w6c -I cs.wwi and w6c_ww -I ww.wwi both succeed, * 2. cs.wwi == ww.wwi byte-for-byte (the new substrate's determinism), * 3. cs.wwi RE-PARSES under the existing parser (wwdump -a exits 0) — * proves the producer emits genuinely re-parseable ww prototype * source, de-risking the M3 consumer. * getopt is the recursion stressor (enum→struct→struct, ptr-to-nominal, * tagged-union return, qualified pkg.Name). A synth fixture then covers the * decl-kinds + type-nodes no lib package reaches: `def` (const-expr unparser * + fold parity), `let` global, `[N]T` array, `fn(..)R` fn-ptr, `!T`, tuple, * storage-less enum. * * NEGATIVE gate — a fixture whose exported fn names a PRIVATE (non- * exported) nominal must be LOUD-REJECTED by check_exported_type, * identically on BOTH stages (same non-zero exit, byte-identical * diagnostic). Without it a vacuous no-op check would pass the positive * gate silently. * * Compile/produce/cmp only — no driver run, everything under a getpid- * keyed /tmp dir (no source-tree writes), so this is phase-1 parallel- * safe. 9xx is full; shares the 989 prefix per the 989_lib_byteid * precedent (the `short` name keys the binary). */ #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; } static const char * absbin(void) { const char *b = getenv("BIN"); if (!b) b = "out/bin"; if (b[0] == '/') return b; static char buf[2048]; char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return NULL; snprintf(buf, sizeof buf, "%s/%s", cwd, b); return buf; } static int slurp(const char *path, char **outbuf, size_t *outlen) { FILE *f = fopen(path, "rb"); if (!f) return -1; fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); if (n < 0) { fclose(f); return -1; } char *b = malloc((size_t)n + 1); if (!b) { fclose(f); return -1; } if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; } b[n] = '\0'; fclose(f); *outbuf = b; *outlen = (size_t)n; return 0; } static int file_contains(const char *path, const char *needle) { char *b = NULL; size_t n = 0; if (slurp(path, &b, &n) < 0) return -1; int found = (strstr(b, needle) != NULL); free(b); return found ? 0 : 1; } static int files_eq(const char *a, const char *b) { char *ba = NULL, *bb = NULL; size_t na = 0, nb = 0; if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) { free(ba); free(bb); return -1; } int eq = (na == nb && memcmp(ba, bb, na) == 0); free(ba); free(bb); return eq ? 0 : 1; } /* one positive package: build the target as primary, produce the `.wwi` * on both stages, require byte-id + re-parse. Returns 0 on pass. */ static int positive(const char *bin, const char *cwd, const char *pkg, int idx) { char td[64], cmd[4096], src[1024]; int rc = -1; snprintf(td, sizeof td, "/tmp/wwm2_%d_%d", getpid(), idx); snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); mkdir(td, 0755); snprintf(src, sizeof src, "%s/lib/%s/%s.ww", cwd, pkg, pkg); snprintf(cmd, sizeof cmd, "cp %s %s/%s.ww", src, td, pkg); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: %s — cp\n", pkg); goto out; } /* #94 sep layout: drive the package file as the --sep build target → * its decls are the PRIMARY module of the resolved ROOT unit, which the * producer's primary filter narrows to exactly this package's * interface. The root unit's amalgamated source is composed at * .sepwork/__root.unit.ww BEFORE codegen; the library has no * main so the link step fails, but the unit (the only thing that * matters here) is already written, so the exit code is deliberately * ignored. WW_PKGCACHE is pinned under td so out/.pkgcache is * untouched. */ snprintf(cmd, sizeof cmd, "cd %s && WW_PKGCACHE=%s/pkgc timeout 180 %s/ww build --sep " "-I %s/lib -o %s/%s %s.ww >/dev/null 2>&1", td, td, bin, cwd, td, pkg, pkg); runwait(cmd); char comb[1100], cs[1100], ws[1100]; snprintf(comb, sizeof comb, "%s/%s.sepwork/__root.unit.ww", td, pkg); snprintf(cs, sizeof cs, "%s/cs.wwi", td); snprintf(ws, sizeof ws, "%s/ww.wwi", td); if (access(comb, 0) != 0) { fprintf(stderr, "m2wwi FAIL: %s — no resolved unit\n", pkg); goto out; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -I %s %s >/dev/null 2>&1", bin, cs, comb); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: %s — w6c -I rejected\n", pkg); goto out; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c_ww -I %s %s >/dev/null 2>&1", bin, ws, comb); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: %s — w6c_ww -I rejected\n", pkg); goto out; } if (files_eq(cs, ws) != 0) { fprintf(stderr, "m2wwi FAIL: %s — cs.wwi != ww.wwi (byte-id " "broken on the .wwi substrate)\n", pkg); goto out; } /* re-parse: the emitted `.wwi` must be valid ww prototype source. */ snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump -a %s >/dev/null 2>&1", bin, cs); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: %s — emitted .wwi does not " "re-parse (wwdump -a)\n", pkg); goto out; } rc = 0; out: snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); return rc; } /* negative gate: an exported fn naming a private nominal must be rejected * identically (exit + diagnostic) by BOTH stages. Returns 0 on pass. */ static const char *leak_src = "package leaktest;\n" "type secret = struct { x: i32 };\n" "export fn leaks(s: secret) i32 = { return s.x; };\n" "export fn clean(a: i32) i32 = { return a; };\n"; static int negative(const char *bin) { char td[64], cmd[4096], p[512]; int rc = -1; snprintf(td, sizeof td, "/tmp/wwm2neg_%d", getpid()); snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); mkdir(td, 0755); snprintf(p, sizeof p, "%s/leak.ww", td); FILE *f = fopen(p, "wb"); if (!f) goto out; fputs(leak_src, f); fclose(f); char cse[1100], wwe[1100]; snprintf(cse, sizeof cse, "%s/cs.err", td); snprintf(wwe, sizeof wwe, "%s/ww.err", td); /* cd into td so both stages report the bare `leak.ww:L:C:` prefix. */ snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s/w6c -I out.wwi leak.ww >/dev/null 2>cs.err", td, bin); int crc = runwait(cmd); snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s/w6c_ww -I out.wwi leak.ww >/dev/null 2>ww.err", td, bin); int wrc = runwait(cmd); if (crc == 0 || wrc == 0) { fprintf(stderr, "m2wwi FAIL: negative — check_exported_type is " "vacuous (cstage exit=%d wwstage exit=%d; both must reject " "the private-type leak)\n", crc, wrc); goto out; } if (files_eq(cse, wwe) != 0) { fprintf(stderr, "m2wwi FAIL: negative — reject diagnostics " "differ across stages (rule-10)\n"); goto out; } rc = 0; out: snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); return rc; } /* synth gate: a single self-contained package exercising the decl-kinds + * type-AST nodes the lib packages above do NOT cover — `export def` (the * const-expr unparser, incl a folded `10+2*3` and a unary `-7`), `export * let` (global), `[N]T` array (decl + array-dim ident-ref), an `fn(...) R` * fn-pointer type, an `!T` error type, a `(a, b)` tuple return, and a * storage-less `enum {...}`. Asserts both stages produce a byte-identical, * re-parseable `.wwi` (the const-expr fold + tokname spelling must agree * cross-stage — paths the lib gate never reaches). `chan T` is omitted: the * wwstage parser does not yet accept it in a return position (pre-existing, * unrelated to M2), so the producer's N_TCHAN arm is unreachable there. * Returns 0 on pass. */ static const char *synth_src = "package synth;\n" "export type color = enum { RED, GREEN = 5, BLUE };\n" "export def LIMIT: i32 = 10 + 2 * 3;\n" "export def NAME: str = \"hi\\n\";\n" "export def FLAG: bool = true;\n" "export def NEG: i32 = -7;\n" "export let counter: i32;\n" "export let grid: [4]i32;\n" "export fn apply(f: fn(x: i32) i32, n: i32) i32;\n" "export fn risky() !i32;\n" "export fn matrix() [LIMIT]u8;\n" "export fn pair() (i32, i32);\n" "export fn opt(p: *color, b: []u8) (i32 | void);\n" /* #47: a codegen/link-relevant attribute (@symbol — the FFI link- * symbol override) MUST round-trip into the `.wwi`; dropping it makes * sep-compile emit `CALL ext` for a `@symbol("rt_ext")` fn. */ "@symbol(\"rt_ext\") export fn ext(n: i32) i32;\n"; static int synth(const char *bin) { char td[64], cmd[4096], p[512]; int rc = -1; snprintf(td, sizeof td, "/tmp/wwm2syn_%d", getpid()); snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); mkdir(td, 0755); snprintf(p, sizeof p, "%s/synth.ww", td); FILE *f = fopen(p, "wb"); if (!f) goto out; fputs(synth_src, f); fclose(f); char cs[1100], ws[1100]; snprintf(cs, sizeof cs, "%s/cs.wwi", td); snprintf(ws, sizeof ws, "%s/ww.wwi", td); /* self-contained package (no imports) → it is its own primary unit, * so w6c -I runs directly on the source (no driver-combined step). */ snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -I %s %s/synth.ww >/dev/null 2>&1", bin, cs, td); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: synth — w6c -I rejected\n"); goto out; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c_ww -I %s %s/synth.ww >/dev/null 2>&1", bin, ws, td); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: synth — w6c_ww -I rejected\n"); goto out; } if (files_eq(cs, ws) != 0) { fprintf(stderr, "m2wwi FAIL: synth — cs.wwi != ww.wwi (const-expr " "/ decl-kind unparse diverges across stages)\n"); goto out; } /* #47: the @symbol attribute must survive the round-trip verbatim. */ if (file_contains(cs, "@symbol(\"rt_ext\") export fn ext") != 0) { fprintf(stderr, "m2wwi FAIL: synth — .wwi dropped @symbol " "(sep-compile would emit the wrong link symbol)\n"); goto out; } snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump -a %s >/dev/null 2>&1", bin, cs); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: synth — emitted .wwi does not " "re-parse (wwdump -a)\n"); goto out; } rc = 0; out: snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); return rc; } /* #48 gate: the lib/types limit constants are `export def`s (Hare exports * types::I32_MAX &c). Produce types.wwi on both stages directly from the * real lib source (no imports → its own primary unit), assert byte-id + * re-parse + that the exported limits actually appear in the .wwi (a non- * exported def is omitted, so this proves the export reached the interface * a cross-package sep-compile consumer reads). Returns 0 on pass. */ static int typesexport(const char *bin, const char *cwd) { char td[64], cmd[4096], src[1024]; int rc = -1; snprintf(td, sizeof td, "/tmp/wwm2typ_%d", getpid()); snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); mkdir(td, 0755); snprintf(src, sizeof src, "%s/lib/types/types.ww", cwd); char cs[1100], ws[1100]; snprintf(cs, sizeof cs, "%s/cs.wwi", td); snprintf(ws, sizeof ws, "%s/ww.wwi", td); snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c -I %s %s >/dev/null 2>&1", bin, cs, src); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: types — w6c -I rejected\n"); goto out; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6c_ww -I %s %s >/dev/null 2>&1", bin, ws, src); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: types — w6c_ww -I rejected\n"); goto out; } if (files_eq(cs, ws) != 0) { fprintf(stderr, "m2wwi FAIL: types — cs.wwi != ww.wwi\n"); goto out; } if (file_contains(cs, "export def I32_MAX") != 0 || file_contains(cs, "export def RUNE_MAX") != 0) { fprintf(stderr, "m2wwi FAIL: types — limit consts not exported " "into the .wwi (cross-package sep-compile would not see " "types.I32_MAX)\n"); goto out; } snprintf(cmd, sizeof cmd, "timeout 180 %s/wwdump -a %s >/dev/null 2>&1", bin, cs); if (runwait(cmd) != 0) { fprintf(stderr, "m2wwi FAIL: types — emitted .wwi does not " "re-parse (wwdump -a)\n"); goto out; } rc = 0; out: snprintf(cmd, sizeof cmd, "rm -rf %s", td); runwait(cmd); return rc; } int main(void) { const char *bin = absbin(); if (!bin) return 1; char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; const char *pkgs[] = { "ascii", "strings", "getopt", NULL }; int fail = 0, npos = 0; for (int i = 0; pkgs[i]; i++) { if (positive(bin, cwd, pkgs[i], i) != 0) fail++; else npos++; } if (synth(bin) != 0) fail++; else npos++; if (typesexport(bin, cwd) != 0) fail++; else npos++; if (negative(bin) != 0) fail++; if (fail) { fprintf(stderr, "m2wwi: %d check(s) failed\n", fail); return 1; } printf("m2wwi: %d package(s) byte-identical + re-parse; negative " "gate rejects (both stages)\n", npos); return 0; }