From 5d596206c667d2f3d86d348b6e5f8638e13ab238 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 6 Jun 2026 13:27:24 +0900 Subject: [PATCH] wcc/cgen: #94 def-array indexed &-base leg (both-stage) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `&D[i]` over a module-level DEF array SEGV'd on BOTH stages: the TK_AMP N_INDEX N_IDENT base classify checked only the local and let legs, so a def-array base fell to a wrong else — cstage zero-based the addend (XORQ BX,BX -> wild pointer, cgen.c) while wwstage value-loaded the symbol (MOVQ name(SB) = D[0], not its address, cgenexpr.ww complex-base fallback). Divergent asm, both wild. Add one def-array leg per stage, mirroring the working let leg: - cs: `def_isarraydef(base) -> LEAQ name(SB),BX` alongside let_islet. - ww: the `defvartnode` fallback the read-side cgindex already takes (cgenexpr.ww:1762) -> N_TARRAY classifies isglobalarr -> LEAQ name(SB). The def DATA symbol already exists (plain &D + D[i]-read work), so once the base is the address the existing i*esz scale + ADDQ round-trips. cs and ww now emit BYTE-IDENTICAL LEAQ-SB asm — the both-broken -> both-correct convergence is the point (#263 class). Rows (944_def_amp_idx_run, all 0/0 byte-id): amp_int [3]int, amp_u32 [3]u32 esz=4 (narrow scale), amp_arg &D[2] as a func-arg; controls ctrl_plain (&D), ctrl_read (D[i]), ctrl_2d (&M[1][1]) keep working. *p spelled `let v: T = *p` — `*p: T` parses as `*(p: T)`. OUT (filed #112): &D[..] slicing a def-array is a distinct parse reject needing a Hare-fidelity ruling — not this leg. --- Makefile | 7 + cmd/w6c/cgen.c | 9 +- selfhost/cmd/w6c/main.combined.ww | 10 ++ selfhost/cmd/wcc/cgenexpr.ww | 10 ++ selfhost/cmd/wwdump/main.combined.ww | 10 ++ test/wcc/944_def_amp_idx_run.c | 242 +++++++++++++++++++++++++++ 6 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 test/wcc/944_def_amp_idx_run.c diff --git a/Makefile b/Makefile index 753c0df9..895a822b 100644 --- a/Makefile +++ b/Makefile @@ -308,6 +308,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_alias_idx_family_run \ $(BIN)/test_alias_amp_idx_run \ $(BIN)/test_alias_def_addr_run \ + $(BIN)/test_def_amp_idx_run \ $(BIN)/test_alias_global_decl_run \ $(BIN)/test_alias_cgen_b5_run \ $(BIN)/test_alias_cgen_b6_run \ @@ -1504,6 +1505,12 @@ $(BIN)/test_alias_def_addr_run: test/wcc/944_alias_def_addr_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_def_amp_idx_run: test/wcc/944_def_amp_idx_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_alias_global_decl_run: test/wcc/944_alias_global_decl_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index c806c868..f041e2e4 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -4248,7 +4248,14 @@ cgexpr(Cg *c, Node *n, Local *locals) amem(D_BP, boff), areg(D_BX)); } - } else if (let_islet(base->str)) { + } else if (let_islet(base->str) + || def_isarraydef(base->str)) { + /* #94: a def-array base has DATA at + * masym(name) (emit_array_data, same as a + * let) — its address is LEAQ name(SB), the + * mirror of the let leg. Without this it + * fell to the XORQ zero-base below = wild + * pointer (&D[i] SEGV). */ if (is_arr) { ins2(c, A_LEAQ, masym(c, base->str), diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 60d7360f..ed16ca43 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -25884,6 +25884,16 @@ fn cgun(c: *cgen, n: *node) void = { // name(SB) (a str/slice's .ptr IS the symbol's // first word). Align UP, mirroring cgindex. let tn: *node = letvartnode(c, base.str); + // #94: a def-array base resolves via + // defvartnode (the def-side sister), the same + // fallback cgindex's read-side already takes + // (cgenexpr.ww:1762). Without it &D[i] over a + // def array fell to the complex-base value-load + // below (MOVQ name(SB) = D[0] not the address), + // divergent from cstage's zero-base SEGV — both + // wild. The N_TARRAY tnode classifies isglobalarr + // → LEAQ name(SB), the cstage-identical base. + if (tn == nil) { tn = defvartnode(c, base.str); }; if (tn != nil) { globalname = base.str; esz = elemsizeofc(c, tn); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index e481fb11..2001cbd9 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -4697,6 +4697,16 @@ fn cgun(c: *cgen, n: *node) void = { // name(SB) (a str/slice's .ptr IS the symbol's // first word). Align UP, mirroring cgindex. let tn: *node = letvartnode(c, base.str); + // #94: a def-array base resolves via + // defvartnode (the def-side sister), the same + // fallback cgindex's read-side already takes + // (cgenexpr.ww:1762). Without it &D[i] over a + // def array fell to the complex-base value-load + // below (MOVQ name(SB) = D[0] not the address), + // divergent from cstage's zero-base SEGV — both + // wild. The N_TARRAY tnode classifies isglobalarr + // → LEAQ name(SB), the cstage-identical base. + if (tn == nil) { tn = defvartnode(c, base.str); }; if (tn != nil) { globalname = base.str; esz = elemsizeofc(c, tn); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 3a9b5d92..e9fa0859 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -25884,6 +25884,16 @@ fn cgun(c: *cgen, n: *node) void = { // name(SB) (a str/slice's .ptr IS the symbol's // first word). Align UP, mirroring cgindex. let tn: *node = letvartnode(c, base.str); + // #94: a def-array base resolves via + // defvartnode (the def-side sister), the same + // fallback cgindex's read-side already takes + // (cgenexpr.ww:1762). Without it &D[i] over a + // def array fell to the complex-base value-load + // below (MOVQ name(SB) = D[0] not the address), + // divergent from cstage's zero-base SEGV — both + // wild. The N_TARRAY tnode classifies isglobalarr + // → LEAQ name(SB), the cstage-identical base. + if (tn == nil) { tn = defvartnode(c, base.str); }; if (tn != nil) { globalname = base.str; esz = elemsizeofc(c, tn); diff --git a/test/wcc/944_def_amp_idx_run.c b/test/wcc/944_def_amp_idx_run.c new file mode 100644 index 00000000..6ea806df --- /dev/null +++ b/test/wcc/944_def_amp_idx_run.c @@ -0,0 +1,242 @@ +/* + * 944_def_amp_idx_run — #94 `&D[i]` over a DEF-array (indexed &-base). + * + * BOTH stages SEGV'd at the TK_AMP N_INDEX N_IDENT base classify: a + * def-array base matched neither the local nor the let leg and fell to + * a wrong else — cstage zero-based the addend (XORQ BX,BX → wild ptr, + * cgen.c:4261) while wwstage value-loaded the symbol (MOVQ name(SB) = + * D[0], not the address, cgenexpr.ww complex-base fallback). DIVERGENT + * asm, both wild. The fix adds ONE def-array leg per stage mirroring + * the working let leg: cs `def_isarraydef → LEAQ name(SB),BX`; ww the + * `defvartnode` fallback the read-side cgindex already takes (#129 A.3, + * cgenexpr.ww:1762) → isglobalarr → LEAQ name(SB). The def DATA symbol + * already exists (plain &D + D[i]-read work), so once the base is the + * address the existing i*esz scale + ADDQ round-trips. cs and ww now + * emit BYTE-IDENTICAL LEAQ-SB asm (the convergence is the point). + * + * row | shape | want + * -----------+-----------------------------------------+----- + * amp_int | def [3]int; &D[2]; read via *p | 0 + * amp_u32 | def [3]u32 esz=4; &D[2]; read via *p | 0 (narrow esz) + * amp_arg | &D[2] as a func-arg (context-indep base) | 0 + * ctrl_plain | plain &D (no index) | 0 (must hold) + * ctrl_read | D[1] indexed READ (no &) | 0 (must hold) + * ctrl_2d | &M[1][1] over a def [2][2]int | 0 (must hold) + * + * Pre-fix: amp_int/amp_u32/amp_arg SEGV both stages (cs≠ww asm); the + * three controls already worked. Post-fix: all six 0/0 byte-id. The + * `*p` deref is spelled `let v: T = *p;` — `*p: T` parses as + * `*(p: T)` (cast binds tighter than prefix-*), a spurious "cannot + * deref" (ken #94 oracle test caveat). + * + * OUT (filed #112): &D[..] slicing a def-array (a distinct parse + * reject, needs a Hare-fidelity ruling) — not this leg. + */ +#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 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), cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + return rc; +} + +struct row { const char *label; const char *src; int want; }; + +static const struct row rows[] = { + { "amp_int", + "package main;\n" + "def D: [3]int = [1000: int, 2000: int, 3000: int];\n" + "export fn main() i32 = {\n" + " let p: *int = &D[2];\n" + " let v: int = *p;\n" + " if (v != 3000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + { "amp_u32", + "package main;\n" + "def D: [3]u32 = [1000: u32, 2000: u32, 3000: u32];\n" + "export fn main() i32 = {\n" + " let p: *u32 = &D[2];\n" + " let v: u32 = *p;\n" + " if (v != 3000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + { "amp_arg", + "package main;\n" + "def D: [3]int = [1000: int, 2000: int, 3000: int];\n" + "fn deref(p: *int) int = { let v: int = *p; return v; };\n" + "export fn main() i32 = {\n" + " if (deref(&D[2]) != 3000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + /* controls — plain &D, indexed READ, and 2D &M[1][1] must keep + * working byte-id (the new leg must not perturb them). */ + { "ctrl_plain", + "package main;\n" + "def D: [3]int = [1000: int, 2000: int, 3000: int];\n" + "export fn main() i32 = {\n" + " let p: *[3]int = &D;\n" + " if ((*p)[0] != 1000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + { "ctrl_read", + "package main;\n" + "def D: [3]int = [1000: int, 2000: int, 3000: int];\n" + "export fn main() i32 = {\n" + " if (D[1] != 2000) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, + { "ctrl_2d", + "package main;\n" + "def M: [2][2]int = [[10: int, 20: int], [30: int, 40: int]];\n" + "export fn main() i32 = {\n" + " let q: *int = &M[1][1];\n" + " let v: int = *q;\n" + " if (v != 40) { return 1; };\n" + " return 0;\n" + "};\n", 0 }, +}; + +static int +run_driver(const char *driver, const struct row *r, int i) +{ + char src[96], tmpdir[96], errf[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/dai_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/dai_%d_d_%d", getpid(), i); + snprintf(errf, sizeof errf, "/tmp/dai_%d_e_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, + "cd %s && timeout 20 %s build %s >/dev/null 2>%s", + tmpdir, driver, src, errf); + int brc = runwait(cmd); + if (brc != 0) { + fprintf(stderr, "row[%s]: build via %s failed\n", + r->label, driver); + unlink(src); unlink(errf); rmdir(tmpdir); + return -1; + } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[256]; + 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); unlink(errf); rmdir(tmpdir); + if (got != r->want) { + fprintf(stderr, "row[%s]: %s exit %d, want %d\n", + r->label, driver, got, r->want); + return 1; + } + return 0; +} + +static int +asm_byte_identical(const char *bin, const struct row *r, int i) +{ + char src[96], cs[96], ws[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/dai_asm_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/dai_asm_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/dai_asm_%d_%d_w.s", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c errored\n", r->label); + unlink(src); + return -1; + } + snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", + bin, ws, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); + unlink(src); unlink(cs); + return -1; + } + int rc = slurp_eq(cs, ws); + if (rc != 0) + fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", + r->label); + unlink(src); unlink(cs); unlink(ws); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[2080]; + 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[2120], wdrv[2120]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + + for (int i = 0; i < n; i++) { + total++; + if (run_driver(cdrv, &rows[i], i) != 0) fail++; + } + if (access(wdrv, X_OK) == 0) { + for (int i = 0; i < n; i++) { + total++; + if (run_driver(wdrv, &rows[i], i) != 0) fail++; + } + for (int i = 0; i < n; i++) { + total++; + if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; + } + } + + if (fail) { + fprintf(stderr, "def_amp_idx: %d/%d checks failed\n", + fail, total); + return 1; + } + printf("def_amp_idx: %d/%d ok\n", total, total); + return 0; +}