/* * 953_f64crossmod_run — runtime + byte-id regression net for the * cross-module f64-return codegen bug, one root with two symptoms: * #101 — `mod.g(): i32` where g returns f64 must CVTTSD2SI X0->AX * (f64->int convert), NOT MOVSXD (integer sign-extend). * #98 — `dbl(mod.g())` forwarding an imported f64 call-result as an * f64 arg must MOVSD-spill it, NOT integer PUSHQ AX / POPQ DI. * * Root: wwstage exprfloatkind's N_CALL arm only resolved an N_IDENT * callee's return type; a module-qualified N_DOT callee (`mod.g()`) * never reached fnretlookup and fell through to integer kind 0. cgcast * (#101) and pushargsrev (#98) then both took the integer path for an * imported f64-returning fn. Fixed in cgenutil.ww by routing the N_DOT * callee through fnretlookupmod (cstage cg_isfloat reads the resolved * call result type directly — cmd/w6c/cgen.c:117,155 — and is correct * for both cases). * * THIS TEST MUST CATCH A WWSTAGE-ONLY REGRESSION. cstage is correct * before and after the fix, so a cstage-only probe (like * 951_f64cgen_run) is gate-blind to a wwstage-only divergence. Each row * therefore carries BOTH dimensions: * (a) cstage `ww build` + run, asserting the exit code — pins that * the asm both stages converge on is the runtime-correct one. * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge. On master * 6f8b658 (pre-fix) this diverges (#101 ~2 lines MOVSXD vs * CVTTSD2SI, #98 ~6 lines PUSHQ/POPQ vs MOVSD-spill); post-fix it * is byte-identical. * * Single-file multi-package form (like 728_match_4arm_cross_module): * `package myf; ... package main; import myf; ...` in one source, so * w6c/w6c_ww see the cross-module call without -I path plumbing. */ #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; } struct row { const char *label; const char *src; int want_exit; }; static const struct row rows[] = { /* #101 basic: g() returns -7.0, cast to i32 -> -7, exit u8 249. */ { "x101_neg", "package myf;\n" "export fn g() f64 = { return -7.0; };\n" "package main;\n" "import myf;\n" "export fn main() i32 = { return myf.g(): i32; };\n", 249 }, /* #101 truncation toward zero, positive: 3.9 -> 3. */ { "x101_trunc_pos", "package myf;\n" "export fn g() f64 = { return 3.9; };\n" "package main;\n" "import myf;\n" "export fn main() i32 = { return myf.g(): i32; };\n", 3 }, /* #101 truncation toward zero, negative: -3.9 -> -3, exit u8 253. */ { "x101_trunc_neg", "package myf;\n" "export fn g() f64 = { return -3.9; };\n" "package main;\n" "import myf;\n" "export fn main() i32 = { return myf.g(): i32; };\n", 253 }, /* #98 nested f64-call-as-f64-arg: dbl(myf.g()) = -7.0*2 = -14, * cast to i32 -> -14, exit u8 242. The imported call-result is * forwarded as an f64 arg, exercising pushargsrev's float spill. */ { "x98_nested_arg", "package myf;\n" "export fn g() f64 = { return -7.0; };\n" "package main;\n" "import myf;\n" "fn dbl(x: f64) f64 = { return x * 2.0; };\n" "export fn main() i32 = { return dbl(myf.g()): i32; };\n", 242 }, /* #98 XMM-pressure: myf.g() forwarded as the first of three f64 * args; pushargsrev must MOVSD-spill the imported call-result into * X2 while two more f64 args are live — a distinct path from the * basic single-arg X0 spill above. sum3(-7,10.5,4.5)=8.0 -> i32 8. */ { "x98_xmm_pressure", "package myf;\n" "export fn g() f64 = { return -7.0; };\n" "package main;\n" "import myf;\n" "fn sum3(a: f64, b: f64, c: f64) f64 = { return a + b + c; };\n" "export fn main() i32 = { return sum3(myf.g(), 10.5, 4.5): i32; };\n", 8 }, { 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, "f64crossmod: 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/wwf64x_%d_%d.ww", getpid(), i); FILE *f = fopen(src, "wb"); if (f == NULL) { fail++; continue; } fputs(rows[i].src, f); fclose(f); /* (a) cstage build + run. Build cwd is a scratch dir so the * output binary (and any intermediates) land there. */ char tmpdir[64]; snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf64x_%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); /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. * FAILS on the pre-fix wwstage divergence. */ char cs_s[64], ws_s[64]; snprintf(cs_s, sizeof cs_s, "/tmp/wwf64x_%d_%d_cs.s", getpid(), i); snprintf(ws_s, sizeof ws_s, "/tmp/wwf64x_%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 f64 cross-module tests failed\n", fail, n); return 1; } printf("f64crossmod: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); return 0; }