/* * 782_fieldfn_leaf_collide_run — cstage-only pin for project #211, 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), so it reads the tagged 2-word return correctly. wwstage cgen * re-derives the return type by NAME (fnretlookup over the leaf, with the * receiver VARIABLE name as the "module"), mis-binds the scalar global, * and widens a 1-word AX into the tagged slot — a silent cs≠ww miscompile * (wrong runtime + divergent .s). See selfhost/cmd/wcc/cgen.ww fnretlookup. * * WHY cstage-only (carve-out idiom, mirror of 777/780/781): exercising * this row under wwstage would trip #211 (asm differs, runtime wrong), so * STAGE_WW + byte_id are withheld until #211 closes. The row pins the * cstage-correct behaviour (the spec) so #211's fix is a graduation, not * a regression. #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. * * GATE POLARITY: must stay GREEN. A red means the cstage call-result * resolution regressed on a value-receiver fn-ptr field call. * * GRADUATES to STAGE_CS | STAGE_WW + byte_id on #211 close. */ #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; }; 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, 0 }, }; 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. Mirror of * 777's cleanup_tmp. */ static void cleanup_tmp(const char *tmpdir, const char *base) { char p[640]; snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p); snprintf(p, sizeof p, "%s/%s.s", tmpdir, base); unlink(p); 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 *cwd, const char *src) { char cmd[2048]; snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s build -I %s/lib %s 2>/dev/null", tmpdir, driver, cwd, src); return runwait(cmd); } static int run_row(const char *driver, const char *cwd, 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; int br = build_via_driver(driver, tmpdir, cwd, src); if (br == 0) { snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); rc = runwait(outbin); } else { rc = -1; } cleanup_tmp(tmpdir, 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]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; int seq = 0; for (int i = 0; i < n; i++) { /* #211: cstage-only carve-out. No STAGE_WW row until #211 * (cgen name-keyed call-return mis-resolution) closes. */ if (rows[i].stage_mask & STAGE_CS) { total++; int got = run_row(cdrv, cwd, &rows[i], seq++); if (got != rows[i].want_exit) { fprintf(stderr, "fieldfn_leaf_collide[cs][%s]: exit=%d want=%d\n", rows[i].label, got, rows[i].want_exit); fail++; } } } if (fail) { fprintf(stderr, "fieldfn_leaf_collide: %d/%d fixtures failed\n", fail, total); return 1; } printf("fieldfn_leaf_collide: %d/%d ok\n", total, total); return 0; }