Files
ww/test/wcc/776_memio_vstream_run.c
Hojun-Cho eb6083e54a test: retarget Pattern-B gates to sep .sepwork layout; 915 off private global (M4 E3, #93)
The driver flip moves build artifacts from next-to-source <stem>.s to a
.sepwork/ scratch dir. 29 Pattern-B gates now build `ww build --sep -o <stem>`
and read <stem>.sepwork/__root.s (multi-package gates concat all
<stem>.sepwork/*.s, since cross-package labels live in per-package .s).
All intermediates redirect to /tmp (WW_PKGCACHE + -o), so the corpus runs
parallel-safe with no source-tree pollution. Tests pass now (--sep is live)
and survive the flip.

915 additionally retargeted off strconv's PRIVATE left_shift_table (a let,
not export) — which separate compilation correctly hides — onto a test-local
package that exports its own probe table (#96). The combined path only linked
it via a single-unit private leak; encapsulation is now honored under sep.

Test-only; all 5 *_ww binaries HOLD. 989_m1mangle_run deferred (blocked by
#99, imported-package fn main mangling under sep).
2026-06-18 11:16:56 +09:00

432 lines
15 KiB
C

/*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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
* <src>.{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 <src-stem>` emits the asm to
* <stem>.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;
}