lib/ww/syntax: export the 16 public types consumed by the wcc backend (#72)
After the frontend consolidated into one syntax package, the wcc backend
imports syntax and calls its exported fns — whose signatures reference
types that were unexported. Producing syntax's .wwi interface re-triggered
check_exported_type ("exported declaration references unexported type"):
the residual of BUG-A at the one surviving syntax->wcc boundary. Export
the 16 types that appear in syntax's wcc-facing public surface (directly
in an exported signature, or via a recursively-referenced exported struct
field): nkind, node, lex, tok, tkind, parser, scope, sym, skind, tinfo,
tykind, tfield, tparam, ttupleelem, tctx, tinfocacheent. The set is
minimal (unexporting any one re-breaks the producer) and complete; pos
stays internal. Pure source change — exporting a type emits no code, so
the bootstrap binaries are byte-identical (verified against a clean base
build); only syntax's .wwi gains the type decls.
Post-frontend-reorg residual (#74). syntax now sep-produces clean both
stages. The separate concern of wcc's currently-unqualified refs to
syntax symbols (#75) is a distinct follow-up. Gate 989_syntaxexport_run.
This commit is contained in:
282
test/wcc/989_syntaxexport_run.c
Normal file
282
test/wcc/989_syntaxexport_run.c
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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 `--sep` 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 (all COLD — per-stage WW_PKGCACHE wipes the package cache):
|
||||
* 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 --sep` and `ww_ww --sep`.
|
||||
* 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.
|
||||
*
|
||||
* Light wwstage-driver test (CLAUDE.md rule 14): all intermediates are
|
||||
* `-o`-redirected to /tmp, so it is phase-1 parallel-safe; it only READS
|
||||
* the in-tree `lib/ww/syntax`. Models 989_seproot_export_run.c.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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;
|
||||
fputs(body, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) return 1;
|
||||
char root[1024];
|
||||
if (getcwd(root, sizeof root) == NULL) return 1;
|
||||
|
||||
char td[64], cmd[8192];
|
||||
int fail = 0;
|
||||
|
||||
snprintf(td, sizeof td, "/tmp/wwsynexp_%d", getpid());
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
mkdir(td, 0755);
|
||||
|
||||
/* 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);
|
||||
/* COLD: per-stage WW_PKGCACHE -> syntax compiles fresh, so the
|
||||
* `.wwi`/`.s`/`.unit.ww` this gate inspects are always produced. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"WW_PKGCACHE='%s/cache.%s' timeout 240 %s/%s build --sep "
|
||||
"-I %s/lib/ww -o %s %s >%s/build.%s.log 2>&1",
|
||||
td, stg[s].tag, bin, stg[s].drv, root, stg[s].prog, rootww,
|
||||
td, stg[s].tag);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: %s build --sep (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:
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user