/* * 946_floatarr_run — runtime + byte-id net for #119: a float ARRAY * ELEMENT load must land in X0 (MOVSS/MOVSD), not the integer register * file (MOVQ → AX). cgindex's element-load sites ended in the integer * loadopsz, so `let a: [3]f64 = [...]; a[0] + a[1]` loaded the element * into AX while the consumer's ADDSD read a stale X0 → wrong sum. #119 * adds a float-element branch (MOVSS f32 / MOVSD f64 into X0) at all * three wwstage cgindex sites (global, baselocal, fallback) and both * cstage N_INDEX element-load sites, deriving float-ness from the same * stamped element tinfo the esz already reads (elemisfloatc/elemisf32c * for ident bases, typeisfloat/typeisf32(n.type_) for N_DOT/N_INDEX * bases — never an unstamped node-stamp, dodging the #121 trap). * * #119 also required a wwstage exprfloatkind N_INDEX arm: the consumer * (cgbin / cgcast) classified an indexed float operand as INTEGER and * fell to PUSHQ/ADDQ/MOVSXD, while the cstage read the stamped operand * type and used ADDSD/CVTTSD2SI — a rule-10 divergence the load fix * exposed. The N_INDEX result type_ is checker-stamped (cgindex reads * it for esz), so this is not the unstamped-N_MLET case deferred under * #121. * * Each row carries (a) a cstage `ww build` + run asserting the exit * code, and (b) a w6c vs w6c_ww `.s` cmp (rule-10 byte-id). A row whose * want_exit is RUN_SKIP runs only the byte-id leg. * * #122 fixes the store-side twin the #119 commit deferred: the f32 * array-element STORE wrote AX (the raw double low-bits) instead of the * CVTSD2SS-narrowed X0 single, so every f32 array slot read back garbage. * Both the array-literal-init store (cgen.c:6889 / cgenstmt:949) and the * arr[i]= index store (cgen.c:3818 / cgenexpr:4209) now route FROM X0 via * MOVSS/MOVSD, mirroring the scalar float store. The f32 rows below now * assert the runtime VALUE (not byte-id only) and add an arr[i]= store * plus a [v...] repeat-fill init (a distinct cgen arm #122 also fixed). */ #include #include #include #include #include #define RUN_SKIP (-1) 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[] = { /* f64 element load + float add: 1.5 + 2.5 == 4.0. On the bug the * elements loaded into AX and the ADDSD read a stale X0 -> != 4.0. */ { "f64_arith", "package main;\n" "export fn main() i32 = {\n" " let a: [3]f64 = [1.5, 2.5, 9.0];\n" " if (a[0] + a[1] != 4.0) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* f64 element -> i32 cast: 1.5 truncates to 1. On the bug the cast * operand classified integer (MOVSXD on AX) not CVTTSD2SI on X0. */ { "f64_trunc", "package main;\n" "export fn main() i32 = {\n" " let a: [3]f64 = [1.5, 2.5, 9.0];\n" " return a[0]: i32;\n" "};\n", 1 }, /* third element, non-adjacent index: a[2] == 9.0. */ { "f64_elem2", "package main;\n" "export fn main() i32 = {\n" " let a: [3]f64 = [1.5, 2.5, 9.0];\n" " if (a[2] != 9.0) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* f32 array-literal-init store + element load + add: suffixed * literals so fold-1 narrows them. Pre-#122 the init store wrote * MOVL AX (raw double low-bits) so the slots read garbage; #122 * routes the store from X0 via MOVSS, so 1.5 + 2.5 == 4.0. */ { "f32_arith", "package main;\n" "export fn main() i32 = {\n" " let b: [2]f32 = [1.5f32, 2.5f32];\n" " if ((b[0] + b[1]): f64 != 4.0) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* f32 arr[i]= index store (#122): assign each slot, read back. * The index-store path popped the value to AX and wrote MOVL (raw * double low-bits, garbage for f32); #122 stores from X0 via MOVSS. * The [0.0f32,0.0f32] init also exercises the array-lit store. */ { "f32_index_store", "package main;\n" "export fn main() i32 = {\n" " let b: [2]f32 = [0.0f32, 0.0f32];\n" " b[0] = 1.5f32;\n" " b[1] = 2.5f32;\n" " if ((b[0] + b[1]): f64 != 4.0) { return 1; };\n" " return 0;\n" "};\n", 0 }, /* f32 [v...] repeat-fill init store (#122): the repeat marker * fills every slot from the last element's X0 single; pre-#122 the * fill wrote MOVL AX (raw double low-bits) per slot so each read * back garbage. Distinct cgen arm from the per-element list store. * 1.5 * 3 == 4.5 (exact in IEEE). */ { "f32_repeat_fill", "package main;\n" "export fn main() i32 = {\n" " let c: [3]f32 = [1.5f32...];\n" " if ((c[0] + c[1] + c[2]): f64 != 4.5) { return 1; };\n" " return 0;\n" "};\n", 0 }, { 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, "floatarr: 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 tmpdir[64]; snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfarr_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160]; snprintf(src, sizeof src, "%s/wwfarr_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/wwfarr_%d_%d", tmpdir, getpid(), i); snprintf(cs_s, sizeof cs_s, "%s/wwfarr_%d_%d_cs.s", tmpdir, getpid(), i); snprintf(ws_s, sizeof ws_s, "%s/wwfarr_%d_%d_ww.s", tmpdir, getpid(), i); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "wb"); if (f == NULL) { fail++; runwait(rmcmd); continue; } fputs(rows[i].src, f); fclose(f); /* (a) cstage build + run (skipped for byte-id-only rows). */ if (rows[i].want_exit != RUN_SKIP) { char cmd[2048]; snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", bin, outbin, src); if (runwait(cmd) != 0) { fprintf(stderr, "row[%s]: cstage build failed\n", rows[i].label); fail++; runwait(rmcmd); continue; } 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++; } } /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */ char cmd[2048]; 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++; runwait(rmcmd); 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++; runwait(rmcmd); 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++; } runwait(rmcmd); } if (fail) { fprintf(stderr, "%d/%d float-array element tests failed\n", fail, n); return 1; } printf("floatarr: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); return 0; }