Files
ww/test/wcc/918_struct_composite_init_run.c
Hojun-Cho ce3a25a0b4 test: contain sepwork scratch per-driver tmpdir, fix /tmp+in-repo leak (#8)
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.
2026-06-22 23:29:39 +09:00

242 lines
7.6 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.
/*
* 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;
}