Every surviving carrier now owns its artifacts: checked mkdir/mkdtemp/ fopen acquisition, one all-exit cleanup funnel per carrier, ENOENT- tolerant checked unlinks, exact-path deletion (rm -rf only for an owned pid-keyed dir or a .sepwork beneath one), and cleanup failure fails a passing carrier without overwriting its diagnostic. In the same pass the carriers adapt to the driver contract this branch lands: --sep and WW_PKGCACHE are gone, -S and the /tmp/ww_run_<pid> scratch contract are asserted, and rows whose runtime or reject coverage moved to test/wcc/data fixtures or test/lang @test owners are trimmed to the byte/artifact/diagnostic observations only they can make. Repair and adaptation ride together because most files interleave both in the same hunks; splitting would manufacture intermediate carrier states that never existed and cannot run against either driver.
360 lines
11 KiB
C
360 lines
11 KiB
C
/*
|
|
* 732_def_const_fold — top-level `def` rhs const-fold extended to
|
|
* sibling/imported def references, casts, and arithmetic (PROJECT #88).
|
|
*
|
|
* Predecessor 631_def_neg_global lifted the LITERAL fold (INTLIT/RUNELIT/
|
|
* TRUE/FALSE/NIL + unary +/-/~) into the shared fold_int_literal /
|
|
* foldintliteral helper; it explicitly left N_CAST, sibling-ident, and
|
|
* N_BIN OUT OF SCOPE (see 631's header, "negative-u32-cast"). #88 brings
|
|
* them in: a Hare-faithful `def SCHAR_MAX = types::I8_MAX;` or
|
|
* `def MASK: i32 = (1 << 7) - 1;` was previously inexpressible — the rhs
|
|
* folded to nothing, no DATA row was emitted, and any reference failed
|
|
* to link.
|
|
*
|
|
* The fix folds at CHECK time (both stages' pass-2 N_DEF arm) via a new
|
|
* eval_def_const / evaldefconst that reuses the shared fold_binop arith
|
|
* core (factored out of eval_enum_value / enumvalfold) and the checker's
|
|
* EXISTING scope lookup (scope_lookup_prefer for a sibling ref,
|
|
* scope_lookup_in_module for a `mod.NAME` qualified ref). On success the
|
|
* rhs is stamped to an N_INTLIT in place, so cgen's literal-only
|
|
* emit_defs / emitdefconstants lays down the DATA row with no codegen
|
|
* change. The fold is GATED on the plain literal fold missing first, so
|
|
* every pre-#88 def keeps its node and the emitted bytes stay
|
|
* byte-identical (the bootstrap has zero def-ref-def, so 990-997 never
|
|
* touch this latent path — hence this targeted fixture).
|
|
*
|
|
* Coverage:
|
|
* 1. EXEC (same-module, via the `ww` driver, + `ww_ww` if built):
|
|
* sibling-ref, sibling+arith, def->def->def chain, shift+arith,
|
|
* and a widening cast — each read back at a use site to prove the
|
|
* DATA row links and carries the right value.
|
|
* 2. BYTE-ID: cstage w6c vs wwstage w6c_ww `.s` for every exec row.
|
|
* 3. CROSS-MODULE (#88 a2): a combined multi-`package` source (the
|
|
* driver's internal concat form, fed straight to w6c like
|
|
* 728_match_4arm_cross_module) where `def K = a.J + 1;` resolves
|
|
* the imported `a.J`. Assert the folded DATA row value AND byte-id.
|
|
* 4. FAIL-LOUD (rule 7): a same-module cycle (def A=B; def B=A), a
|
|
* cross-module cycle, and a narrowing cast whose value overflows
|
|
* the target must each fail the build on BOTH stages (never a
|
|
* silent missing row / silent truncation).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/wait.h>
|
|
#include "wwtestpkg.h"
|
|
|
|
static int
|
|
runwait(const char *cmd)
|
|
{
|
|
int rc = system(cmd);
|
|
if (rc == -1) return -1;
|
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
return -1;
|
|
}
|
|
|
|
/* ---- 1. same-module EXEC rows (compile+link+run) -------------------- */
|
|
|
|
/* Runtime ownership: test/wcc/data/r732_def_{sibling,chain,shift,widening}_*. */
|
|
struct row { const char *label; const char *src; };
|
|
|
|
static const struct row exec_rows[] = {
|
|
/* Bare sibling reference: `def B = A;`. Pre-#88 the rhs (N_IDENT)
|
|
* folded to nothing. */
|
|
{ "sibling-ref",
|
|
"def A: i32 = 7;\n"
|
|
"def B: i32 = A;\n"
|
|
"fn main() i32 = {\n"
|
|
"\tlet x: i32 = B;\n"
|
|
"\tif (x == 7) { return 42; };\n"
|
|
"\treturn 1;\n"
|
|
"};\n" },
|
|
|
|
/* Sibling + arithmetic: the headline `def B = A + 1;`. */
|
|
{ "sibling-add",
|
|
"def A: i32 = 5;\n"
|
|
"def B: i32 = A + 1;\n"
|
|
"fn main() i32 = {\n"
|
|
"\tlet x: i32 = B;\n"
|
|
"\tif (x == 6) { return 42; };\n"
|
|
"\treturn 1;\n"
|
|
"};\n" },
|
|
|
|
/* def -> def -> def chain: C resolves through B through A. Pins
|
|
* the recursive resolve (and that the depth guard doesn't trip on
|
|
* a legitimate short chain). */
|
|
{ "def-chain",
|
|
"def A: i32 = 5;\n"
|
|
"def B: i32 = A + 1;\n"
|
|
"def C: i32 = B * A;\n"
|
|
"fn main() i32 = {\n"
|
|
"\tlet x: i32 = C;\n"
|
|
"\tif (x == 30) { return 42; };\n"
|
|
"\treturn 1;\n"
|
|
"};\n" },
|
|
|
|
/* Pure arithmetic with a shift: `(1 << 7) - 1` == 127. Exercises
|
|
* the fold_binop shift + subtract path off any sibling ref. */
|
|
{ "shift-arith",
|
|
"def MASK: i32 = (1 << 7) - 1;\n"
|
|
"fn main() i32 = {\n"
|
|
"\tlet x: i32 = MASK;\n"
|
|
"\tif (x == 127) { return 42; };\n"
|
|
"\treturn 1;\n"
|
|
"};\n" },
|
|
|
|
/* Widening cast over a sibling ref: `A: i64` where A: i32 = 5.
|
|
* The N_CAST is stripped (i32->i64 widens, value fits), so W folds
|
|
* to 5 in an 8-byte slot. */
|
|
{ "widening-cast",
|
|
"def A: i32 = 5;\n"
|
|
"def W: i64 = A: i64;\n"
|
|
"fn main() i32 = {\n"
|
|
"\tlet x: i64 = W;\n"
|
|
"\tif (x == 5i64) { return 42; };\n"
|
|
"\treturn 1;\n"
|
|
"};\n" },
|
|
};
|
|
|
|
/* ---- shared .s emit + slurp ----------------------------------------- */
|
|
|
|
static int
|
|
emit_s(const char *w6c, const char *src, char *out_s, size_t cap)
|
|
{
|
|
char cmd[1024];
|
|
snprintf(cmd, sizeof cmd, "timeout 180 %s -o %s %s 2>/dev/null",
|
|
w6c, out_s, src);
|
|
return runwait(cmd);
|
|
}
|
|
|
|
static int
|
|
slurp(const char *path, char *buf, size_t cap)
|
|
{
|
|
FILE *f = fopen(path, "rb");
|
|
if (!f) return -1;
|
|
size_t n = fread(buf, 1, cap - 1, f);
|
|
fclose(f);
|
|
buf[n] = '\0';
|
|
return (int)n;
|
|
}
|
|
|
|
/* ---- 2. byte-identity of cstage vs wwstage .s ----------------------- */
|
|
|
|
static int
|
|
asm_byte_identical(const char *bin, const char *src, const char *label, int i)
|
|
{
|
|
char wwsrc[64], cs[64], ws[64];
|
|
snprintf(wwsrc, sizeof wwsrc, "/tmp/dcf_bi_%d_%d.ww", getpid(), i);
|
|
snprintf(cs, sizeof cs, "/tmp/dcf_bi_%d_%d_c.s", getpid(), i);
|
|
snprintf(ws, sizeof ws, "/tmp/dcf_bi_%d_%d_w.s", getpid(), i);
|
|
|
|
FILE *f = fopen(wwsrc, "wb");
|
|
if (!f) return -1;
|
|
wwtest_fputs(src, f);
|
|
fclose(f);
|
|
|
|
char w6c[640], w6c_ww[640];
|
|
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
|
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
|
|
|
if (emit_s(w6c, wwsrc, cs, sizeof cs) != 0) {
|
|
fprintf(stderr, "row[%s]: w6c errored\n", label);
|
|
unlink(wwsrc);
|
|
return -1;
|
|
}
|
|
if (emit_s(w6c_ww, wwsrc, ws, sizeof ws) != 0) {
|
|
fprintf(stderr, "row[%s]: w6c_ww errored\n", label);
|
|
unlink(wwsrc); unlink(cs);
|
|
return -1;
|
|
}
|
|
|
|
FILE *fc = fopen(cs, "rb");
|
|
FILE *fw = fopen(ws, "rb");
|
|
int rc = 0;
|
|
if (!fc || !fw) {
|
|
rc = -1;
|
|
} else {
|
|
for (;;) {
|
|
int a = fgetc(fc);
|
|
int b = fgetc(fw);
|
|
if (a != b) { rc = -1; break; }
|
|
if (a == EOF) break;
|
|
}
|
|
}
|
|
if (fc) fclose(fc);
|
|
if (fw) fclose(fw);
|
|
if (rc != 0)
|
|
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
|
label);
|
|
unlink(wwsrc); unlink(cs); unlink(ws);
|
|
return rc;
|
|
}
|
|
|
|
/* ---- 3. cross-module fold: a combined multi-package source ----------
|
|
* Fed straight to w6c (the driver's internal concat form — `ww build`
|
|
* can't expand an already-concatenated source). `def K = a.J + 1;`
|
|
* resolves the imported `a.J` (41) through scope_lookup_in_module and
|
|
* folds to 42, whose DATA row first byte is '*' (0x2a). */
|
|
static const char xmod_src[] =
|
|
"package a;\n"
|
|
"export def J: i32 = 41;\n"
|
|
"package b;\n"
|
|
"import a;\n"
|
|
"def K: i32 = a.J + 1;\n"
|
|
"export fn main() i32 = {\n"
|
|
"\tlet k: i32 = K;\n"
|
|
"\tif (k == 42) { return 42; };\n"
|
|
"\treturn 1;\n"
|
|
"};\n";
|
|
|
|
/* Assert the folded DATA row for symbol `b.K` is present with value 42
|
|
* (first byte '*'). Returns 0 on success. */
|
|
static int
|
|
xmod_fold_present(const char *w6c, const char *stage)
|
|
{
|
|
char src[64], s[64], buf[1 << 15];
|
|
snprintf(src, sizeof src, "/tmp/dcf_xm_%d_%s.ww", getpid(), stage);
|
|
snprintf(s, sizeof s, "/tmp/dcf_xm_%d_%s.s", getpid(), stage);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) return -1;
|
|
wwtest_fputs(xmod_src, f);
|
|
fclose(f);
|
|
|
|
if (emit_s(w6c, src, s, sizeof s) != 0) {
|
|
fprintf(stderr, "xmod[%s]: w6c emit failed\n", stage);
|
|
unlink(src);
|
|
return -1;
|
|
}
|
|
int rc = 0;
|
|
if (slurp(s, buf, sizeof buf) < 0) {
|
|
rc = -1;
|
|
} else if (strstr(buf, "b.K(SB),\"*") == NULL) {
|
|
fprintf(stderr,
|
|
"xmod[%s]: no folded `DATA b.K(SB),\"*` (value 42) row\n",
|
|
stage);
|
|
rc = -1;
|
|
}
|
|
unlink(src); unlink(s);
|
|
return rc;
|
|
}
|
|
|
|
/* ---- 4. fail-loud rows: must fail the build on BOTH stages ---------- */
|
|
|
|
struct failrow { const char *label; const char *src; };
|
|
|
|
static const struct failrow fail_rows[] = {
|
|
/* Residual combined-source artifact: cannot use the ordinary
|
|
* single-package //ww:error carrier. */
|
|
/* Cross-module cycle (combined source): a.J -> b.K -> a.J. */
|
|
{ "cross-module-cycle",
|
|
"package a;\n"
|
|
"import b;\n"
|
|
"export def J: i32 = b.K;\n"
|
|
"package b;\n"
|
|
"import a;\n"
|
|
"export def K: i32 = a.J;\n"
|
|
"export fn main() i32 = { return 0; };\n" },
|
|
};
|
|
|
|
/* Compile via w6c only (no link/run); success means the build FAILED as
|
|
* required (nonzero w6c exit). */
|
|
static int
|
|
compile_fails(const char *w6c, const struct failrow *r, const char *stage, int i)
|
|
{
|
|
char src[64], s[64], cmd[1024];
|
|
snprintf(src, sizeof src, "/tmp/dcf_fl_%d_%d.ww", getpid(), i);
|
|
snprintf(s, sizeof s, "/tmp/dcf_fl_%d_%d.s", getpid(), i);
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) return -1;
|
|
wwtest_fputs(r->src, f);
|
|
fclose(f);
|
|
|
|
/* A regressed depth guard would HANG the cycle rows rather than fail
|
|
* loud (rule 7). timeout's 124 is distinct from a clean guard failure
|
|
* -- fold it back to a TEST failure so a hang can't pass as fail-loud. */
|
|
snprintf(cmd, sizeof cmd, "timeout 180 %s -o %s %s 2>/dev/null",
|
|
w6c, s, src);
|
|
int rc = runwait(cmd);
|
|
unlink(src); unlink(s);
|
|
if (rc == 0) {
|
|
fprintf(stderr,
|
|
"failrow[%s][%s]: w6c exited 0 (expected loud failure)\n",
|
|
r->label, stage);
|
|
return -1;
|
|
}
|
|
if (rc == 124) {
|
|
fprintf(stderr,
|
|
"failrow[%s][%s]: w6c timed out (depth guard hung?)\n",
|
|
r->label, stage);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
const char *bin = getenv("BIN");
|
|
if (!bin) bin = "out/bin";
|
|
char absbin[1024];
|
|
if (bin[0] != '/') {
|
|
char cwd[1024];
|
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
bin = absbin;
|
|
}
|
|
|
|
char w6c[1024], w6c_ww[1024];
|
|
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
|
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
|
|
|
int have_ww = (access(w6c_ww, X_OK) == 0);
|
|
int total = 0, fail = 0;
|
|
|
|
int nexec = (int)(sizeof exec_rows / sizeof exec_rows[0]);
|
|
/* 2. byte-id on every exec row (only when wwstage is built) */
|
|
if (have_ww) {
|
|
for (int i = 0; i < nexec; i++) {
|
|
total++;
|
|
if (asm_byte_identical(bin, exec_rows[i].src,
|
|
exec_rows[i].label, i) != 0)
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
/* 3. cross-module fold: DATA row value on cstage, + byte-id */
|
|
total++;
|
|
if (xmod_fold_present(w6c, "cstage") != 0) fail++;
|
|
if (have_ww) {
|
|
total++;
|
|
if (xmod_fold_present(w6c_ww, "wwstage") != 0) fail++;
|
|
total++;
|
|
if (asm_byte_identical(bin, xmod_src, "xmod-fold", 900) != 0)
|
|
fail++;
|
|
}
|
|
|
|
/* 4. fail-loud on BOTH stages */
|
|
int nfail = (int)(sizeof fail_rows / sizeof fail_rows[0]);
|
|
for (int i = 0; i < nfail; i++) {
|
|
total++;
|
|
if (compile_fails(w6c, &fail_rows[i], "cstage", i) != 0) fail++;
|
|
if (have_ww) {
|
|
total++;
|
|
if (compile_fails(w6c_ww, &fail_rows[i], "wwstage",
|
|
1000 + i) != 0)
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr,
|
|
"def_const_fold: %d/%d fixtures failed\n", fail, total);
|
|
return 1;
|
|
}
|
|
printf("def_const_fold: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|