Extract emit_struct_data + emit_struct_lit_bytes helpers (both stages, mirrored) for module-level let/def with N_STRUCTLIT initializer. Walks Tfield linked-list in declaration order, zero-fills padding via per- field offset (rule 13, no hardcoded sizes), dispatches per field kind: integer via fold_int_literal, float via inline bitcast + sign-XOR byte- loop (A.1 shape, no INT64_MIN materialised — sibling #144), nested struct via recursion (#145 inner-field-name-leak gates the test row). Out-of-scope field kinds (str/slice/ptr/array) fatal loud per rule 7. LOAD-side widened symmetric to A.1 precedent: cstage cgexpr N_DOT direct-struct-ident + chained-N_DOT widened via new DefStruct registry (def_isstructdef populated in let_collect); wwstage cgdot direct-struct- global falls through to defvarstructinfo on letvarstructinfo nil (defent.dtnode field added, populated in collectdefs). Both stages materialise struct-def via LEAQ name(SB) same as struct-let. Pre-existing cstage scalar 8B short-circuit at emit_lets caused silent fold-fail-continue on 8B struct lits (`struct{i32,i32}`); gate now excludes let_isstruct so 8B struct lits route through emit_struct_data. Wwstage's `!issg` gate was already correct; symmetric ordering restored. Closes (all bootstrap-NEUTRAL pre-impl; γ-cleanup #40 first consumer): - emit_lets `is_struct continue` skip → struct lets emitted no DATA - emit_defs no struct arm → struct defs emitted no DATA - cstage cgexpr N_DOT for struct-def emitted MOVSXD (BP), AX (broken stack-frame read) - cstage emit_lets sz==8 short-circuit silently skipped 8B struct lits Test 918 (7 rows: let_int_struct / def_int_struct / let_float_field / def_float_field / let_empty_struct / let_int_struct_8b / let_norhs_ struct_regression) registered. Make test: 181/181 incl. 990-997 byte-id + combined_ww_fresh. Followups filed: - #145 (task #41) — nested struct-lit inner field-name leaks as extern - #42 — wwstage dotchainresolve missing defvarstructinfo lookup (A.2- scope-clean today; surfaces post-#145 nested-struct shapes) - A.3 (task #39) — array static-init audit (parks shape-4 array-in-struct) - γ-cleanup (task #40) — lib/math const-floatinfo re-fold, blocked-by A.2
248 lines
7.7 KiB
C
248 lines
7.7 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 <string.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 src[64];
|
||
snprintf(src, sizeof src, "/tmp/wwstrc_%d_%d.ww", getpid(), i);
|
||
FILE *f = fopen(src, "wb");
|
||
if (f == NULL) { fail++; continue; }
|
||
fputs(rows[i].src, f);
|
||
fclose(f);
|
||
|
||
char tmpdir[64];
|
||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwstrc_%d_d_%d",
|
||
getpid(), i);
|
||
mkdir(tmpdir, 0755);
|
||
|
||
char cmd[2048];
|
||
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
|
||
tmpdir, bin, src);
|
||
if (runwait(cmd) != 0) {
|
||
fprintf(stderr, "row[%s]: cstage build failed\n",
|
||
rows[i].label);
|
||
fail++;
|
||
unlink(src); rmdir(tmpdir);
|
||
continue;
|
||
}
|
||
|
||
char outbin[128];
|
||
const char *base = strrchr(src, '/');
|
||
base = base ? base + 1 : src;
|
||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||
char *dot = strrchr(outbin, '.');
|
||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||
|
||
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++;
|
||
}
|
||
unlink(outbin); rmdir(tmpdir);
|
||
|
||
char cs_s[64], ws_s[64];
|
||
snprintf(cs_s, sizeof cs_s, "/tmp/wwstrc_%d_%d_cs.s",
|
||
getpid(), i);
|
||
snprintf(ws_s, sizeof ws_s, "/tmp/wwstrc_%d_%d_ww.s",
|
||
getpid(), i);
|
||
|
||
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++; unlink(src); 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++; unlink(src); unlink(cs_s); 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++;
|
||
}
|
||
unlink(src); unlink(cs_s); unlink(ws_s);
|
||
}
|
||
|
||
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;
|
||
}
|