wcc/ww: ww build --sep separate-compilation driver (M3-tail c3, #46)
build_one_sep (both stages + ww/main.combined.ww regen): discover_deps (transitive directory-package set, dotted-path identity), tri-color reverse-topo (cycle bails; loud reject is commit 4), the transitive producer loop (one w6c -c -I pass per package, dep-first, each both consumer and producer of its .wwi), and a flat w6l of the .o set. combined.ww stays the DEFAULT live path; --sep is additive. Every dep is tagged by its full dotted import path on prepend (//ww:module <path>), so the definer's qualified symbol (#53) == the consumer's qualified reference (#40) and the sep .o set links. The prepend is the TRANSITIVE closure (lead-ratified, superseding rob-c3-spec §1.3 direct-deps): a dep's interface can name a transitive dep's type (os exposes time.instant), so the consuming unit needs the whole closure for resolution — direct-deps-only does not type-check. Consistent with the current flat-unit transitive-namespace model (the visibility tighten is #45, post-M4). Gate 989_sepbuild_run drives ww + ww_ww --sep on the real chain root->os->{rt,time}: build+run (exit 7) + cs==ww per-pkg .s/.wwi/.unit + final binary + transitive-topo discovery + keystone bodies==.wwi (os,root) through the real driver. Cold scratch; -o-redirected.
This commit is contained in:
10
Makefile
10
Makefile
@@ -563,6 +563,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_lib_byteid \
|
||||
$(BIN)/test_m2wwi_run \
|
||||
$(BIN)/test_m3sep_run \
|
||||
$(BIN)/test_sepbuild_run \
|
||||
$(BIN)/test_floatlit_run \
|
||||
$(BIN)/test_checked_run \
|
||||
$(BIN)/test_floatarr_run \
|
||||
@@ -3097,6 +3098,15 @@ $(BIN)/test_m3sep_run: test/wcc/989_m3sep_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_sepbuild_run drives `ww build --sep` + `ww_ww build --sep` (the
|
||||
# M3-tail commit-3 build_one_sep driver) end-to-end on the real lib chain
|
||||
# root->os->{rt,time}. Needs BOTH driver stages + both compiler stages
|
||||
# (the keystone re-runs w6c -c on the bodies-unit).
|
||||
$(BIN)/test_sepbuild_run: test/wcc/989_sepbuild_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 $@ $<
|
||||
|
||||
$(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
470
cmd/ww/main.c
470
cmd/ww/main.c
@@ -570,6 +570,451 @@ build_one(const char *src, int entry_is_dir, const char *out,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ====================================================================
|
||||
* ww build --sep — M3-tail separate-compilation driver (task #46/c3).
|
||||
* ====================================================================
|
||||
* The `--sep` path materializes each imported package's `.wwi`
|
||||
* interface and compiles every package on its own (`w6c -c`), then
|
||||
* flat-links the `.o` set. combined.ww stays the DEFAULT live path;
|
||||
* --sep is purely additive (no existing invocation reaches it).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* The load-bearing rule (ken #56, rob-resolved): every dep is tagged by
|
||||
* its FULL DOTTED import path on prepend (`//ww:module <path>`), 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.
|
||||
*
|
||||
* The prepend is the TRANSITIVE closure of a package's deps (lead-
|
||||
* ratified, superseding rob-c3-spec §1.3 "direct deps"): a dep's public
|
||||
* interface can name a transitive dep's type (os exposes time.instant),
|
||||
* so the consuming unit needs the whole closure for name RESOLUTION —
|
||||
* direct-deps-only does not type-check. This mirrors harec reading the
|
||||
* transitive `.td` closure and is consistent with the current flat-unit
|
||||
* transitive-namespace model (the visibility tighten is task #45,
|
||||
* deferred post-M4).
|
||||
*/
|
||||
#define SEP_MAXPKG 256
|
||||
|
||||
struct seppkg {
|
||||
char path[256]; /* dotted import path; "" == root/primary */
|
||||
char entry[1024]; /* resolved package dir (or file, for a file root) */
|
||||
int is_dir;
|
||||
int deps[SEP_MAXPKG]; /* direct-dep indices into sepgraph.pkg */
|
||||
int ndeps;
|
||||
int color; /* tri-color DFS: 0 white, 1 gray, 2 black */
|
||||
};
|
||||
|
||||
struct sepgraph {
|
||||
struct seppkg pkg[SEP_MAXPKG];
|
||||
int n;
|
||||
};
|
||||
|
||||
/* Find a package by dotted path, or add it. Returns its index, -1 full. */
|
||||
static int
|
||||
sep_find_or_add(struct sepgraph *g, const char *path, const char *entry,
|
||||
int is_dir)
|
||||
{
|
||||
for (int i = 0; i < g->n; i++)
|
||||
if (strcmp(g->pkg[i].path, path) == 0) return i;
|
||||
if (g->n >= SEP_MAXPKG) {
|
||||
fprintf(stderr, "ww --sep: too many packages (limit %d)\n",
|
||||
SEP_MAXPKG);
|
||||
return -1;
|
||||
}
|
||||
struct seppkg *p = &g->pkg[g->n];
|
||||
snprintf(p->path, sizeof p->path, "%s", path);
|
||||
snprintf(p->entry, sizeof p->entry, "%s", entry);
|
||||
p->is_dir = is_dir;
|
||||
p->ndeps = 0;
|
||||
p->color = 0;
|
||||
return g->n++;
|
||||
}
|
||||
|
||||
/* Sanitize a package's dotted path into a scratch-file basename. Dots
|
||||
* stay (legal in filenames); the root's empty path becomes "__root". */
|
||||
static void
|
||||
sep_fname(const struct sepgraph *g, int pi, const char *scratch,
|
||||
const char *suffix, char *out, size_t outsz)
|
||||
{
|
||||
const char *base = g->pkg[pi].path[0] ? g->pkg[pi].path : "__root";
|
||||
snprintf(out, outsz, "%s/%s%s", scratch, base, suffix);
|
||||
}
|
||||
|
||||
/* Scan one source file for top-level `import IDENT;`, resolving each. A
|
||||
* DIRECTORY import is a package boundary: add it as a direct dep of pkg
|
||||
* `pi`. A FILE import is an intra-package split — fold its imports into
|
||||
* `pi` (its bytes join pi's body at emit time). Mirrors expand's scan
|
||||
* but collects package PATHS instead of concatenating bytes (§1.1). */
|
||||
static int
|
||||
sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
||||
const char *searchpath, struct ImportSet *filevisit)
|
||||
{
|
||||
if (import_seen(filevisit, file)) return 0;
|
||||
import_add(filevisit, file);
|
||||
FILE *in = fopen(file, "rb");
|
||||
if (in == NULL) {
|
||||
fprintf(stderr, "ww --sep: cannot read %s\n", file);
|
||||
return -1;
|
||||
}
|
||||
char line[2048];
|
||||
int rc = 0;
|
||||
while (fgets(line, sizeof line, in)) {
|
||||
const char *p = line;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (strncmp(p, "import ", 7) != 0 && strncmp(p, "import\t", 7) != 0)
|
||||
continue;
|
||||
p += 7;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
char name[256] = {0};
|
||||
int j = 0;
|
||||
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
||||
|| *p == '_' || *p == '.' || (*p >= '0' && *p <= '9'))
|
||||
if (j + 1 < (int)sizeof name) name[j++] = *p++;
|
||||
if (j == 0) continue;
|
||||
char path_form[256];
|
||||
import_path_form(name, path_form, sizeof path_form);
|
||||
char ipath[1024];
|
||||
int is_dir = 0;
|
||||
/* a locate-miss is an inline-satisfied (single-file multi-
|
||||
* package) import; --sep targets directory packages, so the
|
||||
* combined path owns that case. Skip, mirroring expand. */
|
||||
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
||||
&is_dir))
|
||||
continue;
|
||||
if (is_dir) {
|
||||
int di = sep_find_or_add(g, name, ipath, 1);
|
||||
if (di < 0) { rc = -1; break; }
|
||||
int seen = 0;
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
||||
if (g->pkg[pi].deps[k] == di) { seen = 1; break; }
|
||||
if (!seen) {
|
||||
if (g->pkg[pi].ndeps >= SEP_MAXPKG) { rc = -1; break; }
|
||||
g->pkg[pi].deps[g->pkg[pi].ndeps++] = di;
|
||||
}
|
||||
} else if (sep_scan_file(g, pi, ipath, searchpath, filevisit) < 0) {
|
||||
rc = -1; break;
|
||||
}
|
||||
}
|
||||
fclose(in);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Discover pkg pi's direct deps + recurse. Enumerate the package's own
|
||||
* source files (dir → *.ww less *test.ww; file → the file) and scan
|
||||
* each. `color` doubles as a scanned-marker here (2 == scanned); it is
|
||||
* reset to white before the topo pass. */
|
||||
static int
|
||||
sep_scan_pkg(struct sepgraph *g, int pi, const char *searchpath)
|
||||
{
|
||||
if (g->pkg[pi].color == 2) return 0;
|
||||
g->pkg[pi].color = 2;
|
||||
struct ImportSet fv = {0};
|
||||
int rc = 0;
|
||||
if (g->pkg[pi].is_dir) {
|
||||
char **files = NULL;
|
||||
int n = enumerate_dir_ww(g->pkg[pi].entry, &files);
|
||||
for (int i = 0; i < n && rc == 0; i++) {
|
||||
char fp[1024];
|
||||
snprintf(fp, sizeof fp, "%s/%s", g->pkg[pi].entry, files[i]);
|
||||
rc = sep_scan_file(g, pi, fp, searchpath, &fv);
|
||||
}
|
||||
for (int i = 0; i < n; i++) free(files[i]);
|
||||
free(files);
|
||||
} else {
|
||||
rc = sep_scan_file(g, pi, g->pkg[pi].entry, searchpath, &fv);
|
||||
}
|
||||
for (int i = 0; i < fv.n; i++) free(fv.paths[i]);
|
||||
free(fv.paths);
|
||||
if (rc < 0) return rc;
|
||||
/* recurse into freshly-added deps (sep_find_or_add may have grown
|
||||
* g->n during the scan; iterate by index). */
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
||||
if (sep_scan_pkg(g, g->pkg[pi].deps[k], searchpath) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* DFS post-order over the dep DAG → reverse-topo (deps before importer),
|
||||
* cite Hare gather (deps.ha:123). Tri-color: a back-edge BAILS rather
|
||||
* than spinning. Commit 3 assumes acyclic; the LOUD cycle reject is
|
||||
* commit 4 (this just must not infinite-loop). */
|
||||
static int
|
||||
sep_topo_visit(struct sepgraph *g, int pi, int *order, int *no)
|
||||
{
|
||||
if (g->pkg[pi].color == 2) return 0;
|
||||
if (g->pkg[pi].color == 1) {
|
||||
fprintf(stderr,
|
||||
"ww --sep: import cycle (loud reject lands commit 4)\n");
|
||||
return -1;
|
||||
}
|
||||
g->pkg[pi].color = 1;
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++)
|
||||
if (sep_topo_visit(g, g->pkg[pi].deps[k], order, no) < 0)
|
||||
return -1;
|
||||
g->pkg[pi].color = 2;
|
||||
order[(*no)++] = pi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mark pi's transitive deps (excluding pi) in inset[]. */
|
||||
static void
|
||||
sep_mark_deps(struct sepgraph *g, int pi, char *inset)
|
||||
{
|
||||
for (int k = 0; k < g->pkg[pi].ndeps; k++) {
|
||||
int di = g->pkg[pi].deps[k];
|
||||
if (!inset[di]) { inset[di] = 1; sep_mark_deps(g, di, inset); }
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit one of pi's own source files into the sep-unit under the
|
||||
* //ww:module-reset primary boundary (so -c emits its decls, imported
|
||||
* ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead of the
|
||||
* body); FILE imports fold in (intra-package split). */
|
||||
static void
|
||||
sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
||||
const char *searchpath)
|
||||
{
|
||||
if (import_seen(visited, path)) return;
|
||||
import_add(visited, path);
|
||||
FILE *in = fopen(path, "rb");
|
||||
if (in == NULL) {
|
||||
fprintf(stderr, "ww --sep: cannot read %s\n", path);
|
||||
return;
|
||||
}
|
||||
char line[2048];
|
||||
while (fgets(line, sizeof line, in)) {
|
||||
const char *p = line;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (strncmp(p, "import ", 7) != 0 && strncmp(p, "import\t", 7) != 0)
|
||||
continue;
|
||||
p += 7;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
char name[256] = {0};
|
||||
int j = 0;
|
||||
while ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
||||
|| *p == '_' || *p == '.' || (*p >= '0' && *p <= '9'))
|
||||
if (j + 1 < (int)sizeof name) name[j++] = *p++;
|
||||
if (j == 0) continue;
|
||||
char path_form[256];
|
||||
import_path_form(name, path_form, sizeof path_form);
|
||||
char ipath[1024];
|
||||
int is_dir = 0;
|
||||
if (!locate_import(searchpath, path_form, ipath, sizeof ipath,
|
||||
&is_dir))
|
||||
continue;
|
||||
if (!is_dir)
|
||||
sep_emit_body(out, ipath, visited, searchpath);
|
||||
}
|
||||
fputs("//ww:module-reset\n", out);
|
||||
rewind(in);
|
||||
int ch;
|
||||
while ((ch = fgetc(in)) != EOF) fputc(ch, out);
|
||||
fputc('\n', out);
|
||||
fclose(in);
|
||||
}
|
||||
|
||||
static void
|
||||
sep_emit_dir_body(FILE *out, const char *dir, struct ImportSet *visited,
|
||||
const char *searchpath)
|
||||
{
|
||||
char **files = NULL;
|
||||
int n = enumerate_dir_ww(dir, &files);
|
||||
for (int i = 0; i < n; i++) {
|
||||
char fp[1024];
|
||||
snprintf(fp, sizeof fp, "%s/%s", dir, files[i]);
|
||||
sep_emit_body(out, fp, visited, searchpath);
|
||||
free(files[i]);
|
||||
}
|
||||
free(files);
|
||||
}
|
||||
|
||||
/* Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
||||
* (reverse-topo order, each tagged by its dotted path), then pi's own
|
||||
* body under //ww:module-reset. */
|
||||
static int
|
||||
sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
|
||||
const int *order, int norder, const char *searchpath, const char *unitf)
|
||||
{
|
||||
FILE *u = fopen(unitf, "wb");
|
||||
if (u == NULL) {
|
||||
fprintf(stderr, "ww --sep: cannot open %s\n", unitf);
|
||||
return -1;
|
||||
}
|
||||
char inset[SEP_MAXPKG] = {0};
|
||||
sep_mark_deps(g, pi, inset);
|
||||
for (int oi = 0; oi < norder; oi++) {
|
||||
int dj = order[oi];
|
||||
if (dj == pi || !inset[dj]) continue;
|
||||
char wwi[1024];
|
||||
sep_fname(g, dj, scratch, ".wwi", wwi, sizeof wwi);
|
||||
FILE *wf = fopen(wwi, "rb");
|
||||
if (wf == NULL) {
|
||||
fprintf(stderr, "ww --sep: missing %s\n", wwi);
|
||||
fclose(u);
|
||||
return -1;
|
||||
}
|
||||
fprintf(u, "//ww:module %s\n", g->pkg[dj].path);
|
||||
int ch;
|
||||
while ((ch = fgetc(wf)) != EOF) fputc(ch, u);
|
||||
fputc('\n', u);
|
||||
fclose(wf);
|
||||
}
|
||||
struct ImportSet bodyvisit = {0};
|
||||
if (g->pkg[pi].is_dir)
|
||||
sep_emit_dir_body(u, g->pkg[pi].entry, &bodyvisit, searchpath);
|
||||
else
|
||||
sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath);
|
||||
for (int i = 0; i < bodyvisit.n; i++) free(bodyvisit.paths[i]);
|
||||
free(bodyvisit.paths);
|
||||
fclose(u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* build_one_sep — the --sep orchestration: discover_deps, reverse_topo,
|
||||
* the transitive producer loop (one `w6c -c -I` per package, dep-first),
|
||||
* then a flat `w6l` of the `.o` set (per-pkg `.a` + multi-archive link
|
||||
* is commit 4). Side files land in a cold `<stem>.sepwork` scratch dir
|
||||
* (the structured cache is commit 5). */
|
||||
static int
|
||||
build_one_sep(const char *src, int entry_is_dir, const char *out,
|
||||
const char *objstem, const char *extra_includes, const char *extra_libs,
|
||||
const char *extra_libdirs)
|
||||
{
|
||||
const char *c6 = toolpath("WW_W6C", "w6c");
|
||||
const char *a6 = toolpath("WW_W6A", "w6a");
|
||||
const char *l6 = toolpath("WW_W6L", "w6l");
|
||||
const char *libdir = getenv("WW_LIB");
|
||||
if (libdir == NULL || libdir[0] == 0) {
|
||||
static char libbuf[1024];
|
||||
snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir);
|
||||
libdir = libbuf;
|
||||
}
|
||||
const char *srcdir = getenv("WW_SRCLIB");
|
||||
static char srcbuf[1024];
|
||||
if (srcdir == NULL || srcdir[0] == 0) {
|
||||
snprintf(srcbuf, sizeof srcbuf, "%s/../../lib", self_dir);
|
||||
if (access(srcbuf, 0) == 0) srcdir = srcbuf;
|
||||
else if (access("lib", 0) == 0) srcdir = "lib";
|
||||
else srcdir = libdir;
|
||||
}
|
||||
/* search path: source-dir, then -I dirs, then srcdir (mirrors
|
||||
* build_one). */
|
||||
char srcd[1024];
|
||||
if (entry_is_dir) {
|
||||
snprintf(srcd, sizeof srcd, "%s", src);
|
||||
size_t n = strlen(srcd);
|
||||
while (n > 1 && srcd[n-1] == '/') srcd[--n] = '\0';
|
||||
} else {
|
||||
const char *slash = strrchr(src, '/');
|
||||
if (slash) {
|
||||
size_t n = (size_t)(slash - src);
|
||||
if (n >= sizeof srcd) n = sizeof srcd - 1;
|
||||
memcpy(srcd, src, n);
|
||||
srcd[n] = '\0';
|
||||
} else { srcd[0] = '.'; srcd[1] = '\0'; }
|
||||
}
|
||||
static char searchpath[4096];
|
||||
if (extra_includes && extra_includes[0])
|
||||
snprintf(searchpath, sizeof searchpath, "%s:%s:%s",
|
||||
srcd, extra_includes, srcdir);
|
||||
else
|
||||
snprintf(searchpath, sizeof searchpath, "%s:%s", srcd, srcdir);
|
||||
srcdir = searchpath;
|
||||
|
||||
char stem[1024];
|
||||
if (entry_is_dir) {
|
||||
const char *b = strrchr(srcd, '/');
|
||||
const char *base = b ? b + 1 : srcd;
|
||||
snprintf(stem, sizeof stem, "%s/%s", srcd, base);
|
||||
} else {
|
||||
snprintf(stem, sizeof stem, "%s", src);
|
||||
char *dot = strrchr(stem, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
}
|
||||
const char *ostem = (objstem && objstem[0]) ? objstem : stem;
|
||||
char scratch[1100];
|
||||
snprintf(scratch, sizeof scratch, "%s.sepwork", ostem);
|
||||
{ char m[1200]; snprintf(m, sizeof m, "rm -rf %s", scratch); run(m); }
|
||||
if (mkdir(scratch, 0755) != 0) {
|
||||
fprintf(stderr, "ww --sep: cannot create scratch %s\n", scratch);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct sepgraph *g = calloc(1, sizeof *g);
|
||||
if (g == NULL) return 1;
|
||||
int root = sep_find_or_add(g, "", src, entry_is_dir);
|
||||
if (root < 0 || sep_scan_pkg(g, root, srcdir) < 0) { free(g); return 1; }
|
||||
for (int i = 0; i < g->n; i++) g->pkg[i].color = 0;
|
||||
int *order = calloc((size_t)g->n, sizeof *order);
|
||||
int norder = 0;
|
||||
if (order == NULL || sep_topo_visit(g, root, order, &norder) < 0) {
|
||||
free(order); free(g); return 1;
|
||||
}
|
||||
|
||||
/* producer loop — dep-first, one `w6c -c -I` pass per package. */
|
||||
for (int oi = 0; oi < norder; oi++) {
|
||||
int pi = order[oi];
|
||||
char unitf[1024], wwi[1024], asmf[1024], obj[1024], cmd[8192];
|
||||
sep_fname(g, pi, scratch, ".unit.ww", unitf, sizeof unitf);
|
||||
sep_fname(g, pi, scratch, ".wwi", wwi, sizeof wwi);
|
||||
sep_fname(g, pi, scratch, ".s", asmf, sizeof asmf);
|
||||
sep_fname(g, pi, scratch, ".o", obj, sizeof obj);
|
||||
if (sep_compose_unit(g, pi, scratch, order, norder, srcdir,
|
||||
unitf) < 0) { free(order); free(g); return 1; }
|
||||
snprintf(cmd, sizeof cmd, "%s -c -I %s -o %s %s",
|
||||
c6, wwi, asmf, unitf);
|
||||
if (run(cmd) != 0) {
|
||||
fprintf(stderr, "ww --sep: w6c failed for %s\n",
|
||||
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
|
||||
free(order); free(g); return 1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s", a6, obj, asmf);
|
||||
if (run(cmd) != 0) {
|
||||
fprintf(stderr, "ww --sep: w6a failed for %s\n",
|
||||
g->pkg[pi].path[0] ? g->pkg[pi].path : "(root)");
|
||||
free(order); free(g); return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* flat link: root.o first (order[norder-1]), deps after; runtime
|
||||
* archive selectively pulls only undefined runtime symbols (commit 4
|
||||
* adds per-pkg `.a` + multi-archive reverse-topo link). */
|
||||
char rtargs[2048] = {0};
|
||||
char rtpath[1024];
|
||||
snprintf(rtpath, sizeof rtpath, "%s/libwwrt.a", libdir);
|
||||
if (access(rtpath, 0) == 0) {
|
||||
snprintf(rtargs, sizeof rtargs, "%s", rtpath);
|
||||
} else {
|
||||
char a1[1024], a2[1024];
|
||||
snprintf(a1, sizeof a1, "%s/../obj/rt/start.o", self_dir);
|
||||
snprintf(a2, sizeof a2, "%s/../obj/rt/syscall.o", self_dir);
|
||||
snprintf(rtargs, sizeof rtargs, "%s %s", a1, a2);
|
||||
}
|
||||
char objs[8192] = {0};
|
||||
for (int oi = norder - 1; oi >= 0; oi--) {
|
||||
char obj[1024];
|
||||
sep_fname(g, order[oi], scratch, ".o", obj, sizeof obj);
|
||||
size_t n = strlen(objs);
|
||||
snprintf(objs + n, sizeof objs - n, "%s%s", n ? " " : "", obj);
|
||||
}
|
||||
const char *libargs = (extra_libs && extra_libs[0]) ? extra_libs : "";
|
||||
const char *libdirset = (extra_libdirs && extra_libdirs[0]) ? extra_libdirs : "";
|
||||
char cmd[16384];
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s %s%s%s%s%s",
|
||||
l6, out, objs, rtargs,
|
||||
libdirset[0] ? " " : "", libdirset,
|
||||
libargs[0] ? " " : "", libargs);
|
||||
int rc = run(cmd);
|
||||
free(order); free(g);
|
||||
if (rc != 0) { fprintf(stderr, "ww --sep: w6l failed\n"); return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
do_version(void)
|
||||
{
|
||||
@@ -694,12 +1139,25 @@ parse_build_flags(const char *cmd, int argc, char **argv,
|
||||
char *libdirs, size_t libdirsz,
|
||||
char *libs, size_t libsz,
|
||||
char *outpath, size_t outsz,
|
||||
const char **src_out)
|
||||
const char **src_out, int *want_sep)
|
||||
{
|
||||
*src_out = NULL;
|
||||
int i = 0;
|
||||
for (; i < argc; i++) {
|
||||
if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
||||
if (strcmp(argv[i], "--sep") == 0) {
|
||||
/* M3-tail c3: separate-compilation path, build-only.
|
||||
* `run` has no sep-compile-then-run path (out of #46
|
||||
* commit-3 scope), so it LOUD-REJECTS rather than
|
||||
* silently swallowing a typed flag — both stages reject
|
||||
* identically (rule 10; want_sep==NULL marks the run
|
||||
* caller). */
|
||||
if (want_sep == NULL) {
|
||||
fprintf(stderr,
|
||||
"ww %s: --sep is only valid with build\n", cmd);
|
||||
return -1;
|
||||
}
|
||||
*want_sep = 1;
|
||||
} else if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) {
|
||||
size_t n = strlen(libs);
|
||||
snprintf(libs + n, libsz - n,
|
||||
"%s%s", n ? " " : "", argv[i]);
|
||||
@@ -764,9 +1222,10 @@ do_build(int argc, char **argv)
|
||||
char libdirs[2048] = {0};
|
||||
char incs[2048] = {0};
|
||||
char outflag[1024] = {0};
|
||||
int want_sep = 0;
|
||||
if (parse_build_flags("build", argc, argv, incs, sizeof incs,
|
||||
libdirs, sizeof libdirs, libs, sizeof libs,
|
||||
outflag, sizeof outflag, &src) < 0)
|
||||
outflag, sizeof outflag, &src, &want_sep) < 0)
|
||||
return 2;
|
||||
if (src == NULL) src = "."; /* default: build cwd */
|
||||
char resolved[1024];
|
||||
@@ -792,6 +1251,9 @@ do_build(int argc, char **argv)
|
||||
} else {
|
||||
basename_no_ext(resolved, out, sizeof out);
|
||||
}
|
||||
if (want_sep)
|
||||
return build_one_sep(resolved, is_dir, out, objstem, incs,
|
||||
libs, libdirs);
|
||||
return build_one(resolved, is_dir, out, objstem, incs, libs, libdirs, 0);
|
||||
}
|
||||
|
||||
@@ -805,7 +1267,7 @@ do_run(int argc, char **argv)
|
||||
char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */
|
||||
int next = parse_build_flags("run", argc, argv, incs, sizeof incs,
|
||||
libdirs, sizeof libdirs, libs, sizeof libs,
|
||||
outflag, sizeof outflag, &src);
|
||||
outflag, sizeof outflag, &src, NULL);
|
||||
if (next < 0) return 2;
|
||||
if (src == NULL) src = ".";
|
||||
char resolved[1024];
|
||||
|
||||
@@ -3720,6 +3720,545 @@ type lflags = struct {
|
||||
nlibs: i32,
|
||||
};
|
||||
|
||||
// ---- ww build --sep — M3-tail separate-compilation driver ------------
|
||||
//
|
||||
// Port of cmd/ww/main.c build_one_sep (task #46/c3). The `--sep` path
|
||||
// materializes each imported package's `.wwi` interface and compiles
|
||||
// every package on its own (`w6c -c`), then flat-links the `.o` set.
|
||||
// combined.ww stays the DEFAULT live path; --sep is additive.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// The load-bearing rule (#56, rob-resolved): every dep is tagged by its
|
||||
// FULL DOTTED import path on prepend (`//ww:module <path>`), so the
|
||||
// definer's qualified symbol (#53) equals the consumer's qualified
|
||||
// reference (#40) and the sep `.o`s link. The prepend is the TRANSITIVE
|
||||
// closure of a package's deps (lead-ratified): a dep's interface can
|
||||
// name a transitive dep's type, so the consuming unit needs the whole
|
||||
// closure for resolution. The unit composition is byte-identical to the
|
||||
// cstage driver (rule 10) so w6c/w6c_ww emit identical `.s`.
|
||||
|
||||
def SEP_MAXPKG: i32 = 256;
|
||||
|
||||
type seppkg = struct {
|
||||
path: *u8, // dotted import path, NUL-term; root path[0]==0
|
||||
entry: *u8, // resolved package dir (or file, file root), NUL-term
|
||||
isdir: i32,
|
||||
deps: []i32, // direct-dep indices into sepgraph.pkg
|
||||
ndeps: i32,
|
||||
color: i32, // tri-color DFS: 0 white, 1 gray, 2 black
|
||||
};
|
||||
|
||||
type sepgraph = struct {
|
||||
pkg: []seppkg, // alloc'd SEP_MAXPKG
|
||||
n: i32,
|
||||
};
|
||||
|
||||
// Find a package by dotted path, or add it. Returns index, -1 if full.
|
||||
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < g.n) {
|
||||
if (cstreq(g.pkg[i].path, path)) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
if (g.n >= SEP_MAXPKG) {
|
||||
cerr("ww --sep: too many packages\n");
|
||||
return -1;
|
||||
};
|
||||
let plen: u64 = cstrlen(path);
|
||||
let elen: u64 = cstrlen(entry);
|
||||
g.pkg[g.n].path = arenadupcstr(path, plen);
|
||||
g.pkg[g.n].entry = arenadupcstr(entry, elen);
|
||||
g.pkg[g.n].isdir = isdir;
|
||||
let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!;
|
||||
dslot.len = SEP_MAXPKG;
|
||||
g.pkg[g.n].deps = dslot;
|
||||
g.pkg[g.n].ndeps = 0;
|
||||
g.pkg[g.n].color = 0;
|
||||
let r: i32 = g.n;
|
||||
g.n += 1;
|
||||
return r;
|
||||
};
|
||||
|
||||
// Build "<scratch>/<base><suffix>" NUL-term; base = path, or "__root"
|
||||
// for the empty root path.
|
||||
fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
|
||||
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
buf.len = os.PATH_MAX;
|
||||
let off: u64 = cstrinto(buf.ptr, 0u64, scratch);
|
||||
off = byteinto(buf.ptr, off, 47u8); // '/'
|
||||
if (g.pkg[pi].path[0u64] != 0u8) {
|
||||
off = cstrinto(buf.ptr, off, g.pkg[pi].path);
|
||||
} else {
|
||||
off = strinto(buf.ptr, off, "__root");
|
||||
};
|
||||
off = strinto(buf.ptr, off, suffix);
|
||||
cstrseal(buf.ptr, off);
|
||||
return buf.ptr;
|
||||
};
|
||||
|
||||
// Scan one source file for top-level `import IDENT;`. A DIRECTORY import
|
||||
// is a package boundary: add as a direct dep of pi. A FILE import is an
|
||||
// intra-package split: fold its imports into pi. Mirrors cstage
|
||||
// sep_scan_file (collects PATHS, not bytes).
|
||||
fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
||||
fv: *expctx) i32 = {
|
||||
let fview: str;
|
||||
fview.ptr = file;
|
||||
fview.len = cstrlen(file): i32;
|
||||
let fdup: str = strings.dup(fview);
|
||||
if (visitseen(fv, fdup)) { return 0; };
|
||||
visitadd(fv, fdup);
|
||||
let bufp: *u8;
|
||||
let blen: u64;
|
||||
bufp, blen = slurp(file);
|
||||
if (bufp == nil) {
|
||||
cerr("ww --sep: cannot read source\n");
|
||||
return -1;
|
||||
};
|
||||
let i: u64 = 0u64;
|
||||
for (i < blen) {
|
||||
let j: u64 = i;
|
||||
for (j < blen) {
|
||||
if (bufp[j] == 10u8) { break; }; // '\n'
|
||||
j += 1u64;
|
||||
};
|
||||
let idp: *u8;
|
||||
let idn: u64;
|
||||
idp, idn = scanuse(bufp + i, j - i);
|
||||
if (idp != nil) {
|
||||
let isdir: i32 = 0;
|
||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||
if (ipath != nil) {
|
||||
if (isdir != 0) {
|
||||
let nm: []u8 = alloc([], idn + 1u64)!;
|
||||
let k: u64 = 0u64;
|
||||
for (k < idn) { nm[k] = idp[k]; k += 1u64; };
|
||||
nm[idn] = 0u8;
|
||||
let di: i32 = sepfindoradd(g, nm.ptr, ipath, 1);
|
||||
if (di < 0) { return -1; };
|
||||
let seen: bool = false;
|
||||
let m: i32 = 0;
|
||||
for (m < g.pkg[pi].ndeps) {
|
||||
if (g.pkg[pi].deps[m] == di) { seen = true; };
|
||||
m += 1;
|
||||
};
|
||||
if (!seen) {
|
||||
if (g.pkg[pi].ndeps >= SEP_MAXPKG) { return -1; };
|
||||
g.pkg[pi].deps[g.pkg[pi].ndeps] = di;
|
||||
g.pkg[pi].ndeps += 1;
|
||||
};
|
||||
} else {
|
||||
if (sepscanfile(g, pi, ipath, searchpath, fv) < 0) {
|
||||
return -1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
i = j + 1u64;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Discover pi's direct deps + recurse. Enumerate the package's own
|
||||
// files (dir → *.ww less *test.ww; file → the file) and scan each.
|
||||
// `color` doubles as a scanned-marker (2); reset to white before topo.
|
||||
fn sepscanpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
|
||||
if (g.pkg[pi].color == 2) { return 0; };
|
||||
g.pkg[pi].color = 2;
|
||||
let fv: expctx;
|
||||
fv.out = -1;
|
||||
fv.dirs = searchpath;
|
||||
fv.visit = nil;
|
||||
let rc: i32 = 0;
|
||||
if (g.pkg[pi].isdir != 0) {
|
||||
let names: **u8;
|
||||
let n: i32;
|
||||
names, n = enumeratedir(g.pkg[pi].entry);
|
||||
let dlen: u64 = cstrlen(g.pkg[pi].entry);
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
if (rc == 0) {
|
||||
let nlen: u64 = cstrlen(names[i]);
|
||||
let fp: []u8 = alloc([], dlen + 1u64 + nlen + 1u64)!;
|
||||
let k: u64 = 0u64;
|
||||
for (k < dlen) { fp[k] = g.pkg[pi].entry[k]; k += 1u64; };
|
||||
fp[dlen] = 47u8; // '/'
|
||||
k = 0u64;
|
||||
for (k < nlen) { fp[dlen + 1u64 + k] = names[i][k]; k += 1u64; };
|
||||
fp[dlen + 1u64 + nlen] = 0u8;
|
||||
rc = sepscanfile(g, pi, fp.ptr, searchpath, &fv);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
} else {
|
||||
rc = sepscanfile(g, pi, g.pkg[pi].entry, searchpath, &fv);
|
||||
};
|
||||
if (rc < 0) { return rc; };
|
||||
let k: i32 = 0;
|
||||
for (k < g.pkg[pi].ndeps) {
|
||||
if (sepscanpkg(g, g.pkg[pi].deps[k], searchpath) < 0) { return -1; };
|
||||
k += 1;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// DFS post-order over the dep DAG → reverse-topo (deps before importer).
|
||||
// Tri-color: a back-edge BAILS rather than spinning (loud cycle reject is
|
||||
// commit 4). Cite Hare gather (deps.ha:123).
|
||||
fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32) i32 = {
|
||||
if (g.pkg[pi].color == 2) { return 0; };
|
||||
if (g.pkg[pi].color == 1) {
|
||||
cerr("ww --sep: import cycle (loud reject lands commit 4)\n");
|
||||
return -1;
|
||||
};
|
||||
g.pkg[pi].color = 1;
|
||||
let k: i32 = 0;
|
||||
for (k < g.pkg[pi].ndeps) {
|
||||
if (septopovisit(g, g.pkg[pi].deps[k], order, no) < 0) { return -1; };
|
||||
k += 1;
|
||||
};
|
||||
g.pkg[pi].color = 2;
|
||||
order[*no] = pi;
|
||||
*no += 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Mark pi's transitive deps (excluding pi) in inset[].
|
||||
fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
|
||||
let k: i32 = 0;
|
||||
for (k < g.pkg[pi].ndeps) {
|
||||
let di: i32 = g.pkg[pi].deps[k];
|
||||
if (inset[di] == 0u8) {
|
||||
inset[di] = 1u8;
|
||||
sepmarkdeps(g, di, inset);
|
||||
};
|
||||
k += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// Emit one of pi's own source files into the sep-unit under the
|
||||
// //ww:module-reset primary boundary (so -c emits its decls, imported
|
||||
// ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead);
|
||||
// FILE imports fold in (intra-package split).
|
||||
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8) void = {
|
||||
let pview: str;
|
||||
pview.ptr = path;
|
||||
pview.len = cstrlen(path): i32;
|
||||
let pdup: str = strings.dup(pview);
|
||||
if (visitseen(visit, pdup)) { return; };
|
||||
visitadd(visit, pdup);
|
||||
let bufp: *u8;
|
||||
let blen: u64;
|
||||
bufp, blen = slurp(path);
|
||||
if (bufp == nil) {
|
||||
cerr("ww --sep: cannot read source\n");
|
||||
return;
|
||||
};
|
||||
let i: u64 = 0u64;
|
||||
for (i < blen) {
|
||||
let j: u64 = i;
|
||||
for (j < blen) {
|
||||
if (bufp[j] == 10u8) { break; }; // '\n'
|
||||
j += 1u64;
|
||||
};
|
||||
let idp: *u8;
|
||||
let idn: u64;
|
||||
idp, idn = scanuse(bufp + i, j - i);
|
||||
if (idp != nil) {
|
||||
let isdir: i32 = 0;
|
||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||
if (ipath != nil) {
|
||||
if (isdir == 0) {
|
||||
sepemitbody(fd, ipath, visit, searchpath);
|
||||
};
|
||||
};
|
||||
};
|
||||
i = j + 1u64;
|
||||
};
|
||||
let d: str = "//ww:module-reset\n";
|
||||
os.writeall(fd, d.ptr, d.len: u64);
|
||||
os.writeall(fd, bufp, blen);
|
||||
os.writeall(fd, "\n".ptr, 1u64);
|
||||
};
|
||||
|
||||
fn sepemitdirbody(fd: i32, dir: *u8, visit: *expctx, searchpath: *u8) void = {
|
||||
let names: **u8;
|
||||
let n: i32;
|
||||
names, n = enumeratedir(dir);
|
||||
let dlen: u64 = cstrlen(dir);
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let nlen: u64 = cstrlen(names[i]);
|
||||
let fp: []u8 = alloc([], dlen + 1u64 + nlen + 1u64)!;
|
||||
let k: u64 = 0u64;
|
||||
for (k < dlen) { fp[k] = dir[k]; k += 1u64; };
|
||||
fp[dlen] = 47u8; // '/'
|
||||
k = 0u64;
|
||||
for (k < nlen) { fp[dlen + 1u64 + k] = names[i][k]; k += 1u64; };
|
||||
fp[dlen + 1u64 + nlen] = 0u8;
|
||||
sepemitbody(fd, fp.ptr, visit, searchpath);
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
||||
// (reverse-topo order, each tagged by its dotted path), then pi's own
|
||||
// body under //ww:module-reset.
|
||||
fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, order: []i32,
|
||||
norder: i32, searchpath: *u8, unitf: *u8) i32 = {
|
||||
let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (u < 0) {
|
||||
cerr("ww --sep: cannot open unit\n");
|
||||
return -1;
|
||||
};
|
||||
let inset: []u8 = alloc([], g.n: u64)!;
|
||||
inset.len = g.n;
|
||||
let z: i32 = 0;
|
||||
for (z < g.n) { inset[z] = 0u8; z += 1; };
|
||||
sepmarkdeps(g, pi, inset);
|
||||
let oi: i32 = 0;
|
||||
for (oi < norder) {
|
||||
let dj: i32 = order[oi];
|
||||
if (dj != pi) {
|
||||
if (inset[dj] != 0u8) {
|
||||
let wwi: *u8 = sepfname(g, dj, scratch, ".wwi");
|
||||
let wb: *u8;
|
||||
let wn: u64;
|
||||
wb, wn = slurp(wwi);
|
||||
if (wb == nil) {
|
||||
cerr("ww --sep: missing wwi\n");
|
||||
os.close(u);
|
||||
return -1;
|
||||
};
|
||||
let dm: str = "//ww:module ";
|
||||
os.writeall(u, dm.ptr, dm.len: u64);
|
||||
os.writeall(u, g.pkg[dj].path, cstrlen(g.pkg[dj].path));
|
||||
os.writeall(u, "\n".ptr, 1u64);
|
||||
os.writeall(u, wb, wn);
|
||||
os.writeall(u, "\n".ptr, 1u64);
|
||||
};
|
||||
};
|
||||
oi += 1;
|
||||
};
|
||||
let bv: expctx;
|
||||
bv.out = u;
|
||||
bv.dirs = searchpath;
|
||||
bv.visit = nil;
|
||||
if (g.pkg[pi].isdir != 0) {
|
||||
sepemitdirbody(u, g.pkg[pi].entry, &bv, searchpath);
|
||||
} else {
|
||||
sepemitbody(u, g.pkg[pi].entry, &bv, searchpath);
|
||||
};
|
||||
os.close(u);
|
||||
return 0;
|
||||
};
|
||||
|
||||
// buildonesep — the --sep orchestration: discover deps, reverse-topo,
|
||||
// the transitive producer loop (one `w6c -c -I` per package, dep-first),
|
||||
// then a flat `w6l` of the `.o` set. Side files land in a cold
|
||||
// `<stem>.sepwork` scratch dir. Twin of cstage build_one_sep.
|
||||
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
objstem: *u8, incs: *u8, lf: *lflags) i32 = {
|
||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
||||
|
||||
// Default lib search path: <selfdir>/../../lib
|
||||
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
dotdotlib.len = os.PATH_MAX;
|
||||
{
|
||||
let off: u64 = cstrinto(dotdotlib.ptr, 0u64, selfdir);
|
||||
off = strinto(dotdotlib.ptr, off, "/../../lib");
|
||||
cstrseal(dotdotlib.ptr, off);
|
||||
};
|
||||
|
||||
// Source directory (mirrors buildone).
|
||||
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
srcd.len = os.PATH_MAX;
|
||||
if (entryisdir != 0) {
|
||||
let slen: u64 = cstrlen(src);
|
||||
let k: u64 = 0u64;
|
||||
for (k < slen) { srcd[k] = src[k]; k += 1u64; };
|
||||
for (slen > 1u64) {
|
||||
if (srcd[slen - 1u64] != 47u8) { break; };
|
||||
slen -= 1u64;
|
||||
};
|
||||
srcd[slen] = 0u8;
|
||||
} else {
|
||||
let slen: u64 = cstrlen(src);
|
||||
let last: u64 = slen;
|
||||
let found: bool = false;
|
||||
let i: u64 = slen;
|
||||
for (i > 0u64) {
|
||||
i -= 1u64;
|
||||
if (src[i] == 47u8) { last = i; found = true; i = 0u64; };
|
||||
};
|
||||
if (found) {
|
||||
let k: u64 = 0u64;
|
||||
for (k < last) { srcd[k] = src[k]; k += 1u64; };
|
||||
srcd[last] = 0u8;
|
||||
} else {
|
||||
srcd[0] = 46u8; // '.'
|
||||
srcd[1] = 0u8;
|
||||
};
|
||||
};
|
||||
|
||||
// searchpath = srcd + ':' + incs + ':' + dotdotlib.
|
||||
let searchpath: []u8 = alloc([], (os.PATH_MAX: u64) * 3u64)!;
|
||||
searchpath.len = ((os.PATH_MAX: u64) * 3u64): i32;
|
||||
{
|
||||
let off: u64 = cstrinto(searchpath.ptr, 0u64, srcd.ptr);
|
||||
off = byteinto(searchpath.ptr, off, 58u8); // ':'
|
||||
if (incs[0u64] != 0u8) {
|
||||
off = cstrinto(searchpath.ptr, off, incs);
|
||||
off = byteinto(searchpath.ptr, off, 58u8);
|
||||
};
|
||||
off = cstrinto(searchpath.ptr, off, dotdotlib.ptr);
|
||||
cstrseal(searchpath.ptr, off);
|
||||
};
|
||||
|
||||
// Stem for the scratch dir (mirrors buildone).
|
||||
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
stem.len = os.PATH_MAX;
|
||||
if (entryisdir != 0) {
|
||||
let dlen: u64 = cstrlen(srcd.ptr);
|
||||
let bo: u64 = basenameoff(srcd.ptr, dlen);
|
||||
let off: u64 = cstrinto(stem.ptr, 0u64, srcd.ptr);
|
||||
stem[off] = 47u8; off += 1u64; // '/'
|
||||
let i: u64 = bo;
|
||||
for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; };
|
||||
cstrseal(stem.ptr, off);
|
||||
} else {
|
||||
makestem(stem.ptr, src);
|
||||
};
|
||||
let effstem: *u8 = stem.ptr;
|
||||
if (objstem != nil) { effstem = objstem; };
|
||||
let scratch: *u8 = appendlit(effstem, ".sepwork");
|
||||
|
||||
// rm -rf scratch (cold); recreate. Reuse os.removeall if present;
|
||||
// here we mkdir and rely on TRUNC opens to overwrite stale files.
|
||||
os.mkdir(pathstr(scratch), 493i32); // 0o755 (idempotent; stale files TRUNC'd)
|
||||
|
||||
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
|
||||
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
libwwrt.len = os.PATH_MAX;
|
||||
{
|
||||
let off: u64 = cstrinto(libwwrt.ptr, 0u64, selfdir);
|
||||
off = strinto(libwwrt.ptr, off, "/../lib/libwwrt.a");
|
||||
cstrseal(libwwrt.ptr, off);
|
||||
};
|
||||
|
||||
// Discover.
|
||||
let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!;
|
||||
pkgslot.len = SEP_MAXPKG;
|
||||
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
|
||||
let root: i32 = sepfindoradd(g, "\0".ptr, src, entryisdir);
|
||||
if (root < 0) { return 1; };
|
||||
if (sepscanpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
||||
|
||||
// Reset colors, reverse-topo.
|
||||
let ci: i32 = 0;
|
||||
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
|
||||
let order: []i32 = alloc([], g.n: u64)!;
|
||||
order.len = g.n;
|
||||
let norder: i32 = 0;
|
||||
if (septopovisit(g, root, order, &norder) < 0) { return 1; };
|
||||
|
||||
// Producer loop — dep-first, one `w6c -c -I` per package.
|
||||
let oi: i32 = 0;
|
||||
for (oi < norder) {
|
||||
let pi: i32 = order[oi];
|
||||
let unitf: *u8 = sepfname(g, pi, scratch, ".unit.ww");
|
||||
let wwi: *u8 = sepfname(g, pi, scratch, ".wwi");
|
||||
let asmf: *u8 = sepfname(g, pi, scratch, ".s");
|
||||
let objf: *u8 = sepfname(g, pi, scratch, ".o");
|
||||
if (sepcomposeunit(g, pi, scratch, order, norder, searchpath.ptr, unitf) < 0) {
|
||||
return 1;
|
||||
};
|
||||
{
|
||||
let argv: []*u8 = alloc([], 8u64)!;
|
||||
argv.len = 8;
|
||||
argv[0] = "w6c\0".ptr;
|
||||
argv[1] = "-c\0".ptr;
|
||||
argv[2] = "-I\0".ptr;
|
||||
argv[3] = wwi;
|
||||
argv[4] = "-o\0".ptr;
|
||||
argv[5] = asmf;
|
||||
argv[6] = unitf;
|
||||
argv[7] = nil;
|
||||
if (procrun(c6, argv.ptr) != 0) {
|
||||
cerr("ww --sep: w6c failed\n");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
{
|
||||
let argv: []*u8 = alloc([], 5u64)!;
|
||||
argv.len = 5;
|
||||
argv[0] = "w6a\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = objf;
|
||||
argv[3] = asmf;
|
||||
argv[4] = nil;
|
||||
if (procrun(a6, argv.ptr) != 0) {
|
||||
cerr("ww --sep: w6a failed\n");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
oi += 1;
|
||||
};
|
||||
|
||||
// Flat link: root.o first (order[norder-1]), deps after; libwwrt.a
|
||||
// selectively pulls runtime symbols. argv: 4 fixed (w6l,-o,out) + 1
|
||||
// per .o + 1 libwwrt + 2*nlibdirs + 2*nlibs + 1 nil.
|
||||
let nldirs: i32 = 0;
|
||||
let nllibs: i32 = 0;
|
||||
let ldirs: **u8 = nil;
|
||||
let llibs: **u8 = nil;
|
||||
if (lf != nil) {
|
||||
nldirs = lf.nlibdirs;
|
||||
nllibs = lf.nlibs;
|
||||
ldirs = lf.libdirs;
|
||||
llibs = lf.libs;
|
||||
};
|
||||
let total: i32 = 3 + norder + 1 + 2 * nldirs + 2 * nllibs + 1;
|
||||
let largv: []*u8 = alloc([], total: u64)!;
|
||||
largv.len = total;
|
||||
largv[0] = "w6l\0".ptr;
|
||||
largv[1] = "-o\0".ptr;
|
||||
largv[2] = out;
|
||||
let pos: i32 = 3;
|
||||
let li: i32 = norder - 1;
|
||||
for (li >= 0) {
|
||||
largv[pos] = sepfname(g, order[li], scratch, ".o");
|
||||
pos += 1;
|
||||
li -= 1;
|
||||
};
|
||||
largv[pos] = libwwrt.ptr; pos += 1;
|
||||
let k: i32 = 0;
|
||||
for (k < nldirs) {
|
||||
largv[pos] = "-L\0".ptr;
|
||||
largv[pos + 1] = ldirs[k];
|
||||
pos += 2;
|
||||
k += 1;
|
||||
};
|
||||
k = 0;
|
||||
for (k < nllibs) {
|
||||
largv[pos] = "-l\0".ptr;
|
||||
largv[pos + 1] = llibs[k];
|
||||
pos += 2;
|
||||
k += 1;
|
||||
};
|
||||
largv[pos] = nil;
|
||||
if (procrun(l6, largv.ptr) != 0) {
|
||||
cerr("ww --sep: w6l failed\n");
|
||||
return 1;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// buildone — compile `src` (file or directory) into the executable
|
||||
// named `out`.
|
||||
// selfdir: NUL-terminated dir containing this driver and the
|
||||
@@ -4088,6 +4627,7 @@ fn defaultoutpath(src: *u8) *u8 = {
|
||||
|
||||
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
let src: *u8 = nil;
|
||||
let wantsep: i32 = 0; // --sep: M3-tail separate-compilation path
|
||||
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
|
||||
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
||||
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
||||
@@ -4106,7 +4646,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
for (i < argc) {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) { // '-'
|
||||
if (p[1u64] == 73u8) { // '-I'
|
||||
if (cstreqlit(p, "--sep")) { // M3-tail separate-compile
|
||||
wantsep = 1;
|
||||
} else { if (p[1u64] == 73u8) { // '-I'
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
dir = p + 2u64;
|
||||
@@ -4174,7 +4716,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
} else {
|
||||
cerr("ww build: unknown flag\n");
|
||||
return 2;
|
||||
}; }; }; };
|
||||
}; }; }; }; };
|
||||
} else {
|
||||
if (src == nil) { src = p; };
|
||||
};
|
||||
@@ -4221,6 +4763,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.nlibdirs = nlibdirs;
|
||||
lf.libs = libs.ptr;
|
||||
lf.nlibs = nlibs;
|
||||
if (wantsep != 0) {
|
||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf);
|
||||
};
|
||||
return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
};
|
||||
|
||||
@@ -4276,7 +4821,10 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
else {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) {
|
||||
if (p[1u64] == 73u8) {
|
||||
if (cstreqlit(p, "--sep")) { // build-only; run rejects (rule 10)
|
||||
cerr("ww run: --sep is only valid with build\n");
|
||||
return 2;
|
||||
} else { if (p[1u64] == 73u8) {
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
dir = p + 2u64;
|
||||
@@ -4343,7 +4891,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
} else {
|
||||
cerr("ww run: unknown flag\n");
|
||||
return 2;
|
||||
}; }; }; };
|
||||
}; }; }; }; };
|
||||
i += 1;
|
||||
} else {
|
||||
if (src == nil) {
|
||||
|
||||
@@ -846,6 +846,545 @@ type lflags = struct {
|
||||
nlibs: i32,
|
||||
};
|
||||
|
||||
// ---- ww build --sep — M3-tail separate-compilation driver ------------
|
||||
//
|
||||
// Port of cmd/ww/main.c build_one_sep (task #46/c3). The `--sep` path
|
||||
// materializes each imported package's `.wwi` interface and compiles
|
||||
// every package on its own (`w6c -c`), then flat-links the `.o` set.
|
||||
// combined.ww stays the DEFAULT live path; --sep is additive.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// The load-bearing rule (#56, rob-resolved): every dep is tagged by its
|
||||
// FULL DOTTED import path on prepend (`//ww:module <path>`), so the
|
||||
// definer's qualified symbol (#53) equals the consumer's qualified
|
||||
// reference (#40) and the sep `.o`s link. The prepend is the TRANSITIVE
|
||||
// closure of a package's deps (lead-ratified): a dep's interface can
|
||||
// name a transitive dep's type, so the consuming unit needs the whole
|
||||
// closure for resolution. The unit composition is byte-identical to the
|
||||
// cstage driver (rule 10) so w6c/w6c_ww emit identical `.s`.
|
||||
|
||||
def SEP_MAXPKG: i32 = 256;
|
||||
|
||||
type seppkg = struct {
|
||||
path: *u8, // dotted import path, NUL-term; root path[0]==0
|
||||
entry: *u8, // resolved package dir (or file, file root), NUL-term
|
||||
isdir: i32,
|
||||
deps: []i32, // direct-dep indices into sepgraph.pkg
|
||||
ndeps: i32,
|
||||
color: i32, // tri-color DFS: 0 white, 1 gray, 2 black
|
||||
};
|
||||
|
||||
type sepgraph = struct {
|
||||
pkg: []seppkg, // alloc'd SEP_MAXPKG
|
||||
n: i32,
|
||||
};
|
||||
|
||||
// Find a package by dotted path, or add it. Returns index, -1 if full.
|
||||
fn sepfindoradd(g: *sepgraph, path: *u8, entry: *u8, isdir: i32) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < g.n) {
|
||||
if (cstreq(g.pkg[i].path, path)) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
if (g.n >= SEP_MAXPKG) {
|
||||
cerr("ww --sep: too many packages\n");
|
||||
return -1;
|
||||
};
|
||||
let plen: u64 = cstrlen(path);
|
||||
let elen: u64 = cstrlen(entry);
|
||||
g.pkg[g.n].path = arenadupcstr(path, plen);
|
||||
g.pkg[g.n].entry = arenadupcstr(entry, elen);
|
||||
g.pkg[g.n].isdir = isdir;
|
||||
let dslot: []i32 = alloc([], SEP_MAXPKG: u64)!;
|
||||
dslot.len = SEP_MAXPKG;
|
||||
g.pkg[g.n].deps = dslot;
|
||||
g.pkg[g.n].ndeps = 0;
|
||||
g.pkg[g.n].color = 0;
|
||||
let r: i32 = g.n;
|
||||
g.n += 1;
|
||||
return r;
|
||||
};
|
||||
|
||||
// Build "<scratch>/<base><suffix>" NUL-term; base = path, or "__root"
|
||||
// for the empty root path.
|
||||
fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = {
|
||||
let buf: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
buf.len = os.PATH_MAX;
|
||||
let off: u64 = cstrinto(buf.ptr, 0u64, scratch);
|
||||
off = byteinto(buf.ptr, off, 47u8); // '/'
|
||||
if (g.pkg[pi].path[0u64] != 0u8) {
|
||||
off = cstrinto(buf.ptr, off, g.pkg[pi].path);
|
||||
} else {
|
||||
off = strinto(buf.ptr, off, "__root");
|
||||
};
|
||||
off = strinto(buf.ptr, off, suffix);
|
||||
cstrseal(buf.ptr, off);
|
||||
return buf.ptr;
|
||||
};
|
||||
|
||||
// Scan one source file for top-level `import IDENT;`. A DIRECTORY import
|
||||
// is a package boundary: add as a direct dep of pi. A FILE import is an
|
||||
// intra-package split: fold its imports into pi. Mirrors cstage
|
||||
// sep_scan_file (collects PATHS, not bytes).
|
||||
fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8,
|
||||
fv: *expctx) i32 = {
|
||||
let fview: str;
|
||||
fview.ptr = file;
|
||||
fview.len = cstrlen(file): i32;
|
||||
let fdup: str = strings.dup(fview);
|
||||
if (visitseen(fv, fdup)) { return 0; };
|
||||
visitadd(fv, fdup);
|
||||
let bufp: *u8;
|
||||
let blen: u64;
|
||||
bufp, blen = slurp(file);
|
||||
if (bufp == nil) {
|
||||
cerr("ww --sep: cannot read source\n");
|
||||
return -1;
|
||||
};
|
||||
let i: u64 = 0u64;
|
||||
for (i < blen) {
|
||||
let j: u64 = i;
|
||||
for (j < blen) {
|
||||
if (bufp[j] == 10u8) { break; }; // '\n'
|
||||
j += 1u64;
|
||||
};
|
||||
let idp: *u8;
|
||||
let idn: u64;
|
||||
idp, idn = scanuse(bufp + i, j - i);
|
||||
if (idp != nil) {
|
||||
let isdir: i32 = 0;
|
||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||
if (ipath != nil) {
|
||||
if (isdir != 0) {
|
||||
let nm: []u8 = alloc([], idn + 1u64)!;
|
||||
let k: u64 = 0u64;
|
||||
for (k < idn) { nm[k] = idp[k]; k += 1u64; };
|
||||
nm[idn] = 0u8;
|
||||
let di: i32 = sepfindoradd(g, nm.ptr, ipath, 1);
|
||||
if (di < 0) { return -1; };
|
||||
let seen: bool = false;
|
||||
let m: i32 = 0;
|
||||
for (m < g.pkg[pi].ndeps) {
|
||||
if (g.pkg[pi].deps[m] == di) { seen = true; };
|
||||
m += 1;
|
||||
};
|
||||
if (!seen) {
|
||||
if (g.pkg[pi].ndeps >= SEP_MAXPKG) { return -1; };
|
||||
g.pkg[pi].deps[g.pkg[pi].ndeps] = di;
|
||||
g.pkg[pi].ndeps += 1;
|
||||
};
|
||||
} else {
|
||||
if (sepscanfile(g, pi, ipath, searchpath, fv) < 0) {
|
||||
return -1;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
i = j + 1u64;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Discover pi's direct deps + recurse. Enumerate the package's own
|
||||
// files (dir → *.ww less *test.ww; file → the file) and scan each.
|
||||
// `color` doubles as a scanned-marker (2); reset to white before topo.
|
||||
fn sepscanpkg(g: *sepgraph, pi: i32, searchpath: *u8) i32 = {
|
||||
if (g.pkg[pi].color == 2) { return 0; };
|
||||
g.pkg[pi].color = 2;
|
||||
let fv: expctx;
|
||||
fv.out = -1;
|
||||
fv.dirs = searchpath;
|
||||
fv.visit = nil;
|
||||
let rc: i32 = 0;
|
||||
if (g.pkg[pi].isdir != 0) {
|
||||
let names: **u8;
|
||||
let n: i32;
|
||||
names, n = enumeratedir(g.pkg[pi].entry);
|
||||
let dlen: u64 = cstrlen(g.pkg[pi].entry);
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
if (rc == 0) {
|
||||
let nlen: u64 = cstrlen(names[i]);
|
||||
let fp: []u8 = alloc([], dlen + 1u64 + nlen + 1u64)!;
|
||||
let k: u64 = 0u64;
|
||||
for (k < dlen) { fp[k] = g.pkg[pi].entry[k]; k += 1u64; };
|
||||
fp[dlen] = 47u8; // '/'
|
||||
k = 0u64;
|
||||
for (k < nlen) { fp[dlen + 1u64 + k] = names[i][k]; k += 1u64; };
|
||||
fp[dlen + 1u64 + nlen] = 0u8;
|
||||
rc = sepscanfile(g, pi, fp.ptr, searchpath, &fv);
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
} else {
|
||||
rc = sepscanfile(g, pi, g.pkg[pi].entry, searchpath, &fv);
|
||||
};
|
||||
if (rc < 0) { return rc; };
|
||||
let k: i32 = 0;
|
||||
for (k < g.pkg[pi].ndeps) {
|
||||
if (sepscanpkg(g, g.pkg[pi].deps[k], searchpath) < 0) { return -1; };
|
||||
k += 1;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// DFS post-order over the dep DAG → reverse-topo (deps before importer).
|
||||
// Tri-color: a back-edge BAILS rather than spinning (loud cycle reject is
|
||||
// commit 4). Cite Hare gather (deps.ha:123).
|
||||
fn septopovisit(g: *sepgraph, pi: i32, order: []i32, no: *i32) i32 = {
|
||||
if (g.pkg[pi].color == 2) { return 0; };
|
||||
if (g.pkg[pi].color == 1) {
|
||||
cerr("ww --sep: import cycle (loud reject lands commit 4)\n");
|
||||
return -1;
|
||||
};
|
||||
g.pkg[pi].color = 1;
|
||||
let k: i32 = 0;
|
||||
for (k < g.pkg[pi].ndeps) {
|
||||
if (septopovisit(g, g.pkg[pi].deps[k], order, no) < 0) { return -1; };
|
||||
k += 1;
|
||||
};
|
||||
g.pkg[pi].color = 2;
|
||||
order[*no] = pi;
|
||||
*no += 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Mark pi's transitive deps (excluding pi) in inset[].
|
||||
fn sepmarkdeps(g: *sepgraph, pi: i32, inset: []u8) void = {
|
||||
let k: i32 = 0;
|
||||
for (k < g.pkg[pi].ndeps) {
|
||||
let di: i32 = g.pkg[pi].deps[k];
|
||||
if (inset[di] == 0u8) {
|
||||
inset[di] = 1u8;
|
||||
sepmarkdeps(g, di, inset);
|
||||
};
|
||||
k += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// Emit one of pi's own source files into the sep-unit under the
|
||||
// //ww:module-reset primary boundary (so -c emits its decls, imported
|
||||
// ==0). DIRECTORY imports are skipped (provided as `.wwi` ahead);
|
||||
// FILE imports fold in (intra-package split).
|
||||
fn sepemitbody(fd: i32, path: *u8, visit: *expctx, searchpath: *u8) void = {
|
||||
let pview: str;
|
||||
pview.ptr = path;
|
||||
pview.len = cstrlen(path): i32;
|
||||
let pdup: str = strings.dup(pview);
|
||||
if (visitseen(visit, pdup)) { return; };
|
||||
visitadd(visit, pdup);
|
||||
let bufp: *u8;
|
||||
let blen: u64;
|
||||
bufp, blen = slurp(path);
|
||||
if (bufp == nil) {
|
||||
cerr("ww --sep: cannot read source\n");
|
||||
return;
|
||||
};
|
||||
let i: u64 = 0u64;
|
||||
for (i < blen) {
|
||||
let j: u64 = i;
|
||||
for (j < blen) {
|
||||
if (bufp[j] == 10u8) { break; }; // '\n'
|
||||
j += 1u64;
|
||||
};
|
||||
let idp: *u8;
|
||||
let idn: u64;
|
||||
idp, idn = scanuse(bufp + i, j - i);
|
||||
if (idp != nil) {
|
||||
let isdir: i32 = 0;
|
||||
let ipath: *u8 = locateimport(searchpath, idp, idn, &isdir);
|
||||
if (ipath != nil) {
|
||||
if (isdir == 0) {
|
||||
sepemitbody(fd, ipath, visit, searchpath);
|
||||
};
|
||||
};
|
||||
};
|
||||
i = j + 1u64;
|
||||
};
|
||||
let d: str = "//ww:module-reset\n";
|
||||
os.writeall(fd, d.ptr, d.len: u64);
|
||||
os.writeall(fd, bufp, blen);
|
||||
os.writeall(fd, "\n".ptr, 1u64);
|
||||
};
|
||||
|
||||
fn sepemitdirbody(fd: i32, dir: *u8, visit: *expctx, searchpath: *u8) void = {
|
||||
let names: **u8;
|
||||
let n: i32;
|
||||
names, n = enumeratedir(dir);
|
||||
let dlen: u64 = cstrlen(dir);
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let nlen: u64 = cstrlen(names[i]);
|
||||
let fp: []u8 = alloc([], dlen + 1u64 + nlen + 1u64)!;
|
||||
let k: u64 = 0u64;
|
||||
for (k < dlen) { fp[k] = dir[k]; k += 1u64; };
|
||||
fp[dlen] = 47u8; // '/'
|
||||
k = 0u64;
|
||||
for (k < nlen) { fp[dlen + 1u64 + k] = names[i][k]; k += 1u64; };
|
||||
fp[dlen + 1u64 + nlen] = 0u8;
|
||||
sepemitbody(fd, fp.ptr, visit, searchpath);
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// Compose pi's sep-unit at `unitf`: the transitive-closure `.wwi`s
|
||||
// (reverse-topo order, each tagged by its dotted path), then pi's own
|
||||
// body under //ww:module-reset.
|
||||
fn sepcomposeunit(g: *sepgraph, pi: i32, scratch: *u8, order: []i32,
|
||||
norder: i32, searchpath: *u8, unitf: *u8) i32 = {
|
||||
let u: i32 = os.open(pathstr(unitf), os.flag.WRONLY | os.flag.CREATE | os.flag.TRUNC, 420i32); // 0o644
|
||||
if (u < 0) {
|
||||
cerr("ww --sep: cannot open unit\n");
|
||||
return -1;
|
||||
};
|
||||
let inset: []u8 = alloc([], g.n: u64)!;
|
||||
inset.len = g.n;
|
||||
let z: i32 = 0;
|
||||
for (z < g.n) { inset[z] = 0u8; z += 1; };
|
||||
sepmarkdeps(g, pi, inset);
|
||||
let oi: i32 = 0;
|
||||
for (oi < norder) {
|
||||
let dj: i32 = order[oi];
|
||||
if (dj != pi) {
|
||||
if (inset[dj] != 0u8) {
|
||||
let wwi: *u8 = sepfname(g, dj, scratch, ".wwi");
|
||||
let wb: *u8;
|
||||
let wn: u64;
|
||||
wb, wn = slurp(wwi);
|
||||
if (wb == nil) {
|
||||
cerr("ww --sep: missing wwi\n");
|
||||
os.close(u);
|
||||
return -1;
|
||||
};
|
||||
let dm: str = "//ww:module ";
|
||||
os.writeall(u, dm.ptr, dm.len: u64);
|
||||
os.writeall(u, g.pkg[dj].path, cstrlen(g.pkg[dj].path));
|
||||
os.writeall(u, "\n".ptr, 1u64);
|
||||
os.writeall(u, wb, wn);
|
||||
os.writeall(u, "\n".ptr, 1u64);
|
||||
};
|
||||
};
|
||||
oi += 1;
|
||||
};
|
||||
let bv: expctx;
|
||||
bv.out = u;
|
||||
bv.dirs = searchpath;
|
||||
bv.visit = nil;
|
||||
if (g.pkg[pi].isdir != 0) {
|
||||
sepemitdirbody(u, g.pkg[pi].entry, &bv, searchpath);
|
||||
} else {
|
||||
sepemitbody(u, g.pkg[pi].entry, &bv, searchpath);
|
||||
};
|
||||
os.close(u);
|
||||
return 0;
|
||||
};
|
||||
|
||||
// buildonesep — the --sep orchestration: discover deps, reverse-topo,
|
||||
// the transitive producer loop (one `w6c -c -I` per package, dep-first),
|
||||
// then a flat `w6l` of the `.o` set. Side files land in a cold
|
||||
// `<stem>.sepwork` scratch dir. Twin of cstage build_one_sep.
|
||||
fn buildonesep(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8,
|
||||
objstem: *u8, incs: *u8, lf: *lflags) i32 = {
|
||||
let c6: *u8 = joinpathlit(selfdir, "w6c_ww");
|
||||
let a6: *u8 = joinpathlit(selfdir, "w6a_ww");
|
||||
let l6: *u8 = joinpathlit(selfdir, "w6l_ww");
|
||||
|
||||
// Default lib search path: <selfdir>/../../lib
|
||||
let dotdotlib: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
dotdotlib.len = os.PATH_MAX;
|
||||
{
|
||||
let off: u64 = cstrinto(dotdotlib.ptr, 0u64, selfdir);
|
||||
off = strinto(dotdotlib.ptr, off, "/../../lib");
|
||||
cstrseal(dotdotlib.ptr, off);
|
||||
};
|
||||
|
||||
// Source directory (mirrors buildone).
|
||||
let srcd: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
srcd.len = os.PATH_MAX;
|
||||
if (entryisdir != 0) {
|
||||
let slen: u64 = cstrlen(src);
|
||||
let k: u64 = 0u64;
|
||||
for (k < slen) { srcd[k] = src[k]; k += 1u64; };
|
||||
for (slen > 1u64) {
|
||||
if (srcd[slen - 1u64] != 47u8) { break; };
|
||||
slen -= 1u64;
|
||||
};
|
||||
srcd[slen] = 0u8;
|
||||
} else {
|
||||
let slen: u64 = cstrlen(src);
|
||||
let last: u64 = slen;
|
||||
let found: bool = false;
|
||||
let i: u64 = slen;
|
||||
for (i > 0u64) {
|
||||
i -= 1u64;
|
||||
if (src[i] == 47u8) { last = i; found = true; i = 0u64; };
|
||||
};
|
||||
if (found) {
|
||||
let k: u64 = 0u64;
|
||||
for (k < last) { srcd[k] = src[k]; k += 1u64; };
|
||||
srcd[last] = 0u8;
|
||||
} else {
|
||||
srcd[0] = 46u8; // '.'
|
||||
srcd[1] = 0u8;
|
||||
};
|
||||
};
|
||||
|
||||
// searchpath = srcd + ':' + incs + ':' + dotdotlib.
|
||||
let searchpath: []u8 = alloc([], (os.PATH_MAX: u64) * 3u64)!;
|
||||
searchpath.len = ((os.PATH_MAX: u64) * 3u64): i32;
|
||||
{
|
||||
let off: u64 = cstrinto(searchpath.ptr, 0u64, srcd.ptr);
|
||||
off = byteinto(searchpath.ptr, off, 58u8); // ':'
|
||||
if (incs[0u64] != 0u8) {
|
||||
off = cstrinto(searchpath.ptr, off, incs);
|
||||
off = byteinto(searchpath.ptr, off, 58u8);
|
||||
};
|
||||
off = cstrinto(searchpath.ptr, off, dotdotlib.ptr);
|
||||
cstrseal(searchpath.ptr, off);
|
||||
};
|
||||
|
||||
// Stem for the scratch dir (mirrors buildone).
|
||||
let stem: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
stem.len = os.PATH_MAX;
|
||||
if (entryisdir != 0) {
|
||||
let dlen: u64 = cstrlen(srcd.ptr);
|
||||
let bo: u64 = basenameoff(srcd.ptr, dlen);
|
||||
let off: u64 = cstrinto(stem.ptr, 0u64, srcd.ptr);
|
||||
stem[off] = 47u8; off += 1u64; // '/'
|
||||
let i: u64 = bo;
|
||||
for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; };
|
||||
cstrseal(stem.ptr, off);
|
||||
} else {
|
||||
makestem(stem.ptr, src);
|
||||
};
|
||||
let effstem: *u8 = stem.ptr;
|
||||
if (objstem != nil) { effstem = objstem; };
|
||||
let scratch: *u8 = appendlit(effstem, ".sepwork");
|
||||
|
||||
// rm -rf scratch (cold); recreate. Reuse os.removeall if present;
|
||||
// here we mkdir and rely on TRUNC opens to overwrite stale files.
|
||||
os.mkdir(pathstr(scratch), 493i32); // 0o755 (idempotent; stale files TRUNC'd)
|
||||
|
||||
// libwwrt.a path: <selfdir>/../lib/libwwrt.a
|
||||
let libwwrt: []u8 = alloc([], (os.PATH_MAX: u64))!;
|
||||
libwwrt.len = os.PATH_MAX;
|
||||
{
|
||||
let off: u64 = cstrinto(libwwrt.ptr, 0u64, selfdir);
|
||||
off = strinto(libwwrt.ptr, off, "/../lib/libwwrt.a");
|
||||
cstrseal(libwwrt.ptr, off);
|
||||
};
|
||||
|
||||
// Discover.
|
||||
let pkgslot: []seppkg = alloc([], SEP_MAXPKG: u64)!;
|
||||
pkgslot.len = SEP_MAXPKG;
|
||||
let g: *sepgraph = alloc(sepgraph{pkg = pkgslot, n = 0})!;
|
||||
let root: i32 = sepfindoradd(g, "\0".ptr, src, entryisdir);
|
||||
if (root < 0) { return 1; };
|
||||
if (sepscanpkg(g, root, searchpath.ptr) < 0) { return 1; };
|
||||
|
||||
// Reset colors, reverse-topo.
|
||||
let ci: i32 = 0;
|
||||
for (ci < g.n) { g.pkg[ci].color = 0; ci += 1; };
|
||||
let order: []i32 = alloc([], g.n: u64)!;
|
||||
order.len = g.n;
|
||||
let norder: i32 = 0;
|
||||
if (septopovisit(g, root, order, &norder) < 0) { return 1; };
|
||||
|
||||
// Producer loop — dep-first, one `w6c -c -I` per package.
|
||||
let oi: i32 = 0;
|
||||
for (oi < norder) {
|
||||
let pi: i32 = order[oi];
|
||||
let unitf: *u8 = sepfname(g, pi, scratch, ".unit.ww");
|
||||
let wwi: *u8 = sepfname(g, pi, scratch, ".wwi");
|
||||
let asmf: *u8 = sepfname(g, pi, scratch, ".s");
|
||||
let objf: *u8 = sepfname(g, pi, scratch, ".o");
|
||||
if (sepcomposeunit(g, pi, scratch, order, norder, searchpath.ptr, unitf) < 0) {
|
||||
return 1;
|
||||
};
|
||||
{
|
||||
let argv: []*u8 = alloc([], 8u64)!;
|
||||
argv.len = 8;
|
||||
argv[0] = "w6c\0".ptr;
|
||||
argv[1] = "-c\0".ptr;
|
||||
argv[2] = "-I\0".ptr;
|
||||
argv[3] = wwi;
|
||||
argv[4] = "-o\0".ptr;
|
||||
argv[5] = asmf;
|
||||
argv[6] = unitf;
|
||||
argv[7] = nil;
|
||||
if (procrun(c6, argv.ptr) != 0) {
|
||||
cerr("ww --sep: w6c failed\n");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
{
|
||||
let argv: []*u8 = alloc([], 5u64)!;
|
||||
argv.len = 5;
|
||||
argv[0] = "w6a\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = objf;
|
||||
argv[3] = asmf;
|
||||
argv[4] = nil;
|
||||
if (procrun(a6, argv.ptr) != 0) {
|
||||
cerr("ww --sep: w6a failed\n");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
oi += 1;
|
||||
};
|
||||
|
||||
// Flat link: root.o first (order[norder-1]), deps after; libwwrt.a
|
||||
// selectively pulls runtime symbols. argv: 4 fixed (w6l,-o,out) + 1
|
||||
// per .o + 1 libwwrt + 2*nlibdirs + 2*nlibs + 1 nil.
|
||||
let nldirs: i32 = 0;
|
||||
let nllibs: i32 = 0;
|
||||
let ldirs: **u8 = nil;
|
||||
let llibs: **u8 = nil;
|
||||
if (lf != nil) {
|
||||
nldirs = lf.nlibdirs;
|
||||
nllibs = lf.nlibs;
|
||||
ldirs = lf.libdirs;
|
||||
llibs = lf.libs;
|
||||
};
|
||||
let total: i32 = 3 + norder + 1 + 2 * nldirs + 2 * nllibs + 1;
|
||||
let largv: []*u8 = alloc([], total: u64)!;
|
||||
largv.len = total;
|
||||
largv[0] = "w6l\0".ptr;
|
||||
largv[1] = "-o\0".ptr;
|
||||
largv[2] = out;
|
||||
let pos: i32 = 3;
|
||||
let li: i32 = norder - 1;
|
||||
for (li >= 0) {
|
||||
largv[pos] = sepfname(g, order[li], scratch, ".o");
|
||||
pos += 1;
|
||||
li -= 1;
|
||||
};
|
||||
largv[pos] = libwwrt.ptr; pos += 1;
|
||||
let k: i32 = 0;
|
||||
for (k < nldirs) {
|
||||
largv[pos] = "-L\0".ptr;
|
||||
largv[pos + 1] = ldirs[k];
|
||||
pos += 2;
|
||||
k += 1;
|
||||
};
|
||||
k = 0;
|
||||
for (k < nllibs) {
|
||||
largv[pos] = "-l\0".ptr;
|
||||
largv[pos + 1] = llibs[k];
|
||||
pos += 2;
|
||||
k += 1;
|
||||
};
|
||||
largv[pos] = nil;
|
||||
if (procrun(l6, largv.ptr) != 0) {
|
||||
cerr("ww --sep: w6l failed\n");
|
||||
return 1;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
// buildone — compile `src` (file or directory) into the executable
|
||||
// named `out`.
|
||||
// selfdir: NUL-terminated dir containing this driver and the
|
||||
@@ -1214,6 +1753,7 @@ fn defaultoutpath(src: *u8) *u8 = {
|
||||
|
||||
fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
let src: *u8 = nil;
|
||||
let wantsep: i32 = 0; // --sep: M3-tail separate-compilation path
|
||||
let outflag: *u8 = nil; // -o target (binary + intermediate stem); T3
|
||||
let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!;
|
||||
incs.len = ((os.PATH_MAX: u64) * 2u64): i32;
|
||||
@@ -1232,7 +1772,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
for (i < argc) {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) { // '-'
|
||||
if (p[1u64] == 73u8) { // '-I'
|
||||
if (cstreqlit(p, "--sep")) { // M3-tail separate-compile
|
||||
wantsep = 1;
|
||||
} else { if (p[1u64] == 73u8) { // '-I'
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
dir = p + 2u64;
|
||||
@@ -1300,7 +1842,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
} else {
|
||||
cerr("ww build: unknown flag\n");
|
||||
return 2;
|
||||
}; }; }; };
|
||||
}; }; }; }; };
|
||||
} else {
|
||||
if (src == nil) { src = p; };
|
||||
};
|
||||
@@ -1347,6 +1889,9 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
lf.nlibdirs = nlibdirs;
|
||||
lf.libs = libs.ptr;
|
||||
lf.nlibs = nlibs;
|
||||
if (wantsep != 0) {
|
||||
return buildonesep(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf);
|
||||
};
|
||||
return buildone(selfdir, resolved, isdir, out, objstem, incs.ptr, &lf, 0i32);
|
||||
};
|
||||
|
||||
@@ -1402,7 +1947,10 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
else {
|
||||
let p: *u8 = argv[i];
|
||||
if (p[0u64] == 45u8) {
|
||||
if (p[1u64] == 73u8) {
|
||||
if (cstreqlit(p, "--sep")) { // build-only; run rejects (rule 10)
|
||||
cerr("ww run: --sep is only valid with build\n");
|
||||
return 2;
|
||||
} else { if (p[1u64] == 73u8) {
|
||||
let dir: *u8 = nil;
|
||||
if (p[2u64] != 0u8) {
|
||||
dir = p + 2u64;
|
||||
@@ -1469,7 +2017,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
} else {
|
||||
cerr("ww run: unknown flag\n");
|
||||
return 2;
|
||||
}; }; }; };
|
||||
}; }; }; }; };
|
||||
i += 1;
|
||||
} else {
|
||||
if (src == nil) {
|
||||
|
||||
368
test/wcc/989_sepbuild_run.c
Normal file
368
test/wcc/989_sepbuild_run.c
Normal file
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
* 989_sepbuild_run — M3-tail commit-3 `ww build --sep` driver gate (#46).
|
||||
*
|
||||
* Certifies build_one_sep (cmd/ww/main.c + selfhost/cmd/ww/main.ww twin)
|
||||
* END-TO-END through the REAL `ww` / `ww_ww` drivers on a REAL lib chain
|
||||
* (NOT the 989_m3sep synth fixture, which exercises the M3-CORE codegen
|
||||
* via a hand harness). The driver adds ONLY orchestration around that
|
||||
* proven core: discover_deps, reverse-topo, the transitive `w6c -c -I`
|
||||
* producer loop, and a flat `w6l`.
|
||||
*
|
||||
* Chosen chain (smallest real multi-package graph with a genuine
|
||||
* multi-level dep edge): root → os → { rt, time }. rt and time are
|
||||
* true leaves; os imports both AND its public interface exposes a
|
||||
* transitive type (os returns/takes time.instant in sibling protos), so
|
||||
* the root's sep-unit needs the TRANSITIVE `.wwi` closure for name
|
||||
* resolution — direct-deps-only does not type-check (the lead-ratified
|
||||
* superseding of rob-c3-spec §1.3). The graph thus exercises:
|
||||
* - discovery of the transitive package set,
|
||||
* - reverse-topo dep-first ordering (time/rt before os before root),
|
||||
* - the transitive-closure `.wwi` prepend tagged by dotted path,
|
||||
* - one-pass `w6c -c -I` (consumer + producer) per package,
|
||||
* - flat `w6l` of the `.o` set + libwwrt.a.
|
||||
*
|
||||
* Asserts (all COLD — `<stem>.sepwork` scratch is wiped each run):
|
||||
* 1. Build + run, BOTH stages → exit EXPECT_EXIT (cross-boundary
|
||||
* os.getpid resolves + links across sep `.o`s; the program runs).
|
||||
* 2. cs==ww (rule 10): per-package `.s`/`.wwi`/`.unit.ww` AND the final
|
||||
* binary are byte-identical between `ww --sep` and `ww_ww --sep`.
|
||||
* 3. KEYSTONE (load-bearing, §3.1) THROUGH the driver: for each non-leaf
|
||||
* package P, transform the driver's own `<P>.unit.ww` by substituting
|
||||
* each dep's `.wwi` section with that dep's full directory BODIES,
|
||||
* `w6c -c` it, and cmp vs the driver's `<P>.s`. Byte-identical proves
|
||||
* the `.wwi` conveys exactly the dep facts P's codegen needs. (Leaves
|
||||
* rt/time have no deps → keystone is vacuous; checked on os + root.)
|
||||
*
|
||||
* Light wwstage-driver test (CLAUDE.md rule 14): all intermediates are
|
||||
* `-o`-redirected to /tmp, so it is phase-1 parallel-safe. Models
|
||||
* 989_m3sep_run.c conventions; 989 prefix per the m3sep/m2wwi precedent.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#define EXPECT_EXIT 7
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static int
|
||||
strs_cmp(const void *a, const void *b)
|
||||
{
|
||||
return strcmp(*(const char *const *)a, *(const char *const *)b);
|
||||
}
|
||||
|
||||
/* Concatenate a package directory's *.ww bodies (less *test.ww and
|
||||
* *.combined.ww, byte-sorted) into `out`. Mirrors the driver's body
|
||||
* enumeration so the substituted bodies-unit matches the sep-unit
|
||||
* structurally. */
|
||||
static int
|
||||
append_dir_bodies(FILE *out, const char *dir)
|
||||
{
|
||||
DIR *d = opendir(dir);
|
||||
if (!d) return -1;
|
||||
char *names[256];
|
||||
int n = 0;
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(d)) != NULL && n < 256) {
|
||||
const char *nm = ent->d_name;
|
||||
size_t nl = strlen(nm);
|
||||
if (nl <= 3 || strcmp(nm + nl - 3, ".ww") != 0) continue;
|
||||
if (nl >= 7 && strcmp(nm + nl - 7, "test.ww") == 0) continue;
|
||||
if (nl >= 12 && strcmp(nm + nl - 12, ".combined.ww") == 0) continue;
|
||||
names[n++] = strdup(nm);
|
||||
}
|
||||
closedir(d);
|
||||
if (n > 1) qsort(names, (size_t)n, sizeof names[0], strs_cmp);
|
||||
int rc = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
char fp[1024];
|
||||
snprintf(fp, sizeof fp, "%s/%s", dir, names[i]);
|
||||
char *b = NULL;
|
||||
size_t bn = 0;
|
||||
if (slurp(fp, &b, &bn) == 0) {
|
||||
fwrite(b, 1, bn, out);
|
||||
fputc('\n', out);
|
||||
free(b);
|
||||
} else rc = -1;
|
||||
free(names[i]);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Transform the driver's sep-unit at `unitf` into a bodies-unit at
|
||||
* `bodiesf`: every `//ww:module <path>` dep section (its `.wwi` content)
|
||||
* is replaced by <path>'s full directory bodies; the trailing
|
||||
* `//ww:module-reset` primary body is copied verbatim. The result is the
|
||||
* SAME unit P would compile with deps-as-bodies — the keystone's other
|
||||
* arm. <libdir> roots the dotted-path → dir mapping (dots → slashes). */
|
||||
static int
|
||||
compose_bodies(const char *unitf, const char *libdir, const char *bodiesf)
|
||||
{
|
||||
char *buf = NULL;
|
||||
size_t n = 0;
|
||||
if (slurp(unitf, &buf, &n) < 0) return -1;
|
||||
FILE *out = fopen(bodiesf, "wb");
|
||||
if (!out) { free(buf); return -1; }
|
||||
int rc = 0;
|
||||
size_t i = 0;
|
||||
while (i < n) {
|
||||
size_t j = i;
|
||||
while (j < n && buf[j] != '\n') j++;
|
||||
size_t linelen = j - i;
|
||||
if (strncmp(buf + i, "//ww:module-reset", 17) == 0) {
|
||||
/* primary body: copy this line + everything after. */
|
||||
fwrite(buf + i, 1, n - i, out);
|
||||
break;
|
||||
}
|
||||
if (strncmp(buf + i, "//ww:module ", 12) == 0) {
|
||||
/* dep section: emit the directive, then dir bodies;
|
||||
* skip the original `.wwi` content to the next
|
||||
* directive. */
|
||||
fwrite(buf + i, 1, linelen, out);
|
||||
fputc('\n', out);
|
||||
char path[256];
|
||||
size_t pl = linelen - 12;
|
||||
if (pl >= sizeof path) pl = sizeof path - 1;
|
||||
memcpy(path, buf + i + 12, pl);
|
||||
path[pl] = '\0';
|
||||
char dir[1024];
|
||||
char form[256];
|
||||
size_t k;
|
||||
for (k = 0; path[k]; k++)
|
||||
form[k] = (path[k] == '.') ? '/' : path[k];
|
||||
form[k] = '\0';
|
||||
snprintf(dir, sizeof dir, "%s/%s", libdir, form);
|
||||
if (append_dir_bodies(out, dir) < 0) rc = -1;
|
||||
/* advance past the wwi content to the next directive. */
|
||||
i = j + 1;
|
||||
while (i < n) {
|
||||
if (strncmp(buf + i, "//ww:module", 11) == 0) break;
|
||||
size_t e = i;
|
||||
while (e < n && buf[e] != '\n') e++;
|
||||
i = (e < n) ? e + 1 : n;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
i = j + 1;
|
||||
}
|
||||
fclose(out);
|
||||
free(buf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const char *root_src =
|
||||
"package main;\n"
|
||||
"import os;\n"
|
||||
"fn main() i32 = { return os.getpid() - os.getpid() + 7; };\n";
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) return 1;
|
||||
char td[64], cmd[8192], libdir[2048];
|
||||
int fail = 0;
|
||||
|
||||
snprintf(libdir, sizeof libdir, "%s/../../lib", bin);
|
||||
snprintf(td, sizeof td, "/tmp/wwsep_%d", getpid());
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
mkdir(td, 0755);
|
||||
|
||||
char rootww[1024];
|
||||
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
||||
if (write_file(rootww, root_src)) { fail++; goto out; }
|
||||
|
||||
/* The two driver stages and their scratch dirs. All per-package paths
|
||||
* are rebuilt from `td` (a small fixed buffer) + the stage tag rather
|
||||
* than chained through a large path buffer, so the snprintfs are
|
||||
* provably non-truncating (warning-clean, like 989_m3sep_run). */
|
||||
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);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 240 %s/%s build --sep -o %s %s >/dev/null 2>&1",
|
||||
bin, stg[s].drv, stg[s].prog, rootww);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "sepbuild FAIL: %s build --sep\n", stg[s].drv);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
int rc = runwait(stg[s].prog);
|
||||
if (rc != EXPECT_EXIT) {
|
||||
fprintf(stderr, "sepbuild FAIL: %s prog exit=%d expected %d\n",
|
||||
stg[s].drv, rc, EXPECT_EXIT);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* reverse-topo correctness is implicit: every package compiled (a
|
||||
* dep's `.wwi` existed before its importer) → build succeeded above.
|
||||
* Assert the discovered package set materialized in the scratch. */
|
||||
const char *pkgs[] = { "time", "rt", "os", "__root" };
|
||||
for (int i = 0; i < 4; i++) {
|
||||
char p[1024];
|
||||
snprintf(p, sizeof p, "%s/prog.cs.sepwork/%s.wwi", td, pkgs[i]);
|
||||
if (access(p, 0) != 0) {
|
||||
fprintf(stderr, "sepbuild FAIL: missing %s.wwi (discovery/topo)\n",
|
||||
pkgs[i]);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* cs==ww (rule 10): per-package .s/.wwi/.unit.ww + final binary. */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
const char *suf[] = { ".s", ".wwi", ".unit.ww" };
|
||||
for (int k = 0; k < 3; k++) {
|
||||
char a[1024], b[1024];
|
||||
snprintf(a, sizeof a, "%s/prog.%s.sepwork/%s%s",
|
||||
td, stg[0].tag, pkgs[i], suf[k]);
|
||||
snprintf(b, sizeof b, "%s/prog.%s.sepwork/%s%s",
|
||||
td, stg[1].tag, pkgs[i], suf[k]);
|
||||
if (files_eq(a, b) != 0) {
|
||||
fprintf(stderr, "sepbuild FAIL: cs!=ww for %s%s (rule 10)\n",
|
||||
pkgs[i], suf[k]);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (files_eq(stg[0].prog, stg[1].prog) != 0) {
|
||||
fprintf(stderr, "sepbuild FAIL: cs exe != ww exe (rule 10)\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* KEYSTONE through the driver: bodies-unit == sep-unit codegen, for
|
||||
* each non-leaf package (os, root). Transform the driver's own
|
||||
* <P>.unit.ww (deps as .wwi) into a bodies-unit (deps as full dir
|
||||
* bodies), `w6c -c` it, cmp vs the driver's <P>.s. */
|
||||
const char *keypkgs[] = { "os", "__root" };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
char unitf[1024], bodiesf[1024], bodies_s[1024], sep_s[1024];
|
||||
snprintf(unitf, sizeof unitf, "%s/prog.cs.sepwork/%s.unit.ww", td, keypkgs[i]);
|
||||
snprintf(bodiesf, sizeof bodiesf, "%s/%s.bodies.ww", td, keypkgs[i]);
|
||||
snprintf(bodies_s, sizeof bodies_s, "%s/%s.bodies.s", td, keypkgs[i]);
|
||||
snprintf(sep_s, sizeof sep_s, "%s/prog.cs.sepwork/%s.s", td, keypkgs[i]);
|
||||
if (compose_bodies(unitf, libdir, bodiesf) < 0) {
|
||||
fprintf(stderr, "sepbuild FAIL: compose bodies for %s\n", keypkgs[i]);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 240 %s/w6c -c -o %s %s >/dev/null 2>&1",
|
||||
bin, bodies_s, bodiesf);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "sepbuild FAIL: %s bodies -c\n", keypkgs[i]);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
if (files_eq(bodies_s, sep_s) != 0) {
|
||||
fprintf(stderr, "sepbuild FAIL: %s — bodies.s != driver sep.s "
|
||||
"(the .wwi does not convey the dep facts P needs)\n", keypkgs[i]);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* run --sep symmetry (#46 c3): `run` has no sep-compile-then-run
|
||||
* path (out of commit-3 scope), so BOTH stages must LOUD-REJECT
|
||||
* `run --sep` identically (rule 10) — not one silently ignore it.
|
||||
* The reject exits 2 (the driver's usage/flag-error code); assert
|
||||
* cs and ww agree AND actually rejected (exit 2, not a build+run
|
||||
* that happened to exit nonzero). This row is the gate-visible
|
||||
* guard whose absence let the divergence hide. */
|
||||
{
|
||||
int rc_cs, rc_ww;
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/ww run --sep %s >/dev/null 2>&1", bin, rootww);
|
||||
rc_cs = runwait(cmd);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/ww_ww run --sep %s >/dev/null 2>&1", bin, rootww);
|
||||
rc_ww = runwait(cmd);
|
||||
if (rc_cs != rc_ww || rc_cs != 2) {
|
||||
fprintf(stderr, "sepbuild FAIL: run --sep cs=%d ww=%d "
|
||||
"(both must loud-reject, exit 2)\n", rc_cs, rc_ww);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", td);
|
||||
runwait(cmd);
|
||||
if (fail) {
|
||||
fprintf(stderr, "sepbuild: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("sepbuild: real chain root->os->{rt,time} via build_one_sep — "
|
||||
"build+run (exit %d) + cs==ww per-pkg .s/.wwi/.unit + final binary "
|
||||
"+ transitive-topo discovery + keystone bodies==.wwi (os,root)\n",
|
||||
EXPECT_EXIT);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user