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:
2026-06-16 20:19:14 +09:00
parent 7a8acfb952
commit 697e413113
10 changed files with 343 additions and 48 deletions

View File

@@ -562,6 +562,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_sepbuild_run \
$(BIN)/test_sepdotpath_run \
$(BIN)/test_seproot_export_run \
$(BIN)/test_syntaxexport_run \
$(BIN)/test_sepstructdef_run \
$(BIN)/test_sepcycle_dup \
$(BIN)/test_separchive_run \
@@ -3131,6 +3132,18 @@ $(BIN)/test_seproot_export_run: test/wcc/989_seproot_export_run.c $(BIN)/ww $(BI
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
# 989_syntaxexport_run — BUG-A residual (#72) at the surviving syntax->wcc
# boundary after the #74 consolidation: syntax's exported fns name frontend
# types, so the producer's check_exported_type (recursing exported-struct
# FIELDS too) rejects until those 16 types carry `export`. Drives BOTH driver
# stages on the real lib/ww/syntax via a qualified-ref root + replays the
# producer on the produced unit (both compilers, export-strip = the bug), so
# it needs both driver + both compiler + both linker stages + libwwrt.
$(BIN)/test_syntaxexport_run: test/wcc/989_syntaxexport_run.c $(BIN)/ww $(BIN)/ww_ww \
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \
$(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
# 989_sepstructdef_run — sep-build of a dep exporting aggregate-initializer
# defs (N_STRUCTLIT info / N_ARRLIT arr) (#70, BUG-2): the producer must emit a
# value-LESS prototype `export def X: T;` for an aggregate-init def (DATA-global

View File

@@ -21,7 +21,7 @@ import strconv;
// explicit and must stay in sync — the 990_selfhost test diffs
// astprint against the C side byte-for-byte. Tail-appended entries
// (TYPETEST onward) preserve every prior N_* value.
type nkind = enum i32 {
export type nkind = enum i32 {
N_NONE = 0,
N_INTLIT = 1,
@@ -115,7 +115,7 @@ type nkind = enum i32 {
// ---- Node -------------------------------------------------------------
type node = struct {
export type node = struct {
kind: nkind,
file: str,
line: i32,

View File

@@ -46,7 +46,7 @@ fn hexval(c: rune) (i32 | void) = {
return;
};
type lex = struct {
export type lex = struct {
file: str,
src: *u8, // raw bytes; not necessarily NUL-terminated
srclen: u64,

View File

@@ -17,7 +17,7 @@ package syntax;
import os;
import strings;
type parser = struct {
export type parser = struct {
l: *lex,
errs: i32,
// nocast: while inside `[...]` we treat ':' as the slice

View File

@@ -7,7 +7,7 @@
package syntax;
// Symbol kinds — must stay numerically aligned with cmd/wcc/ww.h Skind.
type skind = enum i32 {
export type skind = enum i32 {
SK_NONE = 0,
SK_VAR = 1,
SK_PARAM = 2,
@@ -18,7 +18,7 @@ type skind = enum i32 {
SK_FIELD = 7,
};
type sym = struct {
export type sym = struct {
name: str,
skind: skind,
type_: *tinfo,
@@ -43,7 +43,7 @@ type sym = struct {
def NBUCKETS: i32 = 16;
type scope = struct {
export type scope = struct {
parent: *scope,
first: *sym,
last: *sym,

View File

@@ -20,7 +20,7 @@ import strings;
// explicit and must stay in sync — the 990_selfhost test diffs wwdump
// output against the C side, byte for byte.
type tkind = enum i32 {
export type tkind = enum i32 {
TK_NONE = 0,
TK_EOF = 1,
TK_ERR = 2,
@@ -143,7 +143,7 @@ type pos = struct {
col: i32,
};
type tok = struct {
export type tok = struct {
kind: tkind,
file: str, // path of the source the token came from
line: i32,

View File

@@ -18,7 +18,7 @@ import os;
// Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values
// are explicit and must stay in sync — the selfhost selfcheck and
// typed-AST printers depend on matching numeric layout.
type tykind = enum i32 {
export type tykind = enum i32 {
TY_NONE = 0,
TY_VOID = 1,
TY_BOOL = 2,
@@ -69,14 +69,14 @@ def SIZE_UNDEFINED: u64 = 18446744073709551615;
// ---- tinfo / tfield / tparam -----------------------------------------
type tfield = struct {
export type tfield = struct {
name: str,
type_: *tinfo,
offset: u64,
tnext: *tfield,
};
type tparam = struct {
export type tparam = struct {
name: str,
type_: *tinfo,
// #61a: per-variant `!T` error mark for TY_TAGGED variants.
@@ -98,13 +98,13 @@ type tparam = struct {
// sea-of-stars mirrors Hare's structural choice; the empty-name idiom
// from #50's TTAGGED-on-tparam would conflate two semantic axes
// (variants can be named; positionals never can).
type ttupleelem = struct {
export type ttupleelem = struct {
type_: *tinfo,
offset: u64,
tnext: *ttupleelem,
};
type tinfo = struct {
export type tinfo = struct {
kind: tykind,
size: u64,
align: u64,
@@ -154,7 +154,7 @@ type tinfo = struct {
// (rule-12): cnext chains WITHIN a bucket; lookup/bind hash then touch
// only one bucket -> O(1) amortized. Identical lookup results (same
// *tinfo for the same node), so emitted asm is byte-identical.
type tinfocacheent = struct {
export type tinfocacheent = struct {
key: *node,
val: *tinfo,
cnext: *tinfocacheent,
@@ -168,7 +168,7 @@ def NBUCKETS_TINFO: u64 = 8192u64;
// ---- tctx — the box of primitive types -------------------------------
type tctx = struct {
export type tctx = struct {
tyvoid: *tinfo,
tybool: *tinfo,
tyrune: *tinfo,

View File

@@ -6441,7 +6441,7 @@ import strconv;
// explicit and must stay in sync — the 990_selfhost test diffs
// astprint against the C side byte-for-byte. Tail-appended entries
// (TYPETEST onward) preserve every prior N_* value.
type nkind = enum i32 {
export type nkind = enum i32 {
N_NONE = 0,
N_INTLIT = 1,
@@ -6535,7 +6535,7 @@ type nkind = enum i32 {
// ---- Node -------------------------------------------------------------
type node = struct {
export type node = struct {
kind: nkind,
file: str,
line: i32,
@@ -7521,7 +7521,7 @@ fn hexval(c: rune) (i32 | void) = {
return;
};
type lex = struct {
export type lex = struct {
file: str,
src: *u8, // raw bytes; not necessarily NUL-terminated
srclen: u64,
@@ -8372,7 +8372,7 @@ package syntax;
import os;
import strings;
type parser = struct {
export type parser = struct {
l: *lex,
errs: i32,
// nocast: while inside `[...]` we treat ':' as the slice
@@ -9323,7 +9323,7 @@ fn parsestmt(p: *parser) *node = {
package syntax;
// Symbol kinds — must stay numerically aligned with cmd/wcc/ww.h Skind.
type skind = enum i32 {
export type skind = enum i32 {
SK_NONE = 0,
SK_VAR = 1,
SK_PARAM = 2,
@@ -9334,7 +9334,7 @@ type skind = enum i32 {
SK_FIELD = 7,
};
type sym = struct {
export type sym = struct {
name: str,
skind: skind,
type_: *tinfo,
@@ -9359,7 +9359,7 @@ type sym = struct {
def NBUCKETS: i32 = 16;
type scope = struct {
export type scope = struct {
parent: *scope,
first: *sym,
last: *sym,
@@ -9671,7 +9671,7 @@ import strings;
// explicit and must stay in sync — the 990_selfhost test diffs wwdump
// output against the C side, byte for byte.
type tkind = enum i32 {
export type tkind = enum i32 {
TK_NONE = 0,
TK_EOF = 1,
TK_ERR = 2,
@@ -9794,7 +9794,7 @@ type pos = struct {
col: i32,
};
type tok = struct {
export type tok = struct {
kind: tkind,
file: str, // path of the source the token came from
line: i32,
@@ -10074,7 +10074,7 @@ import os;
// Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values
// are explicit and must stay in sync — the selfhost selfcheck and
// typed-AST printers depend on matching numeric layout.
type tykind = enum i32 {
export type tykind = enum i32 {
TY_NONE = 0,
TY_VOID = 1,
TY_BOOL = 2,
@@ -10125,14 +10125,14 @@ def SIZE_UNDEFINED: u64 = 18446744073709551615;
// ---- tinfo / tfield / tparam -----------------------------------------
type tfield = struct {
export type tfield = struct {
name: str,
type_: *tinfo,
offset: u64,
tnext: *tfield,
};
type tparam = struct {
export type tparam = struct {
name: str,
type_: *tinfo,
// #61a: per-variant `!T` error mark for TY_TAGGED variants.
@@ -10154,13 +10154,13 @@ type tparam = struct {
// sea-of-stars mirrors Hare's structural choice; the empty-name idiom
// from #50's TTAGGED-on-tparam would conflate two semantic axes
// (variants can be named; positionals never can).
type ttupleelem = struct {
export type ttupleelem = struct {
type_: *tinfo,
offset: u64,
tnext: *ttupleelem,
};
type tinfo = struct {
export type tinfo = struct {
kind: tykind,
size: u64,
align: u64,
@@ -10210,7 +10210,7 @@ type tinfo = struct {
// (rule-12): cnext chains WITHIN a bucket; lookup/bind hash then touch
// only one bucket -> O(1) amortized. Identical lookup results (same
// *tinfo for the same node), so emitted asm is byte-identical.
type tinfocacheent = struct {
export type tinfocacheent = struct {
key: *node,
val: *tinfo,
cnext: *tinfocacheent,
@@ -10224,7 +10224,7 @@ def NBUCKETS_TINFO: u64 = 8192u64;
// ---- tctx — the box of primitive types -------------------------------
type tctx = struct {
export type tctx = struct {
tyvoid: *tinfo,
tybool: *tinfo,
tyrune: *tinfo,

View File

@@ -6441,7 +6441,7 @@ import strconv;
// explicit and must stay in sync — the 990_selfhost test diffs
// astprint against the C side byte-for-byte. Tail-appended entries
// (TYPETEST onward) preserve every prior N_* value.
type nkind = enum i32 {
export type nkind = enum i32 {
N_NONE = 0,
N_INTLIT = 1,
@@ -6535,7 +6535,7 @@ type nkind = enum i32 {
// ---- Node -------------------------------------------------------------
type node = struct {
export type node = struct {
kind: nkind,
file: str,
line: i32,
@@ -7521,7 +7521,7 @@ fn hexval(c: rune) (i32 | void) = {
return;
};
type lex = struct {
export type lex = struct {
file: str,
src: *u8, // raw bytes; not necessarily NUL-terminated
srclen: u64,
@@ -8372,7 +8372,7 @@ package syntax;
import os;
import strings;
type parser = struct {
export type parser = struct {
l: *lex,
errs: i32,
// nocast: while inside `[...]` we treat ':' as the slice
@@ -9323,7 +9323,7 @@ fn parsestmt(p: *parser) *node = {
package syntax;
// Symbol kinds — must stay numerically aligned with cmd/wcc/ww.h Skind.
type skind = enum i32 {
export type skind = enum i32 {
SK_NONE = 0,
SK_VAR = 1,
SK_PARAM = 2,
@@ -9334,7 +9334,7 @@ type skind = enum i32 {
SK_FIELD = 7,
};
type sym = struct {
export type sym = struct {
name: str,
skind: skind,
type_: *tinfo,
@@ -9359,7 +9359,7 @@ type sym = struct {
def NBUCKETS: i32 = 16;
type scope = struct {
export type scope = struct {
parent: *scope,
first: *sym,
last: *sym,
@@ -9671,7 +9671,7 @@ import strings;
// explicit and must stay in sync — the 990_selfhost test diffs wwdump
// output against the C side, byte for byte.
type tkind = enum i32 {
export type tkind = enum i32 {
TK_NONE = 0,
TK_EOF = 1,
TK_ERR = 2,
@@ -9794,7 +9794,7 @@ type pos = struct {
col: i32,
};
type tok = struct {
export type tok = struct {
kind: tkind,
file: str, // path of the source the token came from
line: i32,
@@ -10074,7 +10074,7 @@ import os;
// Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values
// are explicit and must stay in sync — the selfhost selfcheck and
// typed-AST printers depend on matching numeric layout.
type tykind = enum i32 {
export type tykind = enum i32 {
TY_NONE = 0,
TY_VOID = 1,
TY_BOOL = 2,
@@ -10125,14 +10125,14 @@ def SIZE_UNDEFINED: u64 = 18446744073709551615;
// ---- tinfo / tfield / tparam -----------------------------------------
type tfield = struct {
export type tfield = struct {
name: str,
type_: *tinfo,
offset: u64,
tnext: *tfield,
};
type tparam = struct {
export type tparam = struct {
name: str,
type_: *tinfo,
// #61a: per-variant `!T` error mark for TY_TAGGED variants.
@@ -10154,13 +10154,13 @@ type tparam = struct {
// sea-of-stars mirrors Hare's structural choice; the empty-name idiom
// from #50's TTAGGED-on-tparam would conflate two semantic axes
// (variants can be named; positionals never can).
type ttupleelem = struct {
export type ttupleelem = struct {
type_: *tinfo,
offset: u64,
tnext: *ttupleelem,
};
type tinfo = struct {
export type tinfo = struct {
kind: tykind,
size: u64,
align: u64,
@@ -10210,7 +10210,7 @@ type tinfo = struct {
// (rule-12): cnext chains WITHIN a bucket; lookup/bind hash then touch
// only one bucket -> O(1) amortized. Identical lookup results (same
// *tinfo for the same node), so emitted asm is byte-identical.
type tinfocacheent = struct {
export type tinfocacheent = struct {
key: *node,
val: *tinfo,
cnext: *tinfocacheent,
@@ -10224,7 +10224,7 @@ def NBUCKETS_TINFO: u64 = 8192u64;
// ---- tctx — the box of primitive types -------------------------------
type tctx = struct {
export type tctx = struct {
tyvoid: *tinfo,
tybool: *tinfo,
tyrune: *tinfo,

View 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;
}