← board

StdErr writes reach stdout on every target but x86-64

$ printf "program e; begin WriteLn(StdErr,'to-err'); WriteLn('to-out'); end.\n" > e.pas
$ pascal26 --target=i386 e.pas e && qemu-i386 ./e 2>/dev/null
to-err
to-out

FPC and pxx x86-64 print only to-out.

Three layers, one rule

  1. Codegen. The frontend already put the target fd in IRIVal of every IR_WRITE and IR_WRITELN. Only ir_codegen.inc (x86-64) read it, into CurWriteFd. The other backends' inline write syscalls hard-coded fd 1: i386 used EmitI32(STDOUT), arm32 mov r0,#1, aarch64 movz x0,#1, and riscv32 and xtensa a literal 1. They now all take CurWriteFd.
  2. Runtime. The builtinheap writers (pad, char, newline, decimal, bool, string forms and C string) called PXXSysWrite(1, ...). The float and variant writers printed their digits with write(...) statements, which compile once, with fd 1. So even x86-64, which shims its floats onto them, sent WriteLn(StdErr, 1.5:0:1) to stdout. The writers now use PXXOutFd (0 = stdout, so the zeroed BSS is right everywhere), through PXXPutC/PXXPutLit where they used write.
  3. The wrap. Each backend's node emitter asks IRWriteNodeToStdErr(node) (ir.inc) first. For a StdErr node it calls PXXOutToStdErr, sets CurWriteFd = 2, emits the node as before, then restores both. wasm32 does the same in WasmEmitWrite. A stdout write never reaches the wrap, so the code at a stdout write site is unchanged (the runtime writers it calls did change, as above). Under --threadsafe the statement is already inside the I/O lock, which ir.inc emits on x86-64, i386, aarch64 and arm32, so no other thread can see the swapped fd there.

Also fixed:

Measured (2026-09-28, fixedpoint 274fa145eca6)

Two tests, with each stream checked alone (2>/dev/null, then 1>/dev/null):

Both match on x86-64, i386, arm32, aarch64, riscv32, xtensa windowed, xtensa call0 and wasm32.

Controls on the pinned v450:

ESP: both tests build for the C3 and S3 on the IDF profile, and the Pascal test also builds on the bare profiles. On IDF, PXXSysWrite sends fd 1 and fd 2 both to the console. On bare, writes emit nothing, as before. They have not been run on a board.

Not in this fix

The test deliberately declares no PChar variable and never names Output. Either one pulls the Text-file unit, whose StdErr is a Text variable that shadows the constant, and those writes go through the Text RTL. That path already reaches the right fd. But on every target, x86-64 included, it prints a bare Double in natural form (2.25, FPC prints 2.2500000000000000E+000) and a PChar as its address. That is a formatting defect of Text writes, not an fd defect, and it is the next fix.