/* * 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): wall drops * from sum(builds) to max(builds). */ #include #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; size_t nb = 0; for (;;) { int ca = fgetc(fa); int cb = fgetc(fb); if (ca != cb) { rc = -1; break; } if (ca == EOF) break; nb++; } /* two 0-byte artifacts must not compare green (pkgcache 0-byte poison). */ if (rc == 0 && nb == 0) rc = -1; fclose(fa); fclose(fb); return rc; } /* Each tool builds via `ww_ww build -I lib/ww -I selfhost/cmd src`. * lib/ww holds the language introspection (lex/tok/ast/parse/typ/sym); * selfhost/cmd holds the compiler-internal `wcc` directory package. * Dotted `import encoding.utf8;` finds lib/encoding/utf8/ via the * driver's default srclib path post-task-#22 dir-enum. */ struct buildjob { const char *tool; const char *src_rel; char workdir[64]; pid_t pid; }; /* 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.{s,o} and the sepwork tree) 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); if (mkdir(j->workdir, 0755) != 0) { perror(j->workdir); return -1; } char cmd[4096]; snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s/ww_ww build -o %s/main " "-I %s/lib/ww -I %s/selfhost/cmd " "%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) { char clean[128]; snprintf(clean, sizeof clean, "rm -rf %s", j->workdir); if (runwait(clean) != 0) fprintf(stderr, "self-rebuild: cleanup %s failed\n", j->workdir); 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; } 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); int cleanfail = runwait(cleanup) != 0; if (cleanfail) fprintf(stderr, "self-rebuild: cleanup %s failed\n", j->workdir); if (cleanfail && rc == 0) rc = -1; 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", {0}, 0 }, { "w6l", "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; }