From 5d77a71043cd164412639ae9f7ea86f541c22e93 Mon Sep 17 00:00:00 2001 From: araq Date: Mon, 31 Aug 2026 11:18:45 +0200 Subject: [PATCH] IC: stop baking a compile command the process cannot know MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every generated `.c` carries a "Command for C compiler" comment built from `conf.compileOptions`. Under the per-module backend that is a lie, and a non-deterministic one. A global `{.passC.}` — system's `-pthread`, say — reaches `conf.compileOptions` only in a process that compiled the module declaring it, and a `cg` process sees one module's import closure. So the command each `.c` records is a partial snapshot, and WHICH part depends on how modules were grouped into processes. Measured on a 67-module program: 2 of 67 `.c` carried `-pthread` at batch size 1, 4 at size 4, 5 at size 8 — against 16 of 16 for a whole-program `nim c`. The object files were never affected: the `link` stage applies every module's recorded directives (`replayer.applyBackendActions`) before compiling anything. Only the comment was wrong. But it is also the reason two `.c` files differed between batch sizes for no reason of their own, which makes it noise in the one oracle that matters here — the final `.c` set. So under `--icBackendStage` the line says where the command actually comes from instead of guessing at it. Whole-program `nim c` is untouched: byte-identical `.c`, and it still prints the real command, which there it genuinely knows. ic 40/40, `koch boot -d:release` equal executables. --- compiler/cgen.nim | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 0881726bf9..962d713695 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -2592,12 +2592,27 @@ proc headerTop(): Rope = proc getCopyright(conf: ConfigRef; cfile: Cfile): Rope = result = headerTop() if optCompileOnly notin conf.globalOptions: - result.add ("/* Compiled for: $1, $2, $3 */$N" & - "/* Command for C compiler:$n $4 */$N") % + result.add ("/* Compiled for: $1, $2, $3 */$N") % [rope(platform.OS[conf.target.targetOS].name), rope(platform.CPU[conf.target.targetCPU].name), - rope(extccomp.CC[conf.cCompiler].name), - rope(getCompileCFileCmd(conf, cfile))] + rope(extccomp.CC[conf.cCompiler].name)] + # The per-module IC backend cannot write this line truthfully. A global + # `{.passC.}` (system's `-pthread`, say) reaches `conf.compileOptions` only + # in a process that compiled the module declaring it, and a `cg` process + # sees one module's import closure — so the command it would print is a + # partial snapshot, and WHICH part depends on how modules were grouped into + # processes. Measured on a 67-module program: 2 of 67 `.c` carried + # `-pthread` at batch size 1, 4 at size 4, 5 at size 8, against 16 of 16 for + # a whole-program `nim c`. The real command is assembled by the `link` + # stage, which applies every module's recorded directives first + # (`replayer.applyBackendActions`) — so the object files were always + # correct; only this comment was wrong, and non-deterministically so. + if conf.cmd == cmdNifC and conf.icBackendStage.len > 0: + result.add "/* Command for C compiler: assembled by the link stage\L" & + " from every module's recorded C directives. */\L" + else: + result.add ("/* Command for C compiler:$n $1 */$N") % + [rope(getCompileCFileCmd(conf, cfile))] proc getFileHeader(conf: ConfigRef; cfile: Cfile): Rope = var res = newBuilder(getCopyright(conf, cfile))