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.
203 lines
6.1 KiB
C
203 lines
6.1 KiB
C
/*
|
|
* 760_def_widen_const — const-scoped cross-prim-width integer widening
|
|
* in a `def` initializer (PROJECT #113).
|
|
*
|
|
* The gap: a def-reference types as the referent's DECLARED type, so
|
|
* `def INT_MIN: int = I32_MIN;` saw the rhs as i32 and the def-init
|
|
* assignability check (cmd/wcc/check.c, type_assignable) rejected i32 →
|
|
* int. The fix: in a def initializer the rhs is a flexible constant, so
|
|
* a const integer that folds to a concrete primitive width widens to
|
|
* the declared integer type exactly as an UNTYPED_INT literal would,
|
|
* when the value fits the target (def_cast_fits). This is the
|
|
* const-scoped subset of Hare's general integer-widening; non-const
|
|
* widening stays stricter in ww (PROJECT #115). It rides the #88
|
|
* eval_def_const fold + stamp, so cgen's literal-only emit lays the
|
|
* 8-byte DATA row with no codegen change.
|
|
*
|
|
* The fix is cstage-only: the wwstage checker (selfhost/cmd/wcc/check.ww,
|
|
* "let init / return assignability") intentionally never checks def-init
|
|
* assignability (it stays quiet, leaving full inference to the C side),
|
|
* so it never rejected the widening — the #88 stamp already laid the
|
|
* correct row. Relaxing the cstage aligns the richer side DOWN to the
|
|
* leaner side (CLAUDE.md rule 10), and both stages stamp the identical
|
|
* folded value, so the emitted asm is byte-identical.
|
|
*
|
|
* Coverage:
|
|
* 1. The r76_def_widen_* fixtures own symmetric build/runtime semantics.
|
|
* 2. BYTE-ID: cstage w6c vs wwstage w6c_ww `.s` for every fixture row
|
|
* (proves the const-fold stamp is bit-identical across stages).
|
|
* 3. FAIL-LOUD (rule 7), cstage only: an out-of-range narrowing
|
|
* def-ref (`def S: i16 = BIG;` where BIG overflows i16) must still
|
|
* fail the cstage build — def_cast_fits keeps the widening from
|
|
* becoming a silent truncation. Asserted on the cstage alone
|
|
* because the wwstage has no def-init assignability check at all
|
|
* (pre-existing leanness, unchanged by #113).
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.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;
|
|
}
|
|
|
|
struct row { const char *label; const char *path; };
|
|
|
|
static const struct row exec_rows[] = {
|
|
{ "i32-to-int-neg",
|
|
"test/wcc/data/r76_def_widen_i32_int_neg/case.ww" },
|
|
{ "i32-to-int-small",
|
|
"test/wcc/data/r76_def_widen_i32_int_small/case.ww" },
|
|
{ "u64-to-size",
|
|
"test/wcc/data/r76_def_widen_u64_size/case.ww" },
|
|
};
|
|
|
|
/* ---- shared .s emit -------------------------------------------------- */
|
|
|
|
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);
|
|
(void)cap;
|
|
return runwait(cmd);
|
|
}
|
|
|
|
/* ---- 2. byte-identity of cstage vs wwstage .s ----------------------- */
|
|
|
|
static int
|
|
asm_byte_identical(const char *bin, const struct row *r, int i)
|
|
{
|
|
char cs[64], ws[64];
|
|
snprintf(cs, sizeof cs, "/tmp/dwc_bi_%d_%d_c.s", getpid(), i);
|
|
snprintf(ws, sizeof ws, "/tmp/dwc_bi_%d_%d_w.s", getpid(), i);
|
|
|
|
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, r->path, cs, sizeof cs) != 0) {
|
|
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
|
return -1;
|
|
}
|
|
if (emit_s(w6c_ww, r->path, ws, sizeof ws) != 0) {
|
|
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
|
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",
|
|
r->label);
|
|
unlink(cs); unlink(ws);
|
|
return rc;
|
|
}
|
|
|
|
/* ---- 3. fail-loud (cstage only): out-of-range narrowing def-ref ----- */
|
|
|
|
/* `def BIG: i32 = 70000; def S: i16 = BIG;` — 70000 overflows i16, so
|
|
* def_cast_fits says the value does not fit and the const-scoped widen
|
|
* does NOT fire; the def-init assignability check then rejects i32 →
|
|
* i16. Compile via w6c only; success means the build FAILED as required
|
|
* (nonzero w6c exit), never a silent truncation. */
|
|
static const char narrow_src[] =
|
|
"def BIG_I32: i32 = 70000;\n"
|
|
"def S_I16: i16 = BIG_I32;\n"
|
|
"export fn main() i32 = { let s: i16 = S_I16; return s: i32; };\n";
|
|
|
|
static int
|
|
cstage_narrow_fails(const char *w6c)
|
|
{
|
|
char src[64], s[64], cmd[1024];
|
|
snprintf(src, sizeof src, "/tmp/dwc_nl_%d.ww", getpid());
|
|
snprintf(s, sizeof s, "/tmp/dwc_nl_%d.s", getpid());
|
|
|
|
FILE *f = fopen(src, "wb");
|
|
if (!f) return -1;
|
|
wwtest_fputs(narrow_src, f);
|
|
fclose(f);
|
|
|
|
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,
|
|
"narrow[cstage]: w6c exited 0 (expected loud failure, "
|
|
"no silent truncation)\n");
|
|
return -1;
|
|
}
|
|
if (rc == 124) {
|
|
fprintf(stderr, "narrow[cstage]: w6c timed out\n");
|
|
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]);
|
|
/* Runtime rows moved to r76_def_widen_*; retain byte-id and cstage reject. */
|
|
if (have_ww) {
|
|
for (int i = 0; i < nexec; i++) {
|
|
total++;
|
|
if (asm_byte_identical(bin, &exec_rows[i], i) != 0)
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
/* 3. fail-loud (cstage only) */
|
|
total++;
|
|
if (cstage_narrow_fails(w6c) != 0) fail++;
|
|
|
|
if (fail) {
|
|
fprintf(stderr,
|
|
"def_widen_const: %d/%d fixtures failed\n", fail, total);
|
|
return 1;
|
|
}
|
|
printf("def_widen_const: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|