/* * 995_self_rebuild — the wwstage rebuilds itself. * * Drives ww_ww (the ww-side driver, which shells to w6c_ww/w6a_ww/w6l_ww) * over each wwstage tool's source and diffs the result byte-for-byte * against the cstage-built binary in $BIN. A green run means the * toolchain can recompile itself without touching cc, modulo the * cold-start binary — which is the v1.0 lock from PLAN.md. * * This is stricter than `make bootstrap`: that loop only proves the * wwdump cgen self-stabilises; this proves every wwstage tool round- * trips through the wwstage pipeline. * * The five tool builds run concurrently: fork() per tool, parent * collects each pid with waitpid() and then byte-compares. Wall drops * from sum(builds) to max(builds). */ #include #include #include #include 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_eq(const char *a, const char *b) { FILE *fa = fopen(a, "rb"); FILE *fb = fopen(b, "rb"); if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } int rc = 0; for (;;) { int ca = fgetc(fa); int cb = fgetc(fb); if (ca != cb) { rc = -1; break; } if (ca == EOF) break; } fclose(fa); fclose(fb); return rc; } /* Each tool builds via `ww_ww build -I -I lib/ww -I selfhost/cmd/wcc src`. * lib/ww holds the language introspection (lex/tok/ast/parse/typ/sym); * selfhost/cmd/wcc holds the compiler internals (mem/check/cgen*). * Dotted `import encoding.utf8;` finds lib/encoding/utf8/ via the * driver's default srclib path post-task-#22 dir-enum. * Some tools have a local module dir (w6a, w6l with sibling .ww files). * inc_local is "" for tools without one (w6c, ww, wwdump). */ struct buildjob { const char *tool; const char *src_rel; const char *inc_local; char workdir[64]; pid_t pid; }; /* Fork a child that runs the `ww_ww build` for this tool. The child * inherits no concurrent siblings — system() spawns a fresh /bin/sh -c. * stderr lands in /build.err so concurrent builds don't merge * their diagnostics. `-o /main` (T3) makes the driver write its * intermediates (main.{combined.ww,s,o}) beside the output in * instead of next to every traversed source — so concurrent driver * builds no longer share the next-to-source fixed paths, and this gate * may run in the parallel group. The rebuilt binary still lands at * /main where collect_build diffs it. */ static int spawn_build(const char *bin, const char *cwd, struct buildjob *j) { snprintf(j->workdir, sizeof j->workdir, "/tmp/wwsr_%d_%s", getpid(), j->tool); char setup[256]; snprintf(setup, sizeof setup, "rm -rf %s && mkdir -p %s", j->workdir, j->workdir); if (runwait(setup) != 0) return -1; char cmd[4096]; if (j->inc_local && j->inc_local[0]) { snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s/ww_ww build -o %s/main -I %s/%s " "-I %s/lib/ww " "-I %s/selfhost/cmd/wcc %s/%s >/dev/null 2>%s/build.err", j->workdir, bin, j->workdir, cwd, j->inc_local, cwd, cwd, cwd, j->src_rel, j->workdir); } else { snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s/ww_ww build -o %s/main " "-I %s/lib/ww " "-I %s/selfhost/cmd/wcc %s/%s >/dev/null 2>%s/build.err", j->workdir, bin, j->workdir, cwd, cwd, cwd, j->src_rel, j->workdir); } pid_t p = fork(); if (p < 0) return -1; if (p == 0) { int rc = system(cmd); if (rc == -1) _exit(1); _exit(WIFEXITED(rc) ? WEXITSTATUS(rc) : 1); } j->pid = p; return 0; } /* Reap one job, byte-diff rebuilt vs canonical, replay captured stderr * on build failure, rm workdir. Returns 0 on byte-match, -1 otherwise. */ static int collect_build(const char *bin, struct buildjob *j) { int rc = 0; int status; if (waitpid(j->pid, &status, 0) < 0) { fprintf(stderr, "self-rebuild FAIL: waitpid %s\n", j->tool); rc = -1; } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { fprintf(stderr, "self-rebuild FAIL: ww_ww build errored on %s\n", j->tool); /* Replay the captured stderr so the failure is diagnosable. */ char errpath[256]; snprintf(errpath, sizeof errpath, "%s/build.err", j->workdir); FILE *f = fopen(errpath, "r"); if (f) { char buf[1024]; while (fgets(buf, sizeof buf, f)) fputs(buf, stderr); fclose(f); } rc = -1; } else { char rebuilt[256], canonical[256]; snprintf(rebuilt, sizeof rebuilt, "%s/main", j->workdir); snprintf(canonical, sizeof canonical, "%s/%s_ww", bin, j->tool); rc = slurp_eq(rebuilt, canonical); if (rc != 0) { fprintf(stderr, "self-rebuild FAIL: %s rebuilt != cstage %s\n", j->tool, canonical); } } char cleanup[128]; snprintf(cleanup, sizeof cleanup, "rm -rf %s", j->workdir); runwait(cleanup); return rc; } int main(void) { const char *bin = absbin(); if (!bin) return 1; char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; struct buildjob jobs[] = { { "w6c", "selfhost/cmd/w6c/main.ww", "", {0}, 0 }, { "w6a", "selfhost/cmd/w6a/main.ww", "selfhost/cmd/w6a", {0}, 0 }, { "w6l", "selfhost/cmd/w6l/main.ww", "selfhost/cmd/w6l", {0}, 0 }, { "ww", "selfhost/cmd/ww/main.ww", "", {0}, 0 }, { "wwdump", "selfhost/cmd/wwdump/main.ww", "", {0}, 0 }, }; const int n = (int)(sizeof jobs / sizeof jobs[0]); int spawned = 0; for (int i = 0; i < n; i++) { if (spawn_build(bin, cwd, &jobs[i]) != 0) { fprintf(stderr, "self-rebuild FAIL: spawn %s\n", jobs[i].tool); jobs[i].pid = 0; } else { spawned++; } } int fail = n - spawned; for (int i = 0; i < n; i++) { if (jobs[i].pid == 0) continue; if (collect_build(bin, &jobs[i]) != 0) fail++; } if (fail) { fprintf(stderr, "self-rebuild: %d/%d tool(s) diverged\n", fail, n); return 1; } printf("self-rebuild: %d wwstage tool(s) round-trip byte-identical " "through ww_ww + w6c_ww + w6a_ww + w6l_ww\n", n); return 0; }