Merge branch 'devel' into pr_Raffiniert

This commit is contained in:
ringabout
2025-12-08 18:24:27 +08:00
committed by GitHub
25 changed files with 1521 additions and 208 deletions

View File

@@ -549,12 +549,19 @@ proc add*(father, son: PType) =
proc addAllowNil*(father, son: PType) {.inline.} =
father.sonsImpl.add son
template `[]`*(n: PType, i: int): PType = n.sonsImpl[i]
template `[]`*(n: PType, i: int): PType =
if n.state == Partial: loadType(n)
n.sonsImpl[i]
template `[]=`*(n: PType, i: int; x: PType) =
if n.state == Partial: loadType(n)
n.sonsImpl[i] = x
template `[]`*(n: PType, i: BackwardsIndex): PType = n[n.len - i.int]
template `[]=`*(n: PType, i: BackwardsIndex; x: PType) = n[n.len - i.int] = x
template `[]`*(n: PType, i: BackwardsIndex): PType =
if n.state == Partial: loadType(n)
n[n.len - i.int]
template `[]=`*(n: PType, i: BackwardsIndex; x: PType) =
if n.state == Partial: loadType(n)
n[n.len - i.int] = x
proc getDeclPragma*(n: PNode): PNode =
## return the `nkPragma` node for declaration `n`, or `nil` if no pragma was found.
@@ -791,29 +798,57 @@ proc replaceFirstSon*(n, newson: PNode) {.inline.} =
proc replaceSon*(n: PNode; i: int; newson: PNode) {.inline.} =
n.sons[i] = newson
proc last*(n: PType): PType {.inline.} = n.sonsImpl[^1]
proc last*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[^1]
proc elementType*(n: PType): PType {.inline.} = n.sonsImpl[^1]
proc skipModifier*(n: PType): PType {.inline.} = n.sonsImpl[^1]
proc elementType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[^1]
proc indexType*(n: PType): PType {.inline.} = n.sonsImpl[0]
proc baseClass*(n: PType): PType {.inline.} = n.sonsImpl[0]
proc skipModifier*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[^1]
proc indexType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0]
proc baseClass*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0]
proc base*(t: PType): PType {.inline.} =
if t.state == Partial: loadType(t)
result = t.sonsImpl[0]
proc returnType*(n: PType): PType {.inline.} = n.sonsImpl[0]
proc returnType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0]
proc setReturnType*(n, r: PType) {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0] = r
proc setIndexType*(n, idx: PType) {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0] = idx
proc firstParamType*(n: PType): PType {.inline.} = n.sonsImpl[1]
proc firstGenericParam*(n: PType): PType {.inline.} = n.sonsImpl[1]
proc firstParamType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[1]
proc typeBodyImpl*(n: PType): PType {.inline.} = n.sonsImpl[^1]
proc firstGenericParam*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[1]
proc genericHead*(n: PType): PType {.inline.} = n.sonsImpl[0]
proc typeBodyImpl*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[^1]
proc genericHead*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0]
proc skipTypes*(t: PType, kinds: TTypeKinds): PType =
## Used throughout the compiler code to test whether a type tree contains or
@@ -854,14 +889,6 @@ proc newFloatNode*(kind: TNodeKind, floatVal: BiggestFloat): PNode =
result = newNode(kind)
result.floatVal = floatVal
proc newStrNode*(kind: TNodeKind, strVal: string): PNode =
result = newNode(kind)
result.strVal = strVal
proc newStrNode*(strVal: string; info: TLineInfo): PNode =
result = newNodeI(nkStrLit, info)
result.strVal = strVal
proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode,
params,
name, pattern, genericParams,

File diff suppressed because it is too large Load Diff

View File

@@ -981,6 +981,14 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
result.typField = sym.typImpl
result.info = info
proc newStrNode*(kind: TNodeKind, strVal: string): PNode =
result = newNode(kind)
result.strVal = strVal
proc newStrNode*(strVal: string; info: TLineInfo): PNode =
result = newNodeI(nkStrLit, info)
result.strVal = strVal
proc forcePartial*(s: PSym) =
## Resets all impl-fields to their default values and sets state to Partial.
## This is useful for creating a stub symbol that can be lazily loaded later.

View File

@@ -16,13 +16,10 @@
## implementation.
template detectVersion(field, corename) =
if m.g.field == 0:
let core = getCompilerProc(m.g.graph, corename)
if core == nil or core.kind != skConst:
m.g.field = 1
else:
m.g.field = toInt(ast.getInt(core.astdef))
result = m.g.field
if m.g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcHooks}:
result = 2
else:
result = 1
proc detectStrVersion(m: BModule): int =
detectVersion(strVersion, "nimStrVersion")

View File

@@ -721,7 +721,7 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
# have to recurse via 'getTypeDescAux'. And not doing so prevents problems
# with heavily templatized C++ code:
if not isImportedCppType(rectype):
let fieldType = field.loc.lode.typ.skipTypes(abstractInst)
let fieldType = field.loc.t.skipTypes(abstractInst)
var typ: Rope = ""
var isFlexArray = false
var initializer = ""

View File

@@ -1307,16 +1307,15 @@ proc countStateOccurences(ctx: var Ctx, n: PNode, stateOccurences: var openArray
proc replaceDeletedStates(ctx: var Ctx, n: PNode): PNode =
result = n
for i in 0 ..< n.safeLen:
let c = n[i]
if c.kind == nkIntLit:
let idx = c.intVal
if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == c and ctx.states[idx].deletable:
let gt = ctx.replaceDeletedStates(skipStmtList(ctx.states[idx].body))
assert(gt.kind == nkGotoState)
n[i] = gt[0]
else:
n[i] = ctx.replaceDeletedStates(c)
if n.kind == nkIntLit:
let idx = n.intVal
if idx >= 0 and idx < ctx.states.len and ctx.states[idx].label == n and ctx.states[idx].deletable:
let gt = ctx.replaceDeletedStates(skipStmtList(ctx.states[idx].body))
assert(gt.kind == nkGotoState)
result = gt[0]
else:
for i in 0 ..< n.safeLen:
n[i] = ctx.replaceDeletedStates(n[i])
proc replaceInlinedStates(ctx: var Ctx, n: PNode): PNode =
## Find all nkGotoState(stateIdx) nodes that do not follow nkYield.
@@ -1347,6 +1346,7 @@ proc optimizeStates(ctx: var Ctx) =
# Replace deletable state labels to labels of respective non-empty states
for i in 0 .. ctx.states.high:
ctx.states[i].body = ctx.replaceDeletedStates(ctx.states[i].body)
ctx.states[i].excLandingState = ctx.replaceDeletedStates(ctx.states[i].excLandingState)
# Remove deletable states
var i = 0

View File

@@ -498,6 +498,8 @@ proc parseCommand*(command: string): Command =
of "secret": cmdInteractive
of "nop", "help": cmdNop
of "jsonscript": cmdJsonscript
of "nifc": cmdNifC # generate C from NIF files
of "deps": cmdDeps # generate .build.nif for nifmake
else: cmdUnknown
proc setCmd*(conf: ConfigRef, cmd: Command) =
@@ -510,6 +512,12 @@ proc setCmd*(conf: ConfigRef, cmd: Command) =
of cmdCompileToOC: conf.backend = backendObjc
of cmdCompileToJS: conf.backend = backendJs
of cmdCompileToNif: conf.backend = backendNif
of cmdNifC:
conf.backend = backendC # NIF to C compilation
conf.globalOptions.incl optCompress # enable NIF loading
of cmdM:
# cmdM requires optCompress for proper IC handling (include files, etc.)
conf.globalOptions.incl optCompress
else: discard
proc setCommandEarly*(conf: ConfigRef, command: string) =

340
compiler/deps.nim Normal file
View File

@@ -0,0 +1,340 @@
#
#
# The Nim Compiler
# (c) Copyright 2025 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Generate a .build.nif file for nifmake from a Nim project.
## This enables incremental and parallel compilation using the `m` switch.
import std / [os, tables, sets, times, osproc, strutils]
import options, msgs, pathutils, lineinfos
import "../dist/nimony/src/lib" / [nifstreams, nifcursors, bitabs, nifreader, nifbuilder]
import "../dist/nimony/src/gear2" / modnames
type
FilePair = object
nimFile: string
modname: string
Node = ref object
files: seq[FilePair] # main file + includes
deps: seq[int] # indices into DepContext.nodes
id: int
DepContext = object
config: ConfigRef
nifler: string
nodes: seq[Node]
processedModules: Table[string, int] # modname -> node index
includeStack: seq[string]
proc toPair(c: DepContext; f: string): FilePair =
FilePair(nimFile: f, modname: moduleSuffix(f, cast[seq[string]](c.config.searchPaths)))
proc depsFile(c: DepContext; f: FilePair): string =
getNimcacheDir(c.config).string / f.modname & ".deps.nif"
proc parsedFile(c: DepContext; f: FilePair): string =
getNimcacheDir(c.config).string / f.modname & ".p.nif"
proc semmedFile(c: DepContext; f: FilePair): string =
getNimcacheDir(c.config).string / f.modname & ".nif"
proc findNifler(): string =
# Look for nifler in common locations
result = findExe("nifler")
if result.len == 0:
# Try relative to nim executable
let nimDir = getAppDir()
result = nimDir / "nifler"
if not fileExists(result):
result = nimDir / ".." / "nimony" / "bin" / "nifler"
if not fileExists(result):
result = ""
proc runNifler(c: DepContext; nimFile: string): bool =
## Run nifler deps on a file if needed. Returns true on success.
let pair = c.toPair(nimFile)
let depsPath = c.depsFile(pair)
# Check if deps file is up-to-date
if fileExists(depsPath) and fileExists(nimFile):
if getLastModificationTime(depsPath) > getLastModificationTime(nimFile):
return true # Already up-to-date
# Create output directory if needed
createDir(parentDir(depsPath))
# Run nifler deps
let cmd = quoteShell(c.nifler) & " deps " & quoteShell(nimFile) & " " & quoteShell(depsPath)
let exitCode = execShellCmd(cmd)
result = exitCode == 0
proc resolveFile(c: DepContext; origin, toResolve: string): string =
## Resolve an import path relative to origin file
# Handle std/ prefix
var path = toResolve
if path.startsWith("std/"):
path = path.substr(4)
# Try relative to origin first
let originDir = parentDir(origin)
result = originDir / path.addFileExt("nim")
if fileExists(result):
return result
# Try search paths
for searchPath in c.config.searchPaths:
result = searchPath.string / path.addFileExt("nim")
if fileExists(result):
return result
result = ""
proc traverseDeps(c: var DepContext; pair: FilePair; current: Node)
proc processInclude(c: var DepContext; includePath: string; current: Node) =
let resolved = resolveFile(c, current.files[current.files.len - 1].nimFile, includePath)
if resolved.len == 0 or not fileExists(resolved):
return
# Check for recursive includes
for s in c.includeStack:
if s == resolved:
return # Skip recursive include
c.includeStack.add resolved
current.files.add c.toPair(resolved)
traverseDeps(c, c.toPair(resolved), current)
discard c.includeStack.pop()
proc processImport(c: var DepContext; importPath: string; current: Node) =
let resolved = resolveFile(c, current.files[0].nimFile, importPath)
if resolved.len == 0 or not fileExists(resolved):
return
let pair = c.toPair(resolved)
let existingIdx = c.processedModules.getOrDefault(pair.modname, -1)
if existingIdx == -1:
# New module - create node and process it
let newNode = Node(files: @[pair], id: c.nodes.len)
current.deps.add newNode.id
c.processedModules[pair.modname] = newNode.id
c.nodes.add newNode
traverseDeps(c, pair, newNode)
else:
# Already processed - just add dependency
if existingIdx notin current.deps:
current.deps.add existingIdx
proc readDepsFile(c: var DepContext; pair: FilePair; current: Node) =
## Read a .deps.nif file and process imports/includes
let depsPath = c.depsFile(pair)
if not fileExists(depsPath):
return
var s = nifstreams.open(depsPath)
defer: nifstreams.close(s)
discard processDirectives(s.r)
var t = next(s)
if t.kind != ParLe:
return
# Skip to content (past stmts tag)
t = next(s)
while t.kind != EofToken:
if t.kind == ParLe:
let tag = pool.tags[t.tagId]
case tag
of "import", "fromimport":
# Read import path
t = next(s)
# Check for "when" marker (conditional import)
if t.kind == Ident and pool.strings[t.litId] == "when":
t = next(s) # skip it, still process the import
# Handle path expression (could be ident, string, or infix like std/foo)
var importPath = ""
if t.kind == Ident:
importPath = pool.strings[t.litId]
elif t.kind == StringLit:
importPath = pool.strings[t.litId]
elif t.kind == ParLe and pool.tags[t.tagId] == "infix":
# Handle std / foo style imports
t = next(s) # skip infix tag
if t.kind == Ident: # operator (/)
t = next(s)
if t.kind == Ident: # first part (std)
importPath = pool.strings[t.litId]
t = next(s)
if t.kind == Ident: # second part (foo)
importPath = importPath & "/" & pool.strings[t.litId]
if importPath.len > 0:
processImport(c, importPath, current)
# Skip to end of import node
var depth = 1
while depth > 0:
t = next(s)
if t.kind == ParLe: inc depth
elif t.kind == ParRi: dec depth
of "include":
# Read include path
t = next(s)
if t.kind == Ident and pool.strings[t.litId] == "when":
t = next(s) # skip conditional marker
var includePath = ""
if t.kind == Ident:
includePath = pool.strings[t.litId]
elif t.kind == StringLit:
includePath = pool.strings[t.litId]
if includePath.len > 0:
processInclude(c, includePath, current)
# Skip to end
var depth = 1
while depth > 0:
t = next(s)
if t.kind == ParLe: inc depth
elif t.kind == ParRi: dec depth
else:
# Skip unknown node
var depth = 1
while depth > 0:
t = next(s)
if t.kind == ParLe: inc depth
elif t.kind == ParRi: dec depth
t = next(s)
proc traverseDeps(c: var DepContext; pair: FilePair; current: Node) =
## Process a module: run nifler and read deps
if not runNifler(c, pair.nimFile):
rawMessage(c.config, errGenerated, "nifler failed for: " & pair.nimFile)
return
readDepsFile(c, pair, current)
proc generateBuildFile(c: DepContext): string =
## Generate the .build.nif file for nifmake
result = getNimcacheDir(c.config).string / c.nodes[0].files[0].modname & ".build.nif"
var b = nifbuilder.open(result)
defer: b.close()
b.addHeader("nim deps", "nifmake")
b.addTree "stmts"
# Define nifler command
b.addTree "cmd"
b.addSymbolDef "nifler"
b.addStrLit c.nifler
b.addStrLit "parse"
b.addStrLit "--deps"
b.addTree "input"
b.endTree()
b.addTree "output"
b.endTree()
b.endTree()
# Define nim m command
b.addTree "cmd"
b.addSymbolDef "nim_m"
b.addStrLit getAppFilename()
b.addStrLit "m"
# Add search paths
for p in c.config.searchPaths:
b.addStrLit "--path:" & p.string
b.addTree "input"
b.addIntLit 0
b.endTree()
b.endTree()
# Build rules for parsing (nifler)
var seenFiles = initHashSet[string]()
for node in c.nodes:
for pair in node.files:
let parsed = c.parsedFile(pair)
if not seenFiles.containsOrIncl(parsed):
b.addTree "do"
b.addIdent "nifler"
b.addTree "input"
b.addStrLit pair.nimFile
b.endTree()
b.addTree "output"
b.addStrLit parsed
b.endTree()
b.addTree "output"
b.addStrLit c.depsFile(pair)
b.endTree()
b.endTree()
# Build rules for semantic checking (nim m)
for i in countdown(c.nodes.len - 1, 0):
let node = c.nodes[i]
let pair = node.files[0]
b.addTree "do"
b.addIdent "nim_m"
# Input: all parsed files for this module
for f in node.files:
b.addTree "input"
b.addStrLit c.parsedFile(f)
b.endTree()
# Also depend on semmed files of dependencies
for depIdx in node.deps:
b.addTree "input"
b.addStrLit c.semmedFile(c.nodes[depIdx].files[0])
b.endTree()
# Output: semmed file
b.addTree "output"
b.addStrLit c.semmedFile(pair)
b.endTree()
b.addTree "args"
b.addStrLit pair.nimFile
b.endTree()
b.endTree()
b.endTree() # stmts
proc commandDeps*(conf: ConfigRef) =
## Main entry point for `nim deps`
when not defined(nimKochBootstrap):
let nifler = findNifler()
if nifler.len == 0:
rawMessage(conf, errGenerated, "nifler tool not found. Install nimony or add nifler to PATH.")
return
let projectFile = conf.projectFull.string
if not fileExists(projectFile):
rawMessage(conf, errGenerated, "project file not found: " & projectFile)
return
# Create nimcache directory
createDir(getNimcacheDir(conf).string)
var c = DepContext(
config: conf,
nifler: nifler,
nodes: @[],
processedModules: initTable[string, int](),
includeStack: @[]
)
# Create root node for main project file
let rootPair = c.toPair(projectFile)
let rootNode = Node(files: @[rootPair], id: 0)
c.nodes.add rootNode
c.processedModules[rootPair.modname] = 0
# Process dependencies
traverseDeps(c, rootPair, rootNode)
# Generate build file
let buildFile = generateBuildFile(c)
rawMessage(conf, hintSuccess, "generated: " & buildFile)
rawMessage(conf, hintSuccess, "run: nifmake run " & buildFile)
else:
rawMessage(conf, errGenerated, "nim deps not available in bootstrap build")

View File

@@ -1212,8 +1212,10 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
result.ast[bodyPos].add newAsgnStmt(d, src)
else:
var tk: TTypeKind
var skipped: PType = nil
if g.config.selectedGC in {gcArc, gcOrc, gcHooks, gcAtomicArc}:
tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind
skipped = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink})
tk = skipped.kind
else:
tk = tyNone # no special casing for strings and seqs
case tk
@@ -1223,7 +1225,7 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
fillStrOp(a, typ, result.ast[bodyPos], d, src)
else:
fillBody(a, typ, result.ast[bodyPos], d, src)
if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not isObjLackingTypeField(typ):
if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not isObjLackingTypeField(skipped):
# bug #19205: Do not forget to also copy the hidden type field:
genTypeFieldCopy(a, typ, result.ast[bodyPos], d, src)

View File

@@ -273,6 +273,10 @@ const
errFloatToString* = "cannot convert '$1' to '$2'"
type
FileInfoKind* = enum
fikSource, ## A real source file path
fikNifModule ## A NIF module suffix (not a real path)
TFileInfo* = object
fullPath*: AbsoluteFile # This is a canonical full filesystem path
projPath*: RelativeFile # This is relative to the project's root
@@ -291,6 +295,7 @@ type
# for 'nimsuggest'
hash*: string # the checksum of the file
dirty*: bool # for 'nimpretty' like tooling
kind*: FileInfoKind # distinguishes real files from NIF suffixes
when defined(nimpretty):
fullContent*: string
FileIndex* = distinct int32

View File

@@ -32,6 +32,10 @@ import ../dist/checksums/src/checksums/sha1
import pipelines
when not defined(nimKochBootstrap):
import nifbackend
import deps
when not defined(leanCompiler):
import docgen
@@ -133,6 +137,22 @@ proc commandCompileToNif(graph: ModuleGraph) =
setPipeLinePass(graph, NifgenPass)
compilePipelineProject(graph)
proc commandNifC(graph: ModuleGraph) =
## Generate C code from precompiled NIF files.
## This is the new IC approach: compile modules to NIF first with `nim m`,
## then generate C code from the entry.nif file with whole-program DCE.
when not defined(nimKochBootstrap):
let conf = graph.config
extccomp.initVars(conf)
if not extccomp.ccHasSaneOverflow(conf):
conf.symbols.defineSymbol("nimEmulateOverflowChecks")
# Use the NIF backend to generate C code
nifbackend.generateCode(graph, conf.projectMainIdx)
else:
rawMessage(graph.config, errGenerated, "NIF backend not available during bootstrap build")
proc commandCompileToC(graph: ModuleGraph) =
let conf = graph.config
extccomp.initVars(conf)
@@ -420,9 +440,22 @@ proc mainCommand*(graph: ModuleGraph) =
of cmdCheck:
commandCheck(graph)
of cmdM:
graph.config.symbolFiles = v2Sf
setUseIc(graph.config.symbolFiles != disabledSf)
# cmdM uses NIF files, not ROD files
graph.config.symbolFiles = disabledSf
setUseIc(false)
commandCheck(graph)
of cmdNifC:
# Generate C code from NIF files
wantMainModule(conf)
setOutFile(conf)
commandNifC(graph)
of cmdDeps:
# Generate .build.nif for nifmake
wantMainModule(conf)
when not defined(nimKochBootstrap):
commandDeps(conf)
else:
rawMessage(conf, errGenerated, "nim deps not available in bootstrap build")
of cmdParse:
wantMainModule(conf)
discard parseFile(conf.projectMainIdx, cache, conf)

