cgen + memio: cgoutarena → memio.dynamic, grow → dynamicgrow (β-3)

Phase 0 last β-shape site. Two concerns in one commit because the
refactor surfaced the rename:

 - selfhost/cmd/wcc/cgen.ww  cgout buffer (cgoutbuf/cap/len + arena +
   cgout_grow + CGOUT_INIT_CAP) → memio.state + io.stream behind a
   one-shot lazy-init guard. cgout_enable drops its *arena param;
   memio.reset in cgout_flush keeps the buffer sticky across fns so
   the arena's amortisation survives — re-init per fn would abandon
   the buffer and re-grow from 0 via the 8→…→65536 ladder for every
   function (no io.close path → no os.free).

 - lib/memio/memio.ww  private fn grow → dynamicgrow. Symmetric with
   dynamicwrite / dynamicclose; required because cstage bundles all
   imported modules into a flat TU and resolves private fns by
   unqualified name, so the new `import memio;` in wcc's bundle
   collided with selfhost/cmd/wcc/mem.ww's arena `grow`. Module-aware
   private-fn scoping in cstage is task #9.

@test fn dynamicgrow in memiotest.ww (same package as memio.ww)
renamed to dynamicgrowcases to free the name; new suffix mirrors the
file's existing fixedwritecases / borrowedreadcases convention.

Lazy-init guard cgoutinit. memio.dynamic runs once on first
cgout_enable; subsequent enables just set cgoutmode. Mirrors
lib/log/log.ww:124 ensureinit. Without it, ~14 mmap syscalls per fn
and ~100 MiB+ cumulative leak on a typical bootstrap.

io.write bare discard in emitbytes mirrors lib/log/log.ww:169 —
memio.dynamicwrite never returns io.closed (memio.ww:166).

Verified 132/132 incl. 995_self_rebuild byte-identity.
This commit is contained in:
2026-05-21 10:11:40 +09:00
parent f07a032104
commit fd7dee985e
6 changed files with 633 additions and 123 deletions

View File

@@ -31,6 +31,8 @@ import tok;
import typ;
import sym;
import strconv;
import io;
import memio;
// Split files. Bundler pulls these in transitively so consumers only
// need `use cgen;`. Order matters for the flat-bundle concat — utils
// first so cgenexpr/stmt/decl can reference helpers defined here.
@@ -678,60 +680,50 @@ fn localfind(c: *cgen, name: str) i32 = {
// Cgfn defers its prologue (TEXT / SUBQ) until after the body so the
// frame size reflects every emit-time localadd — the scanlocals pre-
// pass that previously pre-computed it was dropped per #15/#26c. The
// body is captured into cgoutbuf while cgoutmode != 0, then flushed
// body is captured into cgoutstate while cgoutmode != 0, then flushed
// after the prologue is written to stdout. Module-level state so the
// existing emitline/emitint/emitlabel/emitsymname callers don't have
// to thread a *cgen they don't already hold. Mirrors cstage's deferred
// Prog-chain emit (cmd/w6c/cgen.c cgfn allocates `subsp`/`text` up
// front and patches `from.offset` after the body finishes).
let cgoutbuf: *u8 = nil;
let cgoutbufcap: i32 = 0;
let cgoutbuflen: i32 = 0;
let cgoutmode: i32 = 0;
let cgoutarena: *arena = nil;
//
// `cgoutinit` guards a one-shot [[memio.dynamic]] wiring so the
// backing buffer is sticky across fns: [[cgout_flush]]'s
// [[memio.reset]] rewinds `pos`/`len` without touching `cap`, so the
// allocation amortises the same way the previous arena buffer did.
// Re-init per fn would abandon the buffer (no [[io.close]] path → no
// [[os.free]]) and re-grow from 0 via the 8→…→65536 ladder for every
// function. Same idiom as lib/log/log.ww:124 `ensureinit`.
let cgoutstate: memio.state;
let cgoutstream: io.stream;
let cgoutmode: i32 = 0;
let cgoutinit: i32 = 0;
def CGOUT_INIT_CAP: i32 = 65536;
fn cgout_grow(need: i32) void = {
if (need <= cgoutbufcap) { return; };
let want: i32 = cgoutbufcap;
if (want == 0) { want = CGOUT_INIT_CAP; };
for (want < need) { want = want * 2; };
let p: *u8 = amalloc(cgoutarena, want: u64): *u8;
let i: i32 = 0;
for (i < cgoutbuflen) {
p[i] = cgoutbuf[i];
i += 1;
fn cgout_enable() void = {
if (cgoutinit == 0) {
memio.dynamic(&cgoutstate, &cgoutstream);
cgoutinit = 1;
};
cgoutbuf = p;
cgoutbufcap = want;
};
fn cgout_enable(a: *arena) void = {
cgoutarena = a;
cgoutbuflen = 0;
cgoutmode = 1;
};
fn cgout_disable() void = { cgoutmode = 0; };
fn cgout_flush() void = {
if (cgoutbuflen > 0) {
os.write(1, cgoutbuf, cgoutbuflen: u64);
cgoutbuflen = 0;
if (cgoutstate.pos > 0) {
os.write(1, cgoutstate.ptr, cgoutstate.pos: u64);
memio.reset(&cgoutstate);
};
};
fn emitbytes(p: *u8, n: u64) void = {
if (cgoutmode != 0) {
let nn: i32 = n: i32;
cgout_grow(cgoutbuflen + nn);
let i: i32 = 0;
for (i < nn) {
cgoutbuf[cgoutbuflen + i] = p[i];
i += 1;
};
cgoutbuflen += nn;
let buf: []u8;
buf.ptr = p;
buf.len = n: i32;
// memio.dynamicwrite never returns io.closed (memio.ww:166);
// bare-discard mirrors lib/log/log.ww:169 fmt.fprintln.
io.write(&cgoutstream, buf);
} else {
os.write(1, p, n);
};