Files
ww/lib/log/vstream.ww
Hojun-Cho d87ee01cdf lib/log: add Option C parallel vstream API (#94 fold-e5)
Last per-caller migration before fold-eFinal (#50). Adds the 10 _v
variants of OLD log.ww's surface (new_v / lprintln_v / println_v /
lprintfln_v / printfln_v / lfatal_v / fatal_v / lfatalf_v / fatalf_v /
setlogger_v) alongside a vlogger vtable + vstdlogger over io.vstream.
Default sink is a module-static stderrsink_ctx_g with vt FIRST field for
the intrusive vstream cast and fd=2 — only scalars/ptrs beyond vt per
ken's mandate, no nested aggregates that would bite #18, no f32 per
#165b. Zero-init at link time per #129 A.2/A.3 SSoT; ensureinit_v wires
vt.reader / vt.writer / fd lazily on first dispatch (mirror of OLD
ensureinit at log.ww:122 + lib/temp's rnginit pattern).

OLD lib/log/log.ww UNCHANGED. fold-eFinal (#50) atomically retires the
OLD logger / stdlogger / globals + the pre-vtable stderrsink and drops
the `_v` suffix wholesale to match Hare's bare names.

Two `export` bumps on lib/fmt/vstream.ww (vfprint, vfprintf) so log's
stdprintln_v / stdprintfln_v dispatch through the existing vstream-side
formatters; additive exposure, eFinal collapses fprint over the unified
surface.

Bootstrap-embed check: log is NOT in any selfhost/cmd/*/main.combined.ww
(grep `package log\|import log` returns empty pre-impl). The fmt
vstream.ww changes are also non-embedded. 990-997 byte-id gates stay
green by virtue of log being test-only and the touched fmt symbols not
being embedded.

Probe wcc/779_log_vstream_run pins the additive surface across 4 rows
(println_v_default_stderr / printfln_v_default_stderr /
lprintln_v_custom_sink / branched_lprintln_v) cstage-only per #209
(wwstage formattable match-arm bail; bites OLD log.println identically).
Byte-id graduates when #209 lands.

Cite refs: ref/hare/log/{logger,funcs,global,silent}.ha; drew acks on
module-static stderrsink_ctx + intrusive vt + 10-fn _v parity; ken
mandates on bootstrap byte-id mechanical + simple-ctx + #129 static-init.

Sibling tasks parked (filed, NOT fixed): eFinal #50; #206 (2 cast sites
at ensureinit_v); #173 (stderrwrite_v constructs nomem + widens to
io.error); #209 (cstage-only).
2026-05-29 11:01:39 +09:00

298 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// vstream — Hare-shaped vtable wrappers over the logger surface.
// Project #94 fold-e5 (Option C, parallel API for lib/log). Last
// per-caller migration before fold-eFinal (task #50).
//
// Adds the ten _v variants of OLD log.ww's surface — new_v /
// lprintln_v / println_v / lprintfln_v / printfln_v / lfatal_v /
// fatal_v / lfatalf_v / fatalf_v / setlogger_v — alongside a vlogger
// vtable + vstdlogger over an io.vstream sink. The OLD surface stays
// untouched here — fold-eFinal (task #50) atomically flips the
// package shape: deletes the OLD logger / stdlogger / globals + the
// pre-vtable stderrsink + ensureinit, renames the `_v` suffix off
// (drops to bare `new` / `println` / etc. per Hare's
// ref/hare/log/{logger,funcs,global,silent}.ha), and migrates the
// few callers (lib/log/logtest.ww plus any downstream).
//
// drew acks (3/3):
// - module-static stderrsink_ctx (zero-init per #129 A.2/A.3 SSoT)
// - vt FIRST field intrusive cast (matches fmt/memio/bufio fold-e
// ctxs)
// - all 10 OLD fns get _v variants (Option-C consistency)
//
// ken mandates honored:
// - bootstrap byte-id mechanical pre==post: log is NOT embedded
// in any selfhost/cmd/*/main.combined.ww (grep-confirmed
// pre-impl), so adding vstream.ww + minimal export bumps in
// lib/fmt does not perturb the embedded surfaces. 990-997
// gates stay green by virtue of log being test-only.
// - module-static ctx via #129 A.2/A.3 (cs == ww symmetric)
// - ctx shape simple scalars/ptrs (vt is the established intrusive
// first-field idiom; the only other field is `fd: i32`. No f32
// per #165b; no nested aggregate beyond vt per #18).
//
// Hare reference: ref/hare/log/{logger.ha,funcs.ha,global.ha,silent.ha}
// + ref/hare/log/log.ha (re-export hub).
//
// Cast workaround per #206: bare `&fn_name` does not type-check as
// a `(*<alias> | void)` field-init / let-binding. Explicit
// `(&fn_name): *io.<role>` cast at each store site is the Hare-
// faithful minimum-touch route — same workaround memio/vstream.ww
// + fmt/vstream.ww + bufio/vstream.ww use. 2 cast tokens here at
// the stderrsink_ctx_g vt wire-up (reader + writer; no closer —
// the process owns fd 2, same divergence as OLD stderrclose at
// log.ww:162). Both casts drop out wholesale once #206 closes.
//
// Chained-N_DOT-on-module-static field-assign per OLD log.ww:134-140
// (`_default.logger.println = stdprintln;`) — proven-working spine.
// stderrsink_ctx_g.vt.X writes pattern through same shape; the
// tagged-union assign with widening cast composes via #199 α
// (concrete *fn → NAMED variant of (*reader | void) parent).
//
// Newline emission: vfprint / vfprintf are bare (no trailing \n);
// Hare's ref/hare/fmt/wrappers.ha:60-68 appends the newline at the
// fprintln site (after fprint). ww mirrors: stdprintln_v / stdprintfln_v
// call fmt.vfprint / fmt.vfprintf then io.st_write a one-byte slice
// `[10u8]` for the LF. Same shape OLD log.stdprintln uses via
// fmt.fprintln (log.ww:169).
//
// Sibling tasks parked here (filed, NOT fixed):
//
// - eFinal (#50): atomic flip + delete OLD log surface + drop the
// `_v` suffix wholesale (graduates to Hare's bare names).
//
// - #206 (bare &fn → (*alias|void)): 2 cast sites at ensureinit_v;
// drop out wholesale on close.
//
// - #173 (TRY-on-tagged-return both-stages broken): stderrwrite_v
// constructs nomem and widens to io.error explicitly rather than
// using `os.trywrite(...)?`; same shape memio.vstream.ww +
// fmt.vstream.ww + bufio.vstream.ww adopt.
//
// - #207 (struct-lit multi-tagged-field copy drops past first):
// not bitten here — module-static stderrsink_ctx_g is zero-init
// at link time per #129; vt fields wired via post-decl chained
// assigns rather than struct-lit, sidestepping #207 wholesale.
// The pattern is the module-static analogue of memio.vstream.ww's
// alloc-zero-chain.
//
// - #209 (wwstage formattable match-arm bail when fmt imported):
// log imports fmt transitively (via the formattable / field
// types in vlogger's vtable + fmt.vfprint / vfprintf calls in
// the std callbacks). All probe rows in test/wcc/779 run
// STAGE_CS-only; eFinal closes the wwstage half when #209 lands.
package log;
import fmt;
import io;
import os;
// stderrsink_ctx — module-static state for the default vlogger's
// stderr sink. vt FIRST field for the intrusive vstream→*stderrsink_ctx
// cast in stderrwrite_v. Mirrors fmt.fd_ctx (fmt/vstream.ww:66) +
// memio.fixed_ctx (memio/vstream.ww:48); only scalars/ptrs beyond
// vt per ken's mandate.
type stderrsink_ctx = struct {
vt: io.vtable,
fd: i32,
};
// Zero-init at link time per #129 A.2/A.3 SSoT; ensureinit_v wires
// vt.reader / vt.writer + fd lazily on first dispatch. Same shape
// OLD log.ww uses for the pre-vtable stderrsink (log.ww:104).
let stderrsink_ctx_g: stderrsink_ctx;
// vlogger — V-side counterpart to OLD logger (log.ww:85). Same shape
// modulo the `_v` field-suffix (collapses on #50). Dispatchers route
// through io.vstream rather than *io.stream.
//
// Mirrors ref/hare/log/logger.ha:9.
export type vlogger = struct {
println_v: fn(l: *vlogger, args: fmt.formattable...) void,
printfln_v: fn(l: *vlogger, format: str, fields: fmt.field...) void,
};
// vstdlogger — concrete vlogger forwarding to an io.vstream sink.
// First-field embed: `&sl.logger` yields a `*vlogger` (intrusive
// outer→inner cast); the vtable callback recovers the outer via
// `*vstdlogger` cast.
//
// Mirrors ref/hare/log/logger.ha:18 (stdlogger).
export type vstdlogger = struct {
logger: vlogger,
sink: io.vstream,
};
// _silent_v / _default_v — backing storage for the silent_v /
// default_v *vlogger globals. Zero-init at link time; ensureinit_v
// fills the println_v / printfln_v slots.
let _silent_v: vlogger;
let _default_v: vstdlogger;
// silent_v / default_v / global_v — Hare-style *vlogger globals.
// Zero-init at link time; lazily wired by ensureinit_v on the first
// call to any exported fn. Same divergence as OLD log.ww:116-118
// (header rationale for the lazy-init route applies wholesale: ww
// cstage emit_lets only constexprs primitive / str-literal /
// array-literal lets — struct- and pointer-literal init aren't
// reachable). #50 / eFinal graduates if static-init lands.
export let silent_v: *vlogger;
export let default_v: *vlogger;
export let global_v: *vlogger;
// initdone_v — guards ensureinit_v so the one-shot wiring runs once.
// Mirrors OLD initdone (log.ww:122) + lib/temp's rnginit pattern.
let initdone_v: i32 = 0;
fn ensureinit_v() void = {
if (initdone_v != 0) { return; };
stderrsink_ctx_g.fd = 2;
stderrsink_ctx_g.vt.reader = (&stderrread_v): *io.reader;
stderrsink_ctx_g.vt.writer = (&stderrwrite_v): *io.writer;
_silent_v.println_v = silentprintln_v;
_silent_v.printfln_v = silentprintfln_v;
_default_v.logger.println_v = stdprintln_v;
_default_v.logger.printfln_v = stdprintfln_v;
_default_v.sink = &stderrsink_ctx_g.vt;
silent_v = &_silent_v;
default_v = &_default_v.logger;
global_v = default_v;
initdone_v = 1;
};
// stderrread_v / stderrwrite_v — vtable callbacks for the private
// stderr sink. Read is eof-only — lib/log writes; never reads.
// Mirror of OLD stderrread / stderrwrite (log.ww:148-160) modulo the
// (size | io.eof | io.error) / (size | io.error) return surface;
// -errno collapses to a nomem-widened io.error per the
// fmt.fdsinkwrite_v precedent (fmt/vstream.ww:88-92).
fn stderrread_v(s: io.vstream, buf: []u8) (size | io.eof | io.error) = {
let e: io.eof;
return e;
};
fn stderrwrite_v(s: io.vstream, buf: []u8) (size | io.error) = {
let c: *stderrsink_ctx = s: *stderrsink_ctx;
let r: i64 = os.write(c.fd, buf.ptr, buf.len: u64);
if (r < 0) {
let nm: nomem;
let e: io.error = nm;
return e;
};
return r: size;
};
// stdprintln_v / stdprintfln_v — vtable callbacks for vstdlogger.
// Forwards to fmt.vfprint / vfprintf on the sink + appends the
// trailing newline via io.st_write. Hare's fmt.fprintln emits the
// newline inside one wrapper (ref/hare/fmt/wrappers.ha:60-68); we
// inline that split at the log site because fmt.vfprint is the bare
// (no-newline) primitive. The (size | io.error) results are dropped
// — lib/log's surface is `void` (matches Hare + OLD stdprintln at
// log.ww:167).
fn stdprintln_v(l: *vlogger, args: fmt.formattable...) void = {
let sl: *vstdlogger = l: *vstdlogger;
fmt.vfprint(sl.sink, args...);
let nl: [1]u8;
nl[0] = 10u8;
let v: []u8 = nl[0:1];
io.st_write(sl.sink, v);
};
fn stdprintfln_v(l: *vlogger, format: str, fields: fmt.field...) void = {
let sl: *vstdlogger = l: *vstdlogger;
fmt.vfprintf(sl.sink, format, fields...);
let nl: [1]u8;
nl[0] = 10u8;
let v: []u8 = nl[0:1];
io.st_write(sl.sink, v);
};
// silentprintln_v / silentprintfln_v — vtable callbacks for the
// silent vlogger. Discard every record without dispatching through
// fmt; keeps silent truly silent if fmt ever gets stateful. Mirror
// of OLD silentprintln / silentprintfln (log.ww:183-187).
fn silentprintln_v(l: *vlogger, args: fmt.formattable...) void = { };
fn silentprintfln_v(l: *vlogger, format: str, fields: fmt.field...) void = { };
// ---- public API (10 _v variants) -----------------------------------------
// new_v — wire `sl` as a vstdlogger over `sink`. Hare returns by
// value (ref/hare/log/logger.ha:20); ww cgen can't return wide
// structs, so we take an out-parameter pointer (same shape as OLD
// new at log.ww:193 + memio.fixed + bufio.init).
export fn new_v(sl: *vstdlogger, sink: io.vstream) void = {
ensureinit_v();
sl.logger.println_v = stdprintln_v;
sl.logger.printfln_v = stdprintfln_v;
sl.sink = sink;
};
// lprintln_v — dispatch `args` through `log`. Mirrors OLD lprintln
// (log.ww:202) + ref/hare/log/funcs.ha:8.
export fn lprintln_v(log: *vlogger, args: fmt.formattable...) void = {
ensureinit_v();
log.println_v(log, args...);
};
// println_v — dispatch through the [[global_v]] vlogger. Mirrors
// OLD println (log.ww:208) + ref/hare/log/funcs.ha:18.
export fn println_v(args: fmt.formattable...) void = {
ensureinit_v();
lprintln_v(global_v, args...);
};
// lfatal_v — lprintln_v to `log` then exit(255). `never` return
// marks the bottom type so flow-control checks treat callers as
// terminated. Mirrors OLD lfatal (log.ww:216) +
// ref/hare/log/funcs.ha:28.
export fn lfatal_v(log: *vlogger, args: fmt.formattable...) never = {
lprintln_v(log, args...);
os.exit(255);
};
// fatal_v — lprintln_v to [[global_v]] then exit(255). Mirrors OLD
// fatal (log.ww:223) + ref/hare/log/funcs.ha:45.
export fn fatal_v(args: fmt.formattable...) never = {
println_v(args...);
os.exit(255);
};
// setlogger_v — install `log` as the [[global_v]] vlogger. Mirrors
// OLD setlogger (log.ww:230) + ref/hare/log/global.ha:20.
export fn setlogger_v(log: *vlogger) void = {
ensureinit_v();
global_v = log;
};
// lprintfln_v — dispatch a format-string record through `log`.
// Mirrors OLD lprintfln (log.ww:237) + ref/hare/log/funcs.ha:13.
export fn lprintfln_v(log: *vlogger, format: str, fields: fmt.field...) void = {
ensureinit_v();
log.printfln_v(log, format, fields...);
};
// printfln_v — dispatch through the [[global_v]] vlogger. Mirrors
// OLD printfln (log.ww:244) + ref/hare/log/funcs.ha:23.
export fn printfln_v(format: str, fields: fmt.field...) void = {
ensureinit_v();
lprintfln_v(global_v, format, fields...);
};
// lfatalf_v — lprintfln_v to `log` then exit(255). Mirrors OLD
// lfatalf (log.ww:251) + ref/hare/log/funcs.ha:35.
export fn lfatalf_v(log: *vlogger, format: str, fields: fmt.field...) never = {
lprintfln_v(log, format, fields...);
os.exit(255);
};
// fatalf_v — lprintfln_v to [[global_v]] then exit(255). Mirrors
// OLD fatalf (log.ww:258) + ref/hare/log/funcs.ha:52.
export fn fatalf_v(format: str, fields: fmt.field...) never = {
printfln_v(format, fields...);
os.exit(255);
};