From 24ef33d23ca45827720ca1f0ede1047a4bb56589 Mon Sep 17 00:00:00 2001 From: Araq Date: Fri, 3 Jul 2026 09:25:45 +0200 Subject: [PATCH] IC: remember toplevel .emits --- compiler/ast2nif.nim | 17 +++++++++++++++-- koch.nim | 2 +- tests/ic/mdefconverter.nim | 2 ++ tests/ic/memit.nim | 14 ++++++++++++++ tests/ic/temit.nim | 7 +++++++ 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/ic/mdefconverter.nim create mode 100644 tests/ic/memit.nim create mode 100644 tests/ic/temit.nim diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index 22af49a4de..839997e50c 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -1101,10 +1101,18 @@ proc writeToplevelNode(w: var Writer; dest, bottom: var IcBuilder; n: PNode) = of nkEmpty: discard "ignore" of nkTypeSection, nkCommentStmt, nkMixinStmt, nkBindStmt, nkUsingStmt, - nkPragma, nkProcDef, nkFuncDef, nkMethodDef, nkIteratorDef, nkConverterDef, nkMacroDef, nkTemplateDef: # We write purely declarative nodes at the bottom of the file writeNode(w, bottom, n) + of nkPragma: + # Top-level pragmas — chiefly `{.emit.}`, plus the `{.push/pop.}` that guard + # its neighbours — must survive the backend reload so the `cg` stage re-runs + # genPragma/genEmit. The bottom (implementation) section is reloaded lazily + # BY SYMBOL INDEX, which a symbol-less pragma can never be on, so a pragma + # written there is silently dropped on reload (e.g. a module-level `#include` + # vanishes and the generated C fails to compile). The header init section is + # replayed verbatim by `processTopLevel`, so write it there instead. + writeNode(w, dest, n) of nkConstSection: writeGlobals(w, bottom, n) of nkLetSection, nkVarSection: @@ -3245,9 +3253,14 @@ proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag] skip cur elif tagIs(cur, "implementation"): cont = false - elif LoadFullAst in flags or tagIs(cur, toNifTag(nkLetSection)) or tagIs(cur, toNifTag(nkVarSection)): + elif LoadFullAst in flags or tagIs(cur, toNifTag(nkLetSection)) or + tagIs(cur, toNifTag(nkVarSection)) or tagIs(cur, toNifTag(nkPragma)): # Parse the full statement. let/var sections are loaded unconditionally # (see above) so `{.compileTime.}` globals reach the eager initializer. + # Top-level pragmas are loaded too: a module-level `{.emit.}` (and the + # `{.push/pop.}` around it) must reach the `cg` stage's genPragma/genEmit, + # else e.g. a `#include` is dropped and the generated C won't compile. + # writeToplevelNode routes these into this header section. let stmtNode = loadNode(c, cur, suffix, localSyms) if stmtNode != nil: result.topLevel.sons.add stmtNode diff --git a/koch.nim b/koch.nim index fa365b6415..41f3441760 100644 --- a/koch.nim +++ b/koch.nim @@ -619,7 +619,7 @@ proc runIcTestFile(inp: string) = const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils", "tcompiletimeglobal", "tsighashstable", "tpureenum", "tgenericoffer", "tconverterreexport", "ttypeoffer", "ttransitiveoffer", - "tmodsymref", "tmethupref"] + "tmodsymref", "tmethupref", "temit"] proc icTest(args: string) = temp("") diff --git a/tests/ic/mdefconverter.nim b/tests/ic/mdefconverter.nim new file mode 100644 index 0000000000..d0a23f801b --- /dev/null +++ b/tests/ic/mdefconverter.nim @@ -0,0 +1,2 @@ + +converter toBool*(x: int): bool = x != 0 diff --git a/tests/ic/memit.nim b/tests/ic/memit.nim new file mode 100644 index 0000000000..5b02334424 --- /dev/null +++ b/tests/ic/memit.nim @@ -0,0 +1,14 @@ +# Helper for temit.nim: a NON-main module with a module-scope `{.emit.}` that +# introduces a C macro consumed by an `{.importc, nodecl.}` const. The IC backend +# reload used to drop top-level emit pragmas — writeToplevelNode wrote them to the +# by-symbol-index implementation section, where a symbol-less pragma is never +# reloaded — so the generated C lost the `#define` and failed to compile with +# "use of undeclared identifier". Mirrors lib/pure/concurrency/cpuinfo.nim's +# `#include ` + `CTL_HW`/`HW_NCPU` importc pattern. +{.emit: """/*TYPESECTION*/ +#define NIM_IC_EMIT_ANSWER 42 +""".} + +let icEmitAnswer {.importc: "NIM_IC_EMIT_ANSWER", nodecl.}: cint + +proc emitAnswer*(): int = int(icEmitAnswer) diff --git a/tests/ic/temit.nim b/tests/ic/temit.nim new file mode 100644 index 0000000000..214ab2f90b --- /dev/null +++ b/tests/ic/temit.nim @@ -0,0 +1,7 @@ +# Regression: a module-scope `{.emit.}` in an imported module must survive the +# `nim ic` backend reload (see memit.nim). Before the fix the dropped `#define` +# made the generated C fail to compile, so `nim ic` exited non-zero. +import memit + +doAssert emitAnswer() == 42 +echo emitAnswer()