IC: remember toplevel .emits

This commit is contained in:
Araq
2026-07-03 09:25:45 +02:00
parent 1c181ec6f7
commit 24ef33d23c
5 changed files with 39 additions and 3 deletions

View File

@@ -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

View File

@@ -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("")

View File

@@ -0,0 +1,2 @@
converter toBool*(x: int): bool = x != 0

14
tests/ic/memit.nim Normal file
View File

@@ -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 <sys/sysctl.h>` + `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)

7
tests/ic/temit.nim Normal file
View File

@@ -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()