lib/io: port ref/hare/io/stream.ha vtable surface (#94 fold-e1)

Additive: keeps lib/io/io.ww's pre-vtable `stream` struct +
`read`/`write`/`close` wrappers (fold-e2-eN migrates the legacy
surface to vtable-backed implementations and retires it).

lib/io/stream.ww — vtable struct (reader/writer/closer slots,
spelled `(*T | void)` per #192 — ww parser rejects `nullable *T`),
vstream = *vtable, and the st_read/st_write/st_close dispatchers
per ref/hare/io/stream.ha:33-68. Void-arm `return e;` chains two
direct widens: concrete `errors.unsupported` → `error` (#199 α)
then `error` → (size|eof|error) (#205 NAMED-variant nominal at
tagged→tagged subset). Hare's `?`-propagating st_close collapses
to a direct `return (*c)(s);` because #173 is still open; the
surface stays Hare-shaped.

lib/io/types.ww — retarget reader/writer/closer fn-aliases from
*stream to vstream. Extend `error` union to include
`errors.unsupported` explicitly (no spread — per ken's #204-block
the wrapper-vs-flatten layout asymmetry would mis-widen; the
deferred fix is filed as #199b layout-extension).

test/wcc/775_io_vtable_run.c — 7-row sentinel: reader/writer/
closer × {set, void} happy paths + branched-callee runtime.
Rows verify the call runs + the constant exit; the void-arm
rows do NOT inspect the resulting variant tag (deferred #199b
wrapped-slot tag-remap mis-routes to dst tag 0). Cstage and
wwstage emit byte-identical asm on every row.

test/wcc/768_io_types_run.c — track the alias retarget; rows
now build a vtable, pass `&vt` (= vstream), and call through
the fn-VALUE param shape.

Combined.ww regen for w6c + wwdump per #110: lib/errors lands
transitively via the new `import errors;` in types.ww.

Sibling filed inline (NOT fixed): the checker rejects bare
`&fn_name` / `let p: *alias = &fn` assignment to a
`(*alias | void)` field — the structural `*fn(...)` value isn't
accepted as the `*alias` NAMED variant. Both stages reject.
Probes route around via explicit `(&fn): *io.reader` cast at
each vtable-field assignment.
This commit is contained in:
2026-05-29 07:28:59 +09:00
parent 487cf91f12
commit 90ed913160
7 changed files with 856 additions and 67 deletions

View File

@@ -113,7 +113,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
selfhost/cmd/wcc/cgendecl.ww \
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/io/types.ww lib/memio/memio.ww lib/fmt/fmt.ww \
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/errors/errors.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww lib/memio/memio.ww lib/fmt/fmt.ww \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(LIB)/libwwrt.a | $(BIN)
@mkdir -p $(BIN)/wwdump_ww.d
@@ -135,7 +135,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
selfhost/cmd/wcc/cgendecl.ww \
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/io/types.ww lib/memio/memio.ww lib/fmt/fmt.ww \
lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strconv/stof_data.ww lib/strconv/decimal.ww lib/strconv/stof.ww lib/strconv/ftos_data.ww lib/strconv/ftos.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/errors/errors.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww lib/memio/memio.ww lib/fmt/fmt.ww \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(LIB)/libwwrt.a | $(BIN)
@mkdir -p $(BIN)/w6c_ww.d
@@ -325,6 +325,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_typeassert_nonident \
$(BIN)/test_isas_spread_variant \
$(BIN)/test_tagged_widen_named_variant \
$(BIN)/test_io_vtable_run \
$(BIN)/test_use_promote_alias \
$(BIN)/test_field_signed $(BIN)/test_frame_argcount \
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
@@ -681,6 +682,12 @@ $(BIN)/test_tagged_widen_named_variant: test/wcc/774_tagged_widen_named_variant.
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_io_vtable_run: test/wcc/775_io_vtable_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_arrlit_str_full: test/wcc/711_arrlit_str_full.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \

95
lib/io/stream.ww Normal file
View File

@@ -0,0 +1,95 @@
// stream — Hare-shaped vtable port. Project #94 fold-e1.
//
// Adds the additive vtable surface alongside lib/io/io.ww's pre-vtable
// stream struct + read/write/close wrappers (which fold-e2-eN migrate
// to vtable-backed implementations and then retire). Three exports:
//
// vtable a struct of optional fn-pointer slots — reader/writer/
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
// nullable as `nullable *T`; ww parser rejects that
// spelling (#192), so each slot is a `(*T | void)` tagged
// union (Plan-9-lean per the Nullable kept ruling; opt-in
// at optionality boundaries only).
// vstream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33). Named `vstream` rather than
// `stream` because lib/io/io.ww still owns the legacy
// `stream` struct name until fold-eN collapses both.
// st_read /
// st_write /
// st_close the three dispatchers per ref/hare/io/stream.ha:44-68.
// Each `match`es the matching vtable slot, returns
// `errors.unsupported` (widened twice) when the slot is
// void, otherwise calls through the fn pointer.
//
// Fold-e1 cgen/checker dependencies (all closed at HEAD): #191
// (N_DOT on aliased-ptr receiver — `s.reader` resolves through the
// `vstream = *vtable` alias), #198 (`is` on cross-module variant
// tag), #199 α (concrete→tagged widen via NAMED-variant nominal
// match, used by `let e: error = u;`), #200 (`as` payload-extract),
// #201 (return-of-tagged-call forwards instead of rewinds), #205
// (tagged→tagged subset accepts NAMED-variant nominal match, used
// by `return e;` where ret type contains `error` as a direct
// variant). Two direct widens compose for the void-arm `return`:
// concrete `errors.unsupported` → tagged `error` via #199 α, then
// tagged `error` → tagged `(size | eof | error)` via #205.
//
// Deferrals (drew-signed): `seeker` lands with fold-e2 once `off`
// + `whence` plug into the signature; Hare's `?`-propagating
// `st_close` body (`c(s)?;`) collapses to a direct `return (*c)(s);`
// here because #173 (TRY-on-tagged-return both-stages broken) is
// still open — the dispatcher's surface stays Hare-shaped, only the
// internal try-prop is omitted.
package io;
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
// rejects `nullable *T`); each slot is `(*T | void)`.
export type vtable = struct {
reader: (*reader | void),
writer: (*writer | void),
closer: (*closer | void),
};
// ref/hare/io/stream.ha:33. `vstream` not `stream` while io.ww still
// owns the legacy `stream` struct name.
export type vstream = *vtable;
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
// direct widens: `errors.unsupported → error` via #199 α, then
// `error → (size | eof | error)` via #205. The call-arm spells the
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
// than implicit `r(args)`).
export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
match (s.reader) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let r: *reader => return (*r)(s, buf);
};
};
// ref/hare/io/stream.ha:53-59.
export fn st_write(s: vstream, buf: []u8) (size | error) = {
match (s.writer) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let w: *writer => return (*w)(s, buf);
};
};
// ref/hare/io/stream.ha:62-68. Hare propagates the close error via
// `c(s)?;` then falls through to the void arm; ww collapses to a
// direct return because #173 (TRY-on-tagged-return) is still open.
// The surface (void | error) matches; only the internal try-prop
// is omitted.
export fn st_close(s: vstream) (void | error) = {
match (s.closer) {
case void => return void;
case let c: *closer => return (*c)(s);
};
};

View File

@@ -1,18 +1,25 @@
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D).
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D); the
// fn-aliases re-targeted from *stream → vstream as part of fold-e1
// (vtable port in stream.ww).
//
// Hare splits the io module across stream.ha + types.ha and ww does
// the same: lib/io/io.ww owns the (pre-vtable) `stream`, `eof`, and
// `underread` shapes; this file owns the surrounding port. Both
// files share `package io;` so forward refs (e.g. reader → stream)
// resolve via the dir-enum concat order.
// `underread` shapes; lib/io/stream.ww owns the Hare vtable port
// (`vtable`, `vstream`, `st_read`/`st_write`/`st_close` dispatch);
// this file owns the surrounding port. The three files share
// `package io;` so cross-file refs resolve via the dir-enum concat
// order (io.ww < stream.ww < types.ww — `vstream` lands before the
// reader/writer/closer aliases below).
//
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
// land with fold-e (need the `handle` sum). Hare's `EOF = done`
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
// stays as `io.eof` until the `done` singleton ships (#93).
package io;
import errors;
// ref/hare/io/types.ha:29-34. RDWR is spelled `3` rather than Hare's
// `READ | WRITE` because the ww parser doesn't fold expressions in
// enum value positions; the value SSoT stays the same bitfield.
@@ -24,7 +31,7 @@ export type mode = enum u8 {
};
// ref/hare/io/types.ha:37-41. Hare leaves the underlying implicit;
// ww requires one. i32 matches the off type that fold-e will plug
// ww requires one. i32 matches the off type that fold-e2 will plug
// into the seeker signature.
export type whence = enum i32 {
SET = 0,
@@ -32,20 +39,24 @@ export type whence = enum i32 {
END = 2,
};
// ref/hare/io/types.ha:11 spreads `errors::error` into the union;
// lib/errors isn't ported yet, so the union carries only the tags
// observable in this fold — `underread` (from io.ww) and the
// predeclared `nomem` (#29, type.c:72 / check.ww:78).
export type error = !(underread | nomem);
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
// ww enumerates the tags explicitly (ken's #204-block — spread of an
// errors-side tagged into this union triggers a wrapper-vs-flatten
// layout mismatch; #199b layout-extension is the deferred fix). The
// st_read/st_write dispatchers in stream.ww return
// `errors.unsupported` when the matching vtable slot is unset, so the
// variant lands here directly.
export type error = !(errors.unsupported | underread | nomem);
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww:8
// rationale; lifts to `done` with #93. `*stream` forward-refs the
// pre-vtable struct in io.ww — same cross-file pattern Hare uses.
export type reader = fn(s: *stream, buf: []u8) (size | eof | error);
// rationale; lifts to `done` with #93. `vstream` forward-refs the
// `*vtable` alias in stream.ww — the dispatcher receives the vtable
// pointer directly (Hare's stream.ha:33 + types.ha:46 collapse).
export type reader = fn(s: vstream, buf: []u8) (size | eof | error);
// ref/hare/io/types.ha:51. No `const` qualifier — ww has none, and
// lib/io/io.ww:30 + lib/sort/sort.ww:18 drop it the same way.
export type writer = fn(s: *stream, buf: []u8) (size | error);
export type writer = fn(s: vstream, buf: []u8) (size | error);
// ref/hare/io/types.ha:55.
export type closer = fn(s: *stream) (void | error);
export type closer = fn(s: vstream) (void | error);

View File

@@ -13921,21 +13921,152 @@ export fn close(s: *stream) (void | closed) = {
return s.close(s);
};
// stream — Hare-shaped vtable port. Project #94 fold-e1.
//
// Adds the additive vtable surface alongside lib/io/io.ww's pre-vtable
// stream struct + read/write/close wrappers (which fold-e2-eN migrate
// to vtable-backed implementations and then retire). Three exports:
//
// vtable a struct of optional fn-pointer slots — reader/writer/
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
// nullable as `nullable *T`; ww parser rejects that
// spelling (#192), so each slot is a `(*T | void)` tagged
// union (Plan-9-lean per the Nullable kept ruling; opt-in
// at optionality boundaries only).
// vstream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33). Named `vstream` rather than
// `stream` because lib/io/io.ww still owns the legacy
// `stream` struct name until fold-eN collapses both.
// st_read /
// st_write /
// st_close the three dispatchers per ref/hare/io/stream.ha:44-68.
// Each `match`es the matching vtable slot, returns
// `errors.unsupported` (widened twice) when the slot is
// void, otherwise calls through the fn pointer.
//
// Fold-e1 cgen/checker dependencies (all closed at HEAD): #191
// (N_DOT on aliased-ptr receiver — `s.reader` resolves through the
// `vstream = *vtable` alias), #198 (`is` on cross-module variant
// tag), #199 α (concrete→tagged widen via NAMED-variant nominal
// match, used by `let e: error = u;`), #200 (`as` payload-extract),
// #201 (return-of-tagged-call forwards instead of rewinds), #205
// (tagged→tagged subset accepts NAMED-variant nominal match, used
// by `return e;` where ret type contains `error` as a direct
// variant). Two direct widens compose for the void-arm `return`:
// concrete `errors.unsupported` → tagged `error` via #199 α, then
// tagged `error` → tagged `(size | eof | error)` via #205.
//
// Deferrals (drew-signed): `seeker` lands with fold-e2 once `off`
// + `whence` plug into the signature; Hare's `?`-propagating
// `st_close` body (`c(s)?;`) collapses to a direct `return (*c)(s);`
// here because #173 (TRY-on-tagged-return both-stages broken) is
// still open — the dispatcher's surface stays Hare-shaped, only the
// internal try-prop is omitted.
package io;
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
// rejects `nullable *T`); each slot is `(*T | void)`.
export type vtable = struct {
reader: (*reader | void),
writer: (*writer | void),
closer: (*closer | void),
};
// ref/hare/io/stream.ha:33. `vstream` not `stream` while io.ww still
// owns the legacy `stream` struct name.
export type vstream = *vtable;
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
// direct widens: `errors.unsupported → error` via #199 α, then
// `error → (size | eof | error)` via #205. The call-arm spells the
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
// than implicit `r(args)`).
export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
match (s.reader) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let r: *reader => return (*r)(s, buf);
};
};
// ref/hare/io/stream.ha:53-59.
export fn st_write(s: vstream, buf: []u8) (size | error) = {
match (s.writer) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let w: *writer => return (*w)(s, buf);
};
};
// ref/hare/io/stream.ha:62-68. Hare propagates the close error via
// `c(s)?;` then falls through to the void arm; ww collapses to a
// direct return because #173 (TRY-on-tagged-return) is still open.
// The surface (void | error) matches; only the internal try-prop
// is omitted.
export fn st_close(s: vstream) (void | error) = {
match (s.closer) {
case void => return void;
case let c: *closer => return (*c)(s);
};
};
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
//
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
// composes with every other module's error surface at the tag level.
// Per-domain errors (io.eof, io.closed, io.underread, strconv.overflow,
// ...) live in their own modules; this module ships only the generic
// conditions Hare's errors:: exports.
//
// Subset shipped today; add more from Hare's errors/common.ha as callers
// need them.
// A function was called with an invalid combination of arguments.
package errors;
export type invalid = !void;
// The user does not have permission to use this resource.
export type noaccess = !void;
// An entry was requested which does not exist.
export type noentry = !void;
// An attempt was made to create a resource which already exists.
export type exists = !void;
// The requested operation is not supported.
export type unsupported = !void;
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D).
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D); the
// fn-aliases re-targeted from *stream → vstream as part of fold-e1
// (vtable port in stream.ww).
//
// Hare splits the io module across stream.ha + types.ha and ww does
// the same: lib/io/io.ww owns the (pre-vtable) `stream`, `eof`, and
// `underread` shapes; this file owns the surrounding port. Both
// files share `package io;` so forward refs (e.g. reader → stream)
// resolve via the dir-enum concat order.
// `underread` shapes; lib/io/stream.ww owns the Hare vtable port
// (`vtable`, `vstream`, `st_read`/`st_write`/`st_close` dispatch);
// this file owns the surrounding port. The three files share
// `package io;` so cross-file refs resolve via the dir-enum concat
// order (io.ww < stream.ww < types.ww — `vstream` lands before the
// reader/writer/closer aliases below).
//
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
// land with fold-e (need the `handle` sum). Hare's `EOF = done`
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
// stays as `io.eof` until the `done` singleton ships (#93).
package io;
import errors;
// ref/hare/io/types.ha:29-34. RDWR is spelled `3` rather than Hare's
// `READ | WRITE` because the ww parser doesn't fold expressions in
// enum value positions; the value SSoT stays the same bitfield.
@@ -13947,7 +14078,7 @@ export type mode = enum u8 {
};
// ref/hare/io/types.ha:37-41. Hare leaves the underlying implicit;
// ww requires one. i32 matches the off type that fold-e will plug
// ww requires one. i32 matches the off type that fold-e2 will plug
// into the seeker signature.
export type whence = enum i32 {
SET = 0,
@@ -13955,23 +14086,27 @@ export type whence = enum i32 {
END = 2,
};
// ref/hare/io/types.ha:11 spreads `errors::error` into the union;
// lib/errors isn't ported yet, so the union carries only the tags
// observable in this fold — `underread` (from io.ww) and the
// predeclared `nomem` (#29, type.c:72 / check.ww:78).
export type error = !(underread | nomem);
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
// ww enumerates the tags explicitly (ken's #204-block — spread of an
// errors-side tagged into this union triggers a wrapper-vs-flatten
// layout mismatch; #199b layout-extension is the deferred fix). The
// st_read/st_write dispatchers in stream.ww return
// `errors.unsupported` when the matching vtable slot is unset, so the
// variant lands here directly.
export type error = !(errors.unsupported | underread | nomem);
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww:8
// rationale; lifts to `done` with #93. `*stream` forward-refs the
// pre-vtable struct in io.ww — same cross-file pattern Hare uses.
export type reader = fn(s: *stream, buf: []u8) (size | eof | error);
// rationale; lifts to `done` with #93. `vstream` forward-refs the
// `*vtable` alias in stream.ww — the dispatcher receives the vtable
// pointer directly (Hare's stream.ha:33 + types.ha:46 collapse).
export type reader = fn(s: vstream, buf: []u8) (size | eof | error);
// ref/hare/io/types.ha:51. No `const` qualifier — ww has none, and
// lib/io/io.ww:30 + lib/sort/sort.ww:18 drop it the same way.
export type writer = fn(s: *stream, buf: []u8) (size | error);
export type writer = fn(s: vstream, buf: []u8) (size | error);
// ref/hare/io/types.ha:55.
export type closer = fn(s: *stream) (void | error);
export type closer = fn(s: vstream) (void | error);
// memio — in-memory io stream.
//

View File

@@ -13921,21 +13921,152 @@ export fn close(s: *stream) (void | closed) = {
return s.close(s);
};
// stream — Hare-shaped vtable port. Project #94 fold-e1.
//
// Adds the additive vtable surface alongside lib/io/io.ww's pre-vtable
// stream struct + read/write/close wrappers (which fold-e2-eN migrate
// to vtable-backed implementations and then retire). Three exports:
//
// vtable a struct of optional fn-pointer slots — reader/writer/
// closer per ref/hare/io/stream.ha:36-42. Hare spells the
// nullable as `nullable *T`; ww parser rejects that
// spelling (#192), so each slot is a `(*T | void)` tagged
// union (Plan-9-lean per the Nullable kept ruling; opt-in
// at optionality boundaries only).
// vstream `*vtable` alias matching Hare's `stream = *vtable`
// (ref/hare/io/stream.ha:33). Named `vstream` rather than
// `stream` because lib/io/io.ww still owns the legacy
// `stream` struct name until fold-eN collapses both.
// st_read /
// st_write /
// st_close the three dispatchers per ref/hare/io/stream.ha:44-68.
// Each `match`es the matching vtable slot, returns
// `errors.unsupported` (widened twice) when the slot is
// void, otherwise calls through the fn pointer.
//
// Fold-e1 cgen/checker dependencies (all closed at HEAD): #191
// (N_DOT on aliased-ptr receiver — `s.reader` resolves through the
// `vstream = *vtable` alias), #198 (`is` on cross-module variant
// tag), #199 α (concrete→tagged widen via NAMED-variant nominal
// match, used by `let e: error = u;`), #200 (`as` payload-extract),
// #201 (return-of-tagged-call forwards instead of rewinds), #205
// (tagged→tagged subset accepts NAMED-variant nominal match, used
// by `return e;` where ret type contains `error` as a direct
// variant). Two direct widens compose for the void-arm `return`:
// concrete `errors.unsupported` → tagged `error` via #199 α, then
// tagged `error` → tagged `(size | eof | error)` via #205.
//
// Deferrals (drew-signed): `seeker` lands with fold-e2 once `off`
// + `whence` plug into the signature; Hare's `?`-propagating
// `st_close` body (`c(s)?;`) collapses to a direct `return (*c)(s);`
// here because #173 (TRY-on-tagged-return both-stages broken) is
// still open — the dispatcher's surface stays Hare-shaped, only the
// internal try-prop is omitted.
package io;
// ref/hare/io/stream.ha:36-42. Nullable spelling per #192 (ww parser
// rejects `nullable *T`); each slot is `(*T | void)`.
export type vtable = struct {
reader: (*reader | void),
writer: (*writer | void),
closer: (*closer | void),
};
// ref/hare/io/stream.ha:33. `vstream` not `stream` while io.ww still
// owns the legacy `stream` struct name.
export type vstream = *vtable;
// ref/hare/io/stream.ha:44-50. The void-arm `return e;` chains two
// direct widens: `errors.unsupported → error` via #199 α, then
// `error → (size | eof | error)` via #205. The call-arm spells the
// fn-ptr call explicitly per #193 (ww requires `(*r)(args)` rather
// than implicit `r(args)`).
export fn st_read(s: vstream, buf: []u8) (size | eof | error) = {
match (s.reader) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let r: *reader => return (*r)(s, buf);
};
};
// ref/hare/io/stream.ha:53-59.
export fn st_write(s: vstream, buf: []u8) (size | error) = {
match (s.writer) {
case void => {
let u: errors.unsupported;
let e: error = u;
return e;
};
case let w: *writer => return (*w)(s, buf);
};
};
// ref/hare/io/stream.ha:62-68. Hare propagates the close error via
// `c(s)?;` then falls through to the void arm; ww collapses to a
// direct return because #173 (TRY-on-tagged-return) is still open.
// The surface (void | error) matches; only the internal try-prop
// is omitted.
export fn st_close(s: vstream) (void | error) = {
match (s.closer) {
case void => return void;
case let c: *closer => return (*c)(s);
};
};
// errors — domain-agnostic error types. Mirrors ref/hare/errors/.
//
// Named-void tagged-union variants, so `(T | errors.invalid | ...)`
// composes with every other module's error surface at the tag level.
// Per-domain errors (io.eof, io.closed, io.underread, strconv.overflow,
// ...) live in their own modules; this module ships only the generic
// conditions Hare's errors:: exports.
//
// Subset shipped today; add more from Hare's errors/common.ha as callers
// need them.
// A function was called with an invalid combination of arguments.
package errors;
export type invalid = !void;
// The user does not have permission to use this resource.
export type noaccess = !void;
// An entry was requested which does not exist.
export type noentry = !void;
// An attempt was made to create a resource which already exists.
export type exists = !void;
// The requested operation is not supported.
export type unsupported = !void;
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D).
// fn-type aliases. Project #94 fold-1 step (d) (drew Fold D); the
// fn-aliases re-targeted from *stream → vstream as part of fold-e1
// (vtable port in stream.ww).
//
// Hare splits the io module across stream.ha + types.ha and ww does
// the same: lib/io/io.ww owns the (pre-vtable) `stream`, `eof`, and
// `underread` shapes; this file owns the surrounding port. Both
// files share `package io;` so forward refs (e.g. reader → stream)
// resolve via the dir-enum concat order.
// `underread` shapes; lib/io/stream.ww owns the Hare vtable port
// (`vtable`, `vstream`, `st_read`/`st_write`/`st_close` dispatch);
// this file owns the surrounding port. The three files share
// `package io;` so cross-file refs resolve via the dir-enum concat
// order (io.ww < stream.ww < types.ww — `vstream` lands before the
// reader/writer/closer aliases below).
//
// Deferrals (drew-signed): `handle`, `seeker`, `copier`, `strerror`
// land with fold-e (need the `handle` sum). Hare's `EOF = done`
// land with fold-e2+ (need the `handle` sum). Hare's `EOF = done`
// stays as `io.eof` until the `done` singleton ships (#93).
package io;
import errors;
// ref/hare/io/types.ha:29-34. RDWR is spelled `3` rather than Hare's
// `READ | WRITE` because the ww parser doesn't fold expressions in
// enum value positions; the value SSoT stays the same bitfield.
@@ -13947,7 +14078,7 @@ export type mode = enum u8 {
};
// ref/hare/io/types.ha:37-41. Hare leaves the underlying implicit;
// ww requires one. i32 matches the off type that fold-e will plug
// ww requires one. i32 matches the off type that fold-e2 will plug
// into the seeker signature.
export type whence = enum i32 {
SET = 0,
@@ -13955,23 +14086,27 @@ export type whence = enum i32 {
END = 2,
};
// ref/hare/io/types.ha:11 spreads `errors::error` into the union;
// lib/errors isn't ported yet, so the union carries only the tags
// observable in this fold — `underread` (from io.ww) and the
// predeclared `nomem` (#29, type.c:72 / check.ww:78).
export type error = !(underread | nomem);
// ref/hare/io/types.ha:11 spreads `...errors::error` into the union;
// ww enumerates the tags explicitly (ken's #204-block — spread of an
// errors-side tagged into this union triggers a wrapper-vs-flatten
// layout mismatch; #199b layout-extension is the deferred fix). The
// st_read/st_write dispatchers in stream.ww return
// `errors.unsupported` when the matching vtable slot is unset, so the
// variant lands here directly.
export type error = !(errors.unsupported | underread | nomem);
// ref/hare/io/types.ha:46. `eof` (not Hare's `done`) per the io.ww:8
// rationale; lifts to `done` with #93. `*stream` forward-refs the
// pre-vtable struct in io.ww — same cross-file pattern Hare uses.
export type reader = fn(s: *stream, buf: []u8) (size | eof | error);
// rationale; lifts to `done` with #93. `vstream` forward-refs the
// `*vtable` alias in stream.ww — the dispatcher receives the vtable
// pointer directly (Hare's stream.ha:33 + types.ha:46 collapse).
export type reader = fn(s: vstream, buf: []u8) (size | eof | error);
// ref/hare/io/types.ha:51. No `const` qualifier — ww has none, and
// lib/io/io.ww:30 + lib/sort/sort.ww:18 drop it the same way.
export type writer = fn(s: *stream, buf: []u8) (size | error);
export type writer = fn(s: vstream, buf: []u8) (size | error);
// ref/hare/io/types.ha:55.
export type closer = fn(s: *stream) (void | error);
export type closer = fn(s: vstream) (void | error);
// memio — in-memory io stream.
//

View File

@@ -1,10 +1,11 @@
/*
* 768_io_types_run — project #94 fold-1 step (d) (drew Fold D) sentinel.
* Pins the new lib/io/types.ww port (ref/hare/io/types.ha) — `mode` /
* `whence` enums, `error = !(underread | nomem)` union, and the
* `reader` / `writer` / `closer` fn-type aliases. The aliases forward-
* ref `*stream` from lib/io/io.ww (Hare splits the same way:
* stream.ha + types.ha share the `io` module).
* Pins lib/io/types.ww (ref/hare/io/types.ha) — `mode` / `whence`
* enums, `error = !(errors.unsupported | underread | nomem)` union,
* and the `reader` / `writer` / `closer` fn-type aliases. Fold-e1
* re-targeted the aliases from *stream → `vstream` (= *vtable in
* lib/io/stream.ww); the rows track. Hare splits the io module the
* same way: stream.ha + types.ha share the `io` module.
*
* Each row is a self-contained `import io;` program: cstage `ww build`
* compiles it, we run the binary and assert the exit. Rows that hit a
@@ -101,57 +102,62 @@ static const struct row rows[] = {
"};\n",
2,
STAGE_CS | STAGE_WW },
/* reader/writer/closer alias rows. Fold-e1 re-targeted these aliases
* from *stream → vstream (= *vtable); the test fixtures track. The
* callee receivers are typed io.vstream and the call sites build a
* stub vtable to take its address from. The vtable values are inert
* — only the dispatch type-shape is exercised. */
{ "reader_alias_param",
"package main;\n"
"import io;\n"
"fn dummy_read(s: *io.stream, buf: []u8) (size | io.eof | io.error) = {\n"
"fn dummy_read(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 0: size;\n"
"};\n"
"fn use_reader(r: io.reader, s: *io.stream, buf: []u8) i32 = {\n"
"fn use_reader(r: io.reader, s: io.vstream, buf: []u8) i32 = {\n"
" let v = r(s, buf);\n"
" if (v is size) { return 11; };\n"
" return -1;\n"
"};\n"
"export fn main() i32 = {\n"
" let st: io.stream;\n"
" let vt: io.vtable;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" return use_reader(dummy_read, &st, bs);\n"
" return use_reader(dummy_read, &vt, bs);\n"
"};\n",
11,
STAGE_CS | STAGE_WW },
{ "writer_alias_param",
"package main;\n"
"import io;\n"
"fn dummy_write(s: *io.stream, buf: []u8) (size | io.error) = {\n"
"fn dummy_write(s: io.vstream, buf: []u8) (size | io.error) = {\n"
" return buf.len: size;\n"
"};\n"
"fn use_writer(w: io.writer, s: *io.stream, buf: []u8) i32 = {\n"
"fn use_writer(w: io.writer, s: io.vstream, buf: []u8) i32 = {\n"
" let v = w(s, buf);\n"
" if (v is size) { return 22; };\n"
" return -1;\n"
"};\n"
"export fn main() i32 = {\n"
" let st: io.stream;\n"
" let vt: io.vtable;\n"
" let buf: [7]u8;\n"
" let bs: []u8 = buf[0:7];\n"
" return use_writer(dummy_write, &st, bs);\n"
" return use_writer(dummy_write, &vt, bs);\n"
"};\n",
22,
STAGE_CS | STAGE_WW },
{ "closer_alias_param",
"package main;\n"
"import io;\n"
"fn dummy_close(s: *io.stream) (void | io.error) = {\n"
"fn dummy_close(s: io.vstream) (void | io.error) = {\n"
" return void;\n"
"};\n"
"fn use_closer(c: io.closer, s: *io.stream) i32 = {\n"
"fn use_closer(c: io.closer, s: io.vstream) i32 = {\n"
" let _v = c(s);\n"
" return 33;\n"
"};\n"
"export fn main() i32 = {\n"
" let st: io.stream;\n"
" return use_closer(dummy_close, &st);\n"
" let vt: io.vtable;\n"
" return use_closer(dummy_close, &vt);\n"
"};\n",
33,
STAGE_CS | STAGE_WW },

View File

@@ -0,0 +1,400 @@
/*
* 775_io_vtable_run — project #94 fold-e1 sentinel. Pins the additive
* lib/io/stream.ww vtable port: `vtable` (the three (*T|void) slots),
* `vstream` (= *vtable), and the `st_read`/`st_write`/`st_close`
* dispatchers (ref/hare/io/stream.ha:33-68). Coexists with lib/io/io.ww's
* legacy `stream` struct + read/write/close wrappers (fold-e2+ migrates
* them to vtable-backed implementations and retires the legacy surface).
*
* Each row imports io, allocates a vtable, optionally points one slot at
* a user fn (via the `(&fn): *io.<role>` cast — see SIBLINGS below),
* calls the matching dispatcher, and checks the exit constant. Both
* stages run; cs.s == ww.s byte-id on the rows that build (rule-10).
*
* row | what it pins
* --------------------------+--------------------------------------
* reader_set_happy | reader slot set + st_read happy path.
* | Callee returns (7: size); probe asserts
* | `r is size && r as size == 7`. Pins
* | the call-arm `case let r: *reader =>
* | return (*r)(s, buf)` end-to-end.
* reader_void_unsupported | reader=void. st_read takes the void-arm
* | (two direct widens, #199 α + #205) and
* | returns the errors.unsupported variant.
* | Probe verifies the call returns + exit
* | constant only — does NOT inspect r's
* | discriminant tag (deferred #199b: the
* | wrapped-slot tag-remap currently lands
* | the value at dst tag 0, not the `error`
* | variant index; see 774 header for the
* | parallel constraint).
* writer_set_happy | symmetric to reader_set_happy on
* | st_write; callee returns (buf.len:size).
* writer_void_unsupported | symmetric to reader_void_unsupported.
* closer_void_noop | closer=void. st_close returns plain
* | `void` (no widen — direct `return void`
* | from the dispatcher). Probe asserts
* | `r is void` — the only row that can
* | inspect the tag, because no nested
* | wrapper is involved.
* closer_set | closer slot set + st_close happy path.
* | Callee returns plain `void`; the
* | dispatcher forwards via `(*c)(s)` and
* | the return surfaces at the caller.
* branched_readers | two distinct vtable instances each with
* | a different reader fn. st_read on each
* | dispatches to the right callee (1:size
* | vs 2:size). Mirrors 774's branched-
* | callee row; catches a constant-fold
* | mistake in the dispatch path.
*
* SIBLINGS (filed inline, NOT fixed here — fold-e1 is purely additive):
*
* - #199b layout-extension (wrapped-slot tag-remap): when widening a
* NAMED wrapper-typed value (e.g. `error`) into a tagged parent
* (e.g. `(size | eof | error)`), cgen walks src VARIANTS rather
* than treating src-as-a-whole, so the dst tag lands at 0
* regardless of which wrapper variant was set. Affects rows 2 & 4
* (unsupported via void-arm). Probes verify runtime exit-clean,
* NOT the variant tag — matches the 774 header's exact constraint.
*
* - NEW: checker rejects bare `&fn_name` assignment to a
* `(*<alias-to-fn> | void)` field. `vt.reader = &myread;` errors
* with "cannot assign *fn(vstream, []u8) (size | eof | error) to
* (*reader | void)" — the structural `*fn(...)` value is not
* accepted as the `*reader` named variant of the tagged field.
* Both stages reject. Symmetric to #178 (typeeqast layer asymmetry)
* at the pointer-to-fn-alias layer + tagged-variant assignment.
* PROBE ROUTES AROUND via an explicit cast: `(&myread): *io.reader`.
* Once closed, the cast drops out of every Hare-faithful vtable
* initialisation.
*
* - NEW: `let p: *io.reader = &myread;` also errors with "init
* *fn(vstream, []u8) ... not assignable to declared *reader" —
* same root as above but at the let-binding layer. The Hare
* `io::vtable { reader = &my_read, ... }` shape needs both holes
* closed.
*
* GATE POLARITY: must stay GREEN. A red here means the vtable port
* regressed at the checker, the dispatcher cgen miscompiled the
* call-arm, or the field-slot zero-init lost the void tag.
*/
#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[] = {
{ "reader_set_happy",
"package main;\n"
"import io;\n"
"fn myread(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 7: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.reader = (&myread): *io.reader;\n"
" let v: io.vstream = &vt;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let r = io.st_read(v, bs);\n"
" if (r is size) {\n"
" let n = r as size;\n"
" if (n == 7: size) { return 70; };\n"
" return 71;\n"
" };\n"
" return 99;\n"
"};\n",
70,
STAGE_CS | STAGE_WW, 1 },
{ "reader_void_unsupported",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.vstream = &vt;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let _r = io.st_read(v, bs);\n"
" return 11;\n"
"};\n",
11,
STAGE_CS | STAGE_WW, 1 },
{ "writer_set_happy",
"package main;\n"
"import io;\n"
"fn mywrite(s: io.vstream, buf: []u8) (size | io.error) = {\n"
" return buf.len: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.writer = (&mywrite): *io.writer;\n"
" let v: io.vstream = &vt;\n"
" let buf: [5]u8;\n"
" let bs: []u8 = buf[0:5];\n"
" let r = io.st_write(v, bs);\n"
" if (r is size) {\n"
" let n = r as size;\n"
" if (n == 5: size) { return 80; };\n"
" return 81;\n"
" };\n"
" return 99;\n"
"};\n",
80,
STAGE_CS | STAGE_WW, 1 },
{ "writer_void_unsupported",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.vstream = &vt;\n"
" let buf: [3]u8;\n"
" let bs: []u8 = buf[0:3];\n"
" let _r = io.st_write(v, bs);\n"
" return 22;\n"
"};\n",
22,
STAGE_CS | STAGE_WW, 1 },
{ "closer_void_noop",
"package main;\n"
"import io;\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" let v: io.vstream = &vt;\n"
" let r = io.st_close(v);\n"
" if (r is void) { return 50; };\n"
" return 99;\n"
"};\n",
50,
STAGE_CS | STAGE_WW, 1 },
{ "closer_set",
"package main;\n"
"import io;\n"
"fn myclose(s: io.vstream) (void | io.error) = {\n"
" return void;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt: io.vtable;\n"
" vt.closer = (&myclose): *io.closer;\n"
" let v: io.vstream = &vt;\n"
" let r = io.st_close(v);\n"
" if (r is void) { return 60; };\n"
" return 99;\n"
"};\n",
60,
STAGE_CS | STAGE_WW, 1 },
{ "branched_readers",
"package main;\n"
"import io;\n"
"fn r1(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 1: size;\n"
"};\n"
"fn r2(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {\n"
" return 2: size;\n"
"};\n"
"export fn main() i32 = {\n"
" let vt1: io.vtable;\n"
" let vt2: io.vtable;\n"
" vt1.reader = (&r1): *io.reader;\n"
" vt2.reader = (&r2): *io.reader;\n"
" let buf: [4]u8;\n"
" let bs: []u8 = buf[0:4];\n"
" let a = io.st_read(&vt1, bs);\n"
" let b = io.st_read(&vt2, bs);\n"
" let asz: size = 0;\n"
" let bsz: size = 0;\n"
" if (a is size) { asz = a as size; };\n"
" if (b is size) { bsz = b as size; };\n"
" if (asz == 1: size && bsz == 2: size) { return 95; };\n"
" return 99;\n"
"};\n",
95,
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 them 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.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/iov_%d_d_%d", getpid(), seq);
snprintf(base, sizeof base, "main775");
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/iov_%d_c_%d", getpid(), seq);
snprintf(tdw, sizeof tdw, "/tmp/iov_%d_w_%d", getpid(), seq);
snprintf(base, sizeof base, "main775");
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.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.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,
"io_vtable_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,
"io_vtable_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,
"io_vtable_run[byte-id][%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
}
}
}
if (!wwpresent)
fprintf(stderr, "io_vtable_run: skip wwstage (no %s)\n", wdrv);
if (fail) {
fprintf(stderr, "io_vtable_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("io_vtable_run: %d/%d ok\n", total, total);
return 0;
}