/* * 782_fieldfn_leaf_collide_run — project #211 close, the cgen sibling of * #208 (the checker fix). * * SHAPE: a value-receiver fn-pointer FIELD call `s.pull(...)` whose leaf * name `pull` COLLIDES with a same-module GLOBAL fn `pull` of a DIFFERENT * register shape — the field returns a tagged `(i64 | sentinel)` (2-word * AX=tag/DX=word0 ABI), the global returns a scalar `i64` (1-word AX). * cstage resolves the call result from the CALLEE's own type (the field's * fn type, cmd/wcc/check.c:1433/1490 `n->type = u->ret`), so it reads the * tagged 2-word return correctly. PRE-#211 wwstage cgen re-derived the * source shape by NAME (rhstaggedabicall → fnretlookupmod over the leaf, * with the receiver VARIABLE name as the "module"), mis-bound the scalar * global, and widened a 1-word AX into the tagged slot — a silent cs≠ww * miscompile (wrong runtime + divergent .s). * * THE FIX (#211, align wwstage UP to cstage, structural): rhstaggedabicall * (selfhost/cmd/wcc/cgenutil.ww) reads the checker-stamped result type off * the N_CALL node (`src.type_`, which check.ww's N_CALL stamps to the * callee fn-type's ret in both the SK_FN and the fn-VALUE/field paths) * instead of the leaf-name lookup. Mirrors harec selecting by interned * type id, not name (ref/harec/src/types.c:714). #211 was MASKED until * #208 landed: pre-#208 the wwstage checker rejected this shape ("is/as: * operand is not a tagged union") before cgen ran, so the cgen path was * unreachable. * * row | what it pins * ---------------------+---------------------------------------------- * field_vs_global_leaf | s.pull(&s,5) routes to the FIELD impl * | (srcpull → 105), NOT the same-named global * | `pull` (→ 14). `r is i64` / `r as i64` prove * | the result is the tagged field type. The * | global stays live (g = pull(3) = 14) so the * | collision is real, not dead-code-elided. * * Graduated to STAGE_CS | STAGE_WW + byte_id on #211 close: builds + runs * on both stages (exit 42) and asserts cs.s == ww.s (rule-10). A red means * #211 regressed — wwstage re-derived the call-result shape by leaf name * again, or the cstage call-result resolution broke. */ #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; } #define STAGE_CS 1 #define STAGE_WW 2 struct row { const char *label; const char *src; int want_exit; int stage_mask; int byte_id; /* assert cs.s == ww.s */ }; static const struct row rows[] = { { "field_vs_global_leaf", "package main;\n" "type sentinel = void;\n" "fn pull(x: i64) i64 = {\n" " return x + 11i64;\n" "};\n" "type src = struct {\n" " pull: fn(s: *src, k: i64) (i64 | sentinel),\n" "};\n" "fn srcpull(s: *src, k: i64) (i64 | sentinel) = {\n" " return k + 100i64;\n" "};\n" "export fn main() i32 = {\n" " let s: src;\n" " s.pull = srcpull;\n" " let g: i64 = pull(3i64);\n" " let r = s.pull(&s, 5i64);\n" " let out: i64 = -1i64;\n" " if (r is i64) { out = r as i64; };\n" " if (out == 105i64 && g == 14i64) { return 42; };\n" " return 1i32;\n" "};\n", 42, STAGE_CS | STAGE_WW, 1 }, }; static int write_source(const char *path, const char *src) { FILE *f = fopen(path, "wb"); if (!f) return -1; fputs(src, f); fclose(f); return 0; } /* Per-row tmpdir cleanup. ww_ww writes intermediates next to the source * (filed task #15); cstage ww does too. Sweep then rmdir. */ static void cleanup_tmp(const char *tmpdir, const char *base) { char p[1024]; snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p); snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); if (system(p)) {} /* best-effort */ snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p); rmdir(tmpdir); } static int build_via_driver(const char *driver, const char *tmpdir, const char *src) { char cmd[2048]; /* #93 sep layout: emit asm to .sepwork/__root.s; pin * WW_PKGCACHE under tmpdir so out/.pkgcache is untouched. */ snprintf(cmd, sizeof cmd, "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " "-o \"${b%%.ww}\" \"$b\" 2>/dev/null", tmpdir, src, tmpdir, driver); return runwait(cmd); } /* run_row — build via driver, run the binary, return exit (or -1 on * build failure). */ static int run_row(const char *driver, const struct row *r, int seq) { char tmpdir[256], src[512], base[64], outbin[768]; snprintf(tmpdir, sizeof tmpdir, "/tmp/ffl_%d_d_%d", getpid(), seq); snprintf(base, sizeof base, "main782"); snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base); mkdir(tmpdir, 0755); if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; } int rc; if (build_via_driver(driver, tmpdir, src) == 0) { snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); rc = runwait(outbin); } else { rc = -1; } cleanup_tmp(tmpdir, base); return rc; } /* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so * ww_ww writing intermediates next to the source doesn't clobber the * cstage .s (CLAUDE.md rule 14 phase split). Mirror of 783's helper. */ static int asm_byte_identical(const char *cdrv, const char *wdrv, const struct row *r, int seq) { char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; snprintf(tdc, sizeof tdc, "/tmp/ffl_%d_c_%d", getpid(), seq); snprintf(tdw, sizeof tdw, "/tmp/ffl_%d_w_%d", getpid(), seq); snprintf(base, sizeof base, "main782"); mkdir(tdc, 0755); mkdir(tdw, 0755); snprintf(src, sizeof src, "%s/%s.ww", tdc, base); if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; } int rc = -1; if (build_via_driver(cdrv, tdc, src) != 0) goto out; snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base); snprintf(src, sizeof src, "%s/%s.ww", tdw, base); if (write_source(src, r->src) != 0) goto out; if (build_via_driver(wdrv, tdw, src) != 0) goto out; snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base); FILE *fc = fopen(cs, "rb"); FILE *fw = fopen(ws, "rb"); if (fc && fw) { rc = 0; for (;;) { int a = fgetc(fc); int b = fgetc(fw); if (a != b) { rc = -1; break; } if (a == EOF) break; } } if (fc) fclose(fc); if (fw) fclose(fw); out: cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return rc; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; char absbin[512]; if (bin[0] != '/') { snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640], wdrv[640]; 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, seq = 0; int wwpresent = (access(wdrv, X_OK) == 0); for (int i = 0; i < n; i++) { const struct row *r = &rows[i]; if (r->stage_mask & STAGE_CS) { total++; int got = run_row(cdrv, r, seq++); if (got != r->want_exit) { fprintf(stderr, "fieldfn_leaf_collide[cs][%s]: exit=%d want=%d\n", r->label, got, r->want_exit); fail++; } } if (wwpresent && (r->stage_mask & STAGE_WW)) { total++; int got = run_row(wdrv, r, seq++); if (got != r->want_exit) { fprintf(stderr, "fieldfn_leaf_collide[ww][%s]: exit=%d want=%d\n", r->label, got, r->want_exit); fail++; } if (r->byte_id) { total++; if (asm_byte_identical(cdrv, wdrv, r, seq++) != 0) { fprintf(stderr, "fieldfn_leaf_collide[byte-id][%s]: cstage vs wwstage asm differs\n", r->label); fail++; } } } } if (fail) { fprintf(stderr, "fieldfn_leaf_collide: %d/%d checks failed\n", fail, total); return 1; } printf("fieldfn_leaf_collide: %d/%d ok\n", total, total); return 0; }