View File

@@ -18,6 +18,7 @@ import ic / [packed_ast, ic]
when not defined(nimKochBootstrap):
import ast2nif
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
when defined(nimPreviewSlimSystem):
import std/assertions
@@ -140,6 +141,7 @@ type
cachedFiles*: StringTableRef
procGlobals*: seq[PNode]
nifReplayActions*: Table[int32, seq[PNode]] # module position -> replay actions for NIF
TPassContext* = object of RootObj # the pass's context
idgen*: IdGenerator
@@ -366,6 +368,10 @@ proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp;
## we also need to record this to the packed module.
g.attachedOps[op][t.itemId] = LazySym(sym: value)
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
## Overload that takes ItemId directly, useful for registering hooks from NIF index.
g.attachedOps[op][typeId] = LazySym(sym: value)
proc setAttachedOpPartial*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
## we also need to record this to the packed module.
g.attachedOps[op][t.itemId] = LazySym(sym: value)
@@ -393,6 +399,10 @@ proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[LazySym]) =
# TODO: add it for packed modules
g.methodsPerType[id] = methods
proc addNifReplayAction*(g: ModuleGraph; module: int32; n: PNode) =
## Stores a replay action for NIF-based incremental compilation.
g.nifReplayActions.mgetOrPut(module, @[]).add n
iterator getMethodsPerType*(g: ModuleGraph; t: PType): PSym =
if g.methodsPerType.contains(t.itemId):
for it in mitems g.methodsPerType[t.itemId]:
@@ -425,7 +435,30 @@ proc copyTypeProps*(g: ModuleGraph; module: int; dest, src: PType) =
proc loadCompilerProc*(g: ModuleGraph; name: string): PSym =
result = nil
if g.config.symbolFiles == disabledSf: return nil
if g.config.symbolFiles == disabledSf:
# For NIF-based compilation, search in loaded NIF modules
when not defined(nimKochBootstrap):
# Only try to resolve from NIF if we're actually using NIF files (cmdNifC)
if g.config.cmd == cmdNifC:
# First try system module (most compilerprocs are there)
let systemFileIdx = g.config.m.systemFileIdx
if systemFileIdx != InvalidFileIdx:
result = tryResolveCompilerProc(ast.program, name, systemFileIdx)
if result != nil:
strTableAdd(g.compilerprocs, result)
return result
# Try threadpool module (some compilerprocs like FlowVar are there)
# Find threadpool module by searching loaded modules
for moduleIdx in 0..<g.ifaces.len:
let module = g.ifaces[moduleIdx].module
if module != nil and module.name.s == "threadpool":
let threadpoolFileIdx = module.position.FileIndex
result = tryResolveCompilerProc(ast.program, name, threadpoolFileIdx)
if result != nil:
strTableAdd(g.compilerprocs, result)
return result
return nil
# slow, linear search, but the results are cached:
for module in 0..<len(g.packed):
@@ -745,9 +778,11 @@ proc moduleFromRodFile*(g: ModuleGraph; fileIdx: FileIndex;
when not defined(nimKochBootstrap):
proc moduleFromNifFile*(g: ModuleGraph; fileIdx: FileIndex;
cachedModules: var seq[FileIndex]): PSym =
cachedModules: var seq[FileIndex];
loadFullAst: bool = false): PSym =
## Returns 'nil' if the module needs to be recompiled.
## Loads module from NIF file when optCompress is enabled.
## When loadFullAst is true, loads the complete module AST for code generation.
if not fileExists(toNifFilename(g.config, fileIdx)):
return nil
@@ -765,7 +800,38 @@ when not defined(nimKochBootstrap):
# Register module in graph
registerModule(g, result)
result.astImpl = loadNifModule(ast.program, fileIdx, g.ifaces[fileIdx.int].interf, g.ifaces[fileIdx.int].interfHidden)
var hooks = initTable[nifstreams.SymId, HooksPerType]()
var converters: seq[(string, string)] = @[]
var classes: seq[ClassIndexEntry] = @[]
result.astImpl = loadNifModule(ast.program, fileIdx, g.ifaces[fileIdx.int].interf,
g.ifaces[fileIdx.int].interfHidden, hooks, converters, classes, loadFullAst)
# Register hooks from NIF index with the module graph
for typSymId, hooksPerType in hooks:
let typeItemId = parseTypeSymIdToItemId(ast.program, typSymId)
if typeItemId.module >= 0:
for op in AttachedOp:
let (hookSymId, isGeneric) = hooksPerType.a[op]
if hookSymId != nifstreams.SymId(0):
let hookSym = resolveHookSym(ast.program, hookSymId)
if hookSym != nil:
setAttachedOp(g, int(fileIdx), typeItemId, toTTypeAttachedOp(op), hookSym)
# Register converters from NIF index with the module's interface
for (destType, convSym) in converters:
let symId = pool.syms.getOrIncl(convSym)
let convPSym = resolveHookSym(ast.program, symId) # reuse hook resolution
if convPSym != nil:
g.ifaces[fileIdx.int].converters.add LazySym(sym: convPSym)
# Register methods per type from NIF index
for classEntry in classes:
let typeItemId = parseTypeSymIdToItemId(ast.program, classEntry.cls)
if typeItemId.module >= 0:
var methodSyms: seq[LazySym] = @[]
for methodEntry in classEntry.methods:
let methodSym = resolveHookSym(ast.program, methodEntry.fn)
if methodSym != nil:
methodSyms.add LazySym(sym: methodSym)
if methodSyms.len > 0:
setMethodsPerType(g, typeItemId, methodSyms)
cachedModules.add fileIdx
proc configComplete*(g: ModuleGraph) =

