/* * 776_memio_vstream_run — project #94 fold-eFinal sentinel. Pins * the single-surface lib/memio: a unified * `stream` struct with `vt: io.vtable` as the first field for the * intrusive cast, three constructors (fixed / dynamic / * dynamicfrom) that return the `stream` BY VALUE (sret — * field-by-field build then `return r;`, ref/hare/memio/stream.ha:46, * 58,64), the unified read / fixedwrite / dynamicwrite / * dynamicclose callbacks that recover the outer stream via a * vstream→*stream pointer cast, and the collapsed single accessors * (string / buffer / reset / borrowedread) over the common * header. The fold-eFinal FLIP collapsed the dual memio surface into * this one Hare-shaped surface (bare names, no suffix). * * Each row imports memio + io, calls one or more constructors, drives * the io.read/io.write/io.close dispatchers, and asserts an exit * constant. Both stages run; cs.s == ww.s byte-id on every row. * * row | what it pins * --------------------------+-------------------------------------- * fixed_read_5 | fixed + io.read of 5 bytes * | over an 8-byte source. Asserts the * | return is `size` and equal to 5, and * | that out[0]/out[4] mirror the buffer. * | Pins the full intrusive shape (build * | stream field-by-field → return r (sret); * | caller takes &st.vt; dispatcher recovers * | via s: *stream). * dynamic_write_grow | dynamic + io.write of 3 * | bytes. Asserts the write reports 3 * | and that the dynamicgrow path * | allocated the backing buffer (initial * | cap=0 forces growth on the first * | write). Closes via io.close to * | exercise dynamicclose's free. * dynamicfrom_alt_rw | dynamicfrom seeded with a * | 4-byte slice. Alternates st_read * | (4 bytes back) → st_write (2 bytes, * | grows past cap=4 → realloc). Asserts * | both paths report their lengths. * | Single ctx flavour, two dispatcher * | paths through the same vtable. * branched_fixed | branched callee per #105: two * | fixed instances with * | different buffer sizes (3 and 5), * | runtime-selected by the exit code * | nudge. st_read on each returns the * | matching size. Catches a constant- * | fold mistake in the dispatch path * | (mirror of 775's branched_readers * | row for the vtable-init side). * * IMPORT ORDER (#208 CLOSED): every row imports `memio; io; os;` — * os LAST. This is the formerly-failing os-late ordering. Pre-#208 it * tripped the wwstage checker on io.read/write/close's `return s.read( * s, buf)` (a fn-pointer field call) with 3 false "return: not * assignable (i64 → )"/"(i32 → )" — because exprtype re-bound the field * leaf to a same-named scalar global (os.read:i64) instead of the * field's tagged fn type. #208 fixed exprtype to resolve value-receiver * field calls from the field type (cmd/wcc/check.c:1378-1433 twin), so * the order no longer matters and all 4 rows now run STAGE_CS|STAGE_WW * byte-id. The earlier `import os;`-FIRST workaround is retired here. * * DEFERRALS / RELATED (NOT addressed by this row): * * - #173 (TRY-on-tagged-return both-stages broken) blocks * fixedwrite from returning Hare's `nomem` when full; * memio.ww surfaces 0 instead (documented divergence). * The probe doesn't pin Hare-equivalent nomem behaviour; * it can graduate once #173 closes. * * - #195 (cgassign N_DOT TK_ASSIGN on aliased-ptr receiver): * not bitten — the value-return constructor builds the stream in * a local `let r: stream;` and field-assigns each slot (including * r.vt.reader) through a plain pointer-to-local, not an aliased-ptr * receiver. The flat ptr/len/cap fields stay flat. * * GATE POLARITY: must stay GREEN. A red here means lib/memio * regressed, the sret value-return path miscompiled, the intrusive * io.stream→*stream cast miscomputed offsets, or io.vtable's * first-field embed lost its offset-0 invariant. */ #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[] = { { "fixed_read_5", "package main;\n" "import memio;\n" "import io;\n" "import os;\n" "export fn main() i32 = {\n" " let buf: [8]u8;\n" " buf[0] = 65u8; buf[1] = 66u8; buf[2] = 67u8; buf[3] = 68u8;\n" " buf[4] = 69u8; buf[5] = 70u8; buf[6] = 71u8; buf[7] = 72u8;\n" " let st: memio.stream = memio.fixed(buf[0:8]);\n" " let s: io.stream = &st.vt;\n" " let out: [5]u8;\n" " let n: size = 0;\n" " let rd = io.read(s, out[0:5]);\n" " if (rd is size) { n = rd as size; };\n" " if (n == 5: size && out[0] == 65u8 && out[4] == 69u8) { return 42; };\n" " return 99;\n" "};\n", 42, STAGE_CS | STAGE_WW, 1 }, { "dynamic_write_grow", "package main;\n" "import memio;\n" "import io;\n" "import os;\n" "export fn main() i32 = {\n" " let st: memio.stream = memio.dynamic();\n" " let s: io.stream = &st.vt;\n" " let src: [3]u8;\n" " src[0] = 88u8; src[1] = 89u8; src[2] = 90u8;\n" " let wr = io.write(s, src[0:3]);\n" " let n: size = 0;\n" " if (wr is size) { n = wr as size; };\n" " let cl = io.close(s);\n" " if (cl is void && n == 3: size) { return 43; };\n" " return 99;\n" "};\n", 43, STAGE_CS | STAGE_WW, 1 }, { "dynamicfrom_alt_rw", "package main;\n" "import memio;\n" "import io;\n" "import os;\n" "export fn main() i32 = {\n" " let seed: [4]u8;\n" " seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n" " let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n" " let s: io.stream = &st.vt;\n" " let out: [4]u8;\n" " let rd = io.read(s, out[0:4]);\n" " let rn: size = 0;\n" " if (rd is size) { rn = rd as size; };\n" " let extra: [2]u8;\n" " extra[0] = 99u8; extra[1] = 100u8;\n" " let wr = io.write(s, extra[0:2]);\n" " let wn: size = 0;\n" " if (wr is size) { wn = wr as size; };\n" " if (rn == 4: size && wn == 2: size && out[0] == 1u8 && out[3] == 4u8) { return 44; };\n" " return 99;\n" "};\n", 44, STAGE_CS | STAGE_WW, 1 }, { "branched_fixed", "package main;\n" "import memio;\n" "import io;\n" "import os;\n" "export fn main() i32 = {\n" " let a: [3]u8;\n" " a[0] = 10u8; a[1] = 11u8; a[2] = 12u8;\n" " let b: [5]u8;\n" " b[0] = 20u8; b[1] = 21u8; b[2] = 22u8; b[3] = 23u8; b[4] = 24u8;\n" " let sta: memio.stream = memio.fixed(a[0:3]);\n" " let stb: memio.stream = memio.fixed(b[0:5]);\n" " let sa: io.stream = &sta.vt;\n" " let sb: io.stream = &stb.vt;\n" " let outa: [3]u8;\n" " let outb: [5]u8;\n" " let rda = io.read(sa, outa[0:3]);\n" " let rdb = io.read(sb, outb[0:5]);\n" " let na: size = 0;\n" " let nb: size = 0;\n" " if (rda is size) { na = rda as size; };\n" " if (rdb is size) { nb = rdb as size; };\n" " if (na == 3: size && nb == 5: size && outa[2] == 12u8 && outb[4] == 24u8) { return 45; };\n" " return 99;\n" "};\n", 45, STAGE_CS | STAGE_WW, 1 }, /* accessors_string_buffer_reset — fold-eFinal PREP: the collapsed * single accessors over the common `stream` header (string / * buffer / reset, ref/hare/memio/stream.ha:81,74,87). Writes 3 * bytes through the value-returned fixed stream, then asserts * string (str view), buffer ([]u8 view), and reset (rewind to * 0) all read the flat header correctly. Pins the value-return * field integrity (st.vt + st.ptr/len/cap/pos survive sret) AND the * accessor collapse. cs.s == ww.s byte-id. */ { "accessors_string_buffer_reset", "package main;\n" "import memio;\n" "import io;\n" "import os;\n" "export fn main() i32 = {\n" " let buf: [16]u8;\n" " let st: memio.stream = memio.fixed(buf[0:16]);\n" " let s: io.stream = &st.vt;\n" " let payload: [3]u8;\n" " payload[0] = 65u8; payload[1] = 66u8; payload[2] = 67u8;\n" " let wr = io.write(s, payload[0:3]);\n" " if (!(wr is size)) { return 90; };\n" " let v: str = memio.string(&st);\n" " if (v.len != 3 || v[0] != 65u8 || v[2] != 67u8) { return 91; };\n" " let bv: []u8 = memio.buffer(&st);\n" " if (bv.len != 3 || bv[1] != 66u8) { return 92; };\n" " memio.reset(&st);\n" " if (memio.string(&st).len != 0) { return 93; };\n" " return 46;\n" "};\n", 46, STAGE_CS | STAGE_WW, 1 }, /* borrowedread_view_eof — the collapsed borrowedread * (ref/hare/memio/stream.ha:94): returns an amt-byte borrowed view * advancing the cursor, eof when fewer remain. Reads 3 of 4 seeded * bytes (view len 3), then a 2-byte borrowedread over the remaining * 1 byte returns io.eof. cs.s == ww.s byte-id. */ { "borrowedread_view_eof", "package main;\n" "import memio;\n" "import io;\n" "import os;\n" "export fn main() i32 = {\n" " let seed: [4]u8;\n" " seed[0] = 1u8; seed[1] = 2u8; seed[2] = 3u8; seed[3] = 4u8;\n" " let st: memio.stream = memio.dynamicfrom(seed[0:4]);\n" " let br = memio.borrowedread(&st, 3);\n" " match (br) {\n" " case let b: []u8 => { if (b.len != 3 || b[0] != 1u8 || b[2] != 3u8) { return 91; }; };\n" " case io.eof => { return 92; };\n" " };\n" " let br2 = memio.borrowedread(&st, 2);\n" " match (br2) {\n" " case let b: []u8 => { return 93; };\n" " case io.eof => { return 47; };\n" " };\n" " return 99;\n" "};\n", 47, 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), so each row's build leaves * .{combined.ww,s,o} + bare exe alongside. Sweep all then * rmdir. */ 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", tmpdir, base); unlink(p); /* #93 sep layout: the sep scratch dir + the tmpdir-pinned pkgcache. */ snprintf(p, sizeof p, "rm -rf %s/%s.sepwork %s/pkgc", tmpdir, base, tmpdir); if (system(p)) {} /* best-effort */ rmdir(tmpdir); } static int build_via_driver(const char *driver, const char *tmpdir, const char *cwd, const char *src) { char cmd[2048]; /* #93 sep layout: `--sep -o ` emits the asm to * .sepwork/__root.s; WW_PKGCACHE is pinned under tmpdir so the * shared out/.pkgcache is untouched and the cache dies with the * tmpdir. PRESERVES the `-I %s/lib` import-dir flag. */ snprintf(cmd, sizeof cmd, "cd %s && b='%s'; WW_PKGCACHE=%s/pkgc timeout 180 %s build --sep " "-I %s/lib -o \"${b%%.ww}\" \"$b\" 2>/dev/null", tmpdir, src, tmpdir, driver, cwd); 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/mvs_%d_d_%d", getpid(), seq); snprintf(base, sizeof base, "main776"); 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 — 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). */ static int asm_byte_identical(const char *cdrv, const char *wdrv, const char *cwd, const struct row *r, int seq) { char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512]; snprintf(tdc, sizeof tdc, "/tmp/mvs_%d_c_%d", getpid(), seq); snprintf(tdw, sizeof tdw, "/tmp/mvs_%d_w_%d", getpid(), seq); snprintf(base, sizeof base, "main776"); 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, cwd, 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, cwd, 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; int wwpresent = (access(wdrv, X_OK) == 0); int seq = 0; for (int i = 0; i < n; i++) { 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, "memio_vstream_run[cs][%s]: exit=%d want=%d\n", rows[i].label, got, rows[i].want_exit); fail++; } } if (wwpresent && (rows[i].stage_mask & STAGE_WW)) { total++; int got = run_row(wdrv, cwd, &rows[i], seq++); if (got != rows[i].want_exit) { fprintf(stderr, "memio_vstream_run[ww][%s]: exit=%d want=%d\n", rows[i].label, got, rows[i].want_exit); fail++; } if (rows[i].byte_id) { total++; if (asm_byte_identical(cdrv, wdrv, cwd, &rows[i], seq++) != 0) { fprintf(stderr, "memio_vstream_run[byte-id][%s]: cstage vs wwstage asm differs\n", rows[i].label); fail++; } } } } if (!wwpresent) fprintf(stderr, "memio_vstream_run: skip wwstage (no %s)\n", wdrv); if (fail) { fprintf(stderr, "memio_vstream_run: %d/%d fixtures failed\n", fail, total); return 1; } printf("memio_vstream_run: %d/%d ok\n", total, total); return 0; }