Files
ww/test/wcc/918_struct_composite_init_run.c
Hojun-Cho 0ed0b3933c wcc: struct-composite let/def DATA emit via SSoT helper (#129 A.2)
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
2026-05-27 05:04:05 +09:00

248 lines
7.7 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 <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;
}