Skip to content

trs: block-buffer stdout when not a tty (out.rs sink) - #146

Open
nanavati wants to merge 1 commit into
trs/26-overlayfrom
trs/27-stdout
Open

trs: block-buffer stdout when not a tty (out.rs sink)#146
nanavati wants to merge 1 commit into
trs/26-overlayfrom
trs/27-stdout

Conversation

@nanavati

@nanavati nanavati commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

What

Batch stdout is now block-buffered when fd 1 is not a terminal, matching the reference C engines' stdio contract (Bluesim/Verilator inherit block buffering from libc when piped; Rust's std stdout is unconditionally a LineWriter, costing one write(2) per $display line on piped runs — TrafficBRAM pays ~3,600 of them, SparseRF ~5,600).

New trs-interp/src/out.rs: a process-level stdout sink — std stdout (line-buffered) on a tty, a 64KiB BufWriter over raw fd 1 otherwise. Every stdout writer in the runtime routes through it: write_display, the $fwrite stdout slot, qprintln! (mem-file loader messages, prim warnings), the BRAM write-collision warning, and Output error: reporting.

Flush points (the ordering contract)

  • $fflush — explicit user flush (foreign.rs)
  • BDPI phase 0 — user C code writes through libc's own buffers, so ours flushes before every BDPI call (bdpi.rs, jit_stdio_cb)
  • run teardown — the batch exit path leaves via libc::_exit (which skips atexit), so the pre-_exit flush is explicit; RunCore's teardown mirrors it
  • atexit — registered on block-mode init, covering every exit(3) path (process::exit(86) selfcheck bails included)

Panics and signals lose buffered output, exactly like C block buffering.

The two-buffers-one-fd hazard

Two tiers share fd 1 with a foreign writer and must NOT block-buffer independently:

  • libtrs_capi.so is dlopened into bluetcl, whose own stdio interleaves with sim output — bk_init pins line mode before any write.
  • the in-process script tier (trs run -c/-f) prints command responses through std stdout between sim advances — run_script pins line mode.

TRS_STDOUT_LINE (any value) forces line mode as a user escape hatch.

Witnesses

  • FloatTest bench artifact: 345 stdout writes → 1 in block mode; output byte-identical across line / block / RunCore boots.
  • Regress battery 22/22, classic and TRS_RUNCORE=1.
  • Dual full-corpus seals at this head: 1003 PASS / 0 DIFF twice (witnessed TRS_RUNCORE_CHECK=1 TRS_SELFCHECK=1, and driver-armed TRS_RUNCORE=1), engines 998 aot + 5 interp — byte parity holds with buffering in play, loader diagnostics and warning text included.

Stamp (min-of-N interleaved, same host, trs legs byte-verified vs the Bluesim oracle)

design trs (ms) verilator bluesim vs previous stamp
TrafficBRAM 29.98 29.92 95.2 trs −3.3ms: the −3.6ms Verilator gap is now a 0.06ms dead heat
SparseRF 5.56 57.0 trs −3.4ms (38% faster; 5.6k output lines)
Mesa 36.89 67.3 trs −1.9ms
CFL 6.82 5.25 56.0 gap −2.2 → −1.57
Dividers 2.14 2.89 48.8 win widens
FloatTest 52.74 45.39 91.5 unchanged (345 lines — output cost was never its gap)
BRAM0Test / Sudoku / DFT64v1 / DFT64v5 / Long 7.66 / 139.4 / 8.85 / 9.11 / 1049 4.72 / 376.3 / 12.29 / 14.82 / 16314 51.6 / 309.2 / 55.9 / 62.1 / 5144 within noise; wins hold

Verilator scoreboard 5W/3L/1T (was 5W/4L); Bluesim 11/11. The output-volume prediction registered before the stamp hit on all seven ranges; TrafficBRAM's remaining 0.06ms is the guarded-FIFO bounce rung's target.

The reference C engines inherit stdio's contract: block-buffered when
piped, line-buffered on a terminal.  Rust's std stdout is always a
LineWriter — one write(2) per $display line on piped batch runs
(TrafficBRAM ~3.6k, SparseRF ~5.6k).  All runtime stdout writers now
route through a process-level sink (trs-interp/src/out.rs): std stdout
on a tty or under TRS_STDOUT_LINE, a 64KiB BufWriter over raw fd 1
otherwise.

Flush points: $fflush; BDPI phase 0 (user C writes through libc's own
buffers); run teardown (the batch path exits via _exit, which skips
atexit, so its flush is explicit; RunCore teardown mirrors it); and an
atexit net for every exit(3) path.  Panics/signals lose buffered
output, exactly like C block buffering.

Two tiers share fd 1 with a foreign writer and pin line mode instead:
bk_init (libtrs_capi.so is dlopened into bluetcl, whose stdio
interleaves with sim output) and run_script (command responses print
through std stdout between advances).

Witnesses: FloatTest artifact 345 stdout writes -> 1 in block mode,
output byte-identical across line/block/RunCore; battery 22/22 classic
and TRS_RUNCORE=1; dual corpus seals 1003 PASS / 0 DIFF (witnessed +
driver-armed), 998 aot + 5 interp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CerPH99xuDTaGhaBQ3wwqS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants