/* * 815_fmt_int_run — #6 fmt.formattable widen (bare int / uint). Pins * that lib/fmt/fmt.ww now renders bare `int` and `uint` as REAL union * members (formattable = (i64|str|bool|rune|f64|int|uint), int=5, * uint=6 appended last). Before #6 cstage LOUD-rejected a bare int arg * to the fmt family (no `int` arm to widen into) while wwstage leniently * accepted via the #128 size/name-keyed int->i64 coercion — cs!=ww. * Making int/uint real members makes BOTH accept by membership; this * test is the cstage-now-accepts + correct-render + cs==ww byte-id gate. * * row | what it pins * ---------------+-------------------------------------------------- * bare_int | fprint(int 42) over the writeone path -> "42" * bare_int_neg | fprint(int -7) -> "-7" (signed i64dec path) * bare_uint | fprint(uint 42) -> "42" * uint_highbit | fprint(uint 2^63+1) -> "9223372036854775809" * | LOAD-BEARING: proves the strconv.u64tos UNSIGNED * | route (len 19, first byte '9' not '-') — i64dec * | would render this negative ("-9223372036854775807", * | len 20). Also proves wwstage tags it uint (6), not * | i64, under the widened union. * int_printf | bsprintf("{}", int 42) -> "42" (formatfield -> * | formatone -> formatraw placeholder path) * unaffected_i64 | fprint(42i64) -> "42" (existing arm regression guard) * unaffected_str | fprint("hi") -> "hi" (existing arm regression guard) * mixed | fprint(1:int, 2i64, true) -> "1 2 true": int(tag5) * | + i64(tag0) + bool(tag2) coexist in one call, * | distinct tags, ww auto-space separator. * * Each row builds + runs through BOTH `ww` (cstage) and `ww_ww` * (wwstage) via the driver (-I lib for the fmt import), then a byte-id * pass builds the combined.ww via the cstage driver and diffs the asm * w6c emits against w6c_ww — any cs!=ww tag/render divergence reds here. * * BOOTSTRAP-EMBED: fmt is NOT embedded in any selfhost combined.ww — * w6c/wwdump main.ww never reach wcc/err.ww (the lone `import fmt`), so * neither selfhost/cmd/{w6c,wwdump}/main.combined.ww carries an fmt fn * (grep-verified: 0 putbytes/writeone/formatraw; the 2 "formattable" * hits are cgen comment text). #6 therefore rides NO combined.ww regen. * This test covers the user-program path, which is the only fmt consumer. * * GATE POLARITY: must stay GREEN. A red means the int/uint arm dropped, * uint mis-routed through the signed renderer (highbit prints negative), * the wwstage mis-tagged uint as i64, or an existing i64/str arm * regressed. */ #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[] = { { "bare_int", "package main;\n" "import os;\n" "import fmt;\n" "import io;\n" "import memio;\n" "export fn main() i32 = {\n" " let buf: [32]u8;\n" " let st: memio.stream = memio.fixed(buf[0:32]);\n" " let vs: io.stream = &st.vt;\n" " let x: int = 42;\n" " match (fmt.fprint(vs, x)) { case size => {}; case io.error => { return 91; }; };\n" " let v: str = memio.string(&st);\n" " if (v.len != 2) { return 92; };\n" " if (v[0] != 52u8 || v[1] != 50u8) { return 93; };\n" " return 50;\n" "};\n", 50 }, { "bare_int_neg", "package main;\n" "import os;\n" "import fmt;\n" "import io;\n" "import memio;\n" "export fn main() i32 = {\n" " let buf: [32]u8;\n" " let st: memio.stream = memio.fixed(buf[0:32]);\n" " let vs: io.stream = &st.vt;\n" " let x: int = -7;\n" " match (fmt.fprint(vs, x)) { case size => {}; case io.error => { return 91; }; };\n" " let v: str = memio.string(&st);\n" " if (v.len != 2) { return 92; };\n" " if (v[0] != 45u8 || v[1] != 55u8) { return 93; };\n" " return 51;\n" "};\n", 51 }, { "bare_uint", "package main;\n" "import os;\n" "import fmt;\n" "import io;\n" "import memio;\n" "export fn main() i32 = {\n" " let buf: [32]u8;\n" " let st: memio.stream = memio.fixed(buf[0:32]);\n" " let vs: io.stream = &st.vt;\n" " let x: uint = 42u64: uint;\n" " match (fmt.fprint(vs, x)) { case size => {}; case io.error => { return 91; }; };\n" " let v: str = memio.string(&st);\n" " if (v.len != 2) { return 92; };\n" " if (v[0] != 52u8 || v[1] != 50u8) { return 93; };\n" " return 52;\n" "};\n", 52 }, { "uint_highbit", "package main;\n" "import os;\n" "import fmt;\n" "import io;\n" "import memio;\n" "export fn main() i32 = {\n" " let buf: [32]u8;\n" " let st: memio.stream = memio.fixed(buf[0:32]);\n" " let vs: io.stream = &st.vt;\n" " let x: uint = 9223372036854775809u64: uint;\n" " match (fmt.fprint(vs, x)) { case size => {}; case io.error => { return 91; }; };\n" " let v: str = memio.string(&st);\n" " if (v.len != 19) { return 92; };\n" " if (v[0] != 57u8) { return 93; };\n" /* '9', not '-' (45) */ " if (v[18] != 57u8) { return 94; };\n" /* trailing '9' */ " return 53;\n" "};\n", 53 }, { "int_printf", "package main;\n" "import os;\n" "import fmt;\n" "import io;\n" "export fn main() i32 = {\n" " let buf: [16]u8;\n" " let x: int = 42;\n" " let v: str = \"\";\n" " match (fmt.bsprintf(buf[0:16], \"{}\", x)) { case let s: str => { v = s; }; case io.error => { return 91; }; };\n" " if (v.len != 2) { return 92; };\n" " if (v[0] != 52u8 || v[1] != 50u8) { return 93; };\n" " return 54;\n" "};\n", 54 }, { "unaffected_i64", "package main;\n" "import os;\n" "import fmt;\n" "import io;\n" "import memio;\n" "export fn main() i32 = {\n" " let buf: [32]u8;\n" " let st: memio.stream = memio.fixed(buf[0:32]);\n" " let vs: io.stream = &st.vt;\n" " match (fmt.fprint(vs, 42i64)) { case size => {}; case io.error => { return 91; }; };\n" " let v: str = memio.string(&st);\n" " if (v.len != 2) { return 92; };\n" " if (v[0] != 52u8 || v[1] != 50u8) { return 93; };\n" " return 55;\n" "};\n", 55 }, { "unaffected_str", "package main;\n" "import os;\n" "import fmt;\n" "import io;\n" "import memio;\n" "export fn main() i32 = {\n" " let buf: [32]u8;\n" " let st: memio.stream = memio.fixed(buf[0:32]);\n" " let vs: io.stream = &st.vt;\n" " match (fmt.fprint(vs, \"hi\")) { case size => {}; case io.error => { return 91; }; };\n" " let v: str = memio.string(&st);\n" " if (v.len != 2) { return 92; };\n" " if (v[0] != 104u8 || v[1] != 105u8) { return 93; };\n" " return 56;\n" "};\n", 56 }, { "mixed", "package main;\n" "import os;\n" "import fmt;\n" "import io;\n" "import memio;\n" "export fn main() i32 = {\n" " let buf: [32]u8;\n" " let st: memio.stream = memio.fixed(buf[0:32]);\n" " let vs: io.stream = &st.vt;\n" " let x: int = 1;\n" " match (fmt.fprint(vs, x, 2i64, true)) { case size => {}; case io.error => { return 91; }; };\n" " let v: str = memio.string(&st);\n" " if (v.len != 8) { return 92; };\n" /* "1 2 true" */ " if (v[0] != 49u8 || v[1] != 32u8) { return 93; };\n" /* '1' ' ' */ " if (v[2] != 50u8 || v[3] != 32u8) { return 94; };\n" /* '2' ' ' */ " if (v[4] != 116u8 || v[7] != 101u8) { return 95; };\n" /* 't' .. 'e' */ " return 57;\n" "};\n", 57 }, }; 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 * (task #15), so each row's build leaves .{combined.ww,s,o} + bare * exe alongside. Mirror of 781's. */ 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/fic_%d_d_%d", getpid(), seq); snprintf(base, sizeof base, "main815"); 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; } /* asm_byte_identical — #94 sep layout: the cstage driver's --sep build * (which needs -I lib for fmt import resolution that raw w6c can't do) * emits per-package w6c asm to _c.sepwork/, and the wwstage driver's * --sep build emits per-package w6c_ww asm to _w.sepwork/. Concat * (sorted glob, identical package set) and diff — proves no cs!=ww * tag/render divergence on the widened union. */ static int asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd, const struct row *r, int seq) { char tmpdir[256], base[64], src[512], cs[640], ws[640]; char cmd[2048]; snprintf(tmpdir, sizeof tmpdir, "/tmp/fic_%d_b_%d", getpid(), seq); snprintf(base, sizeof base, "main815b"); snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base); snprintf(cs, sizeof cs, "%s/all_cs.s", tmpdir); snprintf(ws, sizeof ws, "%s/all_ww.s", tmpdir); mkdir(tmpdir, 0755); if (write_source(src, r->src) != 0) { snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); if (system(cmd)) {} return -1; } int rc = 0; snprintf(cmd, sizeof cmd, "cd %s && WW_PKGCACHE=%s/pkgc_c timeout 180 %s build --sep -I %s/lib " "-o %s/%s_c %s 2>/dev/null", tmpdir, tmpdir, cdrv, cwd, tmpdir, base, src); if (runwait(cmd) != 0) { fprintf(stderr, "fmt_int_run[byteid][%s]: cstage sep build failed\n", r->label); rc = -1; } if (rc == 0) { snprintf(cmd, sizeof cmd, "cd %s && WW_PKGCACHE=%s/pkgc_w timeout 180 %s build --sep " "-I %s/lib -o %s/%s_w %s 2>/dev/null", tmpdir, tmpdir, wdrv, cwd, tmpdir, base, src); if (runwait(cmd) != 0) { fprintf(stderr, "fmt_int_run[byteid][%s]: wwstage sep build " "failed\n", r->label); rc = -1; } } if (rc == 0) { snprintf(cmd, sizeof cmd, "cat %s/%s_c.sepwork/*.s > %s 2>/dev/null", tmpdir, base, cs); if (system(cmd)) {} snprintf(cmd, sizeof cmd, "cat %s/%s_w.sepwork/*.s > %s 2>/dev/null", tmpdir, base, ws); if (system(cmd)) {} FILE *fc = fopen(cs, "rb"); FILE *fw = fopen(ws, "rb"); if (!fc || !fw) { rc = -1; } else { 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); if (rc != 0) fprintf(stderr, "fmt_int_run[byteid][%s]: cstage vs wwstage asm differs\n", r->label); } snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir); if (system(cmd)) {} 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; int wwpresent = (access(wdrv, X_OK) == 0); int seq = 0; for (int i = 0; i < n; i++) { total++; int got = run_row(cdrv, cwd, &rows[i], seq++); if (got != rows[i].want_exit) { fprintf(stderr, "fmt_int_run[cs][%s]: exit=%d want=%d\n", rows[i].label, got, rows[i].want_exit); fail++; } if (wwpresent) { total++; int gw = run_row(wdrv, cwd, &rows[i], seq++); if (gw != rows[i].want_exit) { fprintf(stderr, "fmt_int_run[ww][%s]: exit=%d want=%d\n", rows[i].label, gw, rows[i].want_exit); fail++; } } } if (wwpresent) { for (int i = 0; i < n; i++) { total++; if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) fail++; } } else { fprintf(stderr, "fmt_int_run: skip wwstage (no %s)\n", wdrv); } if (fail) { fprintf(stderr, "fmt_int_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("fmt_int_run: %d/%d ok\n", total, total); return 0; }