From 94a55c565f6b4556148d29d797c02a7a29ef6057 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 13 Jun 2026 00:14:35 +0900 Subject: [PATCH] wcc/ww: no-init array global emits one DATAW slot, not two The str-size arm of the global data emit was size-keyed and matched a 24-sized array, emitting a second DATAW for the same symbol. Gate on the array kind (!isarr8). Review item #12. --- Makefile | 10 ++ selfhost/cmd/w6c/main.combined.ww | 9 +- selfhost/cmd/wcc/cgen.ww | 9 +- selfhost/cmd/wwdump/main.combined.ww | 9 +- test/wcc/689_arrglob_nodup_run.c | 162 +++++++++++++++++++++++++++ 5 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 test/wcc/689_arrglob_nodup_run.c diff --git a/Makefile b/Makefile index 6684e760..0331dc44 100644 --- a/Makefile +++ b/Makefile @@ -293,6 +293,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_slice_literal_global \ $(BIN)/test_global_arr_elem_field \ $(BIN)/test_structlit_slice_field \ + $(BIN)/test_arrglob_nodup_run \ $(BIN)/test_dot_str_chained_arg \ $(BIN)/test_dot_slice_arg \ $(BIN)/test_dot_tagged_source \ @@ -1101,6 +1102,15 @@ $(BIN)/test_structlit_slice_field: test/wcc/689_structlit_slice_field.c $(BIN)/w $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# 689_arrglob_nodup_run (#12): a no-init array global emits its zero-fill +# DATAW row exactly once (the str-size arm was size-keyed and double-matched +# arrays). Builds+runs both twins; asserts one DATAW row per stage. +$(BIN)/test_arrglob_nodup_run: test/wcc/689_arrglob_nodup_run.c $(BIN)/ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_dot_str_chained_arg: test/wcc/692_dot_str_chained_arg.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index c1852346..0c8a1d98 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -43467,7 +43467,14 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - if (sz == primtypesize("str"): i32 && !issg && !istup && !istagged && !letvarisslice(c, nm)) { + // #12: this arm is SIZE-keyed (sz == 24), not type-keyed, + // so a no-init array global whose bytes sum to str width + // (e.g. `let g: [3]u64;`) matched here AND the TY_ARRAY arm + // below → two identical `DATAW g` rows (cstage is type- + // keyed via let_isstr and emits one). Exclude arrays — the + // emitarraydata path owns them — mirroring the existing + // isarr8 guard on the sz==8 scalar arm. + if (sz == primtypesize("str"): i32 && !issg && !istup && !istagged && !isarr8 && !letvarisslice(c, nm)) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 1b902a99..486361ac 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -2936,7 +2936,14 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - if (sz == primtypesize("str"): i32 && !issg && !istup && !istagged && !letvarisslice(c, nm)) { + // #12: this arm is SIZE-keyed (sz == 24), not type-keyed, + // so a no-init array global whose bytes sum to str width + // (e.g. `let g: [3]u64;`) matched here AND the TY_ARRAY arm + // below → two identical `DATAW g` rows (cstage is type- + // keyed via let_isstr and emits one). Exclude arrays — the + // emitarraydata path owns them — mirroring the existing + // isarr8 guard on the sz==8 scalar arm. + if (sz == primtypesize("str"): i32 && !issg && !istup && !istagged && !isarr8 && !letvarisslice(c, nm)) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 91bcb5ce..16ac9fe8 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -43467,7 +43467,14 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - if (sz == primtypesize("str"): i32 && !issg && !istup && !istagged && !letvarisslice(c, nm)) { + // #12: this arm is SIZE-keyed (sz == 24), not type-keyed, + // so a no-init array global whose bytes sum to str width + // (e.g. `let g: [3]u64;`) matched here AND the TY_ARRAY arm + // below → two identical `DATAW g` rows (cstage is type- + // keyed via let_isstr and emits one). Exclude arrays — the + // emitarraydata path owns them — mirroring the existing + // isarr8 guard on the sz==8 scalar arm. + if (sz == primtypesize("str"): i32 && !issg && !istup && !istagged && !isarr8 && !letvarisslice(c, nm)) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; diff --git a/test/wcc/689_arrglob_nodup_run.c b/test/wcc/689_arrglob_nodup_run.c new file mode 100644 index 00000000..c9c3a79f --- /dev/null +++ b/test/wcc/689_arrglob_nodup_run.c @@ -0,0 +1,162 @@ +/* + * 689_arrglob_nodup_run (#12) — a no-init module-level array global emits + * its zero-fill DATAW row EXACTLY ONCE, and cstage / wwstage agree + * byte-for-byte. + * + * THE BUG (wwstage only): emitletdataw's str-payload zero-fallback arm is + * SIZE-keyed (`sz == size(str)` == 24), not type-keyed. A no-init array + * global whose bytes sum to 24 (e.g. `let g: [3]u64;`) matched that arm AND + * the dedicated TY_ARRAY arm below → two identical `DATAW main.g` rows. + * cstage discriminates on the declared type kind (let_isstr) and emits one. + * Runtime reads the null bytes correctly either way (the linker keeps one + * symbol), so the divergence is byte-id-visible only — the asm-byte-id check + * is the real discriminator; the row-count + run rows pin absolute correctness. + * + * THE FIX: exclude arrays from the str-size arm (`&& !isarr8`), mirroring the + * isarr8 guard already on the sz==8 scalar arm. The emitarraydata path owns + * array globals. + * + * check | shape | want + * -------------------+-----------------------------+-------------- + * run | g[0]+g[1]+g[2], = 1+2+3 | 6 + * dataw_rows (ww) | count of `DATAW main.g` rows | 1 + * asm_byte_identical | w6c vs w6c_ww .s | identical + * Pre-fix: ww emitted 2 `DATAW main.g` rows; cs vs ww .s differed. + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +static const char *SRC = + "package main;\n" + "let g: [3]u64;\n" + "export fn main() i32 = {\n" + " g[0] = 1; g[1] = 2; g[2] = 3;\n" + " return (g[0] + g[1] + g[2]): i32;\n" + "};\n"; + +static int +run_driver(const char *driver) +{ + char src[64], tmpdir[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/agnd_%d.ww", getpid()); + snprintf(tmpdir, sizeof tmpdir, "/tmp/agnd_%d_d", getpid()); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(SRC, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", + tmpdir, driver, src); + if (runwait(cmd) != 0) { unlink(src); rmdir(tmpdir); return -1; } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[128]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + int got = runwait(outbin); + + unlink(src); unlink(outbin); rmdir(tmpdir); + return got; +} + +/* emit .s via `tool`, return the count of `DATAW main.g(SB)` rows, or -1. */ +static int +dataw_rows(const char *bin, const char *tool) +{ + char src[64], asmf[80], cmd[1024]; + snprintf(src, sizeof src, "/tmp/agnd_asm_%d.ww", getpid()); + snprintf(asmf, sizeof asmf, "/tmp/agnd_asm_%d_%s.s", getpid(), tool); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(SRC, f); + fclose(f); + + snprintf(cmd, sizeof cmd, "%s/%s -o %s %s 2>/dev/null", bin, tool, asmf, src); + if (runwait(cmd) != 0) { unlink(src); return -1; } + + FILE *a = fopen(asmf, "rb"); + int n = 0; + if (a) { + char line[512]; + while (fgets(line, sizeof line, a)) + if (strncmp(line, "DATAW main.g(SB)", 16) == 0) n++; + fclose(a); + } else { + n = -1; + } + unlink(src); unlink(asmf); + return n; +} + +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 cdrv[1024], wdrv[1024]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + int have_ww = (access(wdrv, X_OK) == 0); + + int fail = 0, total = 0; + + /* run correctness, both drivers */ + total++; + if (run_driver(cdrv) != 6) { + fprintf(stderr, "arrglob_nodup[cstage]: run != 6\n"); fail++; + } + if (have_ww) { + total++; + if (run_driver(wdrv) != 6) { + fprintf(stderr, "arrglob_nodup[wwstage]: run != 6\n"); fail++; + } + } + + /* exactly one DATAW row per stage */ + total++; + if (dataw_rows(bin, "w6c") != 1) { + fprintf(stderr, "arrglob_nodup[w6c]: DATAW main.g rows != 1\n"); fail++; + } + if (access(wdrv, X_OK) == 0) { + total++; + int r = dataw_rows(bin, "w6c_ww"); + if (r != 1) { + fprintf(stderr, "arrglob_nodup[w6c_ww]: DATAW main.g rows = %d " + "(want 1 — double-emit #12)\n", r); + fail++; + } + } + + if (fail) { + fprintf(stderr, "arrglob_nodup_run: %d/%d checks failed\n", fail, total); + return 1; + } + printf("arrglob_nodup_run: %d/%d ok\n", total, total); + return 0; +}