Files
ww/test/wcc/911_attest_drop.c
Hojun-Cho 947e0a01f1 test/wcc: 911_attest_drop direct-w6c gate — @test nondrop/undefbody/linkfail (M4 E2-C2b, #22)
New direct-w6c -T gate adopting the 3 import-free fixtures 910/997 own (attest_nondrop/undefbody/calldropped.ww) so they outlive the .c gates the E3 flip retires. 8-row nondrop table: compile attest_nondrop.ww four ways (cstage/wwstage x non-T/-T) to .s; non-T keeps only the live sym, -T keeps all @test bodies; cs==ww byte-id on both flags (rule 10). undefbody: both stages reject (body type-checked before the splice). linkfail: full triple link must fail on the dropped @test symbol, guarded by a stderr grep so it fails for the right reason. Test-only; all 5 pins hold; 442 to 443.
2026-06-18 00:32:13 +09:00

276 lines
9.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
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 — `<comp> <fixture>` (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;
}