View File

@@ -60,11 +60,12 @@ proc makeCString*(s: string): Rope =
toCChar(s[i], result)
result.add('\"')
proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo =
proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile; kind = fikSource): TFileInfo =
result = TFileInfo(fullPath: fullPath, projPath: projPath,
shortName: fullPath.extractFilename,
quotedFullName: fullPath.string.makeCString,
lines: @[]
lines: @[],
kind: kind
)
result.quotedName = result.shortName.makeCString
when defined(nimpretty):
@@ -138,11 +139,18 @@ proc registerNifSuffix*(conf: ConfigRef; suffix: string; isKnownFile: var bool):
if result == InvalidFileIdx:
isKnownFile = false
result = conf.m.fileInfos.len.FileIndex
conf.m.fileInfos.add(newFileInfo(AbsoluteFile suffix, RelativeFile suffix))
conf.m.fileInfos.add(newFileInfo(AbsoluteFile suffix, RelativeFile suffix, fikNifModule))
conf.m.filenameToIndexTbl[suffix] = result
else:
isKnownFile = true
proc fileInfoKind*(conf: ConfigRef; fileIdx: FileIndex): FileInfoKind =
## Returns the kind of a FileIndex (source file or NIF module suffix).
if fileIdx.int >= 0 and fileIdx.int < conf.m.fileInfos.len:
result = conf.m.fileInfos[fileIdx.int].kind
else:
result = fikSource # Default to source for unknown indices
proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo =
result = TLineInfo(fileIndex: fileInfoIdx)
if line < int high(uint16):

