The wcc test drivers ran `ww build <bare-/tmp src>` with no -o, so the compiler's <stem>.sepwork scratch landed beside the source and was never cleaned: unbounded /tmp growth (2195 stale dirs observed) that fills tmpfs and fabricates phantom test failures + silent harness aborts, and for in-repo fixture builds leaked .sepwork into the tracked tree. Each leaking build now writes its source + output inside a per-invocation tmpdir, passes -o <tmpdir>/<stem> so the .sepwork lands inside it, and rm -rf's the tmpdir on every exit path -- including fopen-fail and the expected-fail reject builds (scratch is mkdir'd before the build can fail). `ww run` and explicit-`-o`/byte-id helpers are left as-is; the 990/993 byte-id comparison logic is byte-for-byte unchanged. Two items filed separately (this commit holds the no-Makefile / no-main.c rail): - #13: a stale <src>.s byte-id readback (749) silently no-ops since separate-compile emits .s to <ostem>.sepwork/__root.s; documented inline. - #14: build-system Makefile recipes build selfhost/cmd/*/main.ww with no -o and leak main.sepwork in-tree (bounded, gitignored; own commit). One concern -- sepwork leak hygiene -- across 228 drivers; uniform transform applied per-file and two-round reviewed. make test: all 402 passed, zero net-new /tmp scratch, zero test-driven in-repo .sepwork.
242 lines
7.6 KiB
C
242 lines
7.6 KiB
C
/*
|
||
* 918_struct_composite_init_run — runtime + byte-id net for #129
|
||
* Phase A.2: module-level `let`/`def` with composite-struct
|
||
* initializer silently emitted undefined ref OR wrong bytes pre-fix.
|
||
*
|
||
* Pre-A.2 failure modes:
|
||
* - cstage emit_lets / wwstage emitletdataw skipped struct-typed lets
|
||
* entirely (`if (is_struct) continue;` / parallel) — link surfaced
|
||
* `undefined reference to 'main.NAME'`.
|
||
* - cstage emit_defs / wwstage emitdefconstants had no struct arm —
|
||
* same undef ref for def, plus a SEPARATE LOAD-side cgen bug
|
||
* emitting `MOVSXD (BP), AX` (reading stack frame byte 0) when the
|
||
* LOAD did get past the link.
|
||
*
|
||
* Phase A.2 fix (per A.1 SSoT-helper precedent):
|
||
* - cstage: emit_struct_data + emit_struct_lit_bytes helpers walk
|
||
* Tfield list in declaration order, zero-fill padding via per-
|
||
* field offsets (rule 13), dispatch per field type. Float-field
|
||
* bytes inlined (mirroring A.1's emit_floatlit_data shape but
|
||
* localised so the byte loop covers padding too). Nested struct
|
||
* recurses. Out-of-scope field kinds (array / str / slice / ptr)
|
||
* fatal loud per rule-7.
|
||
* - cstage: emit_lets + emit_defs gain struct arms routing through
|
||
* the helper.
|
||
* - cstage: LOAD-side widening at `if (u && u->kind == TY_STRUCT
|
||
* && lhs->kind == N_IDENT)` arm — the `let_islet`-gated LEAQ
|
||
* name(SB) shape now also fires for struct defs via the new
|
||
* `def_isstructdef` registry (mirror of letvars).
|
||
* - wwstage: parallel emitstructdata + emitstructlitbytes helpers,
|
||
* defent.dtnode field, defvarstructinfo (cgdot LOAD widening).
|
||
*
|
||
* Bootstrap NEUTRAL: zero `let/def: T = T{...}` consumers in lib/ or
|
||
* selfhost/. γ-cleanup is the first consumer (lib/math:floatinfo).
|
||
*
|
||
* Rows cover all A.2-scope shapes:
|
||
* - (a) `let CFG: cfg_t = cfg_t{a=1, b=2u64};` — plain mixed-int let
|
||
* - (b) `def CFG: cfg_t = cfg_t{a=1, b=2u64};` — plain mixed-int def
|
||
* (exercises LOAD-widening; both stages)
|
||
* - (c) `let F: ft = ft{a=1.5, b=99u32};` — let with float field
|
||
* - (d) `def F: ft = ft{a=1.5, b=99u32};` — def with float field
|
||
* (storage + LOAD + float-narrow path together)
|
||
* - (e) `let Z: zt = zt{};` — empty struct-lit zero-fill
|
||
* - (f) `let CFG: cfg_t;` — regression: no-rhs (pre-existing path
|
||
* unchanged)
|
||
*
|
||
* Each row carries (a) cstage `ww build` + run asserting exit code
|
||
* and (b) w6c vs w6c_ww `.s` cmp (rule-10 byte-id).
|
||
*
|
||
* Nested-struct shape (#3 in design report) is OMITTED here — the
|
||
* helper implements the recursion but #145 (parser/checker inner-
|
||
* literal field-name leak) blocks end-to-end correctness; nested
|
||
* row deferred until #145 lands.
|
||
*
|
||
* Array-in-struct shape parked to Phase A.3 boundary.
|
||
*/
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <unistd.h>
|
||
#include <sys/stat.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;
|
||
}
|
||
|
||
struct row { const char *label; const char *src; int want_exit; };
|
||
|
||
static const struct row rows[] = {
|
||
{ "let_int_struct",
|
||
"package main;\n"
|
||
"type cfg_t = struct { a: i32, b: u64 };\n"
|
||
"let CFG: cfg_t = cfg_t{a=1, b=2u64};\n"
|
||
"export fn main() i32 = {\n"
|
||
" return CFG.a;\n"
|
||
"};\n", 1 },
|
||
{ "def_int_struct",
|
||
"package main;\n"
|
||
"type cfg_t = struct { a: i32, b: u64 };\n"
|
||
"def CFG: cfg_t = cfg_t{a=1, b=2u64};\n"
|
||
"export fn main() i32 = {\n"
|
||
" return CFG.a;\n"
|
||
"};\n", 1 },
|
||
{ "let_float_field",
|
||
"package main;\n"
|
||
"type ft = struct { a: f64, b: u32 };\n"
|
||
"let F: ft = ft{a=1.5, b=99u32};\n"
|
||
"export fn main() i32 = {\n"
|
||
" return (F.a: i32);\n"
|
||
"};\n", 1 },
|
||
{ "def_float_field",
|
||
"package main;\n"
|
||
"type ft = struct { a: f64, b: u32 };\n"
|
||
"def F: ft = ft{a=1.5, b=99u32};\n"
|
||
"export fn main() i32 = {\n"
|
||
" return (F.a: i32);\n"
|
||
"};\n", 1 },
|
||
{ "let_empty_struct",
|
||
"package main;\n"
|
||
"type zt = struct { a: i32, b: u64 };\n"
|
||
"let Z: zt = zt{};\n"
|
||
"export fn main() i32 = {\n"
|
||
" return Z.a;\n"
|
||
"};\n", 0 },
|
||
/* 8B struct hits the cstage emit_lets scalar-8B short-circuit
|
||
* (sz==8 fold_int_literal arm) — without the `!let_isstruct`
|
||
* gate the struct lit fold-fails and the let drops entirely,
|
||
* emitting no DATA. Both stages now route via the struct arm. */
|
||
{ "let_int_struct_8b",
|
||
"package main;\n"
|
||
"type s8 = struct { a: i32, b: i32 };\n"
|
||
"let X: s8 = s8{a=7, b=42};\n"
|
||
"export fn main() i32 = {\n"
|
||
" return X.a;\n"
|
||
"};\n", 7 },
|
||
/* Regression: no-rhs path unchanged (emit_data_row_zero / wwstage
|
||
* parallel). */
|
||
{ "let_norhs_struct",
|
||
"package main;\n"
|
||
"type cfg_t = struct { a: i32, b: u64 };\n"
|
||
"let CFG: cfg_t;\n"
|
||
"export fn main() i32 = {\n"
|
||
" return CFG.a;\n"
|
||
"};\n", 0 },
|
||
{ NULL, NULL, 0 }
|
||
};
|
||
|
||
static int
|
||
slurp_eq(const char *a, const char *b)
|
||
{
|
||
FILE *fa = fopen(a, "rb");
|
||
FILE *fb = fopen(b, "rb");
|
||
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
||
int rc = 0;
|
||
for (;;) {
|
||
int ca = fgetc(fa);
|
||
int cb = fgetc(fb);
|
||
if (ca != cb) { rc = -1; break; }
|
||
if (ca == EOF) break;
|
||
}
|
||
fclose(fa); fclose(fb);
|
||
return rc;
|
||
}
|
||
|
||
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[1100], w6c_ww[1100];
|
||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||
if (access(w6c_ww, X_OK) != 0) {
|
||
fprintf(stderr, "strcomp: w6c_ww missing — cannot run "
|
||
"the cs==ww byte-id gate (the whole point of this test)\n");
|
||
return 1;
|
||
}
|
||
|
||
int n = 0, fail = 0;
|
||
for (int i = 0; rows[i].src; i++, n++) {
|
||
char tmpdir[64];
|
||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwstrc_%d_d_%d",
|
||
getpid(), i);
|
||
mkdir(tmpdir, 0755);
|
||
|
||
char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160];
|
||
snprintf(src, sizeof src, "%s/wwstrc_%d_%d.ww",
|
||
tmpdir, getpid(), i);
|
||
snprintf(outbin, sizeof outbin, "%s/wwstrc_%d_%d",
|
||
tmpdir, getpid(), i);
|
||
snprintf(cs_s, sizeof cs_s, "%s/wwstrc_%d_%d_cs.s",
|
||
tmpdir, getpid(), i);
|
||
snprintf(ws_s, sizeof ws_s, "%s/wwstrc_%d_%d_ww.s",
|
||
tmpdir, getpid(), i);
|
||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
|
||
|
||
FILE *f = fopen(src, "wb");
|
||
if (f == NULL) { fail++; runwait(rmcmd); continue; }
|
||
fputs(rows[i].src, f);
|
||
fclose(f);
|
||
|
||
char cmd[2048];
|
||
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
|
||
bin, outbin, src);
|
||
if (runwait(cmd) != 0) {
|
||
fprintf(stderr, "row[%s]: cstage build failed\n",
|
||
rows[i].label);
|
||
fail++;
|
||
runwait(rmcmd);
|
||
continue;
|
||
}
|
||
|
||
int got = runwait(outbin);
|
||
if (got != rows[i].want_exit) {
|
||
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
|
||
rows[i].label, got, rows[i].want_exit);
|
||
fail++;
|
||
}
|
||
|
||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||
w6c, cs_s, src);
|
||
if (runwait(cmd) != 0) {
|
||
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
|
||
fail++; runwait(rmcmd); continue;
|
||
}
|
||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||
w6c_ww, ws_s, src);
|
||
if (runwait(cmd) != 0) {
|
||
fprintf(stderr, "row[%s]: w6c_ww failed\n",
|
||
rows[i].label);
|
||
fail++; runwait(rmcmd); continue;
|
||
}
|
||
if (slurp_eq(cs_s, ws_s) != 0) {
|
||
fprintf(stderr,
|
||
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
|
||
"byte-id violation)\n", rows[i].label);
|
||
fail++;
|
||
}
|
||
runwait(rmcmd);
|
||
}
|
||
|
||
if (fail) {
|
||
fprintf(stderr, "%d/%d struct-composite tests failed\n", fail, n);
|
||
return 1;
|
||
}
|
||
printf("strcomp: %d/%d ok (cstage run + cs==ww byte-id)\n",
|
||
n, n);
|
||
return 0;
|
||
}
|