From edd3f4ee83810b4e69df1d673a7844e998214293 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 12 Aug 2026 18:42:22 +0900 Subject: [PATCH] build: pass direct exports to package compilers --- Makefile | 4 +- cmd/w6c/main.c | 110 +++++++-- cmd/w6c/wwi.c | 4 +- cmd/ww/main.c | 117 ++++----- selfhost/cmd/w6c/main.ww | 115 +++++++-- selfhost/cmd/wcc/wwi.ww | 2 +- selfhost/cmd/ww/main.ww | 99 ++++---- test/byteid/wwi_test.ww | 62 +++-- test/package/package_test.ww | 446 ++++++++++++++++------------------- test/sep/importdir_test.ww | 21 +- test/sep/localbuild_test.ww | 154 ++++++------ test/sep/sepbuild_test.ww | 127 +--------- test/tool/wwdump_test.ww | 28 ++- 13 files changed, 678 insertions(+), 611 deletions(-) diff --git a/Makefile b/Makefile index cef7e55a..56832cb5 100644 --- a/Makefile +++ b/Makefile @@ -365,8 +365,8 @@ BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%) # Ww-native sep-driver/tool observers: single-file ww tests under # test/sep/ on the test/testenv helper package, porting the residual # 989_sep* driver-observer C carriers. These are compiler/driver -# gates (module trees, sepwork artifact layout, composed //ww:module -# units, link rejects), NOT byteid suites: they run under +# gates (module trees, sepwork artifact layout, owner-only units and direct +# export arguments, link rejects), NOT byteid suites: they run under # test-compiler beside the surviving residual carriers. SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww \ test/sep/seplink_test.ww test/sep/sepscratch_test.ww \ diff --git a/cmd/w6c/main.c b/cmd/w6c/main.c index be51e607..287aabf0 100644 --- a/cmd/w6c/main.c +++ b/cmd/w6c/main.c @@ -22,6 +22,41 @@ slurp(const char *path, char **outbuf, u64 *outlen) return 0; } +struct importin { + const char *path; + const char *file; + char *buf; + u64 len; +}; + +static Node * +parseinput(Arena *a, const char *file, char *buf, u64 len, + const char *mod, const char *testsupport, int *bad) +{ + Lex l; + Parser p; + lexinit(&l, a, file, buf, len); + parserinit(&p, a, &l); + p.testmodule = testsupport; + if (mod != NULL) { + p.pathmod = mod; + p.curmod = mod; + } + Node *f = parsefile(&p); + *bad = l.errs || p.errs; + return f; +} + +static void +appendnodes(Node **head, Node **tail, Node *list) +{ + if (list == NULL) return; + if (*head == NULL) *head = list; + else (*tail)->next = list; + while (list->next != NULL) list = list->next; + *tail = list; +} + int main(int argc, char **argv) { @@ -31,8 +66,14 @@ main(int argc, char **argv) const char *testsupport = NULL; int testmode = 0; int sepmode = 0; /* -c: #22 M3 separate-compile / primary- - * only codegen (emit imported==0 decls - * only; treat `.wwi` deps as external) */ + * only codegen (emit imported==0 decls + * only; treat `.wwi` deps as external) */ + struct importin *imports = calloc((size_t)argc, sizeof *imports); + if (imports == NULL) { + fputs("w6c: out of memory\n", stderr); + return 1; + } + int nimports = 0; for (int i = 1; i < argc; i++) { const char *a = argv[i]; if (strcmp(a, "-o") == 0 && i + 1 < argc) { @@ -49,6 +90,14 @@ main(int argc, char **argv) testsupport = argv[++i]; } else if (strcmp(a, "-c") == 0) { sepmode = 1; + } else if (strcmp(a, "--import") == 0) { + if (i + 2 >= argc) { + fputs("w6c: --import requires path and file\n", stderr); + return 2; + } + imports[nimports].path = argv[++i]; + imports[nimports].file = argv[++i]; + nimports++; } else if (a[0] == '-') { fprintf(stderr, "w6c: unknown flag %s\n", a); return 2; @@ -60,9 +109,24 @@ main(int argc, char **argv) } } if (src == NULL) { - fputs("usage: w6c [-T] [-c] [-I out.wwi] [-o out.s] file.ww\n", stderr); + fputs("usage: w6c [-T] [-c] [-I out.wwi] " + "[--import path dep.wwi]... [-o out.s] file.ww\n", stderr); return 2; } + if (nimports > 0 && !sepmode) { + fputs("w6c: --import requires -c\n", stderr); + return 2; + } + for (int i = 0; i < nimports; i++) { + if (imports[i].path[0] == '\0') { + fputs("w6c: --import path is empty\n", stderr); + return 2; + } + if (i > 0 && strcmp(imports[i-1].path, imports[i].path) >= 0) { + fputs("w6c: --import paths must be sorted and unique\n", stderr); + return 2; + } + } if (testsupport != NULL && (!sepmode || (strcmp(testsupport, "test") != 0 && strcmp(testsupport, "__wwtest") != 0))) { @@ -70,24 +134,38 @@ main(int argc, char **argv) return 2; } + Arena *a = newarena(); + Checker c; + Cg cg; + + Node *head = NULL, *tail = NULL; + for (int i = 0; i < nimports; i++) { + if (slurp(imports[i].file, &imports[i].buf, + &imports[i].len) < 0) { + fprintf(stderr, "w6c: import %s: cannot read %s\n", + imports[i].path, imports[i].file); + return 1; + } + int bad = 0; + Node *f = parseinput(a, imports[i].file, imports[i].buf, + imports[i].len, imports[i].path, testsupport, &bad); + if (bad) return 1; + appendnodes(&head, &tail, f->list); + } + char *buf; u64 len; if (slurp(src, &buf, &len) < 0) { fprintf(stderr, "w6c: %s: cannot read\n", src); return 1; } - - Arena *a = newarena(); - Lex l; - Parser p; - Checker c; - Cg cg; - - lexinit(&l, a, src, buf, len); - parserinit(&p, a, &l); - p.testmodule = testsupport; - Node *file = parsefile(&p); - if (l.errs || p.errs) return 1; + int bad = 0; + Node *file = parseinput(a, src, buf, len, NULL, testsupport, &bad); + if (bad) return 1; + if (head != NULL) { + tail->next = file->list; + file->list = head; + } check_init(&c, a); c.is_test = testmode; @@ -128,6 +206,8 @@ main(int argc, char **argv) if (of != stdout) fclose(of); freearena(a); + for (int i = 0; i < nimports; i++) free(imports[i].buf); + free(imports); free(buf); return 0; } diff --git a/cmd/w6c/wwi.c b/cmd/w6c/wwi.c index 3c14bbf0..d7c783bc 100644 --- a/cmd/w6c/wwi.c +++ b/cmd/w6c/wwi.c @@ -2,8 +2,8 @@ * wwi.c — `.wwi` export-data producer (w6c -I): a re-parseable ww-prototype * rendering of a package's EXPORTED surface. Since the sep-compile flip * (epic #22) this is the LIVE import path — the driver runs one `w6c -c -I` - * per package and feeds each dep's `.wwi` to its importers as import scope; - * the combined.ww amalgamator is gone. + * per package and feeds each dep's `.wwi` to its importers through a separate + * canonical `--import` input; the combined.ww amalgamator is gone. * * Specs: .ai/rob-M2-spec.md (producer) + .ai/drew-M2-checkexported.md * (check_exported_type). Two load-bearing choices follow them: diff --git a/cmd/ww/main.c b/cmd/ww/main.c index b932309b..2de89c42 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -495,21 +495,15 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package, * package's `.wwi` interface is materialized and every package is * compiled on its own (`w6c -c`), then the `.o` set is flat-linked. * - * Each w6c pass is BOTH consumer (reads dep `.wwi` as import scope) AND - * producer (writes this package's `.wwi` for its importers via -I), so - * a package's interface is materialized as a side effect of compiling - * it. Reverse-topo order guarantees a package's deps' `.wwi` exist - * before it compiles. + * Each w6c pass is BOTH consumer (reads each direct dep `.wwi` through a + * separate --import argument) AND producer (writes this package's `.wwi` + * for its importers via -I). Reverse-topo order guarantees a package's + * deps' `.wwi` exist before it compiles. * - * The load-bearing rule (ken #56, rob-resolved): every dep is tagged by - * its FULL DOTTED import path on prepend (`//ww:module `), so the - * definer's qualified symbol (#53) equals the consumer's qualified - * reference (#40) and the sep `.o`s link. A dep is NEVER bare-embedded. - * - * Only DIRECT dependency artifacts are prepended. A `.wwi` relocates the - * recursively reachable public foreign type/const facts required by its own - * API, retaining their origin modules without turning them into source - * imports. Full transitive reachability remains a linker concern. + * Only DIRECT dependency artifacts enter a compile action. The canonical + * import path paired with each `.wwi` preserves qualified symbol identity; + * a `.wwi` relocates the public foreign type/const facts required by its own + * API. Full transitive reachability remains a linker concern. */ #define SEP_MAXPKG 256 @@ -529,6 +523,7 @@ struct seppkg { int failed; /* discovery/compile failure reaches this action */ int test_support; /* compiler-generated -T support package */ int loaded; /* directory membership/name loaded exactly once */ + int export_changed; /* staged export differs from committed export */ int emit_context; /* first verified resolution context */ unsigned char context_state[SEP_MAXCONTEXT]; /* 0 new, 1 active, 2 checked */ struct ImportSet bindings; /* first context's canonical import bindings */ @@ -701,6 +696,7 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, p->failed = 0; p->test_support = 0; p->loaded = 0; + p->export_changed = 0; p->emit_context = -1; memset(p->context_state, 0, sizeof p->context_state); p->bindings.paths = NULL; @@ -1315,9 +1311,8 @@ sep_validate_module_closure(struct sepgraph *g, const int *order, int n, } /* Emit one of pi's own source files into the sep-unit under the - * //ww:module-reset primary boundary. Imports were already resolved to - * directory-package edges and their direct `.wwi` files were prepended; - * no source outside pi's sorted owned-source set may enter this unit. */ + * //ww:module-reset primary boundary. No source or export outside pi's + * sorted owned-source set may enter this unit. */ static int sep_emit_body(FILE *out, const char *path, const char *modpath) { @@ -1348,13 +1343,11 @@ sep_emit_body(FILE *out, const char *path, const char *modpath) return 0; } -/* Compose pi's sep-unit at `unitf`: its byte-sorted DIRECT dependency - * `.wwi`s, each tagged by its dotted path, then pi's own body under - * //ww:module-reset. Compiler exports are self-contained for public type - * facts; the linker separately retains the reachable archive closure. */ +/* Compose pi's sep-unit at `unitf` from only pi's byte-sorted sources. + * Direct exports are separate compiler inputs; the linker separately retains + * the reachable archive closure. */ static int -sep_compose_unit(struct sepgraph *g, int pi, const char *scratch, - const char *unitf) +sep_compose_unit(struct sepgraph *g, int pi, const char *unitf) { if (g->pkg[pi].emit_context < 0 || g->pkg[pi].emit_context >= g->ncontext) @@ -1364,28 +1357,6 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch, fprintf(stderr, "ww: cannot open %s\n", unitf); return -1; } - for (int k = 0; k < g->pkg[pi].ndeps; k++) { - int dj = g->pkg[pi].deps[k]; - char wwi[SEP_ARTIFACT_MAX]; - sep_fname(g, dj, scratch, ".wwi", wwi, sizeof wwi); - FILE *wf = fopen(wwi, "rb"); - if (wf == NULL) { - fprintf(stderr, "ww: missing %s\n", wwi); - fclose(u); - return -1; - } - int bad = fprintf(u, "//ww:module %s\n", g->pkg[dj].path) < 0; - int ch; - while (!bad && (ch = fgetc(wf)) != EOF) - if (fputc(ch, u) == EOF) bad = 1; - if (ferror(wf) || fputc('\n', u) == EOF) bad = 1; - if (fclose(wf) != 0) bad = 1; - if (bad) { - fprintf(stderr, "ww: cannot compose package unit %s\n", unitf); - fclose(u); - return -1; - } - } int bodyrc = 0; if (g->pkg[pi].is_dir) { for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++) @@ -1467,11 +1438,11 @@ archive_o(const char *objpath, const char *apath) /* -w workdir freshness: a `-w DIR` workdir is a caller-owned persistent * package-artifact tree that replaces the fresh `.sepwork` scratch. - * Staleness is pure content - * identity, never mtime: a package is reused only when its freshly - * composed unit byte-equals the committed unit AND the driver/tool copies - * recorded in the dir byte-equal the live executables — every decision is - * reproducible by hand with cmp(1) against plain files. Artifacts commit + * Staleness is pure content identity, never mtime: a package is reused only + * when its freshly composed unit byte-equals the committed unit, no direct + * dependency emitted a changed export, AND the driver/tool copies recorded in + * the dir byte-equal the live executables — every decision is reproducible by + * hand with cmp(1) against plain files. Artifacts commit * via temp + rename with the unit renamed last, so a killed build can * never leave a committed unit vouching for uncommitted artifacts. The * caller serializes invocations per workdir (Make target = one workdir) @@ -1565,7 +1536,7 @@ static void workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm) { snprintf(buf, bufsz, "ww workdir fmt %d mode %s asm %d\n", - is_test ? 6 : 5, is_test ? "test" : "build", emit_asm); + is_test ? 7 : 6, is_test ? "test" : "build", emit_asm); } /* A stale global builder identity invalidates every committed unit voucher in @@ -1601,8 +1572,8 @@ invalidate_workdir_units(const char *scratch) /* build_sep_plan — discover dependencies for every requested product in one * package universe, compile the dependency-first union once, then link each - * root from its own complete reachable archive closure. The transitive - * producer loop (one `w6c -c -I` per package, dep-first, + * root from its own complete reachable archive closure. The dependency-first + * producer loop (one `w6c -c -I` per package, * each DEP `.o` wrapped in its own deterministic `.a`), then a * reverse-topo `w6l` of each root `.o` + dep `.a` set + libwwrt.a. Side * files land in a cold `.sepwork` dir, or under the persistent @@ -1913,12 +1884,17 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *ca = warm ? anew : apath; int needs_export = !g->pkg[pi].root || root_package; int needs_archive = !g->pkg[pi].root || root_package; - if (sep_compose_unit(g, pi, scratch, cu) < 0) { + if (sep_compose_unit(g, pi, cu) < 0) { g->pkg[pi].failed = 1; any_failed = 1; continue; } - if (warm && !stale_all && file_equal(unitnew, unitf) + int deps_changed = 0; + for (int k = 0; k < g->pkg[pi].ndeps; k++) + if (g->pkg[g->pkg[pi].deps[k]].export_changed) + deps_changed = 1; + if (warm && !stale_all && !deps_changed + && file_equal(unitnew, unitf) && file_is_reg(asmf) && (!needs_export || file_is_reg(wwi)) && (emit_asm || (file_size_nonzero(obj) @@ -1938,7 +1914,20 @@ build_one_sep_impl(const char *src, int entry_is_dir, * LOCAL type (the root is never imported), which the * export-check rejects. Skip -I for the root; its `.wwi` * is never consumed. */ - char *cargv[12]; + size_t cargvcap = (size_t)(12 + 3 * g->pkg[pi].ndeps); + char **cargv = calloc(cargvcap, sizeof *cargv); + char (*importfiles)[SEP_ARTIFACT_MAX] = NULL; + if (g->pkg[pi].ndeps > 0) + importfiles = calloc((size_t)g->pkg[pi].ndeps, + sizeof *importfiles); + if (cargv == NULL || (g->pkg[pi].ndeps > 0 + && importfiles == NULL)) { + fprintf(stderr, "ww: out of memory\n"); + free(importfiles); + free(cargv); + free(order); + return 1; + } int cpos = 0; cargv[cpos++] = "w6c"; if (!needs_export && is_test && g->pkg[pi].root) { @@ -1953,6 +1942,14 @@ build_one_sep_impl(const char *src, int entry_is_dir, cargv[cpos++] = (char *)test_support_module; } cargv[cpos++] = "-c"; + for (int k = 0; k < g->pkg[pi].ndeps; k++) { + int dj = g->pkg[pi].deps[k]; + sep_fname(g, dj, scratch, ".wwi", importfiles[k], + sizeof importfiles[k]); + cargv[cpos++] = "--import"; + cargv[cpos++] = g->pkg[dj].path; + cargv[cpos++] = importfiles[k]; + } if (needs_export) { cargv[cpos++] = "-I"; cargv[cpos++] = (char *)cw; @@ -1961,13 +1958,19 @@ build_one_sep_impl(const char *src, int entry_is_dir, cargv[cpos++] = (char *)cs; cargv[cpos++] = (char *)cu; cargv[cpos] = NULL; - if (run_argv(c6, cargv) != 0) { + int compilerc = run_argv(c6, cargv); + free(importfiles); + free(cargv); + if (compilerc != 0) { fprintf(stderr, "ww: w6c failed for %s\n", g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)"); g->pkg[pi].failed = 1; any_failed = 1; continue; } + if (needs_export) + g->pkg[pi].export_changed = !warm + || !file_equal(wwinew, wwi); if (!emit_asm) { char *aargv[] = {"w6a", "-o", (char *)co, (char *)cs, NULL}; diff --git a/selfhost/cmd/w6c/main.ww b/selfhost/cmd/w6c/main.ww index 840949bf..7167b382 100644 --- a/selfhost/cmd/w6c/main.ww +++ b/selfhost/cmd/w6c/main.ww @@ -69,6 +69,11 @@ export fn main(argc: i32, argv: **u8) i32 = { let sepmode: i32 = 0i32; // -c: #22 M3 separate-compile / primary- // only codegen (emit imported==0 decls // only; treat `.wwi` deps as external) + let importpaths: []*u8 = alloc([], argc: u64)!; + importpaths.len = argc; + let importfiles: []*u8 = alloc([], argc: u64)!; + importfiles.len = argc; + let nimports: i32 = 0; let i: i32 = 1; for (i < argc) { @@ -101,6 +106,16 @@ export fn main(argc: i32, argv: **u8) i32 = { testsupport = argv[i]; } else { if (cstreq(a, "-c")) { sepmode = 1i32; + } else { if (cstreq(a, "--import")) { + if (i + 2 >= argc) { + let m: str = "w6c: --import requires path and file\n"; + os.write(2, m.ptr, m.len: u64); + return 2; + }; + importpaths[nimports] = argv[i + 1]; + importfiles[nimports] = argv[i + 2]; + nimports += 1; + i += 2; } else { if (a[0u64] == 45u8) { let m: str = "w6c: unknown flag\n"; os.write(2, m.ptr, m.len: u64); @@ -112,15 +127,35 @@ export fn main(argc: i32, argv: **u8) i32 = { return 2; }; src = a; - }; }; }; }; }; }; + }; }; }; }; }; }; }; i += 1; }; if (src == nil) { - let m: str = "usage: w6c_ww [-T] [-o out.s] file.ww\n"; + let m: str = "usage: w6c_ww [-T] [-c] [-I out.wwi] [--import path dep.wwi]... [-o out.s] file.ww\n"; os.write(2, m.ptr, m.len: u64); return 2; }; + if (nimports > 0 && sepmode == 0) { + let m: str = "w6c: --import requires -c\n"; + os.write(2, m.ptr, m.len: u64); + return 2; + }; + let importi: i32 = 0; + for (importi < nimports) { + if (importpaths[importi][0u64] == 0u8) { + let m: str = "w6c: --import path is empty\n"; + os.write(2, m.ptr, m.len: u64); + return 2; + }; + if (importi > 0 && strings.compare(pathstr(importpaths[importi - 1]), + pathstr(importpaths[importi])) >= 0) { + let m: str = "w6c: --import paths must be sorted and unique\n"; + os.write(2, m.ptr, m.len: u64); + return 2; + }; + importi += 1; + }; if (testsupport != nil && (sepmode == 0 || (!cstreq(testsupport, "test") && !cstreq(testsupport, "__wwtest")))) { @@ -129,6 +164,46 @@ export fn main(argc: i32, argv: **u8) i32 = { return 2; }; + let importhead: *node = nil; + let importtail: *node = nil; + importi = 0; + for (importi < nimports) { + let ibuf: *u8; + let ilen: u64; + ibuf, ilen = slurp(importfiles[importi]); + if (ibuf == nil) { + let pre: str = "w6c: import "; + let mid: str = ": cannot read "; + let nl: str = "\n"; + let ipath: str = pathstr(importpaths[importi]); + let ifile: str = pathstr(importfiles[importi]); + os.write(2, pre.ptr, pre.len: u64); + os.write(2, ipath.ptr, ipath.len: u64); + os.write(2, mid.ptr, mid.len: u64); + os.write(2, ifile.ptr, ifile.len: u64); + os.write(2, nl.ptr, nl.len: u64); + return 1; + }; + let il: lex; + lexinit(&il, strings.dup(pathstr(importfiles[importi])), ibuf, ilen); + let ips: parser; + parserinit(&ips, &il); + let imod: str = pathstr(importpaths[importi]); + ips.pathmod = imod; + ips.curmod = imod; + if (testsupport != nil) { ips.testmodule = pathstr(testsupport); }; + let imported: *node = parsefile(&ips); + if (il.errs > 0 || ips.errs > 0) { return 1; }; + let d: *node = imported.list; + if (d != nil) { + if (importhead == nil) { importhead = d; } + else { importtail.next = d; }; + for (d.next != nil) { d = d.next; }; + importtail = d; + }; + importi += 1; + }; + let buf: *u8; let blen: u64; buf, blen = slurp(src); @@ -137,10 +212,22 @@ export fn main(argc: i32, argv: **u8) i32 = { os.write(2, m.ptr, m.len: u64); return 1; }; + let l: lex; + lexinit(&l, strings.dup(pathstr(src)), buf, blen); + let ps: parser; + parserinit(&ps, &l); + if (testsupport != nil) { ps.testmodule = pathstr(testsupport); }; + let f: *node = parsefile(&ps); + // Gate cgen on parse-stage errors. Mirrors cmd/w6c/main.c's + // `if (l.errs || p.errs) return 1;` — broken AST otherwise reaches + // cgen and emits junk asm with a zero exit (silent miscompile). + if (l.errs > 0 || ps.errs > 0) { return 1; }; + if (importhead != nil) { + importtail.next = f.list; + f.list = importhead; + }; - // Redirect fd 1 to the output file before any cgen emit runs. - // cgen.ww writes directly to fd 1; dup2 lets us reuse it without - // threading a file descriptor through the emit helpers. + // cgen writes directly to fd 1; dup2 keeps that implementation private. if (out != nil) { let ofd: i32 = os.open(pathstr(out), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 @@ -158,24 +245,6 @@ export fn main(argc: i32, argv: **u8) i32 = { os.close(ofd); }; - let nlen: u64 = cstrlen(src); - let view: str; - view.ptr = src; - view.len = nlen: i32; - let fname: str = strings.dup(view); - - let l: lex; - lexinit(&l, fname, buf, blen); - - let ps: parser; - parserinit(&ps, &l); - if (testsupport != nil) { ps.testmodule = pathstr(testsupport); }; - let f: *node = parsefile(&ps); - // Gate cgen on parse-stage errors. Mirrors cmd/w6c/main.c's - // `if (l.errs || p.errs) return 1;` — broken AST otherwise reaches - // cgen and emits junk asm with a zero exit (silent miscompile). - if (l.errs > 0 || ps.errs > 0) { return 1; }; - let testmodule: str; if (testsupport != nil) { testmodule = pathstr(testsupport); }; let interfaceout: str; diff --git a/selfhost/cmd/wcc/wwi.ww b/selfhost/cmd/wcc/wwi.ww index 91609ae0..e8272a80 100644 --- a/selfhost/cmd/wcc/wwi.ww +++ b/selfhost/cmd/wcc/wwi.ww @@ -2,7 +2,7 @@ // ww-prototype rendering of a package's EXPORTED surface. Since the // sep-compile flip (epic #22) this is the LIVE import path — the driver // runs one `w6c -c -I` per package and feeds each dep's `.wwi` to its -// importers as import scope. +// importers through a separate canonical `--import` input. // // wwstage twin of cmd/w6c/wwi.c — byte-identical output is a rule-10 // requirement (`.wwi` is a cross-stage byte-id substrate). Specs: diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 86ab5857..a1b6e5b5 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -741,16 +741,12 @@ type lflags = struct { // every package on its own (`w6c -c`), then flat-links the `.o` set. // Separate compilation is the SOLE build path (E3-C1 flip, task #87). // -// Each w6c pass is BOTH consumer (reads dep `.wwi` as import scope) AND -// producer (writes this package's `.wwi` via -I). Reverse-topo order -// guarantees a package's deps' `.wwi` exist before it compiles. -// -// Every direct dep is tagged by its FULL DOTTED import path on prepend -// (`//ww:module `), so the definer's qualified symbol equals the -// consumer's qualified reference and the sep `.o`s link. A dependency's -// compiler-owned `.wwi` carries its reachable public foreign type facts; -// separately prepending transitive interfaces is neither necessary nor -// allowed. The unit composition is byte-identical to the cstage driver. +// Each w6c pass is BOTH consumer (reads each direct dep `.wwi` through a +// separate --import argument) AND producer (writes this package's `.wwi` +// via -I). Reverse-topo order guarantees a package's deps' `.wwi` exist. +// The canonical import path paired with each self-contained export preserves +// qualified symbol identity; transitive exports remain outside the compile +// action. Unit composition is byte-identical to the cstage driver. def SEP_MAXPKG: i32 = 256; @@ -775,6 +771,7 @@ type seppkg = struct { failed: bool, testsupport: bool, loaded: bool, + exportchanged: bool, emitcontext: i32, contextstate: []u8, bindings: []sepbind, @@ -929,6 +926,7 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, g.pkg[g.n].failed = false; g.pkg[g.n].testsupport = false; g.pkg[g.n].loaded = false; + g.pkg[g.n].exportchanged = false; g.pkg[g.n].emitcontext = -1; let cslot: []u8 = alloc([], SEP_MAXCONTEXT: u64)!; cslot.len = SEP_MAXCONTEXT; @@ -1546,8 +1544,8 @@ fn sepvalidatemoduleclosure(g: *sepgraph, order: []i32, n: i32, return 0; }; -// Imported source never enters this body: its direct export data was -// prepended above, while this package owns exactly its sorted source set. +// No imported source or export enters this body: the package owns exactly its +// sorted source set. fn sepwriteall(fd: i32, buf: *u8, n: u64) bool = { match (os.writeall(fd, buf, n)) { case let wrote: i64 => return wrote == n: i64; @@ -1589,12 +1587,9 @@ fn sepemitbody(fd: i32, path: *u8, modpath: *u8) i32 = { return 0; }; -// Compose pi's sep-unit at `unitf`: its byte-sorted DIRECT dependency -// `.wwi`s, each tagged by its dotted path, then pi's own body under -// //ww:module-reset. Compiler exports are self-contained for public type -// facts; the linker separately retains the reachable archive closure. -fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, - unitf: *u8) i32 = { +// Compose pi's sep-unit from only pi's byte-sorted sources. Direct exports are +// separate compiler inputs; the linker retains the reachable archive closure. +fn sepcomposeunit(g: *sepgraph, pi: i32, unitf: *u8) i32 = { if (g.pkg[pi].emitcontext < 0 || g.pkg[pi].emitcontext >= g.ncontext) { return -1; }; let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644 @@ -1602,31 +1597,6 @@ fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, cerr("ww: cannot open unit\n"); return -1; }; - let k: i32 = 0; - for (k < g.pkg[pi].ndeps) { - let dj: i32 = g.pkg[pi].deps[k]; - let wwi: *u8 = sepfname(g, dj, scratch, ".wwi"); - let wb: *u8; - let wn: u64; - wb, wn = slurp(wwi); - if (wb == nil) { - cerr("ww: missing wwi\n"); - os.close(u); - return -1; - }; - let dm: str = "//ww:module "; - if (!sepwriteall(u, dm.ptr, dm.len: u64) - || !sepwriteall(u, g.pkg[dj].path, - cstrlen(g.pkg[dj].path)) - || !sepwriteall(u, "\n".ptr, 1u64) - || !sepwriteall(u, wb, wn) - || !sepwriteall(u, "\n".ptr, 1u64)) { - cerr("ww: cannot compose package unit\n"); - os.close(u); - return -1; - }; - k += 1; - }; let bodyrc: i32 = 0; if (g.pkg[pi].isdir != 0) { let i: i32 = 0; @@ -1720,7 +1690,7 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = { }; // buildonesep — discover deps, reverse-topo, -// the transitive producer loop (one `w6c -c -I` per package, dep-first, +// the dependency-first producer loop (one `w6c -c -I` per package, // each dependency `.o` wrapped in its own deterministic per-package `.a`), then a // reverse-topo `w6l` of the root `.o` + dependency `.a` set + libwwrt.a. // Side files land in a cold `.sepwork` scratch dir. Twin of cstage @@ -1728,9 +1698,10 @@ fn archiveo(objpath: *u8, apath: *u8) i32 = { // A `-w DIR` workdir is a caller-owned persistent package-artifact tree // that replaces the fresh `.sepwork` scratch. Staleness is pure content -// identity, never mtime: a package is reused only when its freshly -// composed unit byte-equals the committed unit AND the driver/tool copies -// recorded in the dir byte-equal the live executables — every decision is +// identity, never mtime: a package is reused only when its freshly composed +// unit byte-equals the committed unit, no direct dependency emitted a changed +// export, AND the driver/tool copies recorded in the dir byte-equal the live +// executables — every decision is // reproducible by hand with cmp(1) against plain files. Artifacts commit // via temp + rename with the unit renamed last, so a killed build can // never leave a committed unit vouching for uncommitted artifacts. The @@ -1840,14 +1811,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = { fn workdirstamptext(istest: i32, emitasm: i32) str = { if (istest != 0) { if (emitasm != 0) { - return "ww workdir fmt 6 mode test asm 1\n"; + return "ww workdir fmt 7 mode test asm 1\n"; }; - return "ww workdir fmt 6 mode test asm 0\n"; + return "ww workdir fmt 7 mode test asm 0\n"; }; if (emitasm != 0) { - return "ww workdir fmt 5 mode build asm 1\n"; + return "ww workdir fmt 6 mode build asm 1\n"; }; - return "ww workdir fmt 5 mode build asm 0\n"; + return "ww workdir fmt 6 mode build asm 0\n"; }; fn stampmatches(path: *u8, want: str) bool = { @@ -2338,15 +2309,23 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; let needsexport: bool = !g.pkg[pi].root || rootpackage; let needsarchive: bool = !g.pkg[pi].root || rootpackage; - if (sepcomposeunit(g, pi, scratch, cu) < 0) { + if (sepcomposeunit(g, pi, cu) < 0) { g.pkg[pi].failed = true; anyfailed = true; oi += 1; continue; }; + let depschanged: bool = false; + let changedk: i32 = 0; + for (changedk < g.pkg[pi].ndeps) { + if (g.pkg[g.pkg[pi].deps[changedk]].exportchanged) { + depschanged = true; + }; + changedk += 1; + }; let fresh: bool = false; if (warm) { - if (!staleall) { + if (!staleall && !depschanged) { fresh = fileequal(unitnew, unitf); if (fresh) { fresh = fileisreg(asmf); }; if (fresh) { @@ -2390,6 +2369,7 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, let alen: u64 = 8u64; if (!needsexport) { alen = 6u64; if (roott) { alen = 9u64; }; }; if (supportt) { alen += 2u64; }; + alen += (g.pkg[pi].ndeps: u64) * 3u64; let argv: []str = alloc([], alen)!; append(argv, "w6c"); if (roott) { @@ -2402,6 +2382,14 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, append(argv, testsupportmodule); }; append(argv, "-c"); + let importk: i32 = 0; + for (importk < g.pkg[pi].ndeps) { + let dj: i32 = g.pkg[pi].deps[importk]; + append(argv, "--import"); + append(argv, pathstr(g.pkg[dj].path)); + append(argv, pathstr(sepfname(g, dj, scratch, ".wwi"))); + importk += 1; + }; if (needsexport) { append(argv, "-I"); append(argv, pathstr(cw)); @@ -2430,6 +2418,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, continue; }; }; + if (needsexport) { + if (!warm || !fileequal(wwinew, wwi)) { + g.pkg[pi].exportchanged = true; + }; + }; if (emitasm == 0) { let argv: []str = alloc([], 4u64)!; append(argv, "w6a"); diff --git a/test/byteid/wwi_test.ww b/test/byteid/wwi_test.ww index e3080472..c674f620 100644 --- a/test/byteid/wwi_test.ww +++ b/test/byteid/wwi_test.ww @@ -8,7 +8,8 @@ package wwi_test; // 10): the `w6c -I ` producer IS the live import path, and // `.wwi` is a cross-stage byte-id substrate pinned directly. POSITIVE: // for ascii/strings/getopt (drew2-audited leak-free), drive the target -// as the PRIMARY module of a driver-combined unit, then (1) w6c -c -I and +// as the PRIMARY owner-only driver unit with separate direct exports, then +// (1) w6c -c -I and // w6c_ww -c -I both succeed, (2) cs.wwi == ww.wwi byte-for-byte, (3) the // emitted .wwi re-parses (wwdump -a exit 0). getopt is the recursion // stressor. A synth fixture covers the decl-kinds + type-nodes no lib @@ -28,10 +29,10 @@ package wwi_test; // producer->importer flow on BOTH stages: (a) the dep `.wwi` package // line equals the real leaf, (b) the dep `.wwi` is byte-identical // across stages, (c) a root importing the dep RESOLVES on both stages -// and the two importer `.s` are byte-identical. The composed units are -// fed straight to w6c / w6c_ww (not `ww build`): the producer-unit -// shape `//ww:module-reset ` + body is exactly what the driver's -// sep_emit_body emits. +// and the two importer `.s` are byte-identical. The owner units and +// separate exports are fed straight to w6c / w6c_ww (not `ww build`): +// the producer-unit shape `//ww:module-reset ` + body is exactly +// what the driver's sep_emit_body emits. import os; import os.exec; @@ -88,18 +89,40 @@ fn m2positive(pkg: str) void = { let co: testenv.commandout; testenv.runcommand(td, td, "drv", av, lifetime(), &co); - let comb: str = strings.concat(td, "/", pkg, - ".sepwork/__root.unit.ww"); + let work: str = strings.concat(td, "/", pkg, ".sepwork/"); + let comb: str = strings.concat(work, "__root.unit.ww"); if (!testenv.exists(comb)) { fail(pkg, "no resolved unit"); }; let cs: str = strings.concat(td, "/cs.wwi"); let ws: str = strings.concat(td, "/ww.wwi"); - // The driver-composed unit uses package separators and may contain the - // same compiler-owned origin fact through multiple direct interfaces; - // exact fact interning is intentionally tied to -c package mode. - let cav: []str = [testenv.driver("w6c"), "-c", "-I", cs, comb]; + let deps: []str = []; + if (testenv.same(pkg, "ascii")) { append(deps, "strings"); }; + if (testenv.same(pkg, "strings")) { + append(deps, "bytes"); append(deps, "encoding.utf8"); + append(deps, "os"); append(deps, "types"); + }; + if (testenv.same(pkg, "getopt")) { + append(deps, "encoding.utf8"); append(deps, "fmt"); + append(deps, "io"); append(deps, "os"); append(deps, "strings"); + }; + if (deps.len == 0) { abort("unknown m2 package"); }; + let cav: []str = [testenv.driver("w6c"), "-c"]; + let di: i32 = 0; + for (di < deps.len) { + append(cav, "--import"); append(cav, deps[di]); + append(cav, strings.concat(work, deps[di], ".wwi")); + di += 1; + }; + append(cav, "-I"); append(cav, cs); append(cav, comb); if (!runok(td, "w6c", cav)) { fail(pkg, "w6c -c -I rejected"); }; - let wav: []str = [testenv.driver("w6c_ww"), "-c", "-I", ws, comb]; + let wav: []str = [testenv.driver("w6c_ww"), "-c"]; + di = 0; + for (di < deps.len) { + append(wav, "--import"); append(wav, deps[di]); + append(wav, strings.concat(work, deps[di], ".wwi")); + di += 1; + }; + append(wav, "-I"); append(wav, ws); append(wav, comb); if (!runok(td, "w6c_ww", wav)) { fail(pkg, "w6c_ww -c -I rejected"); }; if (!testenv.same(testenv.readfile(cs), testenv.readfile(ws))) { @@ -318,22 +341,21 @@ fn leafrow(tag: str, path: str, leaf: str, body: str, want: str) void = { fail(tag, "w6c vs w6c_ww .wwi differ (rule 10)"); }; - // Leg c — a root importing the dep RESOLVES on both stages - // (pre-fix: "package main does not match import path" REJECT). The - // dep `.wwi` is prepended under //ww:module , exactly as the - // driver composes an importer unit. + // Leg c — a root importing the dep RESOLVES from a separate export on + // both stages (pre-fix: "package main does not match import path" + // REJECT). testenv.writefile(root, strings.concat( - "//ww:module ", path, "\n", - testenv.readfile(cswwi), "\n", "//ww:module-reset\n", "package main;\n", "import ", path, ";\n", "export fn main() i32 = { return 42; };\n")); - let rcav: []str = [testenv.driver("w6c"), "-c", "-o", rcss, root]; + let rcav: []str = [testenv.driver("w6c"), "-c", "--import", path, + cswwi, "-o", rcss, root]; if (!runok(td, "csroot", rcav)) { fail(tag, "w6c rejected the import (BUG-C)"); }; - let rwav: []str = [testenv.driver("w6c_ww"), "-c", "-o", rwws, root]; + let rwav: []str = [testenv.driver("w6c_ww"), "-c", "--import", path, + wwwwi, "-o", rwws, root]; if (!runok(td, "wsroot", rwav)) { fail(tag, "w6c_ww rejected the import (BUG-C)"); }; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 4b107aaa..325c44dd 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -726,8 +726,9 @@ fn workescape(s: str) str = { let prodwork: str = strings.concat(prodout, ".sepwork/"); let produnit: str = readfile(strings.concat(prodwork, "pkg.unit.ww")); - assert(has(produnit, "//ww:module api\n")); - assert(!has(produnit, "//ww:module implementation\n")); + assert(has(produnit, "//ww:module-reset pkg\n")); + assert(!has(produnit, "//ww:module ")); + assert(!has(produnit, "IMPLEMENTATION_SOURCE")); assert(!has(produnit, "__same")); assert(!has(produnit, "__external")); assert(!os.exists(strings.concat(prodwork, "__same.unit.ww"))); @@ -807,30 +808,20 @@ fn workescape(s: str) str = { let externalproduction: str = readfile(strings.concat(sharedwork, "__ww-test-001-external-production.unit.ww")); assert(!os.exists(strings.concat(externalbin, ".sepwork"))); - assert(has(sameunit, "//ww:module api\n")); - assert(has(sameunit, "//ww:module __same\n")); - assert(has(sameunit, "//ww:module test\n")); - assert(!has(sameunit, "//ww:module implementation\n")); - assert(!has(sameunit, "//ww:module __external\n")); + assert(!has(sameunit, "//ww:module ")); assert(os.exists(strings.concat(sharedwork, "__external.unit.ww"))); assert(has(sameunit, "PACKAGE_PRODUCTION_A")); assert(has(sameunit, "PACKAGE_PRODUCTION_Z")); assert(has(sameunit, "SAME_TEST_SOURCE")); assert(!has(sameunit, "EXTERNAL_TEST_SOURCE")); - assert(has(externalunit, "//ww:module pkg\n")); - assert(has(externalunit, "//ww:module __external\n")); - assert(has(externalunit, "//ww:module test\n")); - assert(!has(externalunit, "//ww:module api\n")); - assert(!has(externalunit, "//ww:module implementation\n")); - assert(!has(externalunit, "//ww:module __same\n")); + assert(!has(externalunit, "//ww:module ")); assert(has(externalunit, "EXTERNAL_TEST_SOURCE")); assert(!has(externalunit, "PACKAGE_PRODUCTION_A")); assert(!has(externalunit, "PACKAGE_PRODUCTION_Z")); assert(has(externalproduction, "PACKAGE_PRODUCTION_A")); assert(has(externalproduction, "PACKAGE_PRODUCTION_Z")); - assert(has(externalproduction, "//ww:module api\n")); - assert(!has(externalproduction, "//ww:module implementation\n")); + assert(!has(externalproduction, "//ww:module ")); assert(!has(externalproduction, "SAME_TEST_SOURCE")); assert(!has(externalproduction, "EXTERNAL_TEST_SOURCE")); assert(!has(readfile(strings.concat(sharedwork, "api.unit.ww")), @@ -872,6 +863,30 @@ fn workescape(s: str) str = { "__ww-test-001-external.unit.ww") == 1); assert(occurrences(ctrace, "-T --test-support-module") == 2); + let samecompile: str = linecontaining(ctrace, + "__ww-test-000-same.unit.ww"); + let externalcompile: str = linecontaining(ctrace, + "__ww-test-001-external.unit.ww"); + let productioncompile: str = linecontaining(ctrace, + "__ww-test-001-external-production.unit.ww"); + assert(same(samecompile, strings.concat( + "-T --test-support-module test -c --import __same ", + sharedwork, "__same.wwi --import api ", sharedwork, + "api.wwi --import test ", sharedwork, "test.wwi -o ", + sharedwork, "__ww-test-000-same.s ", sharedwork, + "__ww-test-000-same.unit.ww"))); + assert(same(externalcompile, strings.concat( + "-T --test-support-module test -c --import __external ", + sharedwork, "__external.wwi --import pkg ", sharedwork, + "__ww-test-001-external-production.wwi --import test ", + sharedwork, "test.wwi -o ", sharedwork, + "__ww-test-001-external.s ", sharedwork, + "__ww-test-001-external.unit.ww"))); + assert(same(productioncompile, strings.concat( + "-c --import api ", sharedwork, "api.wwi -I ", sharedwork, + "__ww-test-001-external-production.wwi -o ", sharedwork, + "__ww-test-001-external-production.s ", sharedwork, + "__ww-test-001-external-production.unit.ww"))); let ltrace: str = readfile(linkertrace); assert(occurrences(ltrace, "\n") == 2); let samelink: str = linecontaining(ltrace, @@ -1204,43 +1219,33 @@ fn workescape(s: str) str = { alphaartifact, ".unit.ww")); let betaunit: str = readfile(strings.concat(sharedwork, betaartifact, ".unit.ww")); - assert(has(commonunit, "//ww:module leaf\n")); - assert(!has(commonunit, "//ww:module test\n")); - assert(has(alphaunit, "//ww:module common\n")); - assert(!has(alphaunit, "//ww:module leaf\n")); + assert(has(commonunit, "//ww:module-reset common\n")); + assert(!has(commonunit, "//ww:module ")); + assert(!has(alphaunit, "//ww:module ")); assert(has(alphaunit, "MULTIDIR_ALPHA_A")); assert(has(alphaunit, "MULTIDIR_ALPHA_Z")); - assert(has(betaunit, "//ww:module common\n")); - assert(has(betaunit, "//ww:module alpha\n")); - assert(!has(betaunit, "//ww:module leaf\n")); + assert(!has(betaunit, "//ww:module ")); + assert(has(betaunit, "MULTIDIR_BETA")); assert(!has(commonunit, "DEPENDENCY_TEST_FILE_MUST_NOT_COMPILE")); let roots: []str = [readfile(strings.concat(sharedwork, rootkeys[0], ".unit.ww")), readfile(strings.concat(sharedwork, rootkeys[1], ".unit.ww")), readfile(strings.concat(sharedwork, rootkeys[2], ".unit.ww")), readfile(strings.concat(sharedwork, rootkeys[3], ".unit.ww"))]; - assert(has(roots[0], "//ww:module _alpha_same\n")); - assert(has(roots[0], "//ww:module common\n")); - assert(has(roots[0], "//ww:module test\n")); - assert(!has(roots[0], "//ww:module leaf\n")); - assert(!has(roots[0], "//ww:module alpha\n")); + assert(!has(roots[0], "//ww:module ")); assert(has(roots[0], "MULTIDIR_ALPHA_A")); assert(has(roots[0], "MULTIDIR_ALPHA_Z")); - assert(has(roots[1], "//ww:module alpha\n")); - assert(has(roots[1], "//ww:module _alpha_external\n")); - assert(has(roots[1], "//ww:module test\n")); - assert(!has(roots[1], "//ww:module common\n")); - assert(!has(roots[1], "//ww:module _alpha_same\n")); - assert(has(roots[2], "//ww:module _beta_same\n")); - assert(has(roots[2], "//ww:module common\n")); - assert(has(roots[2], "//ww:module alpha\n")); - assert(has(roots[2], "//ww:module test\n")); - assert(!has(roots[2], "//ww:module leaf\n")); - assert(!has(roots[2], "//ww:module beta\n")); - assert(has(roots[3], "//ww:module beta\n")); - assert(has(roots[3], "//ww:module _beta_external\n")); - assert(has(roots[3], "//ww:module test\n")); - assert(!has(roots[3], "//ww:module common\n")); + assert(has(roots[0], "MULTIDIR_ALPHA_SAME")); + assert(!has(roots[0], "MULTIDIR_ALPHA_EXTERNAL")); + assert(!has(roots[1], "//ww:module ")); + assert(has(roots[1], "MULTIDIR_ALPHA_EXTERNAL")); + assert(!has(roots[1], "MULTIDIR_ALPHA_A")); + assert(!has(roots[2], "//ww:module ")); + assert(has(roots[2], "MULTIDIR_BETA")); + assert(has(roots[2], "MULTIDIR_BETA_SAME")); + assert(!has(roots[3], "//ww:module ")); + assert(has(roots[3], "MULTIDIR_BETA_EXTERNAL")); + assert(!has(roots[3], "MULTIDIR_BETA_SAME")); let products: []str = ["leaf", "common", alphaartifact, betaartifact, "_alpha_same", "_alpha_external", "_beta_same", "_beta_external", "test"]; @@ -1625,13 +1630,7 @@ fn workescape(s: str) str = { let ri: i32 = 0; for (ri < keys.len) { let unit: str = readfile(strings.concat(work, keys[ri], ".unit.ww")); - assert(has(unit, "//ww:module __wwtest\n")); - if (ri == 1 || ri == 2) { - assert(has(unit, "//ww:module test\n")); - }; - if (ri == 3) { - assert(has(unit, "//ww:module consumer\n")); - }; + assert(!has(unit, "//ww:module ")); let runav: []str = [bins[ri]]; runcommand(root, strings.concat("mixed-run-", stages[si], "-", keys[ri]), runav, @@ -1748,8 +1747,8 @@ fn workescape(s: str) str = { let cbin: str = readfile(bin); let rootunit: str = readfile(strings.concat(work, "__ww-test-000-same.unit.ww")); - assert(has(rootunit, "//ww:module p00\n")); - assert(!has(rootunit, "//ww:module p01\n")); + assert(has(rootunit, "//ww:module-reset\npackage target;")); + assert(!has(rootunit, "//ww:module ")); let linkargs: str = readfile(trace); assert(linkargs.len > 8192); assert(!has(linkargs, ".wwi\n")); @@ -2075,8 +2074,7 @@ fn workescape(s: str) str = { "__ww-test-000-external-production.unit.ww")); let supportunit: str = readfile(strings.concat(work, "__wwtest.unit.ww")); - assert(has(rootunit, "//ww:module test\n")); - assert(has(rootunit, "//ww:module __wwtest\n")); + assert(!has(rootunit, "//ww:module ")); assert(has(rootunit, "USER_TEST_EXTERNAL")); assert(!has(rootunit, "USER_TEST_PACKAGE_A")); assert(!has(rootunit, "USER_TEST_PACKAGE_Z")); @@ -2115,121 +2113,52 @@ fn workescape(s: str) str = { clean(root); }; -@test fn diamond_test_dependency_compiles_once() void = { - let root: str = fresh(); - let shared: str = strings.concat(root, "/shared"); - let left: str = strings.concat(root, "/left"); - let right: str = strings.concat(root, "/right"); - let target: str = strings.concat(root, "/diamond"); - assert(os.mkdir(shared, 448i32) == 0); - assert(os.mkdir(left, 448i32) == 0); - assert(os.mkdir(right, 448i32) == 0); - assert(os.mkdir(target, 448i32) == 0); - writefile(strings.concat(shared, "/shared.ww"), - "package shared;\nexport fn value() i32 = { return 20; };\n"); - writefile(strings.concat(left, "/left.ww"), strings.concat( - "package left;\nimport shared;\n", - "export fn value() i32 = { return shared.value(); };\n")); - writefile(strings.concat(right, "/right.ww"), strings.concat( - "package right;\nimport shared;\n", - "export fn value() i32 = { return shared.value() + 1; };\n")); - writefile(strings.concat(target, "/diamond.ww"), - "package diamond;\nfn local() i32 = { return 1; };\n"); - writefile(strings.concat(target, "/diamond_test.ww"), strings.concat( - "package diamond;\nimport left;\nimport right;\n", - "@test fn one_shared_compile() void = {\n", - " assert(left.value() + right.value() + local() == 42);\n};\n")); - let trace: str = strings.concat(root, "/compiler.trace"); - let wrapper: str = strings.concat(root, "/trace-w6c.sh"); - writefile(trace, ""); - writeexecutable(wrapper, strings.concat( - "#!/bin/sh\n", - "printf '%s\\n' \"$*\" >> \"$WW_PACKAGE_TRACE\"\n", - "exec \"$WW_PACKAGE_W6C\" \"$@\"\n")); - let baseenv: []str = os.getenvs(); - let env: []str = alloc([], (baseenv.len + 3): u64)!; - let ei: i32 = 0; - for (ei < baseenv.len) { - if (!strings.hasprefix(baseenv[ei], "WW_W6C=") - && !strings.hasprefix(baseenv[ei], "WW_PACKAGE_TRACE=") - && !strings.hasprefix(baseenv[ei], "WW_PACKAGE_W6C=")) { - append(env, baseenv[ei]); - }; - ei += 1; - }; - append(env, strings.concat("WW_W6C=", wrapper)); - append(env, strings.concat("WW_PACKAGE_TRACE=", trace)); - append(env, strings.concat("WW_PACKAGE_W6C=", driver("w6c"))); - let bin: str = strings.concat(root, "/diamond.test"); - let av: []str = [driver("ww"), "test", "-c", "-o", bin, - "-I", root, target]; - let out: commandout; - runcommandenv(root, "diamond-build", av, env, - (120i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 0); - assert(occurrences(readfile(trace), "shared.unit.ww") == 1); - let work: str = strings.concat(bin, ".sepwork/"); - let unit: str = readfile(strings.concat(work, - "__ww-test-000-same.unit.ww")); - assert(has(unit, "//ww:module left\n")); - assert(has(unit, "//ww:module right\n")); - assert(!has(unit, "//ww:module shared\n")); - assert(has(readfile(strings.concat(work, "left.unit.ww")), - "//ww:module shared\n")); - assert(has(readfile(strings.concat(work, "right.unit.ww")), - "//ww:module shared\n")); - assert(os.exists(strings.concat(work, "shared.a"))); - let runav: []str = [bin]; - runcommand(root, "diamond-run", runav, - (60i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 0); - assert(has(out.stdout, "one_shared_compile ... ok\n")); - clean(root); -}; - @test fn exact_package_tool_argv_stage_parity() void = { let root: str = fresh(); let source: str = strings.concat(root, "/source tree"); let runtime: str = strings.concat(root, "/runtime library"); - let leaf: str = strings.concat(source, "/ww_root_parity_leaf_7f3"); - let dep: str = strings.concat(source, "/ww_root_parity_dep_7f3"); + let base: str = strings.concat(source, "/ww_root_parity_base_7f3"); + let left: str = strings.concat(source, "/ww_root_parity_left_7f3"); + let right: str = strings.concat(source, "/ww_root_parity_right_7f3"); let target: str = strings.concat(source, "/ww_root_parity_target_7f3"); let tools: str = strings.concat(root, "/tool wrappers"); assert(os.mkdir(source, 448i32) == 0); assert(os.mkdir(runtime, 448i32) == 0); - let copyav: []str = ["/bin/cp", "-R", strings.concat(repo(), "/lib/."), - source]; - let copyout: commandout; - runcommand(root, "copy-source-library", copyav, - (30i64 * (time.second: i64)): time.duration, ©out); - expectexit(©out, 0); - assert(copyout.stderr.len == 0); - let support: str = strings.concat(source, "/test/run.ww"); - let supportbody: str = readfile(support); - assert(os.remove(support) == 0); - writefile(support, strings.concat("// WW_SRCLIB fixture marker\n", - supportbody)); writefile(strings.concat(runtime, "/libwwrt.a"), readfile(strings.concat(repo(), "/out/lib/libwwrt.a"))); - assert(os.mkdir(leaf, 448i32) == 0); - assert(os.mkdir(dep, 448i32) == 0); + assert(os.mkdir(base, 448i32) == 0); + assert(os.mkdir(left, 448i32) == 0); + assert(os.mkdir(right, 448i32) == 0); assert(os.mkdir(target, 448i32) == 0); assert(os.mkdir(tools, 448i32) == 0); - writefile(strings.concat(leaf, "/leaf.ww"), strings.concat( - "package ww_root_parity_leaf_7f3;\n", - "export fn value() i32 = { return 40; };\n")); - writefile(strings.concat(dep, "/dep.ww"), strings.concat( - "package ww_root_parity_dep_7f3;\n", - "import ww_root_parity_leaf_7f3;\n", + let basea: str = strings.concat( + "package ww_root_parity_base_7f3;\n", + "export fn value() i32 = { return 20; };\n"); + let basez: str = strings.concat( + "package ww_root_parity_base_7f3;\n", + "fn private_value() i32 = { return 99; };\n"); + let leftbody: str = strings.concat( + "package ww_root_parity_left_7f3;\n", + "import ww_root_parity_base_7f3;\n", + "import ww_root_parity_base_7f3;\n", "export fn value() i32 = { return ", - "ww_root_parity_leaf_7f3.value() + 2; };\n")); - writefile(strings.concat(target, "/target.ww"), strings.concat( - "package ww_root_parity_target_7f3;\n", - "import ww_root_parity_dep_7f3;\n", - "fn value() i32 = { return ww_root_parity_dep_7f3.value(); };\n")); - writefile(strings.concat(target, "/target_test.ww"), strings.concat( - "package ww_root_parity_target_7f3;\n", - "@test fn exact_argv() void = { assert(value() == 42); };\n")); + "ww_root_parity_base_7f3.value(); };\n"); + let rightbody: str = strings.concat( + "package ww_root_parity_right_7f3;\n", + "import ww_root_parity_base_7f3;\n", + "export fn value() i32 = { return ", + "ww_root_parity_base_7f3.value() + 1; };\n"); + let rootbody: str = strings.concat( + "package main;\n", + "import ww_root_parity_right_7f3;\n", + "import ww_root_parity_left_7f3;\n", + "fn main() i32 = { return ww_root_parity_left_7f3.value() + ", + "ww_root_parity_right_7f3.value() + 1; };\n"); + writefile(strings.concat(base, "/a.ww"), basea); + writefile(strings.concat(base, "/z.ww"), basez); + writefile(strings.concat(left, "/left.ww"), leftbody); + writefile(strings.concat(right, "/right.ww"), rightbody); + writefile(strings.concat(target, "/main.ww"), rootbody); let compilerwrapper: str = strings.concat(tools, "/w6c wrapper.sh"); let assemblerwrapper: str = strings.concat(tools, "/w6a wrapper.sh"); @@ -2262,19 +2191,43 @@ fn workescape(s: str) str = { "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", "\"$WW_ARGV_FAILURE_TRACE\"; done\n", "printf '\\n' >> \"$WW_ARGV_FAILURE_TRACE\"\n", - "exit 23\n")); + "owner=\n", + "for arg in \"$@\"; do if [ \"$arg\" = ", + "\"$WW_ARGV_MISSING_OWNER\" ]; then owner=1; fi; done\n", + "if [ -n \"$owner\" ]; then\n", + " mv -- \"$WW_ARGV_MISSING_EXPORT\" ", + "\"$WW_ARGV_MISSING_EXPORT.saved\" || exit 24\n", + " \"$WW_ARGV_REAL_COMPILER\" \"$@\"\n", + " rc=$?\n", + " mv -- \"$WW_ARGV_MISSING_EXPORT.saved\" ", + "\"$WW_ARGV_MISSING_EXPORT\" || exit 25\n", + " exit $rc\n", + "fi\n", + "exec \"$WW_ARGV_REAL_COMPILER\" \"$@\"\n")); - let stages: []str = ["ww", "ww_ww"]; - let compilers: []str = ["w6c", "w6c_ww"]; - let assemblers: []str = ["w6a", "w6a_ww"]; - let linkers: []str = ["w6l", "w6l_ww"]; - let tags: []str = ["c", "ww"]; - let bin: str = strings.concat(root, "/published test binary"); + let stages: []str = ["ww", "ww", "ww_ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c", "w6c_ww", "w6c_ww"]; + let assemblers: []str = ["w6a", "w6a", "w6a_ww", "w6a_ww"]; + let linkers: []str = ["w6l", "w6l", "w6l_ww", "w6l_ww"]; + let tags: []str = ["c1", "c2", "ww1", "ww2"]; + let bin: str = strings.concat(root, "/published binary"); let workroot: str = strings.concat(bin, ".sepwork"); let work: str = strings.concat(workroot, "/"); - let referenceunit: str = ""; - let referencewwi: str = ""; - let referencearchive: str = ""; + let artifacts: []str = ["ww_root_parity_base_7f3", + "ww_root_parity_left_7f3", "ww_root_parity_right_7f3"]; + let unitartifacts: []str = ["ww_root_parity_base_7f3", + "ww_root_parity_left_7f3", "ww_root_parity_right_7f3", "__root"]; + let wantunits: []str = [strings.concat( + "//ww:module-reset ww_root_parity_base_7f3\n", basea, + "\n//ww:module-reset ww_root_parity_base_7f3\n", basez, "\n"), + strings.concat("//ww:module-reset ww_root_parity_left_7f3\n", + leftbody, "\n"), + strings.concat("//ww:module-reset ww_root_parity_right_7f3\n", + rightbody, "\n"), + strings.concat("//ww:module-reset\n", rootbody, "\n")]; + let referenceunits: []str = ["", "", "", ""]; + let referenceexports: []str = ["", "", ""]; + let referencearchives: []str = ["", "", ""]; let referencebin: str = ""; let referencecompiler: str = ""; let referenceassembler: str = ""; @@ -2316,7 +2269,13 @@ fn workescape(s: str) str = { && !strings.hasprefix(baseenv[ei], "WW_ARGV_REAL_ASSEMBLER=") && !strings.hasprefix(baseenv[ei], - "WW_ARGV_REAL_LINKER=")) { + "WW_ARGV_REAL_LINKER=") + && !strings.hasprefix(baseenv[ei], + "WW_ARGV_FAILURE_TRACE=") + && !strings.hasprefix(baseenv[ei], + "WW_ARGV_MISSING_EXPORT=") + && !strings.hasprefix(baseenv[ei], + "WW_ARGV_MISSING_OWNER=")) { append(env, baseenv[ei]); }; ei += 1; @@ -2336,72 +2295,89 @@ fn workescape(s: str) str = { append(env, strings.concat("WW_ARGV_REAL_LINKER=", driver(linkers[si]))); - let av: []str = [driver(stages[si]), "test", "-c", "-o", bin, + let av: []str = [driver(stages[si]), "build", "-o", bin, target]; let out: commandout; runcommandenv(root, strings.concat("exact-argv-", tags[si]), av, env, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 0); assert(out.stderr.len == 0); - let rootunit: str = readfile(strings.concat(work, - "__ww-test-000-same.unit.ww")); - let depunit: str = readfile(strings.concat(work, - "ww_root_parity_dep_7f3.unit.ww")); - assert(has(rootunit, "//ww:module ww_root_parity_dep_7f3\n")); - assert(!has(rootunit, "//ww:module ww_root_parity_leaf_7f3\n")); - assert(has(depunit, "//ww:module ww_root_parity_leaf_7f3\n")); - assert(os.exists(strings.concat(work, - "ww_root_parity_leaf_7f3.wwi"))); - assert(os.exists(strings.concat(work, - "ww_root_parity_leaf_7f3.a"))); - assert(os.exists(strings.concat(work, - "ww_root_parity_dep_7f3.wwi"))); - assert(os.exists(strings.concat(work, - "ww_root_parity_dep_7f3.a"))); - assert(has(readfile(strings.concat(work, "test.unit.ww")), - "// WW_SRCLIB fixture marker\n")); - assert(!os.exists(strings.concat(work, - "__ww-test-000-same.wwi"))); - assert(!os.exists(strings.concat(work, - "__ww-test-000-same.a"))); + let ui: i32 = 0; + for (ui < unitartifacts.len) { + let unit: str = readfile(strings.concat(work, unitartifacts[ui], + ".unit.ww")); + assert(same(unit, wantunits[ui])); + assert(!has(unit, "//ww:module ")); + if (si == 0) { referenceunits[ui] = strings.dup(unit); } + else { assert(same(referenceunits[ui], unit)); }; + ui += 1; + }; + let ai: i32 = 0; + for (ai < artifacts.len) { + let exportf: str = readfile(strings.concat(work, artifacts[ai], + ".wwi")); + let archive: str = readfile(strings.concat(work, artifacts[ai], + ".a")); + if (si == 0) { + referenceexports[ai] = strings.dup(exportf); + referencearchives[ai] = strings.dup(archive); + } else { + assert(same(referenceexports[ai], exportf)); + assert(same(referencearchives[ai], archive)); + }; + ai += 1; + }; + assert(!os.exists(strings.concat(work, "__root.wwi"))); + assert(!os.exists(strings.concat(work, "__root.a"))); assert(os.exists(bin)); let ctrace: str = readfile(compilertrace); let atrace: str = readfile(assemblertrace); let ltrace: str = readfile(linkertrace); - assert(occurrences(ctrace, strings.concat("<", work, - "ww_root_parity_leaf_7f3.unit.ww>")) == 1); - assert(occurrences(ctrace, strings.concat("<", work, - "ww_root_parity_dep_7f3.unit.ww>")) == 1); - assert(occurrences(ctrace, strings.concat("<", work, - "__ww-test-000-same.unit.ww>")) == 1); - assert(pos(ctrace, strings.concat("<", work, - "ww_root_parity_leaf_7f3.unit.ww>")) - < pos(ctrace, strings.concat("<", work, - "ww_root_parity_dep_7f3.unit.ww>"))); - assert(pos(ctrace, strings.concat("<", work, - "ww_root_parity_dep_7f3.unit.ww>")) - < pos(ctrace, strings.concat("<", work, - "__ww-test-000-same.unit.ww>"))); - assert(has(ctrace, strings.concat("BEGIN<-c><-I><", work, - "ww_root_parity_dep_7f3.wwi><-o><", work, - "ww_root_parity_dep_7f3.s><", work, - "ww_root_parity_dep_7f3.unit.ww>"))); - assert(has(ctrace, strings.concat( - "BEGIN<-T><--test-support-module><-c><-o><", work, - "__ww-test-000-same.s><", work, - "__ww-test-000-same.unit.ww>"))); + assert(occurrences(ctrace, "\n") == 4); + let baseline: str = strings.concat("BEGIN<-c><-I><", work, + "ww_root_parity_base_7f3.wwi><-o><", work, + "ww_root_parity_base_7f3.s><", work, + "ww_root_parity_base_7f3.unit.ww>"); + let leftline: str = strings.concat("BEGIN<-c><--import>", + "<", work, + "ww_root_parity_base_7f3.wwi><-I><", work, + "ww_root_parity_left_7f3.wwi><-o><", work, + "ww_root_parity_left_7f3.s><", work, + "ww_root_parity_left_7f3.unit.ww>"); + let rightline: str = strings.concat("BEGIN<-c><--import>", + "<", work, + "ww_root_parity_base_7f3.wwi><-I><", work, + "ww_root_parity_right_7f3.wwi><-o><", work, + "ww_root_parity_right_7f3.s><", work, + "ww_root_parity_right_7f3.unit.ww>"); + let rootline: str = strings.concat("BEGIN<-c><--import>", + "<", work, + "ww_root_parity_left_7f3.wwi><--import>", + "<", work, + "ww_root_parity_right_7f3.wwi><-o><", work, + "__root.s><", work, "__root.unit.ww>"); + assert(same(linecontaining(ctrace, + "ww_root_parity_base_7f3.unit.ww"), baseline)); + assert(same(linecontaining(ctrace, + "ww_root_parity_left_7f3.unit.ww"), leftline)); + assert(same(linecontaining(ctrace, + "ww_root_parity_right_7f3.unit.ww"), rightline)); + assert(same(linecontaining(ctrace, "__root.unit.ww"), rootline)); + assert(!has(rootline, "ww_root_parity_base_7f3.wwi")); + assert(occurrences(atrace, "\n") == 4); assert(has(atrace, strings.concat("BEGIN<-o><", work, - "ww_root_parity_dep_7f3.o><", work, - "ww_root_parity_dep_7f3.s>"))); + "ww_root_parity_left_7f3.o><", work, + "ww_root_parity_left_7f3.s>"))); assert(occurrences(ltrace, "\n") == 1); assert(has(ltrace, strings.concat("BEGIN<-o><", bin, "><", work, - "__ww-test-000-same.o>"))); - assert(has(ltrace, strings.concat("<", work, - "ww_root_parity_dep_7f3.a>"))); - assert(has(ltrace, strings.concat("<", work, - "ww_root_parity_leaf_7f3.a>"))); - assert(has(ltrace, strings.concat("<", work, "test.a>"))); + "__root.o>"))); + ai = 0; + for (ai < artifacts.len) { + assert(occurrences(ltrace, strings.concat("<", work, artifacts[ai], + ".a>")) == 1); + ai += 1; + }; assert(has(ltrace, strings.concat("<", runtime, "/libwwrt.a>"))); assert(!has(ltrace, strings.concat("<", repo(), "/out/lib/libwwrt.a>"))); @@ -2410,14 +2386,9 @@ fn workescape(s: str) str = { let runav: []str = [bin]; runcommand(root, strings.concat("exact-argv-run-", tags[si]), runav, (60i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 0); - assert(has(out.stdout, "exact_argv ... ok\n")); + expectexit(&out, 42); + assert(out.stdout.len == 0 && out.stderr.len == 0); if (si == 0) { - referenceunit = strings.dup(rootunit); - referencewwi = strings.dup(readfile(strings.concat(work, - "ww_root_parity_dep_7f3.wwi"))); - referencearchive = strings.dup(readfile(strings.concat(work, - "ww_root_parity_dep_7f3.a"))); referencebin = strings.dup(readfile(bin)); referencecompiler = strings.dup(ctrace); referenceassembler = strings.dup(atrace); @@ -2425,13 +2396,6 @@ fn workescape(s: str) str = { referenceout = strings.dup(out.stdout); referenceerr = strings.dup(out.stderr); } else { - assert(same(referenceunit, rootunit)); - assert(same(referencewwi, - readfile(strings.concat(work, - "ww_root_parity_dep_7f3.wwi")))); - assert(same(referencearchive, - readfile(strings.concat(work, - "ww_root_parity_dep_7f3.a")))); assert(same(referencebin, readfile(bin))); assert(same(referencecompiler, ctrace)); assert(same(referenceassembler, atrace)); @@ -2442,7 +2406,7 @@ fn workescape(s: str) str = { clean(workroot); clean(bin); - let failenv: []str = alloc([], (env.len + 2): u64)!; + let failenv: []str = alloc([], (env.len + 4): u64)!; ei = 0; for (ei < env.len) { if (!strings.hasprefix(env[ei], "WW_W6C=") @@ -2454,13 +2418,21 @@ fn workescape(s: str) str = { }; append(failenv, strings.concat("WW_W6C=", failurewrapper)); append(failenv, strings.concat("WW_ARGV_FAILURE_TRACE=", failuretrace)); + append(failenv, strings.concat("WW_ARGV_MISSING_EXPORT=", work, + "ww_root_parity_base_7f3.wwi")); + append(failenv, strings.concat("WW_ARGV_MISSING_OWNER=", work, + "ww_root_parity_left_7f3.unit.ww")); runcommandenv(root, strings.concat("exact-argv-fail-", tags[si]), av, failenv, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); - assert(has(out.stderr, - "ww: w6c failed for ww_root_parity_leaf_7f3\n")); - assert(has(readfile(failuretrace), strings.concat("<-I><", work, - "ww_root_parity_leaf_7f3.wwi>"))); + let wantfailure: str = strings.concat( + "w6c: import ww_root_parity_base_7f3: cannot read ", work, + "ww_root_parity_base_7f3.wwi\n", + "ww: w6c failed for ww_root_parity_left_7f3\n"); + assert(same(out.stderr, wantfailure)); + assert(has(readfile(failuretrace), strings.concat( + "<--import><", work, + "ww_root_parity_base_7f3.wwi>"))); if (si == 0) { referencefailure = strings.dup(out.stderr); } else { assert(same(referencefailure, out.stderr)); }; clean(workroot); diff --git a/test/sep/importdir_test.ww b/test/sep/importdir_test.ww index f1fe5e44..d0b851d0 100644 --- a/test/sep/importdir_test.ww +++ b/test/sep/importdir_test.ww @@ -88,25 +88,22 @@ fn samefile(a: str, b: str, why: str) void = { let cwork: str = strings.concat(stems[0], ".sepwork/"); let cwork2: str = strings.concat(stems[1], ".sepwork/"); let wwork: str = strings.concat(stems[2], ".sepwork/"); - let bariface: str = testenv.readfile(strings.concat(cwork, "example.bar.wwi")); - let fooiface: str = testenv.readfile(strings.concat(cwork, "example.foo.wwi")); let foounit: str = testenv.readfile(strings.concat(cwork, "example.foo.unit.ww")); - let wantfoo: str = strings.concat("//ww:module example.bar\n", bariface, - "\n//ww:module-reset example.foo\n", afoosrc, + let wantfoo: str = strings.concat("//ww:module-reset example.foo\n", afoosrc, "\n//ww:module-reset example.foo\n", zfoosrc, "\n"); if (!testenv.same(foounit, wantfoo) - || testenv.has(foounit, "DECOY_FILE")) { - fail("foo unit is not its sorted, owned source set plus direct bar export"); + || testenv.has(foounit, "DECOY_FILE") + || testenv.has(foounit, "//ww:module ")) { + fail("foo unit is not exactly its sorted, owned source set"); }; - let wantroot: str = strings.concat("//ww:module example.foo\n", fooiface, - "\n//ww:module-reset\n", appsrc, "\n"); + let wantroot: str = strings.concat("//ww:module-reset\n", appsrc, "\n"); if (!testenv.same(wantroot, testenv.readfile(strings.concat(cwork, "__root.unit.ww")))) { - fail("root unit is not direct-foo-only"); + fail("root unit is not exactly its owned source"); }; let keys: []str = ["example.base", "example.bar", "example.foo"]; - let suffixes: []str = [".wwi", ".a"]; + let suffixes: []str = [".unit.ww", ".wwi", ".a"]; i = 0; for (i < keys.len) { let j: i32 = 0; @@ -120,6 +117,10 @@ fn samefile(a: str, b: str, why: str) void = { }; i += 1; }; + samefile(strings.concat(cwork, "__root.unit.ww"), strings.concat(cwork2, + "__root.unit.ww"), "root unit changed across clean C builds"); + samefile(strings.concat(cwork, "__root.unit.ww"), strings.concat(wwork, + "__root.unit.ww"), "root unit differs between stages"); samefile(stems[0], stems[1], "C executables are not deterministic"); samefile(stems[0], stems[2], "C/WW executables differ"); diff --git a/test/sep/localbuild_test.ww b/test/sep/localbuild_test.ww index 732e83e4..e7940f77 100644 --- a/test/sep/localbuild_test.ww +++ b/test/sep/localbuild_test.ww @@ -286,12 +286,11 @@ fn rejectstable(dir: str, label: str, target: str, needle: str) void = { || testenv.has(apiiface, "hidden")) { fail("self-contained", "api export leaked unrelated or private declarations"); }; - let expectedunit: str = strings.concat("//ww:module api\n", apiiface, - "\n//ww:module-reset\n", mainsrc, "\n"); + let expectedunit: str = strings.concat("//ww:module-reset\n", mainsrc, "\n"); if (!testenv.same(expectedunit, testenv.readfile(strings.concat(cwork, "__root.unit.ww"))) || !testenv.same(expectedunit, testenv.readfile(strings.concat(wwork, "__root.unit.ww")))) { - fail("self-contained", "consumer compiler input was not direct-api-only"); + fail("self-contained", "consumer unit was not exactly its owned source"); }; if (!testenv.exists(strings.concat(cwork, "implementation.a")) || !testenv.exists(strings.concat(cwork, "dimensions.a"))) { @@ -405,10 +404,12 @@ fn writediamond(td: str, reverse: bool) str = { }; let unit: str = testenv.readfile(strings.concat(scratch, "__root.unit.ww")); - let lp: i32 = testenv.pos(unit, "//ww:module left\n"); - let rp: i32 = testenv.pos(unit, "//ww:module right\n"); - if (lp < 0 || rp < 0 || lp >= rp) { - fail("diamond", "dependency traversal did not byte-sort imports"); + let rootbody: str = testenv.readfile(strings.concat(main, "/main.ww")); + let wantroot: str = strings.concat("//ww:module-reset\n", rootbody, + "\n"); + if (!testenv.same(unit, wantroot) || testenv.has(unit, + "//ww:module ")) { + fail("diamond", "root unit contains non-owned export text"); }; let sharedunit: str = testenv.readfile(strings.concat(scratch, "shared.unit.ww")); @@ -426,8 +427,7 @@ fn writediamond(td: str, reverse: bool) str = { }; if (testenv.occurrences(leftiface, "export type token") != 1 || testenv.occurrences(rightiface, "export type token") != 1 - || testenv.occurrences(unit, "export type token") != 2 - || testenv.occurrences(unit, "//ww:module shared\n") != 2) { + || testenv.has(unit, "export type token")) { fail("diamond", "origin fact closure did not merge deterministically"); }; i += 1; @@ -452,43 +452,6 @@ fn writediamond(td: str, reverse: bool) str = { fail("diamond", "shuffled enumeration changed package units"); }; - // Count actual compiler actions, rather than inferring interning from - // artifact names. The wrapper is selected through the production - // driver's existing tool override and records one row per w6c process. - let trace: str = strings.concat(td, "/compiler.trace"); - let wrapper: str = strings.concat(td, "/trace-w6c.sh"); - testenv.writefile(trace, ""); - testenv.writeexecutable(wrapper, strings.concat( - "#!/bin/sh\n", - "printf '%s\\n' \"$*\" >> \"$WW_LOCALBUILD_TRACE\"\n", - "exec \"$WW_LOCALBUILD_W6C\" \"$@\"\n")); - let baseenv: []str = os.getenvs(); - let env: []str = alloc([], (baseenv.len + 3): u64)!; - let ei: i32 = 0; - for (ei < baseenv.len) { - if (!strings.hasprefix(baseenv[ei], "WW_W6C=") - && !strings.hasprefix(baseenv[ei], "WW_LOCALBUILD_TRACE=") - && !strings.hasprefix(baseenv[ei], "WW_LOCALBUILD_W6C=")) { - append(env, baseenv[ei]); - }; - ei += 1; - }; - append(env, strings.concat("WW_W6C=", wrapper)); - append(env, strings.concat("WW_LOCALBUILD_TRACE=", trace)); - append(env, strings.concat("WW_LOCALBUILD_W6C=", - testenv.driver("w6c"))); - let countedout: str = strings.concat(td, "/diamond-counted"); - let countedav: []str = [testenv.driver("ww"), "build", "-I", treea, - "-o", countedout, main]; - let counted: testenv.commandout; - testenv.runcommandenv(td, td, "diamond_compile_count", countedav, - env, tmo(), &counted); - if (counted.termination != exec.termination.EXIT || counted.code != 0) { - fail("diamond", "instrumented build failed"); - }; - if (testenv.occurrences(testenv.readfile(trace), "shared.unit.ww") != 1) { - fail("diamond", "shared package was not compiled exactly once"); - }; testenv.clean(td); }; @@ -604,10 +567,11 @@ fn writediamond(td: str, reverse: bool) str = { "__root.unit.ww")); let depunit: str = testenv.readfile(strings.concat(work, "dep.unit.ww")); - if (!testenv.has(rootunit, "//ww:module dep\n") - || testenv.has(rootunit, "//ww:module leaf\n") - || !testenv.has(depunit, "//ww:module leaf\n")) { - fail("library-roots", "compiler inputs crossed the direct-export boundary"); + if (!testenv.has(rootunit, "//ww:module-reset\npackage main;") + || testenv.has(rootunit, "//ww:module ") + || !testenv.has(depunit, "//ww:module-reset dep\npackage dep;") + || testenv.has(depunit, "//ww:module ")) { + fail("library-roots", "package units contain non-owned export text"); }; if (!testenv.exists(strings.concat(work, "leaf.wwi")) || !testenv.exists(strings.concat(work, "leaf.a")) @@ -636,7 +600,8 @@ fn writediamond(td: str, reverse: bool) str = { fail("library-roots", "compiler actions were not deterministic postorder"); }; if (!testenv.has(ctrace, strings.concat( - "BEGIN<-c><-I><", work, "dep.wwi><-o><", work, + "BEGIN<-c><--import><", work, "leaf.wwi><-I><", work, + "dep.wwi><-o><", work, "dep.s><", work, "dep.unit.ww>")) || testenv.occurrences(atrace, "\n") != 3 || !testenv.has(atrace, strings.concat("BEGIN<-o><", work, @@ -758,7 +723,8 @@ fn writediamond(td: str, reverse: bool) str = { runav) != 42) { fail("library-roots", "empty override did not use default roots"); }; - if (!testenv.has(rootunit, "//ww:module types\n") + if (!testenv.has(rootunit, "//ww:module-reset\npackage main;") + || testenv.has(rootunit, "//ww:module ") || !testenv.exists(strings.concat(emptywork, "types.wwi")) || !testenv.exists(strings.concat(emptywork, "types.a")) || testenv.occurrences(ctrace, strings.concat("<", emptywork, @@ -904,10 +870,11 @@ fn writediamond(td: str, reverse: bool) str = { let depunitpath: str = strings.concat(work, "/dep.unit.ww"); let rootunit: str = testenv.readfile(rootunitpath); let depunit: str = testenv.readfile(depunitpath); - if (!testenv.has(rootunit, "//ww:module dep\n") - || testenv.has(rootunit, "//ww:module leaf\n") - || !testenv.has(depunit, "//ww:module leaf\n")) { - fail("driver-identity", "warm units crossed the direct-export boundary"); + if (!testenv.has(rootunit, "//ww:module-reset\npackage main;") + || testenv.has(rootunit, "//ww:module ") + || !testenv.has(depunit, "//ww:module-reset dep\npackage dep;") + || testenv.has(depunit, "//ww:module ")) { + fail("driver-identity", "warm units contain non-owned export text"); }; if (!testenv.exists(strings.concat(work, "/leaf.wwi")) || !testenv.exists(strings.concat(work, "/leaf.a")) @@ -923,7 +890,7 @@ fn writediamond(td: str, reverse: bool) str = { "/.wwtool.w6a")), testenv.readfile(assembler)) || !testenv.same(testenv.readfile(strings.concat(work, "/.wwtool.stamp")), - "ww workdir fmt 5 mode build asm 0\n")) { + "ww workdir fmt 6 mode build asm 0\n")) { fail("driver-identity", "persistent artifacts or identities are incomplete"); }; let coldwwi: str = testenv.readfile(strings.concat(work, "/dep.wwi")); @@ -934,10 +901,12 @@ fn writediamond(td: str, reverse: bool) str = { let coldlinker: str = testenv.readfile(linkertrace); if (testenv.occurrences(coldcompiler, "\n") != 3 || !testenv.has(coldcompiler, strings.concat( - "BEGIN<-c><-I><", work, "/dep.wwi.new><-o><", work, + "BEGIN<-c><--import><", work, "/leaf.wwi><-I><", work, + "/dep.wwi.new><-o><", work, "/dep.s.new><", work, "/dep.unit.new>")) || !testenv.has(coldcompiler, strings.concat( - "BEGIN<-c><-o><", work, "/__root.s.new><", work, + "BEGIN<-c><--import><", work, "/dep.wwi><-o><", work, + "/__root.s.new><", work, "/__root.unit.new>")) || testenv.occurrences(coldassembler, "\n") != 3 || !testenv.has(coldassembler, strings.concat( @@ -964,21 +933,62 @@ fn writediamond(td: str, reverse: bool) str = { fail("driver-identity", "cold binary returned the wrong value"); }; + testenv.writefile(strings.concat(leaf, "/extra.ww"), strings.concat( + "package leaf;\n", + "export fn unchanged_api() i32 = { return 1; };\n")); + testenv.runcommandenv(cwd, td, strings.concat("driver-export-change-", + tags[si]), av, env, tmo(), &out); + if (out.termination != exec.termination.EXIT || out.code != 0 + || out.stderr.len != 0) { + fail("driver-identity", "dependency export-change build failed"); + }; + let changedexportcompiler: str = testenv.readfile(compilertrace); + let changedexportassembler: str = testenv.readfile(assemblertrace); + if (testenv.occurrences(changedexportcompiler, strings.concat("<", work, + "/leaf.unit.new>")) != 2 + || testenv.occurrences(changedexportcompiler, strings.concat("<", work, + "/dep.unit.new>")) != 2 + || testenv.occurrences(changedexportcompiler, strings.concat("<", work, + "/__root.unit.new>")) != 1 + || testenv.occurrences(changedexportcompiler, "\n") != 5 + || testenv.occurrences(changedexportassembler, "\n") != 5) { + fail("driver-identity", "changed export did not stop at an unchanged importer export"); + }; + if (code(cwd, strings.concat("driver-export-change-run-", tags[si]), + runav) != 42) { + fail("driver-identity", "export-change binary returned the wrong value"); + }; + let changedexportbin: str = testenv.readfile(bin); + let beforewarmcompiler: str = strings.dup(changedexportcompiler); + let beforewarmassembler: str = strings.dup(changedexportassembler); + testenv.runcommandenv(cwd, td, strings.concat("driver-warm-", tags[si]), av, env, tmo(), &out); if (out.termination != exec.termination.EXIT || out.code != 0 || out.stderr.len != 0 - || !testenv.same(coldcompiler, testenv.readfile(compilertrace)) - || !testenv.same(coldassembler, testenv.readfile(assemblertrace)) - || testenv.occurrences(testenv.readfile(linkertrace), "\n") != 2 + || !testenv.same(beforewarmcompiler, testenv.readfile(compilertrace)) + || !testenv.same(beforewarmassembler, testenv.readfile(assemblertrace)) + || testenv.occurrences(testenv.readfile(linkertrace), "\n") != 3 || !testenv.same(rootunit, testenv.readfile(rootunitpath)) - || !testenv.same(coldwwi, testenv.readfile(strings.concat(work, - "/dep.wwi"))) - || !testenv.same(coldarchive, testenv.readfile(strings.concat(work, - "/dep.a"))) - || !testenv.same(coldbin, testenv.readfile(bin))) { + || !testenv.same(changedexportbin, testenv.readfile(bin))) { fail("driver-identity", "unchanged warm build did not reuse packages"); }; + assert(os.remove(strings.concat(leaf, "/extra.ww")) == 0); + testenv.runcommandenv(cwd, td, strings.concat("driver-export-restore-", + tags[si]), av, env, tmo(), &out); + if (out.termination != exec.termination.EXIT || out.code != 0 + || out.stderr.len != 0 + || testenv.occurrences(testenv.readfile(compilertrace), strings.concat( + "<", work, "/leaf.unit.new>")) != 3 + || testenv.occurrences(testenv.readfile(compilertrace), strings.concat( + "<", work, "/dep.unit.new>")) != 3 + || testenv.occurrences(testenv.readfile(compilertrace), strings.concat( + "<", work, "/__root.unit.new>")) != 1 + || testenv.occurrences(testenv.readfile(compilertrace), "\n") != 7 + || testenv.occurrences(testenv.readfile(assemblertrace), "\n") != 7 + || !testenv.same(coldbin, testenv.readfile(bin))) { + fail("driver-identity", "restored export did not rebuild only its direct importer"); + }; assert(os.remove(copied[si]) == 0); testenv.writeexecutable(copied[si], strings.concat(driverbytes, @@ -994,14 +1004,14 @@ fn writediamond(td: str, reverse: bool) str = { let changedassembler: str = testenv.readfile(assemblertrace); let changedlinker: str = testenv.readfile(linkertrace); if (testenv.occurrences(changedcompiler, strings.concat("<", work, - "/leaf.unit.new>")) != 2 + "/leaf.unit.new>")) != 4 || testenv.occurrences(changedcompiler, strings.concat("<", work, - "/dep.unit.new>")) != 2 + "/dep.unit.new>")) != 4 || testenv.occurrences(changedcompiler, strings.concat("<", work, "/__root.unit.new>")) != 2 - || testenv.occurrences(changedcompiler, "\n") != 6 - || testenv.occurrences(changedassembler, "\n") != 6 - || testenv.occurrences(changedlinker, "\n") != 3) { + || testenv.occurrences(changedcompiler, "\n") != 10 + || testenv.occurrences(changedassembler, "\n") != 10 + || testenv.occurrences(changedlinker, "\n") != 5) { fail("driver-identity", "driver change reused stale package actions"); }; if (!testenv.same(testenv.readfile(strings.concat(work, diff --git a/test/sep/sepbuild_test.ww b/test/sep/sepbuild_test.ww index 1e6b0a5d..96243c53 100644 --- a/test/sep/sepbuild_test.ww +++ b/test/sep/sepbuild_test.ww @@ -3,26 +3,24 @@ package sepbuild_test; // `ww build` sep-driver layout observers, both stages. Ports of the // retired native carriers test/wcc/989_sepbuild_run.c, // 989_seproot_export_run.c, 989_sepstructdef_run.c and -// 989_wwispread_sep.c; every assertion preserved. +// 989_wwispread_sep.c; assertions retained where their production boundary +// remains live. // // sepbuild (#46 commit-3) — build_one_sep END-TO-END on the real lib // chain root -> os -> {rt,time} (transitive-closure discovery + topo): // build+run exit 7 both stages; {time,rt,os}.wwi + __root.s // materialize (#69: the root is compiled without -I, so no // __root.wwi); per-package .s/.wwi/.unit.ww and the final binary -// byte-id cs vs ww; the KEYSTONE (spec 3.1) recompiles the driver's -// own non-leaf .unit.ww with each dep's .wwi section replaced by that -// dep's full directory bodies and requires byte-identical .s — the -// proof the .wwi conveys exactly the dep facts P's codegen needs; -// `ww run` routes through the same sole sep path (exit 7 both +// byte-id cs vs ww; every .unit.ww is the package's own sorted source +// set; `ww run` routes through the same sole sep path (exit 7 both // stages). // // seproot (#69 BUG-1) — a ROOT whose `export fn use(a: *t)` names an // unexported local `type t` builds (exit 37 both stages) because the // root is compiled WITHOUT the -I .wwi-producer flag: __root.wwi must -// NOT exist; replaying the driver's own __root.unit.ww through -// w6c/w6c_ww with -I must REJECT (the isolated bug) while -c -o alone -// must accept; __root.s carries the exported fn. +// NOT exist; replaying the driver's own __root.unit.ww plus c.wwi +// through w6c/w6c_ww with -I must REJECT (the isolated bug) while -c +// -o alone must accept; __root.s carries the exported fn. // // sepstructdef (#70 BUG-2) — a dep exporting aggregate-init defs // (struct-lit + array-lit) sep-builds and links (exit 20 both @@ -69,80 +67,6 @@ fn samefile(label: str, what: str, a: str, b: str) void = { }; }; -// A package directory's *.ww bodies (less *test.ww), byte-sorted, each -// newline-terminated — mirrors the driver's body enumeration so the -// substituted bodies-unit matches the sep-unit structurally. -fn dirbodies(dir: str) str = { - let out: str = ""; - let names: []str = testenv.listdir(dir); - let i: i32 = 0; - for (i < names.len) { - if (strings.hassuffix(names[i], ".ww") - && !strings.hassuffix(names[i], "test.ww")) { - out = strings.concat(out, - testenv.readfile(strings.concat(dir, "/", names[i])), - "\n"); - }; - i += 1; - }; - return out; -}; - -// Byte-offset borrow: strings.sub is rune-indexed, and the driver -// units embed lib comment bytes outside ASCII, so rune indices would -// mis-address the byte cursor below (readfile's borrow idiom). -fn bslice(s: str, start: i32, end: i32) str = { - assert(start <= end && end <= s.len); - let r: str; - r.ptr = s.ptr + (start: u64); - r.len = end - start; - return r; -}; - -// Transform a driver sep-unit into a bodies-unit: every `//ww:module -// ` dep section (its .wwi content) is replaced by 's full -// directory bodies (dots -> slashes under libdir); the trailing -// `//ww:module-reset` primary body is copied verbatim. The keystone's -// other arm. -fn composebodies(unit: str, libdir: str) str = { - let out: str = ""; - let n: i32 = unit.len; - let i: i32 = 0; - for (i < n) { - let j: i32 = i; - for (j < n && unit[j] != '\n') { j += 1; }; - let line: str = bslice(unit, i, j); - if (strings.hasprefix(line, "//ww:module-reset")) { - out = strings.concat(out, bslice(unit, i, n)); - return out; - }; - if (strings.hasprefix(line, "//ww:module ")) { - out = strings.concat(out, line, "\n"); - let path: str = bslice(line, 12, line.len); - let d: []u8 = alloc([], (path.len + 1): u64)!; - let k: i32 = 0; - for (k < path.len) { - if (path[k] == '.') { append(d, '/'); } - else { append(d, path[k]); }; - k += 1; - }; - out = strings.concat(out, dirbodies(strings.concat(libdir, - "/", strings.frombytes(d)))); - i = j + 1; - for (i < n) { - let e: i32 = i; - for (e < n && unit[e] != '\n') { e += 1; }; - if (strings.hasprefix(bslice(unit, i, e), - "//ww:module")) { break; }; - i = e + 1; - }; - continue; - }; - i = j + 1; - }; - return out; -}; - @test fn sepbuild() void = { let td: str = testenv.fresh(); let rootww: str = strings.concat(td, "/root.ww"); @@ -200,34 +124,6 @@ fn composebodies(unit: str, libdir: str) str = { samefile("sepbuild", "the final binary", strings.concat(td, "/prog.cs"), strings.concat(td, "/prog.ww")); - // KEYSTONE through the driver, non-leaf packages only (leaves have - // no dep sections, so their keystone is vacuous) - let libdir: str = strings.concat(testenv.repo(), "/lib"); - let keypkgs: []str = ["os", "__root"]; - let kp: i32 = 0; - for (kp < keypkgs.len) { - let unit: str = testenv.readfile(strings.concat(td, - "/prog.cs.sepwork/", keypkgs[kp], ".unit.ww")); - let bodiesf: str = strings.concat(td, "/", keypkgs[kp], - ".bodies.ww"); - testenv.writefile(bodiesf, composebodies(unit, libdir)); - let bodiess: str = strings.concat(td, "/", keypkgs[kp], - ".bodies.s"); - let av: []str = [testenv.driver("w6c"), "-c", "-o", bodiess, - bodiesf]; - if (runcode(td, strings.concat("key_", keypkgs[kp]), av) != 0) { - fail("sepbuild", strings.concat(keypkgs[kp], " bodies -c failed")); - }; - if (!testenv.same(testenv.readfile(bodiess), - testenv.readfile(strings.concat(td, "/prog.cs.sepwork/", - keypkgs[kp], ".s")))) { - fail("sepbuild", strings.concat(keypkgs[kp], - " bodies.s != driver sep.s (the .wwi does not convey ", - "the dep facts P needs)")); - }; - kp += 1; - }; - // `ww run` routes through the sole sep path on both stages let cav: []str = [testenv.driver("ww"), "run", rootww]; let rccs: i32 = runcode(td, "run_cs", cav); @@ -309,19 +205,20 @@ fn composebodies(unit: str, libdir: str) str = { // with -I the export-check FIRES (the isolated bug); without it the // post-fix invocation accepts. Both compilers. let unit: str = strings.concat(td, "/prog.cs.sepwork/__root.unit.ww"); + let ciface: str = strings.concat(td, "/prog.cs.sepwork/c.wwi"); let comps: []str = ["w6c", "w6c_ww"]; let c: i32 = 0; for (c < 2) { let wwi: str = strings.concat(td, "/nv.", comps[c], ".wwi"); let asmf: str = strings.concat(td, "/nv.", comps[c], ".s"); - let rav: []str = [testenv.driver(comps[c]), "-c", "-I", wwi, - "-o", asmf, unit]; + let rav: []str = [testenv.driver(comps[c]), "-c", "--import", "c", + ciface, "-I", wwi, "-o", asmf, unit]; if (runcode(td, strings.concat("nvI_", comps[c]), rav) == 0) { fail("seproot", strings.concat(comps[c], " -c -I accepted the ", "root export-over-unexported-type (vacuous gate)")); }; - let aav: []str = [testenv.driver(comps[c]), "-c", "-o", asmf, - unit]; + let aav: []str = [testenv.driver(comps[c]), "-c", "--import", "c", + ciface, "-o", asmf, unit]; if (runcode(td, strings.concat("nvO_", comps[c]), aav) != 0) { fail("seproot", strings.concat(comps[c], " -c -o (no -I) ", "rejected the root unit (post-fix invocation)")); diff --git a/test/tool/wwdump_test.ww b/test/tool/wwdump_test.ww index c90b45b4..1ef89689 100644 --- a/test/tool/wwdump_test.ww +++ b/test/tool/wwdump_test.ww @@ -18,8 +18,9 @@ package wwdump_test; // wwdump_ww's exit code is deliberately NOT gated: the diagnostics // land on stderr regardless, and the armed bail flips the exit code — // the line count is the one signal stable across the fold sequence. -// #90 sep-feed: import-bearing fixtures feed their RESOLVED sep unit -// (`ww build -S` composes .sepwork/__root.unit.ww); the +// #90 sep-feed: `ww build -S` resolves import-bearing fixtures to an +// owner unit and direct exports; this raw wwdump-only gate explicitly +// composes those artifacts because wwdump has no package-input CLI. The // import-free test/wcc/901_*.ww companions stay raw-fed. // // wwdumpgate (#52, F15 c4) — the -c and -r arms gate on parse-stage @@ -93,11 +94,30 @@ fn countdiag(stderr: str) i32 = { fail(classes[i], strings.concat(rels[i], ": no resolved sep unit (ww build -S failed)")); }; - src = strings.concat(stem, ".sepwork/__root.unit.ww"); - if (!testenv.exists(src)) { + let work: str = strings.concat(stem, ".sepwork/"); + let owner: str = strings.concat(work, "__root.unit.ww"); + if (!testenv.exists(owner)) { fail(classes[i], strings.concat(rels[i], ": __root.unit.ww missing after ww build -S")); }; + let deps: []str = []; + if (i == 0) { append(deps, "math.checked"); append(deps, "types"); }; + if (i == 1) { + append(deps, "ascii"); append(deps, "os"); append(deps, "strconv"); + }; + if (i == 3) { append(deps, "fnmatch"); }; + if (i == 4) { append(deps, "math.random"); append(deps, "test"); }; + let composed: str = ""; + let di: i32 = 0; + for (di < deps.len) { + composed = strings.concat(composed, "//ww:module ", deps[di], + "\n", testenv.readfile(strings.concat(work, deps[di], + ".wwi")), "\n"); + di += 1; + }; + composed = strings.concat(composed, testenv.readfile(owner)); + src = strings.concat(td, "/wwdump.raw.ww"); + testenv.writefile(src, composed); }; let co: testenv.commandout; let av: []str = [testenv.driver("wwdump_ww"), "-c", src];