/* * 911_attest_drop — the #6 @test DROP/KEEP/LINK emission property, driven * by `w6c` / `w6c_ww` DIRECTLY on import-free fixtures (task #83, M4 E2-C2b). * * The drop/keep behavior is a property of w6c's `-T` flag, NOT the build * model: a @test fn is spliced out of a non-test build (harec-faithful, * ref/harec/src/check.c:3941 — checked but never emitted) and kept under * -T (the synth entry references it). Direct-w6c is the only host that can * compare BOTH flags on the same input — `ww test` is always -T, `ww build` * always non-T. The fixtures are import-free so they feed w6c with no driver * resolution; the -T synth's `test.run` callee is left as an external CALL * (resolved at link, never needed for the emission grep), so no combined * unit is required. Re-hosts the 910/997 nondrop+undefbody+linkfail legs, * which die when those gates are deleted at the M4 flip. * * Legs: * nondrop — @test defs ABSENT non-T, PRESENT under -T (8 rows); the .s * is cs==ww in BOTH modes (rule 10 — the drop is symmetric). * undefbody — a @test body referencing an undef sym rejects in non-T: * the body is type-checked BEFORE the splice drops it * (harec checks at :3913, drops at :3941). Both stages. * linkfail — non-test code CALLs a @test fn: non-T drops the def but the * CALL survives, so the link fails on the dangling symbol — * harec-faithful loud failure, never a silent mis-link. Both * stages. */ #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; } /* cmp_asm — w6c and w6c_ww asm for the same input must be byte-identical * (rule 10). Returns 0 on match. */ static int cmp_asm(const char *csa, const char *wsa, const char *what) { char *bc = NULL, *bw = NULL; size_t nc = 0, nw = 0; int rc = 0; if (slurp(csa, &bc, &nc) < 0 || slurp(wsa, &bw, &nw) < 0) { fprintf(stderr, "911 FAIL: slurp %s asm\n", what); rc = 1; } else if (nc != nw || memcmp(bc, bw, nc) != 0) { fprintf(stderr, "911 FAIL: %s asm cs!=ww (cs %zu, ww %zu)\n", what, nc, nw); rc = 1; } free(bc); free(bw); return rc; } /* * #6: each row asserts whether a symbol's TEXT def appears in the asm emitted * for attest_nondrop.ww in a given mode. A @test fn is spliced out of a * non-test build (ref/harec/src/check.c:3941) and kept under -T (the synth * entry references it); a plain fn is always emitted. The fixture layout * exercises the splice loop's unlink edges: @test head (prev==nil), a * surviving plain fn, then two consecutive @test fns (unlink-after-unlink). */ static const struct { const char *sym; /* TEXT label to grep for */ int testmode; /* 1 => -T, 0 => plain build */ int present; /* expected: 1 present, 0 absent */ const char *what; } nondrop_rows[] = { { "data.nondrop_keep", 0, 1, "plain fn kept non-T" }, { "data.nondrop_test_a", 0, 0, "head @test dropped non-T" }, { "data.nondrop_test_b", 0, 0, "mid @test dropped non-T" }, { "data.nondrop_test_c", 0, 0, "consecutive @test dropped non-T" }, { "data.nondrop_keep", 1, 1, "plain fn kept under -T" }, { "data.nondrop_test_a", 1, 1, "head @test kept under -T" }, { "data.nondrop_test_b", 1, 1, "mid @test kept under -T" }, { "data.nondrop_test_c", 1, 1, "consecutive @test kept under -T" }, }; /* nondrop — compile attest_nondrop.ww four ways (cs/ww × non-T/-T) straight * to asm (no driver resolution), assert each row's TEXT def is present/absent * on the cstage output, then byte-compare cs vs ww in BOTH modes. The * present/absent rows run against the cstage .s alone; the cs==ww byte-id * carries the assertion onto the wwstage .s by construction. */ static int nondrop(const char *bin) { int pid = getpid(); char cp[256], ct[256], wp[256], wt[256], cmd[4096]; snprintf(cp, sizeof cp, "/tmp/at911nd_cp_%d.s", pid); snprintf(ct, sizeof ct, "/tmp/at911nd_ct_%d.s", pid); snprintf(wp, sizeof wp, "/tmp/at911nd_wp_%d.s", pid); snprintf(wt, sizeof wt, "/tmp/at911nd_wt_%d.s", pid); int rc = 0; struct { const char *comp; const char *flag; const char *out; } jobs[] = { { "w6c", "", cp }, { "w6c", "-T ", ct }, { "w6c_ww", "", wp }, { "w6c_ww", "-T ", wt }, }; for (size_t i = 0; i < sizeof jobs / sizeof jobs[0]; i++) { snprintf(cmd, sizeof cmd, "%s/%s %stest/wcc/data/attest_nondrop.ww -o %s 2>/dev/null", bin, jobs[i].comp, jobs[i].flag, jobs[i].out); if (runwait(cmd) != 0) { fprintf(stderr, "911 FAIL: %s %snondrop compile\n", jobs[i].comp, jobs[i].flag); rc = 1; } } for (size_t i = 0; rc == 0 && i < sizeof nondrop_rows / sizeof nondrop_rows[0]; i++) { const char *f = nondrop_rows[i].testmode ? ct : cp; snprintf(cmd, sizeof cmd, "grep -q '^TEXT %s,' %s", nondrop_rows[i].sym, f); int found = runwait(cmd) == 0; if (found != nondrop_rows[i].present) { fprintf(stderr, "911 FAIL: %s: %s %s (expected %s)\n", nondrop_rows[i].what, nondrop_rows[i].sym, found ? "present" : "absent", nondrop_rows[i].present ? "present" : "absent"); rc = 1; } } if (rc == 0 && cmp_asm(cp, wp, "non-T @test-drop") != 0) rc = 1; if (rc == 0 && cmp_asm(ct, wt, "-T @test-keep") != 0) rc = 1; unlink(cp); unlink(ct); unlink(wp); unlink(wt); return rc; } /* reject_plain — ` ` (NON-T) must exit nonzero. Pins that a * @test body is type-checked before the #6 splice drops it: an undefined * symbol in the body is caught loud even though the fn never reaches codegen * (harec checks at :3913, drops at :3941). */ static int reject_plain(const char *bin, const char *comp, const char *fixture, const char *what) { char cmd[4096]; snprintf(cmd, sizeof cmd, "%s/%s %s -o /dev/null 2>/dev/null", bin, comp, fixture); if (runwait(cmd) == 0) { fprintf(stderr, "911 FAIL: %s (non-T) accepted %s " "(expected reject)\n", comp, what); return 1; } return 0; } /* linkfail — a plain fn calling a @test fn, built non-T: compile + assemble * succeed, but the link MUST fail (the dropped @test def leaves the call's * symbol dangling — harec-faithful loud failure, never a silent mis-link). */ static int linkfail(const char *bin, const char *comp, const char *asmt, const char *linkt) { int pid = getpid(); char asmf[256], obj[256], exe[256], lerr[256], rt[1024], cmd[4096]; snprintf(asmf, sizeof asmf, "/tmp/at911lf_%s_%d.s", comp, pid); snprintf(obj, sizeof obj, "/tmp/at911lf_%s_%d.o", comp, pid); snprintf(exe, sizeof exe, "/tmp/at911lf_%s_%d.exe", comp, pid); snprintf(lerr, sizeof lerr, "/tmp/at911lf_%s_%d.err", comp, pid); snprintf(rt, sizeof rt, "%s/../lib/libwwrt.a", bin); int rc = 0; snprintf(cmd, sizeof cmd, "%s/%s test/wcc/data/attest_calldropped.ww -o %s 2>/dev/null", bin, comp, asmf); if (runwait(cmd) != 0) { fprintf(stderr, "911 FAIL: %s non-T calldropped compile\n", comp); rc = 1; } if (rc == 0) { snprintf(cmd, sizeof cmd, "%s/%s -o %s %s 2>/dev/null", bin, asmt, obj, asmf); if (runwait(cmd) != 0) { fprintf(stderr, "911 FAIL: %s calldropped\n", asmt); rc = 1; } } if (rc == 0) { snprintf(cmd, sizeof cmd, "%s/%s -o %s %s %s 2>%s", bin, linkt, exe, obj, rt, lerr); if (runwait(cmd) == 0) { fprintf(stderr, "911 FAIL: %s linked calldropped (expected " "undefined-reference to the dropped @test sym)\n", linkt); rc = 1; } else { /* Non-vacuity: the link must fail ON the dropped @test * symbol, not for an unrelated reason — a libwwrt path drift * would otherwise mask the property by failing every link. */ snprintf(cmd, sizeof cmd, "grep -q calldropped_test %s", lerr); if (runwait(cmd) != 0) { fprintf(stderr, "911 FAIL: %s link failed but not on the " "dropped @test sym (masked)\n", linkt); rc = 1; } } } unlink(asmf); unlink(obj); unlink(exe); unlink(lerr); return rc; } /* Both stages: each tool triple compiles + assembles + links the same * import-free fixtures, so the drop/keep/link property is pinned on cstage * AND wwstage (the nondrop leg's cs==ww byte-id proves the .s identical; * the reject + link legs re-run the whole pipeline per stage). */ static const struct { const char *comp; const char *asmt; const char *linkt; } stages[] = { { "w6c", "w6a", "w6l" }, { "w6c_ww", "w6a_ww", "w6l_ww" }, }; int main(void) { const char *bin = absbin(); if (!bin) { fprintf(stderr, "911 FAIL: getcwd\n"); return 1; } if (nondrop(bin) != 0) return 1; for (size_t i = 0; i < sizeof stages / sizeof stages[0]; i++) { if (reject_plain(bin, stages[i].comp, "test/wcc/data/attest_undefbody.ww", "undefined symbol in @test body") != 0) return 1; if (linkfail(bin, stages[i].comp, stages[i].asmt, stages[i].linkt) != 0) return 1; } printf("@test -T direct-w6c: non-T drop + -T keep (8 rows, cs==ww both " "modes) + checked-body reject + dangling-call link-fail, both " "stages (#6)\n"); return 0; }