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:
13
Makefile
13
Makefile
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
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