121
compiler/nifbackend.nim Normal file
View File

@@ -0,0 +1,121 @@
#
#
# The Nim Compiler
# (c) Copyright 2025 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## NIF-based C/C++ code generator backend.
##
## This module implements C code generation from precompiled NIF files.
## It traverses the module dependency graph starting from the main module
## and generates C code for all reachable modules.
##
## Usage:
## 1. Compile modules to NIF: nim m mymodule.nim
## 2. Generate C from NIF: nim nifc myproject.nim
import std/[intsets, tables, sets, os]
when defined(nimPreviewSlimSystem):
import std/assertions
import ast, options, lineinfos, modulegraphs, cgendata, cgen,
pathutils, extccomp, msgs, modulepaths, idents, types, ast2nif
proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex): seq[PSym] =
## Traverse the module dependency graph using a stack.
## Returns all modules that need code generation, in dependency order.
var visited = initIntSet()
var stack: seq[FileIndex] = @[mainFileIdx]
result = @[]
var cachedModules: seq[FileIndex] = @[]
while stack.len > 0:
let fileIdx = stack.pop()
if not visited.containsOrIncl(int(fileIdx)):
# Only load full AST for main module; others are loaded lazily by codegen
let isMainModule = fileIdx == mainFileIdx
let module = moduleFromNifFile(g, fileIdx, cachedModules, loadFullAst=isMainModule)
if module != nil:
result.add module
# Add dependencies to stack (they come from cachedModules)
for dep in cachedModules:
if not visited.contains(int(dep)):
stack.add dep
cachedModules.setLen(0)
proc setupNifBackendModule(g: ModuleGraph; module: PSym): BModule =
## Set up a BModule for code generation from a NIF module.
if g.backend == nil:
g.backend = cgendata.newModuleList(g)
result = cgen.newModule(BModuleList(g.backend), module, g.config)
proc generateCodeForModule(g: ModuleGraph; module: PSym) =
## Generate C code for a single module.
let moduleId = module.position
var bmod = BModuleList(g.backend).modules[moduleId]
if bmod == nil:
bmod = setupNifBackendModule(g, module)
# Generate code for the module's top-level statements
if module.ast != nil:
cgen.genTopLevelStmt(bmod, module.ast)
# Finalize the module (this adds it to modulesClosed)
# Create an empty stmt list as the init body - genInitCode in writeModule will set it up properly
let initStmt = newNodeI(nkStmtList, module.info)
finalCodegenActions(g, bmod, initStmt)
# Generate dispatcher methods
for disp in getDispatchers(g):
genProcAux(bmod, disp)
proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
## Main entry point for NIF-based C code generation.
## Traverses the module dependency graph and generates C code.
# Reset backend state
resetForBackend(g)
let mainModule = g.getModule(mainFileIdx)
# Also ensure system module is set up and generated if it exists
if g.systemModule != nil and g.systemModule != mainModule:
let systemBmod = BModuleList(g.backend).modules[g.systemModule.position]
if systemBmod == nil:
discard setupNifBackendModule(g, g.systemModule)
generateCodeForModule(g, g.systemModule)
# Load all modules in dependency order using stack traversal
let modules = loadModuleDependencies(g, mainFileIdx)
if modules.len == 0:
rawMessage(g.config, errGenerated,
"Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx))
return
# Set up backend modules for all modules that need code generation
for module in modules:
discard setupNifBackendModule(g, module)
# Generate code for all modules except main (main goes last)
# This ensures all modules are added to modulesClosed
for module in modules:
if module != mainModule:
generateCodeForModule(g, module)
# Generate main module last (so all init procs are registered)
if mainModule != nil:
generateCodeForModule(g, mainModule)
# Write C files
if g.backend != nil:
cgenWriteModules(g.backend, g.config)
# Run C compiler
if g.config.cmd != cmdTcc:
extccomp.callCCompiler(g.config)
if not g.config.hcrOn:
extccomp.writeJsonBuildInstructions(g.config, g.cachedFiles)

View File

@@ -118,7 +118,7 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
if conf.selectedGC == gcUnselected:
if conf.backend in {backendC, backendCpp, backendObjc} or
(conf.cmd in cmdDocLike and conf.backend != backendJs) or
conf.cmd == cmdGendepend:
conf.cmd in {cmdGendepend, cmdNifC, cmdDeps, cmdM}:
initOrcDefines(conf)
mainCommand(graph)

View File

@@ -175,6 +175,8 @@ type
cmdJsonscript # compile a .json build file
# old unused: cmdInterpret, cmdDef: def feature (find definition for IDEs)
cmdCompileToNif
cmdNifC # generate C code from NIF files
cmdDeps # generate .build.nif for nifmake
const
cmdBackends* = {cmdCompileToC, cmdCompileToCpp, cmdCompileToOC,

View File

@@ -1,10 +1,11 @@
import sem, cgen, modulegraphs, ast, llstream, parser, msgs,
lineinfos, reorder, options, semdata, cgendata, modules, pathutils,
packages, syntaxes, depends, vm, pragmas, idents, lookups, wordrecg,
packages, syntaxes, depends, vm, vmdef, pragmas, idents, lookups, wordrecg,
liftdestructors, nifgen
when not defined(nimKochBootstrap):
import ast2nif
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
import pipelineutils
@@ -38,7 +39,12 @@ proc processPipeline(graph: ModuleGraph; semNode: PNode; bModule: PPassContext):
of GenDependPass:
result = addDotDependency(bModule, semNode)
of SemPass:
result = graph.emptyNode
# Return the semantic node for cmdM (NIF generation needs it)
# For regular check, we don't need the result
if graph.config.cmd == cmdM:
result = semNode
else:
result = graph.emptyNode
of Docgen2Pass, Docgen2TexPass:
when not defined(leanCompiler):
result = processNode(bModule, semNode)
@@ -160,7 +166,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
s = stream
graph.interactive = stream.kind == llsStdIn
var topLevelStmts =
if optCompress in graph.config.globalOptions:
if optCompress in graph.config.globalOptions or graph.config.cmd == cmdM:
newNodeI(nkStmtList, module.info)
else:
nil
@@ -235,14 +241,60 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
raiseAssert "use setPipeLinePass to set a proper PipelinePass"
when not defined(nimKochBootstrap):
if optCompress in graph.config.globalOptions and not graph.config.isDefined("nimscript"):
if (optCompress in graph.config.globalOptions or graph.config.cmd == cmdM) and
not graph.config.isDefined("nimscript"):
topLevelStmts.add finalNode
writeNifModule(graph.config, module.position.int32, topLevelStmts)
# Collect replay actions from both pragma computations and VM state diff
var replayActions: seq[PNode] = @[]
# Get pragma-recorded replay actions (compile, link, passC, passL, etc.)
if graph.nifReplayActions.hasKey(module.position.int32):
replayActions.add graph.nifReplayActions[module.position.int32]
# Also get VM state diff (macro cache operations)
if graph.vm != nil:
for (m, n) in PCtx(graph.vm).vmstateDiff:
if m == module:
replayActions.add n
# Collect hooks from the module graph for the current module
var hooks = default array[AttachedOp, seq[HookIndexEntry]]
for op in TTypeAttachedOp:
if op == attachedDeepCopy: continue # Not supported in nimony
let nimonyOp = toAttachedOp(op)
for typeId, lazySym in graph.attachedOps[op]:
if typeId.module == module.position.int32:
let sym = lazySym.sym
if sym != nil:
hooks[nimonyOp].add toHookIndexEntry(graph.config, typeId, sym)
# Collect converters from the module's interface
var converters: seq[(nifstreams.SymId, nifstreams.SymId)] = @[]
for lazySym in graph.ifaces[module.position].converters:
let sym = lazySym.sym
if sym != nil:
let entry = toConverterIndexEntry(graph.config, sym)
if entry[0] != nifstreams.SymId(0):
converters.add entry
# Collect methods per type for classes
var classes: seq[ClassIndexEntry] = @[]
for typeId, methodList in graph.methodsPerType:
if typeId.module == module.position.int32:
var methods: seq[MethodIndexEntry] = @[]
for lazySym in methodList:
let sym = lazySym.sym
if sym != nil:
# Generate a method signature (simplified - name and param count)
let sig = sym.name.s & "/" & $sym.typImpl.sonsImpl.len
methods.add toMethodIndexEntry(graph.config, sym, sig)
if methods.len > 0:
classes.add ClassIndexEntry(
cls: toClassSymId(graph.config, typeId),
methods: methods
)
writeNifModule(graph.config, module.position.int32, topLevelStmts, hooks, converters, classes, replayActions)
if graph.config.backend notin {backendC, backendCpp, backendObjc}:
if graph.config.backend notin {backendC, backendCpp, backendObjc} and graph.config.cmd != cmdM:
# We only write rod files here if no C-like backend is active.
# The C-like backends have been patched to support the IC mechanism.
# They are responsible for closing the rod files. See `cbackend.nim`.
# cmdM uses NIF files only, not ROD files.
closeRodFile(graph, module)
result = true
@@ -261,11 +313,21 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
if result == nil:
var cachedModules: seq[FileIndex] = @[]
when not defined(nimKochBootstrap):
# Try loading from NIF file first if optCompress is enabled
if optCompress in graph.config.globalOptions and not graph.config.isDefined("nimscript"):
# For cmdM: load imports from NIF files (but compile the main module from source)
# Skip when withinSystem is true (compiling system.nim itself)
if graph.config.cmd == cmdM and
sfMainModule notin flags and
not graph.withinSystem and
not graph.config.isDefined("nimscript"):
result = moduleFromNifFile(graph, fileIdx, cachedModules)
if result == nil:
# Fall back to ROD file loading
if result == nil:
let nifPath = toNifFilename(graph.config, fileIdx)
localError(graph.config, unknownLineInfo,
"nim m requires precompiled NIF for import: " & toFullPath(graph.config, fileIdx) &
" (expected: " & nifPath & ")")
return nil # Don't fall through to compile from source
if result == nil and graph.config.cmd != cmdM:
# Fall back to ROD file loading (not used for cmdM which uses NIF only)
result = moduleFromRodFile(graph, fileIdx, cachedModules)
let path = toFullPath(graph.config, fileIdx)
let filename = AbsoluteFile path
@@ -287,8 +349,11 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
partialInitModule(result, graph, fileIdx, filename)
for m in cachedModules:
registerModuleById(graph, m)
if sfMainModule in flags and graph.config.cmd == cmdM:
discard
if graph.config.cmd == cmdM:
# cmdM uses NIF files - replay from module AST loaded by loadNifModule
let module = graph.getModule(m)
if module != nil and module.ast != nil:
replayStateChanges(module, graph)
else:
replayStateChanges(graph.packed.pm[m.int].module, graph)
replayGenericCacheInformation(graph, m.int)
@@ -346,6 +411,20 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
graph.withinSystem = true
discard graph.compilePipelineModule(projectFile, {sfMainModule, sfSystemModule})
graph.withinSystem = false
elif graph.config.cmd == cmdM:
# For cmdM: load system.nim from NIF first, then compile the main module
connectPipelineCallbacks(graph)
graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
graph.config.libpath / RelativeFile"system.nim")
var cachedModules: seq[FileIndex] = @[]
when not defined(nimKochBootstrap):
graph.systemModule = moduleFromNifFile(graph, graph.config.m.systemFileIdx, cachedModules)
if graph.systemModule == nil:
let nifPath = toNifFilename(graph.config, graph.config.m.systemFileIdx)
localError(graph.config, unknownLineInfo,
"nim m requires precompiled NIF for system module (expected: " & nifPath & ")")
return
discard graph.compilePipelineModule(projectFile, {sfMainModule})
else:
graph.compilePipelineSystemModule()
discard graph.compilePipelineModule(projectFile, {sfMainModule})

View File

@@ -358,6 +358,9 @@ proc addImportFileDep*(c: PContext; f: FileIndex) =
proc addPragmaComputation*(c: PContext; n: PNode) =
if c.config.symbolFiles != disabledSf:
addPragmaComputation(c.encoder, c.packedRepr, n)
# Also store for NIF-based IC (cmdM mode or optCompress)
if optCompress in c.config.globalOptions or c.config.cmd == cmdM:
addNifReplayAction(c.graph, c.module.position.int32, n)
proc inclSym(sq: var seq[PSym], s: PSym): bool =
for i in 0..<sq.len:

View File

@@ -85,9 +85,3 @@ func countSetBitsImpl*(x: SomeInteger): int {.inline.} =
else:
when sizeof(x) <= 4: result = countBitsImpl(x.uint32)
else: result = countBitsImpl(x.uint64)
proc countBits32*(n: uint32): int {.compilerproc, inline.} =
result = countSetBitsImpl(n)
proc countBits64*(n: uint64): int {.compilerproc, inline.} =
result = countSetBitsImpl(n)

View File

@@ -10,6 +10,13 @@
# set handling
# IC: compilerprocs now must be defined in system.nim or threadpool.nim!
proc countBits32*(n: uint32): int {.compilerproc, inline.} =
result = countSetBitsImpl(n)
proc countBits64*(n: uint64): int {.compilerproc, inline.} =
result = countSetBitsImpl(n)
proc cardSetImpl(s: ptr UncheckedArray[uint8], len: int): int {.inline.} =
var i = 0
result = 0

View File

@@ -0,0 +1,7 @@
discard """
cmd: "nim c --mm:orc $file"
output: ""
"""
import ./t25341_aux/a, ./t25341_aux/b
a()
b()

View File

@@ -0,0 +1,4 @@
import ./module
proc a*() =
discard make1[4]().make2()

View File

@@ -0,0 +1,6 @@
import ./module
var globalObj: Distinct2[4]
proc b*() =
globalObj = make1[4]().make2()

View File

@@ -0,0 +1,14 @@
type
BaseObject*[N: static int] = object
value*: int
Distinct1*[N: static int] = distinct BaseObject[N]
Distinct2*[N: static int] = distinct BaseObject[N]
proc `=copy`*[N: static int](dest: var Distinct2[N], src: Distinct2[N]) {.error: "no".}
proc make1*[N: static int](): Distinct1[N] =
Distinct1[N](BaseObject[N](value: 0))
proc make2*[N: static int](u: sink Distinct1[N]): Distinct2[N] =
Distinct2[N](BaseObject[N](u))

View File

@@ -17,9 +17,12 @@ proc testClosureIterAux(it: iterator(): int, exceptionExpected: bool, expectedRe
var exceptionCaught = false
var maxIterations = 10000
try:
for i in it():
closureIterResult.add(i)
dec maxIterations
doAssert(maxIterations > 0, "Too many iterations in test. Infinite loop?")
except TestError:
exceptionCaught = true
@@ -847,3 +850,50 @@ block:
doAssert(w() == 123)
doAssert(getCurrentExceptionMsg() == "Outer error")
doAssert(getCurrentExceptionMsg() == "")
block: #25330 (v1)
iterator count1(): int {.closure.} =
yield 1
raiseTestError()
iterator count0(): int {.closure.} =
try:
var count = count1
while true:
yield count()
if finished(count): break
finally:
try:
checkpoint(2)
var count2 = count1
while true:
yield count2()
if finished(count2): break
discard # removing this outputs "raise"
except:
checkpoint(3)
raise
testExc(count0, 1, 2, 1, 3)
block: #25330 (v2)
iterator count1(): int {.closure.} =
yield 1
raiseTestError()
iterator count0(): int {.closure.} =
try:
var count = count1
for x in 0 .. 10:
yield count()
finally:
try:
checkpoint(2)
var count2 = count1
for x in 0 .. 10:
yield count2()
except:
checkpoint(3)
raise
testExc(count0, 1, 2, 1, 3)