/* * Explicit bootstrap gate: the C-built `w6a` and the ww-built `w6a_ww` * must produce byte-identical ELF .o output over the corpus. Any * divergence is a port bug in selfhost/cmd/w6a/. The corpus pairs * hand-written rt/X.s (small, varied operands) with cgen-generated .s * (large — exercises every encoding path w6c emits). */ #include #include #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(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 diff_one(const char *bin, const char *src) { char co[64], wo[64], cmd[2048]; char *bc = NULL, *bw = NULL; size_t nc = 0, nw = 0; int rc = -1; snprintf(co, sizeof co, "/tmp/wwa_%d_c.o", getpid()); snprintf(wo, sizeof wo, "/tmp/wwa_%d_w.o", getpid()); snprintf(cmd, sizeof cmd, "timeout 180 %s/w6a -o %s %s 2>/dev/null", bin, co, src); if (runwait(cmd) != 0) { fprintf(stderr, "w6a_ww FAIL: C w6a errored on %s\n", src); goto cleanup; } snprintf(cmd, sizeof cmd, "timeout 180 %s/w6a_ww -o %s %s 2>/dev/null", bin, wo, src); if (runwait(cmd) != 0) { fprintf(stderr, "w6a_ww FAIL: ww w6a errored on %s\n", src); goto cleanup; } if (slurp(co, &bc, &nc) < 0 || slurp(wo, &bw, &nw) < 0) { fprintf(stderr, "w6a_ww FAIL: cannot read .o for %s\n", src); } else if (nc == 0) { fprintf(stderr, "w6a_ww FAIL: empty .o for %s\n", src); } else if (nc != nw || memcmp(bc, bw, nc) != 0) { fprintf(stderr, "w6a_ww FAIL: %s — C %zu vs ww %zu bytes\n", src, nc, nw); } else rc = 0; cleanup: free(bc); free(bw); /* co/wo are reused across corpus files; a stale .o left behind * would be slurped as a later file's output. */ int cleanfail = 0; if (unlink(co) != 0 && errno != ENOENT) { perror(co); cleanfail = 1; } if (unlink(wo) != 0 && errno != ENOENT) { perror(wo); cleanfail = 1; } 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; /* #93 sep layout: the selfhost tools no longer leave a single * next-to-source main.s. Self-build one real directory package and * feed EVERY emitted * .sepwork/.s as * the generated-asm corpus — the root tool code plus each dep * package's .s (richer than the old 3 monolithic main.s). */ char stem[256], cmd[4096]; int fail = 0; int n = 0; int gen = 0; snprintf(stem, sizeof stem, "/tmp/wwa_%d_sh", getpid()); snprintf(cmd, sizeof cmd, "timeout 400 %s/ww build -o %s " "%s/selfhost/cmd/w6a >/dev/null 2>&1", bin, stem, cwd); if (runwait(cmd) != 0) { fprintf(stderr, "w6a_ww FAIL: cannot sep-build selfhost/cmd/w6a\n"); /* a failed build can leave a partial stem/.sepwork tree. */ fail = 1; goto cleanup; } /* hand-written runtime — small, covers MOVQ/LEAQ/CALL/SYSCALL/RET; * exists in-tree regardless of layout (not a casualty). */ const char *rtfiles[] = { "rt/start.s", "rt/syscall.s", "rt/abort.s", "rt/alloc.s", "rt/streq.s", NULL, }; for (int i = 0; rtfiles[i]; i++) { char p[2048]; snprintf(p, sizeof p, "%s/%s", cwd, rtfiles[i]); if (diff_one(bin, p) != 0) fail++; n++; } /* generated .s from the real tool's sep build — large, exercises * every encoding w6c emits (the long tail of operand shapes). */ glob_t g; char pat[320]; snprintf(pat, sizeof pat, "%s.sepwork/*.s", stem); if (glob(pat, 0, NULL, &g) == 0) { for (size_t i = 0; i < g.gl_pathc; i++) { /* skip empty units (e.g. an import-only rt.s). */ FILE *f = fopen(g.gl_pathv[i], "rb"); long sz = -1; if (f) { fseek(f, 0, SEEK_END); sz = ftell(f); fclose(f); } if (sz <= 0) continue; if (diff_one(bin, g.gl_pathv[i]) != 0) fail++; n++; gen++; } globfree(&g); } /* the PASS banner claims generated-asm coverage; an empty glob * (or all-empty units) must not go green on the rt corpus alone. */ if (gen == 0) { fprintf(stderr, "w6a_ww FAIL: no non-empty generated .s under %s.sepwork\n", stem); fail++; } cleanup: snprintf(cmd, sizeof cmd, "rm -rf %s.sepwork %s", stem, stem); int cleanfail = runwait(cmd) != 0; if (cleanfail) fprintf(stderr, "w6a_ww: cleanup of %s{,.sepwork} failed\n", stem); if (fail) { fprintf(stderr, "w6a_ww: %d/%d diff(s) failed\n", fail, n); return 1; } if (cleanfail) return 1; printf("w6a_ww: byte-identical to C w6a on %d corpus files " "(rt/*.s + selfhost/cmd/w6a sep-built .s)\n", n); return 0; }