Every surviving carrier now owns its artifacts: checked mkdir/mkdtemp/ fopen acquisition, one all-exit cleanup funnel per carrier, ENOENT- tolerant checked unlinks, exact-path deletion (rm -rf only for an owned pid-keyed dir or a .sepwork beneath one), and cleanup failure fails a passing carrier without overwriting its diagnostic. In the same pass the carriers adapt to the driver contract this branch lands: --sep and WW_PKGCACHE are gone, -S and the /tmp/ww_run_<pid> scratch contract are asserted, and rows whose runtime or reject coverage moved to test/wcc/data fixtures or test/lang @test owners are trimmed to the byte/artifact/diagnostic observations only they can make. Repair and adaptation ride together because most files interleave both in the same hunks; splitting would manufacture intermediate carrier states that never existed and cannot run against either driver.
307 lines
10 KiB
C
307 lines
10 KiB
C
/*
|
|
* 989_syntaxexport_run — BUG-A residual at the surviving syntax->wcc
|
|
* package boundary (#72), post the #74 frontend consolidation.
|
|
*
|
|
* Root cause it guards: `lib/ww/syntax/` exports the fns wcc calls, and
|
|
* those fns' signatures name frontend types (tok, tinfo, tctx, ...). When
|
|
* the separate-compilation producer emits syntax's `.wwi`,
|
|
* check_exported_type recurses every exported decl — and, for an exported
|
|
* struct, its FIELDS too (the
|
|
* harec STORAGE_STRUCT precedent, wwi.c wwi_check_type N_TSTRUCT). Any
|
|
* referenced type that is itself unexported aborts the producer with
|
|
* "exported declaration references unexported type". Pre-fix, syntax's
|
|
* surface left 16 such types unexported, so syntax's `.wwi` never produced
|
|
* -> every wcc sep-build was blocked. Fix: `export` those 16 type decls
|
|
* (10 fn-signature-direct + 6 surfaced by the struct-field recursion:
|
|
* tykind/tfield/tparam/ttupleelem via tinfo, tctx via typesinit, and
|
|
* tinfocacheent via tctx). Exporting a TYPE emits no code -> the bug is
|
|
* byte-id-neutral; this gate proves the PRODUCER residual is closed.
|
|
*
|
|
* The consumer here uses QUALIFIED refs (`syntax.tokname`, `syntax.tkind`)
|
|
* to isolate the producer fix from the separate unqualified-ref question
|
|
* (#75). It happens to link + run end-to-end, which also shows the
|
|
* qualified-ref path resolves across the sep boundary.
|
|
*
|
|
* Asserts (direct builds compile every package):
|
|
* 1. Build + run, BOTH stages -> exit EXPECT_EXIT (`syntax.tokname(
|
|
* TK_INT)` == "int", len 3). Pre-fix the syntax producer exited 1, so
|
|
* the build never produced a binary.
|
|
* 2. syntax's `.wwi` is produced and carries the 16 `export type` decls.
|
|
* 3. cs==ww (rule 10): syntax's `.wwi`/`.s`/`.unit.ww` AND the final
|
|
* binary are byte-identical between `ww` and `ww_ww`.
|
|
* 4. NON-VACUITY (the bug, isolated, BOTH compilers): replay the producer
|
|
* `w6c -c -I` on the produced `syntax.unit.ww` -> exits 0 (exports
|
|
* present). On a copy with `export ` stripped from the type decls ->
|
|
* MUST exit non-zero (the export-check fires). Proven for w6c + w6c_ww.
|
|
*
|
|
* All intermediates are `-o`-redirected to /tmp; it only reads
|
|
* the in-tree `lib/ww/syntax`. Models 989_seproot_export_run.c.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
|
|
#define EXPECT_EXIT 3
|
|
|
|
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
|
|
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;
|
|
}
|
|
|
|
/* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */
|
|
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
|
|
write_file(const char *path, const char *body)
|
|
{
|
|
FILE *f = fopen(path, "wb");
|
|
if (!f) return -1;
|
|
if (fputs(body, f) == EOF) { fclose(f); return -1; }
|
|
return fclose(f);
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
const char *bin = absbin();
|
|
if (!bin) return 1;
|
|
char root[1024];
|
|
if (getcwd(root, sizeof root) == NULL) return 1;
|
|
|
|
char td[] = "/tmp/wwsynexp_XXXXXX";
|
|
char cmd[8192];
|
|
int fail = 0, cleanup_fail = 0;
|
|
|
|
if (mkdtemp(td) == NULL) return 1;
|
|
|
|
/* Root: QUALIFIED refs into the real syntax package — `syntax.tokname`
|
|
* over `syntax.tkind`. The producer must serialize syntax's `.wwi`
|
|
* (exported fns over frontend types) WITHOUT the unexported-type abort. */
|
|
char rootww[1024];
|
|
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
|
if (write_file(rootww,
|
|
"package main;\n"
|
|
"import syntax;\n"
|
|
"fn main() i32 = {\n"
|
|
"\tlet s: str = syntax.tokname(syntax.tkind.TK_INT);\n"
|
|
"\treturn s.len: i32;\n"
|
|
"};\n"))
|
|
{ fail++; goto out; }
|
|
|
|
struct { const char *drv, *tag; char prog[1024]; }
|
|
stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } };
|
|
|
|
for (int s = 0; s < 2; s++) {
|
|
snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag);
|
|
/* Direct builds produce the `.wwi`/`.s`/`.unit.ww` artifacts this
|
|
* gate inspects. */
|
|
snprintf(cmd, sizeof cmd,
|
|
"timeout 240 %s/%s build "
|
|
"-I %s/lib/ww -o %s %s >%s/build.%s.log 2>&1",
|
|
bin, stg[s].drv, root, stg[s].prog, rootww,
|
|
td, stg[s].tag);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "syntaxexport FAIL: %s build (pre-fix: syntax "
|
|
"producer rejects export-fn-over-unexported-type)\n", stg[s].drv);
|
|
fail++;
|
|
continue;
|
|
}
|
|
/* the producer abort must not appear in the build log. */
|
|
char log[1024], wwi[1024];
|
|
snprintf(log, sizeof log, "%s/build.%s.log", td, stg[s].tag);
|
|
if (file_contains(log, "unexported type") == 0) {
|
|
fprintf(stderr, "syntaxexport FAIL: %s build log shows "
|
|
"'unexported type' (producer residual not closed)\n", stg[s].drv);
|
|
fail++;
|
|
}
|
|
snprintf(wwi, sizeof wwi, "%s/prog.%s.sepwork/syntax.wwi", td, stg[s].tag);
|
|
if (access(wwi, 0) != 0) {
|
|
fprintf(stderr, "syntaxexport FAIL: %s produced no syntax.wwi\n",
|
|
stg[s].drv);
|
|
fail++;
|
|
}
|
|
int rc = runwait(stg[s].prog);
|
|
if (rc != EXPECT_EXIT) {
|
|
fprintf(stderr, "syntaxexport FAIL: %s prog exit=%d expected %d\n",
|
|
stg[s].drv, rc, EXPECT_EXIT);
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
/* syntax's `.wwi` carries the now-exported frontend types — spot-check
|
|
* a representative set spanning each surfacing path (fn-direct + the
|
|
* struct-field and tctx-field cascades). */
|
|
{
|
|
char wwi[1024];
|
|
snprintf(wwi, sizeof wwi, "%s/prog.cs.sepwork/syntax.wwi", td);
|
|
const char *want[] = {
|
|
"export type tok ", "export type tkind ", "export type node ",
|
|
"export type nkind ", "export type tinfo ", "export type tfield ",
|
|
"export type tctx ", "export type tinfocacheent ",
|
|
};
|
|
for (int i = 0; i < (int)(sizeof want / sizeof want[0]); i++)
|
|
if (file_contains(wwi, want[i]) != 0) {
|
|
fprintf(stderr, "syntaxexport FAIL: syntax.wwi lacks '%s'\n",
|
|
want[i]);
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
/* cs==ww (rule 10): syntax's per-package artefacts + the final binary. */
|
|
{
|
|
const char *suf[] = { ".wwi", ".s", ".unit.ww" };
|
|
for (int k = 0; k < 3; k++) {
|
|
char a[1024], b[1024];
|
|
snprintf(a, sizeof a, "%s/prog.cs.sepwork/syntax%s", td, suf[k]);
|
|
snprintf(b, sizeof b, "%s/prog.ww.sepwork/syntax%s", td, suf[k]);
|
|
if (files_eq(a, b) != 0) {
|
|
fprintf(stderr, "syntaxexport FAIL: cs!=ww for syntax%s "
|
|
"(rule 10)\n", suf[k]);
|
|
fail++;
|
|
}
|
|
}
|
|
if (files_eq(stg[0].prog, stg[1].prog) != 0) {
|
|
fprintf(stderr, "syntaxexport FAIL: cs exe != ww exe (rule 10)\n");
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
/* NON-VACUITY (the bug, isolated): replay the producer on the produced
|
|
* `syntax.unit.ww`. With the exports present `w6c -c -I` exits 0; with
|
|
* `export ` stripped from the type decls it MUST exit non-zero (the
|
|
* export-check fires). Proven for BOTH compilers. */
|
|
{
|
|
char unit[1024], ne[1024];
|
|
snprintf(unit, sizeof unit, "%s/prog.cs.sepwork/syntax.unit.ww", td);
|
|
snprintf(ne, sizeof ne, "%s/syntax.noexport.ww", td);
|
|
snprintf(cmd, sizeof cmd,
|
|
"sed 's/export type /type /g' %s > %s", unit, ne);
|
|
if (runwait(cmd) != 0) { fail++; goto out; }
|
|
|
|
struct { const char *comp; } cc[] = { { "w6c" }, { "w6c_ww" } };
|
|
for (int j = 0; j < 2; j++) {
|
|
char wwi[1024], asmf[1024];
|
|
snprintf(wwi, sizeof wwi, "%s/nv.%s.wwi", td, cc[j].comp);
|
|
snprintf(asmf, sizeof asmf, "%s/nv.%s.s", td, cc[j].comp);
|
|
snprintf(cmd, sizeof cmd, "%s/%s -c -I %s -o %s %s >/dev/null 2>&1",
|
|
bin, cc[j].comp, wwi, asmf, unit);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "syntaxexport FAIL: %s -c -I rejected the "
|
|
"EXPORTED unit (post-fix producer must succeed)\n",
|
|
cc[j].comp);
|
|
fail++;
|
|
}
|
|
snprintf(cmd, sizeof cmd, "%s/%s -c -I %s -o %s %s >/dev/null 2>&1",
|
|
bin, cc[j].comp, wwi, asmf, ne);
|
|
if (runwait(cmd) == 0) {
|
|
fprintf(stderr, "syntaxexport FAIL: %s -c -I accepted the "
|
|
"UNEXPORTED-type unit (bug not reproduced -> vacuous "
|
|
"gate)\n", cc[j].comp);
|
|
fail++;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
for (int s = 0; s < 2; s++) {
|
|
const char *tag = s == 0 ? "cs" : "ww";
|
|
char path[1200];
|
|
snprintf(path, sizeof path, "%s/prog.%s.sepwork", td, tag);
|
|
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
|
|
if (runwait(cmd) != 0) cleanup_fail = 1;
|
|
snprintf(path, sizeof path, "%s/prog.%s", td, tag);
|
|
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
|
|
snprintf(path, sizeof path, "%s/build.%s.log", td, tag);
|
|
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
|
|
}
|
|
{
|
|
const char *files[] = {
|
|
"root.ww", "syntax.noexport.ww",
|
|
"nv.w6c.wwi", "nv.w6c.s",
|
|
"nv.w6c_ww.wwi", "nv.w6c_ww.s",
|
|
};
|
|
for (size_t i = 0; i < sizeof files / sizeof files[0]; i++) {
|
|
char path[1200];
|
|
snprintf(path, sizeof path, "%s/%s", td, files[i]);
|
|
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
|
|
}
|
|
}
|
|
if (rmdir(td) != 0) cleanup_fail = 1;
|
|
if (cleanup_fail) {
|
|
fprintf(stderr, "syntaxexport FAIL: cleanup incomplete under %s\n", td);
|
|
fail++;
|
|
}
|
|
if (fail) {
|
|
fprintf(stderr, "syntaxexport: %d check(s) failed\n", fail);
|
|
return 1;
|
|
}
|
|
printf("syntaxexport: syntax->wcc boundary sep-PRODUCES clean (16 export "
|
|
"type decls) + qualified-ref consumer build+run (exit %d) + cs==ww "
|
|
"syntax.wwi/.s/.unit.ww/binary + non-vacuity (export strip rejects, "
|
|
"both compilers)\n", EXPECT_EXIT);
|
|
return 0;
|
|
}
|