From 1b39de32b0cc85f04e1e004bff1979c418b4647d Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 12 Sep 2026 21:51:29 +0700 Subject: [PATCH] A closed fd 1 is a fd 1 waiting to be handed to something else MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The merged exit closes stdout so the compiler reads EOF and learns the program has finished. POSIX then hands descriptor 1 to the next thing that asks — a socket, a module's object file — and the llc after that inherits it as its stdout. /dev/null takes the slot back, and the EOF is unaffected because the pipe's write end is genuinely gone. Verified beyond the headless suite: sand.flan builds and runs merged under Xvfb, one process with no children, and a game-draw typed in at the socket is drawing frames a second later. --- lib/dev.ml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/dev.ml b/lib/dev.ml index 54ef201..b90c70e 100644 --- a/lib/dev.ml +++ b/lib/dev.ml @@ -1888,6 +1888,7 @@ let merged_main_source = {c| #include #include #include +#include #include #include #include @@ -1918,6 +1919,11 @@ static void flan_merged_exit(int32_t status) { (void)status; fflush(NULL); close(1); + /* ...and take fd 1 straight back, because POSIX hands out the lowest free + * descriptor: leave it open and the compiler thread's next socket or file + * becomes this process's stdout, and the next llc inherits it. The EOF is + * unaffected — the pipe's write end is genuinely gone. */ + if (open("/dev/null", O_WRONLY) < 0) { /* nothing useful to do about it */ } for (;;) pause(); }