IC: keep package information (#25350)

This commit is contained in:
Andreas Rumpf
2025-12-18 09:34:39 +01:00
committed by GitHub
parent 80cf9a8ce8
commit 9bb57a64ba
13 changed files with 624 additions and 282 deletions

View File

@@ -46,6 +46,12 @@ template loadType(t: PType) =
when not defined(nimKochBootstrap):
ast2nif.loadType(program, t)
proc loadSymCallback*(s: PSym) {.nimcall.} =
loadSym(s)
proc loadTypeCallback*(t: PType) {.nimcall.} =
loadType(t)
proc ensureMutable*(s: PSym) {.inline.} =
assert s.state != Sealed
if s.state == Partial: loadSym(s)
@@ -82,9 +88,6 @@ proc setOwner*(s: PType; owner: PSym) {.inline.} =
if s.state == Partial: loadType(s)
s.ownerFieldImpl = owner
# Accessor procs for TSym fields
# Note: kind is kept as a direct field for case statement compatibility
# but we still provide an accessor that checks state
proc kind*(s: PSym): TSymKind {.inline.} =
if s.state == Partial: loadSym(s)
result = s.kindImpl
@@ -227,7 +230,7 @@ proc offset*(s: PSym): int32 {.inline.} =
result = s.offsetImpl
proc `offset=`*(s: PSym, val: int32) {.inline.} =
assert s.state != Sealed
#assert s.state != Sealed
if s.state == Partial: loadSym(s)
s.offsetImpl = val
@@ -293,7 +296,8 @@ proc incl*(s: PSym; flags: set[TSymFlag]) {.inline.} =
s.flagsImpl.incl(flags)
proc incl*(s: PSym; flag: TLocFlag) {.inline.} =
assert s.state != Sealed
#assert s.state != Sealed
# locImpl is a backend field so do not protect it against mutations
if s.state == Partial: loadSym(s)
s.locImpl.flags.incl(flag)

View File

@@ -19,45 +19,16 @@ import "../dist/nimony/src/lib" / [bitabs, nifstreams, nifcursors, lineinfos,
nifindexes, nifreader]
import "../dist/nimony/src/gear2" / modnames
import "../dist/nimony/src/models" / nifindex_tags
import typekeys
import ic / [enum2nif]
# Re-export types needed for hook, converter, and method handling
export nifindexes.AttachedOp, nifindexes.HookIndexEntry, nifindexes.HooksPerType
export nifindexes.ClassIndexEntry, nifindexes.MethodIndexEntry
proc toAttachedOp*(op: TTypeAttachedOp): AttachedOp =
## Maps Nim compiler's TTypeAttachedOp to nimony's AttachedOp.
## Returns attachedDestroy for attachedDeepCopy (caller should skip it).
case op
of attachedDestructor: attachedDestroy
of attachedAsgn: attachedCopy
of attachedWasMoved: nifindexes.attachedWasMoved
of attachedDup: nifindexes.attachedDup
of attachedSink: nifindexes.attachedSink
of attachedTrace: nifindexes.attachedTrace
of attachedDeepCopy: attachedDestroy # Not supported, caller should skip
proc toTTypeAttachedOp*(op: AttachedOp): TTypeAttachedOp =
## Maps nimony's AttachedOp back to Nim compiler's TTypeAttachedOp.
case op
of attachedDestroy: attachedDestructor
of attachedCopy: attachedAsgn
of nifindexes.attachedWasMoved: astdef.attachedWasMoved
of nifindexes.attachedDup: astdef.attachedDup
of nifindexes.attachedSink: astdef.attachedSink
of nifindexes.attachedTrace: astdef.attachedTrace
proc cachedModuleSuffix*(config: ConfigRef; fileIdx: FileIndex): string =
## Gets or computes the module suffix for a FileIndex.
## For NIF modules, the suffix is already stored in the file info.
## For source files, computes it from the path.
let fullPath = toFullPath(config, fileIdx)
if fileInfoKind(config, fileIdx) == fikNifModule:
result = fullPath # Already a suffix
else:
result = moduleSuffix(fullPath, cast[seq[string]](config.searchPaths))
proc typeToNifSym(typ: PType; config: ConfigRef): string =
result = "`t"
result.addInt ord(typ.kind)
result.add '.'
result.addInt typ.uniqueId.item
result.add '.'
result.add modname(typ.uniqueId.module, config)
proc toHookIndexEntry*(config: ConfigRef; typeId: ItemId; hookSym: PSym): HookIndexEntry =
## Converts a type ItemId and hook symbol to a HookIndexEntry for the NIF index.
@@ -150,17 +121,6 @@ proc oldLineInfo(w: var LineInfoWriter; info: PackedLineInfo): TLineInfo =
result = TLineInfo(line: x.line.uint16, col: x.col.int16, fileIndex: fileIdx)
# -------------- Module name handling --------------------------------------------
proc modname(module: int; conf: ConfigRef): string =
cachedModuleSuffix(conf, module.FileIndex)
proc modname(module: PSym; conf: ConfigRef): string =
assert module.kindImpl == skModule
modname(module.positionImpl, conf)
# ------------- Writer ---------------------------------------------------------------
#[
@@ -197,9 +157,10 @@ type
decodedFileIndices: HashSet[FileIndex]
locals: HashSet[ItemId] # track proc-local symbols
inProc: int
writtenTypes: seq[PType] # types written in this module, to be unloaded later
writtenSyms: seq[PSym] # symbols written in this module, to be unloaded later
#writtenTypes: seq[PType] # types written in this module, to be unloaded later
#writtenSyms: seq[PSym] # symbols written in this module, to be unloaded later
exports: Table[FileIndex, HashSet[string]] # module -> specific symbol names (empty = all)
writtenPackages: HashSet[string]
const
# Symbol kinds that are always local to a proc and should never have module suffix
@@ -217,10 +178,11 @@ proc toNifSymName(w: var Writer; sym: PSym): string =
result.addInt sym.disamb
if not isLocalSym(sym) and sym.itemId notin w.locals:
# Global symbol: ident.disamb.moduleSuffix
let module = sym.itemId.module
result.add '.'
let module = if sym.kindImpl == skPackage: w.currentModule else: sym.itemId.module
result.add modname(module, w.infos.config)
proc globalName(sym: PSym; config: ConfigRef): string =
result = sym.name.s
result.add '.'
@@ -282,14 +244,6 @@ proc writeNode(w: var Writer; dest: var TokenBuf; n: PNode; forAst = false)
proc writeType(w: var Writer; dest: var TokenBuf; typ: PType)
proc writeSym(w: var Writer; dest: var TokenBuf; sym: PSym)
proc typeToNifSym(typ: PType; config: ConfigRef): string =
result = "`t"
result.addInt ord(typ.kind)
result.add '.'
result.addInt typ.uniqueId.item
result.add '.'
result.add modname(typ.uniqueId.module, config)
proc writeLoc(w: var Writer; dest: var TokenBuf; loc: TLoc) =
dest.addIdent toNifTag(loc.k)
dest.addIdent toNifTag(loc.storage)
@@ -329,8 +283,6 @@ proc writeType(w: var Writer; dest: var TokenBuf; typ: PType) =
elif typ.itemId.module == w.currentModule and typ.state == Complete:
typ.state = Sealed
writeTypeDef(w, dest, typ)
# Collect for later unloading after entire module is written
w.writtenTypes.add typ
else:
dest.addSymUse pool.syms.getOrIncl(typeToNifSym(typ, w.infos.config)), NoLineInfo
@@ -396,6 +348,8 @@ proc writeSymDef(w: var Writer; dest: var TokenBuf; sym: PSym) =
else:
dest.addIntLit sym.positionImpl
writeLib(w, dest, sym.annexImpl)
# For routine symbols, pre-collect generic params into w.locals before writing
# the type. This ensures they get consistent short names, and their sdefs are
# written in the type where lazy loading can find them via extractLocalSymsFromTree.
@@ -417,15 +371,11 @@ proc writeSymDef(w: var Writer; dest: var TokenBuf; sym: PSym) =
writeSym(w, dest, sym.instantiatedFromImpl)
dest.addParRi
# Collect for later unloading after entire module is written
if sym.kindImpl notin {skPackage}:
# do not unload modules
w.writtenSyms.add sym
proc shouldWriteSymDef(w: Writer; sym: PSym): bool {.inline.} =
proc shouldWriteSymDef(w: var Writer; sym: PSym): bool {.inline.} =
# Don't write module/package symbols - they don't have NIF files
if sym.kindImpl in {skPackage}:
return false
if sym.kindImpl == skPackage:
return not w.writtenPackages.containsOrIncl(sym.name.s)
# Already written - don't write again
if sym.state == Sealed:
return false
@@ -442,10 +392,6 @@ proc shouldWriteSymDef(w: Writer; sym: PSym): bool {.inline.} =
proc writeSym(w: var Writer; dest: var TokenBuf; sym: PSym) =
if sym == nil:
dest.addDotToken()
elif sym.kindImpl in {skPackage}:
# Write module/package symbols as dots - they're resolved differently
# (by position/FileIndex, not by NIF lookup)
dest.addDotToken()
elif shouldWriteSymDef(w, sym):
sym.state = Sealed
writeSymDef(w, dest, sym)
@@ -693,11 +639,55 @@ proc buildExportBuf(w: var Writer): TokenBuf =
result.addParRi()
let replayTag = registerTag("replay")
let repConverterTag = registerTag("repconverter")
let repDestroyTag = registerTag("repdestroy")
let repWasMovedTag = registerTag("repwasmoved")
let repCopyTag = registerTag("repcopy")
let repSinkTag = registerTag("repsink")
let repDupTag = registerTag("repdup")
let repTraceTag = registerTag("reptrace")
let repDeepCopyTag = registerTag("repdeepcopy")
let repEnumToStrTag = registerTag("repenumtostr")
let repMethodTag = registerTag("repmethod")
#let repClassTag = registerTag("repclass")
let includeTag = registerTag("include")
let importTag = registerTag("import")
proc writeOp(w: var Writer; content: var TokenBuf; op: LogEntry) =
case op.kind
of HookEntry:
case op.op
of attachedDestructor:
content.addParLe repDestroyTag, NoLineInfo
of attachedAsgn:
content.addParLe repCopyTag, NoLineInfo
of attachedWasMoved:
content.addParLe repWasMovedTag, NoLineInfo
of attachedDup:
content.addParLe repDupTag, NoLineInfo
of attachedSink:
content.addParLe repSinkTag, NoLineInfo
of attachedTrace:
content.addParLe repTraceTag, NoLineInfo
of attachedDeepCopy:
content.addParLe repDeepCopyTag, NoLineInfo
content.add strToken(pool.strings.getOrIncl(op.key), NoLineInfo)
content.add symToken(pool.syms.getOrIncl(w.toNifSymName(op.sym)), NoLineInfo)
content.addParRi()
of ConverterEntry:
content.addParLe repConverterTag, NoLineInfo
content.add strToken(pool.strings.getOrIncl(op.key), NoLineInfo)
content.add symToken(pool.syms.getOrIncl(w.toNifSymName(op.sym)), NoLineInfo)
content.addParRi()
of MethodEntry:
discard "to implement"
of EnumToStrEntry:
discard "to implement"
of GenericInstEntry:
discard "will only be written later to ensure it is materialized"
proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
hooks: array[AttachedOp, seq[HookIndexEntry]];
converters: seq[(nifstreams.SymId, nifstreams.SymId)];
classes: seq[ClassIndexEntry];
opsLog: seq[LogEntry];
replayActions: seq[PNode] = @[]) =
var w = Writer(infos: LineInfoWriter(config: config), currentModule: thisModule)
var content = createTokenBuf(300)
@@ -711,6 +701,10 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
for action in replayActions:
writeNode(w, content, action)
content.addParRi()
# Only write ops that belong to this module
for op in opsLog:
if op.module == thisModule.int:
writeOp(w, content, op)
w.writeToplevelNode content, n
@@ -723,25 +717,26 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
var dest = createTokenBuf(600)
createStmtList(dest, rootInfo)
dest.add w.deps
dest.add content
# do not write the (stmts .. ) wrapper:
for i in 3 ..< content.len-1:
dest.add content[i]
# ensure the hooks we announced end up in the NIF file regardless of
# whether they have been used:
for op in opsLog:
if op.module == thisModule.int:
let s = op.sym
if s.state != Sealed:
s.state = Sealed
writeSymDef w, dest, s
dest.addParRi()
writeFile(dest, d)
# Build index with export, hook, converter, and method information
let exportBuf = buildExportBuf(w)
createIndex(d, dest[0].info, false,
IndexSections(hooks: hooks, converters: converters, classes: classes, exportBuf: exportBuf))
# Don't unload symbols/types yet - they may be needed by other modules that haven't
# had their NIF files written. For recursive module dependencies (like system.nim),
# we need all NIFs to exist before we can safely unload and reload.
# TODO: Implement deferred unloading at end of compilation for memory savings.
#for typ in w.writtenTypes:
# forcePartial(typ)
#for sym in w.writtenSyms:
# forcePartial(sym)
IndexSections(exportBuf: exportBuf))
# --------------------------- Loader (lazy!) -----------------------------------------------
@@ -1107,6 +1102,8 @@ proc loadSymFromCursor(c: var DecodeContext; s: PSym; n: var Cursor; thisModule:
else:
loadField s.positionImpl
s.annexImpl = loadAnnex(c, n, thisModule, localSyms)
# Local symbols were already extracted upfront in loadSym, so we can use
# the simple loadTypeStub here.
s.typImpl = loadTypeStub(c, n, localSyms)
@@ -1220,10 +1217,12 @@ proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string;
inc n # skip `sd` tag
loadSymFromCursor(c, sym, n, thisModule, localSyms)
sym.state = Sealed # mark as fully loaded
result = newSymNode(sym, info)
else:
sym = c.loadSymStub(name.symId, thisModule, localSyms)
skip n # skip the entire sdef for indexed symbols
result = newSymNode(sym, info)
result = newSymNode(sym, info)
result.flags.incl nfLazyType
of typeDefTagName:
raiseAssert "`td` tag in invalid context"
of "none":
@@ -1410,24 +1409,6 @@ proc toNifIndexFilename*(conf: ConfigRef; f: FileIndex): string =
let suffix = moduleSuffix(conf, f)
result = toGeneratedFile(conf, AbsoluteFile(suffix), ".s.idx.nif").string
proc parseTypeSymIdToItemId*(c: var DecodeContext; symId: nifstreams.SymId): ItemId =
## Parses a type SymId (format: `"`tN.modulesuffix"`) to extract ItemId.
let s = pool.syms[symId]
if not s.startsWith("`t"):
return ItemId(module: -1, item: 0)
var i = 2 # skip "`t"
var item = 0'i32
while i < s.len and s[i] in {'0'..'9'}:
item = item * 10 + int32(ord(s[i]) - ord('0'))
inc i
if i < s.len and s[i] == '.':
inc i
let suffix = s.substr(i)
let module = moduleId(c, suffix)
result = ItemId(module: int32(module), item: item)
else:
result = ItemId(module: -1, item: item)
proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: bool): PSym =
result = c.syms.getOrDefault(symAsStr)[0]
if result != nil:
@@ -1456,8 +1437,9 @@ proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: boo
proc resolveHookSym*(c: var DecodeContext; symId: nifstreams.SymId): PSym =
## Resolves a hook SymId to PSym.
## Hook symbols are often private (generated =destroy, =wasMoved, etc.)
let symAsStr = pool.syms[symId]
result = resolveSym(c, symAsStr, false)
result = resolveSym(c, symAsStr, true)
proc tryResolveCompilerProc*(c: var DecodeContext; name: string; moduleFileIdx: FileIndex): PSym =
## Tries to resolve a compiler proc from a module by checking the NIF index.
@@ -1466,10 +1448,115 @@ proc tryResolveCompilerProc*(c: var DecodeContext; name: string; moduleFileIdx:
let symName = name & ".0." & suffix
result = resolveSym(c, symName, true)
proc loadLogOp(c: var DecodeContext; logOps: var seq[LogEntry]; s: var Stream; kind: LogEntryKind; op: TTypeAttachedOp; module: int): PackedToken =
result = next(s)
var key = ""
if result.kind == StringLit:
key = pool.strings[result.litId]
result = next(s)
else:
raiseAssert "expected StringLit but got " & $result.kind
if result.kind == Symbol:
let sym = resolveHookSym(c, result.symId)
if sym != nil:
logOps.add LogEntry(kind: kind, op: op, module: module, key: key, sym: sym)
# else: symbol not indexed, skip this hook entry
result = next(s)
if result.kind == ParRi:
result = next(s)
else:
raiseAssert "expected ParRi but got " & $result.kind
proc skipTree(s: var Stream): PackedToken =
result = next(s)
var nested = 1
while nested > 0:
if result.kind == ParLe:
inc nested
elif result.kind == ParRi:
dec nested
elif result.kind == EofToken:
break
result = next(s)
proc nextSubtree(r: var Stream; dest: var TokenBuf; tok: var PackedToken) =
r.parents[0] = tok.info
var nested = 1
dest.add tok # tag
while true:
tok = r.next()
dest.add tok
if tok.kind == EofToken:
break
elif tok.kind == ParLe:
inc nested
elif tok.kind == ParRi:
dec nested
if nested == 0: break
proc processTopLevel(c: var DecodeContext; s: var Stream; loadFullAst: bool; suffix: string; logOps: var seq[LogEntry]; module: int): PNode =
result = newNode(nkStmtList)
var localSyms = initTable[string, PSym]()
var t = next(s) # skip dot
var cont = true
while cont and t.kind != EofToken:
if t.kind == ParLe:
if t.tagId == replayTag:
# Always load replay actions (macro cache operations)
t = next(s) # move past (replay
while t.kind != ParRi and t.kind != EofToken:
if t.kind == ParLe:
var buf = createTokenBuf(50)
nextSubtree(s, buf, t)
var cursor = cursorAt(buf, 0)
let replayNode = loadNode(c, cursor, suffix, localSyms)
if replayNode != nil:
result.sons.add replayNode
t = next(s)
if t.kind == ParRi:
t = next(s)
else:
raiseAssert "expected ParRi but got " & $t.kind
elif t.tagId == repConverterTag:
t = loadLogOp(c, logOps, s, ConverterEntry, attachedTrace, module)
elif t.tagId == repDestroyTag:
t = loadLogOp(c, logOps, s, HookEntry, attachedDestructor, module)
elif t.tagId == repWasMovedTag:
t = loadLogOp(c, logOps, s, HookEntry, attachedWasMoved, module)
elif t.tagId == repCopyTag:
t = loadLogOp(c, logOps, s, HookEntry, attachedAsgn, module)
elif t.tagId == repSinkTag:
t = loadLogOp(c, logOps, s, HookEntry, attachedSink, module)
elif t.tagId == repDupTag:
t = loadLogOp(c, logOps, s, HookEntry, attachedDup, module)
elif t.tagId == repTraceTag:
t = loadLogOp(c, logOps, s, HookEntry, attachedTrace, module)
elif t.tagId == repDeepCopyTag:
t = loadLogOp(c, logOps, s, HookEntry, attachedDeepCopy, module)
elif t.tagId == repEnumToStrTag:
t = loadLogOp(c, logOps, s, EnumToStrEntry, attachedTrace, module)
elif t.tagId == repMethodTag:
t = loadLogOp(c, logOps, s, MethodEntry, attachedTrace, module)
#elif t.tagId == repClassTag:
# t = loadLogOp(c, logOps, s, ClassEntry, attachedTrace, module)
elif t.tagId == includeTag or t.tagId == importTag:
t = skipTree(s)
elif loadFullAst:
# Parse the full statement
var buf = createTokenBuf(50)
nextSubtree(s, buf, t)
var cursor = cursorAt(buf, 0)
let stmtNode = loadNode(c, cursor, suffix, localSyms)
if stmtNode != nil:
result.sons.add stmtNode
else:
cont = false
else:
cont = false
proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: var TStrTable;
hooks: var Table[nifstreams.SymId, HooksPerType];
converters: var seq[(string, string)];
classes: var seq[ClassIndexEntry];
logOps: var seq[LogEntry];
loadFullAst: bool = false): PNode =
let suffix = moduleSuffix(c.infos.config, f)
@@ -1480,70 +1567,18 @@ proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: va
# Symbols are created as stubs (Partial state) and will be loaded lazily via loadSym
populateInterfaceTablesFromIndex(c, module, interf, interfHidden, suffix)
# Return hooks from the index
hooks = move c.mods[module].index.hooks
# Return converters from the index
converters = move c.mods[module].index.converters
# Return classes/methods from the index
classes = move c.mods[module].index.classes
# Load the module AST (or just replay actions if loadFullAst is false)
result = newNode(nkStmtList)
let s = addr c.mods[module].stream
s.r.jumpTo 0 # Start from beginning
discard processDirectives(s.r)
var localSyms = initTable[string, PSym]()
var t = next(s[])
if t.kind == ParLe and pool.tags[t.tagId] == toNifTag(nkStmtList):
t = next(s[]) # skip (stmts
t = next(s[]) # skip flags
t = next(s[]) # skip type
# Process all top-level statements
while t.kind != ParRi and t.kind != EofToken:
if t.kind == ParLe:
let tag = pool.tags[t.tagId]
if tag == "replay":
# Always load replay actions (macro cache operations)
t = next(s[]) # move past (replay
while t.kind != ParRi and t.kind != EofToken:
if t.kind == ParLe:
var buf = createTokenBuf(50)
nifcursors.parse(s[], buf, t.info)
var cursor = cursorAt(buf, 0)
let replayNode = loadNode(c, cursor, suffix, localSyms)
if replayNode != nil:
result.sons.add replayNode
t = next(s[])
elif loadFullAst:
# Parse the full statement
var buf = createTokenBuf(50)
buf.add t # Add the ParLe token we already read
var nested = 1
while nested > 0:
t = next(s[])
buf.add t
if t.kind == ParLe:
inc nested
elif t.kind == ParRi:
dec nested
elif t.kind == EofToken:
break
var cursor = cursorAt(buf, 0)
let stmtNode = loadNode(c, cursor, suffix, localSyms)
if stmtNode != nil:
result.sons.add stmtNode
else:
# Skip over the statement by counting parentheses
var nested = 1
while nested > 0:
t = next(s[])
if t.kind == ParLe:
inc nested
elif t.kind == ParRi:
dec nested
elif t.kind == EofToken:
break
else:
t = next(s[])
result = processTopLevel(c, s[], loadFullAst, suffix, logOps, f.int)
else:
result = newNode(nkStmtList)
when isMainModule:
import std / syncio

View File

@@ -990,6 +990,22 @@ proc newStrNode*(strVal: string; info: TLineInfo): PNode =
result = newNodeI(nkStrLit, info)
result.strVal = strVal
# Hooks, converters, method dispatchers and enum-to-string generated procs need special
# handling for IC, they end up in IC indexes etc. Thus we "log" them in the module graph
# and to pass them around to the NIF writer. This is not very elegant but it works.
type
LogEntryKind* = enum
HookEntry, ConverterEntry, MethodEntry, EnumToStrEntry, GenericInstEntry
LogEntry* = object
kind*: LogEntryKind
op*: TTypeAttachedOp
isGeneric*: bool
module*: int # Which module this entry belongs to
key*: string
sym*: PSym
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

@@ -3363,7 +3363,7 @@ proc genConstSetup(p: BProc; sym: PSym): bool =
useHeader(m, sym)
if sym.loc.k == locNone:
fillBackendName(p.module, sym)
ensureMutable sym
backendEnsureMutable sym
fillLoc(sym.locImpl, locData, sym.astdef, OnStatic)
if m.hcrOn: incl(sym, lfIndirect)
result = lfNoDecl notin sym.loc.flags
@@ -3710,7 +3710,7 @@ proc expr(p: BProc, n: PNode, d: var TLoc) =
inc p.splitDecls
genGotoState(p, n)
of nkBreakState: genBreakState(p, n, d)
of nkMixinStmt, nkBindStmt: discard
of nkMixinStmt, nkBindStmt, nkReplayAction: discard
else: internalError(p.config, n.info, "expr(" & $n.kind & "); unknown node kind")
proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo; result: var Builder) =

View File

@@ -11,7 +11,7 @@
# ------------------------- Name Mangling --------------------------------
import sighashes, modulegraphs, std/strscans
import sighashes, std/strscans
import ../dist/checksums/src/checksums/md5
import std/sequtils
@@ -124,7 +124,7 @@ proc fillLocalName(p: BProc; s: PSym) =
elif s.kind != skResult:
result.add "_" & rope(counter+1)
p.sigConflicts.inc(key)
ensureMutable s
backendEnsureMutable s
s.locImpl.snippet = result
proc scopeMangledParam(p: BProc; param: PSym) =

View File

@@ -16,7 +16,7 @@ import
rodutils, renderer, cgendata, aliases,
lowerings, lineinfos, pathutils, transf,
injectdestructors, astmsgs, modulepaths, pushpoppragmas,
mangleutils, cbuilderbase
mangleutils, cbuilderbase, modulegraphs
from expanddefaults import caseObjDefaultBranch
@@ -61,18 +61,25 @@ proc hcrOn(p: BProc): bool = p.module.config.hcrOn
proc addForwardedProc(m: BModule, prc: PSym) =
m.g.forwardedProcs.add(prc)
proc newModule*(g: BModuleList; module: PSym; conf: ConfigRef): BModule
proc newModule*(g: BModuleList; module: PSym; conf: ConfigRef; idgen: IdGenerator): BModule
proc findPendingModule(m: BModule, s: PSym): BModule =
# TODO fixme
if m.config.symbolFiles == v2Sf or optCompress in m.config.globalOptions:
let ms = s.itemId.module #getModule(s)
result = m.g.modules[ms]
elif m.config.cmd in {cmdNifC, cmdM}:
var ms = getModule(s)
registerModule m.g.graph, ms
if ms.position >= m.g.modules.len:
result = newModule(m.g, ms, m.config, idGeneratorFromModule(ms))
else:
result = m.g.modules[ms.position]
if result == nil:
result = newModule(m.g, ms, m.config, idGeneratorFromModule(ms))
else:
var ms = getModule(s)
result = m.g.modules[ms.position]
if result == nil:
result = newModule(m.g, ms, m.config)
proc initLoc(k: TLocKind, lode: PNode, s: TStorageLoc, flags: TLocFlags = {}): TLoc =
result = TLoc(k: k, storage: s, lode: lode,
@@ -651,7 +658,7 @@ proc localVarDecl(res: var Builder, p: BProc; n: PNode,
let s = n.sym
if s.loc.k == locNone:
fillLocalName(p, s)
ensureMutable s
backendEnsureMutable s
fillLoc(s.locImpl, locLocalVar, n, OnStack)
if s.kind == skLet: incl(s, lfNoDeepCopy)
@@ -713,7 +720,7 @@ proc assignGlobalVar(p: BProc, n: PNode; value: Rope) =
let s = n.sym
if s.loc.k == locNone:
fillBackendName(p.module, s)
ensureMutable s
backendEnsureMutable s
fillLoc(s.locImpl, locGlobalVar, n, OnHeap)
if treatGlobalDifferentlyForHCR(p.module, s): incl(s, lfIndirect)
@@ -722,7 +729,7 @@ proc assignGlobalVar(p: BProc, n: PNode; value: Rope) =
if q != nil and not containsOrIncl(q.declaredThings, s.id):
varInDynamicLib(q, s)
else:
ensureMutable s
backendEnsureMutable s
s.locImpl.snippet = mangleDynLibProc(s)
if value != "":
internalError(p.config, n.info, ".dynlib variables cannot have a value")
@@ -763,13 +770,13 @@ proc assignGlobalVar(p: BProc, n: PNode; value: Rope) =
genGlobalVarDecl(p.module.s[cfsVars], p, n, td, initializer = initializer)
if p.withinLoop > 0 and value == "":
# fixes tests/run/tzeroarray:
ensureMutable s
backendEnsureMutable s
resetLoc(p, s.locImpl)
proc callGlobalVarCppCtor(p: BProc; v: PSym; vn, value: PNode; didGenTemp: var bool) =
let s = vn.sym
fillBackendName(p.module, s)
ensureMutable s
backendEnsureMutable s
fillLoc(s.locImpl, locGlobalVar, vn, OnHeap)
let td = getTypeDesc(p.module, vn.sym.typ, dkVar)
var val = genCppParamsForCtor(p, value, didGenTemp)
@@ -959,7 +966,7 @@ proc symInDynamicLib(m: BModule, sym: PSym) =
var extname = sym.loc.snippet
if not isCall: loadDynamicLib(m, lib)
var tmp = mangleDynLibProc(sym)
ensureMutable sym
backendEnsureMutable sym
sym.locImpl.snippet = tmp # from now on we only need the internal name
sym.typ.sym = nil # generate a new name
inc(m.labels, 2)
@@ -1004,7 +1011,7 @@ proc varInDynamicLib(m: BModule, sym: PSym) =
loadDynamicLib(m, lib)
incl(sym, lfIndirect)
var tmp = mangleDynLibProc(sym)
ensureMutable sym
backendEnsureMutable sym
sym.locImpl.snippet = tmp # from now on we only need the internal name
inc(m.labels, 2)
let t = ptrType(getTypeDesc(m, sym.typ, dkVar))
@@ -1018,7 +1025,7 @@ proc varInDynamicLib(m: BModule, sym: PSym) =
m.s[cfsVars].addVar(name = sym.loc.snippet, typ = t)
proc symInDynamicLibPartial(m: BModule, sym: PSym) =
ensureMutable sym
backendEnsureMutable sym
sym.locImpl.snippet = mangleDynLibProc(sym)
sym.typ.sym = nil # generate a new name
@@ -1336,9 +1343,9 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
returnStmt = extract(returnBuilder)
elif sfConstructor in prc.flags:
resNode.sym.incl lfIndirect
ensureMutable resNode.sym
backendEnsureMutable resNode.sym
fillLoc(resNode.sym.locImpl, locParam, resNode, "this", OnHeap)
ensureMutable prc
backendEnsureMutable prc
prc.locImpl.snippet = getTypeDesc(m, resNode.sym.locImpl.t, dkVar)
else:
fillResult(p.config, resNode, prc.typ)
@@ -1352,11 +1359,11 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
if sfNoInit in prc.flags: discard
elif allPathsAsgnResult(p, procBody) == InitSkippable: discard
else:
ensureMutable res
backendEnsureMutable res
resetLoc(p, res.locImpl)
if skipTypes(res.typ, abstractInst).kind == tyArray:
#incl(res.loc.flags, lfIndirect)
ensureMutable res
backendEnsureMutable res
res.locImpl.storage = OnUnknown
for i in 1..<prc.typ.n.len:
@@ -2103,7 +2110,7 @@ proc hcrGetProcLoadCode(builder: var Builder, m: BModule, sym, prefix, handle, g
var extname = prefix & sym
var tmp = mangleDynLibProc(prc)
ensureMutable prc
backendEnsureMutable prc
prc.locImpl.snippet = tmp
prc.typ.sym = nil
@@ -2372,9 +2379,10 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule
proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule =
result = rawNewModule(g, module, AbsoluteFile toFullPath(conf, module.position.FileIndex))
proc newModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule =
proc newModule(g: BModuleList; module: PSym; conf: ConfigRef; idgen: IdGenerator): BModule =
# we should create only one cgen module for each module sym
result = rawNewModule(g, module, conf)
result.idgen = idgen
if module.position >= g.modules.len:
setLen(g.modules, module.position + 1)
#growCache g.modules, module.position
@@ -2387,8 +2395,7 @@ template injectG() {.dirty.} =
proc setupCgen*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext =
injectG()
result = newModule(g, module, graph.config)
result.idgen = idgen
result = newModule(g, module, graph.config, idgen)
if optGenIndex in graph.config.globalOptions and g.generatedHeader == nil:
let f = if graph.config.headerFile.len > 0: AbsoluteFile graph.config.headerFile
else: graph.config.projectFull
@@ -2562,7 +2569,7 @@ proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: PNode;
result = newSym(skProc, procname, m.idgen, m.module.owner, m.module.info)
result.typ = newProcType(m.module.info, m.idgen, m.module.owner)
result.typ.callConv = ccCDecl
ensureMutable result
backendEnsureMutable result
incl result.flagsImpl, sfExportc
result.locImpl.snippet = prefixedName
if isDynlib:

View File

@@ -37,8 +37,7 @@ proc setupBackendModule(g: ModuleGraph; m: var LoadedModule) =
if g.backend == nil:
g.backend = cgendata.newModuleList(g)
assert g.backend != nil
var bmod = cgen.newModule(BModuleList(g.backend), m.module, g.config)
bmod.idgen = idgenFromLoadedModule(m)
var bmod = cgen.newModule(BModuleList(g.backend), m.module, g.config, idgenFromLoadedModule(m))
proc generateCodeForModule(g: ModuleGraph; m: var LoadedModule; alive: var AliveSyms) =
var bmod = BModuleList(g.backend).modules[m.module.position]

View File

@@ -20,6 +20,8 @@ when not defined(nimKochBootstrap):
import ast2nif
import "../dist/nimony/src/lib" / [nifstreams, bitabs]
import typekeys
when defined(nimPreviewSlimSystem):
import std/assertions
@@ -79,6 +81,8 @@ type
typeInstCache*: Table[ItemId, seq[LazyType]] # A symbol's ItemId.
procInstCache*: Table[ItemId, seq[LazyInstantiation]] # A symbol's ItemId.
attachedOps*: array[TTypeAttachedOp, Table[ItemId, LazySym]] # Type ID, destructors, etc.
loadedOps: array[TTypeAttachedOp, Table[string, PSym]] # This can later by unified with `attachedOps` once it's stable
opsLog*: seq[LogEntry]
methodsPerGenericType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods
memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual,member and ctor so far).
initializersPerType*: Table[ItemId, PNode] # Type ID, AST call to the default ctor (c++ only)
@@ -166,6 +170,9 @@ proc resetForBackend*(g: ModuleGraph) =
g.enumToStringProcs.clear()
g.dispatchers.setLen(0)
g.methodsPerType.clear()
for a in mitems(g.loadedOps):
a.clear()
g.opsLog.setLen(0)
const
cb64 = [
@@ -361,11 +368,26 @@ proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym =
## if no such operation exists.
if g.attachedOps[op].contains(t.itemId):
result = resolveAttachedOp(g, g.attachedOps[op][t.itemId])
elif g.config.cmd in {cmdNifC, cmdM}:
# Fall back to key-based lookup for NIF-loaded hooks
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
result = g.loadedOps[op].getOrDefault(key)
#echo "fallback ", key, " ", op, " ", result
else:
result = nil
proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) =
## we also need to record this to the packed module.
if not g.attachedOps[op].contains(t.itemId):
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
# Use key-based deduplication for opsLog because different type objects
# (e.g. canon vs orig) can have different itemIds but same structural key
if key notin g.loadedOps[op]:
# Hooks should be written to the module where the type is defined,
# not the module that triggered the registration
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: ownerModule, key: key, sym: value)
g.loadedOps[op][key] = value
g.attachedOps[op][t.itemId] = LazySym(sym: value)
proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) =
@@ -414,6 +436,9 @@ proc getToStringProc*(g: ModuleGraph; t: PType): PSym =
proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) =
g.enumToStringProcs[t.itemId] = LazySym(sym: value)
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: value.itemId.module.int
g.opsLog.add LogEntry(kind: EnumToStrEntry, module: ownerModule, key: key, sym: value)
iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
if g.methodsPerGenericType.contains(t.itemId):
@@ -422,6 +447,17 @@ iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) =
proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) =
g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, LazySym(sym: m))
let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback)
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
g.opsLog.add LogEntry(kind: MethodEntry, module: ownerModule, key: key, sym: m)
proc logGenericInstance*(g: ModuleGraph; inst: PSym) =
## Log a generic instance so it gets written to the NIF file.
## This is needed when generic instances are created during compile-time
## evaluation and may be referenced from other modules compiled in the same run.
if g.config.cmd in {cmdNifC, cmdM}:
let ownerModule = inst.itemId.module.int
g.opsLog.add LogEntry(kind: GenericInstEntry, module: ownerModule, sym: inst)
proc hasDisabledAsgn*(g: ModuleGraph; t: PType): bool =
let op = getAttachedOp(g, t, attachedAsgn)
@@ -783,7 +819,6 @@ when not defined(nimKochBootstrap):
## 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
@@ -794,44 +829,29 @@ when not defined(nimKochBootstrap):
itemId: ItemId(module: int32(fileIdx), item: 0'i32),
name: getIdent(g.cache, splitFile(filename).name),
infoImpl: newLineInfo(fileIdx, 1, 1),
positionImpl: int(fileIdx),
)
positionImpl: int(fileIdx))
setOwner(result, getPackage(g.config, g.cache, fileIdx))
# Register module in graph
registerModule(g, result)
var hooks = initTable[nifstreams.SymId, HooksPerType]()
var converters: seq[(string, string)] = @[]
var classes: seq[ClassIndexEntry] = @[]
var opsLog: seq[LogEntry] = @[]
result.astImpl = loadNifModule(ast.program, fileIdx, g.ifaces[fileIdx.int].interf,
g.ifaces[fileIdx.int].interfHidden, hooks, converters, classes, loadFullAst)
g.ifaces[fileIdx.int].interfHidden, opsLog, 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)
for x in opsLog:
case x.kind
of HookEntry:
g.loadedOps[x.op][x.key] = x.sym
of ConverterEntry:
g.ifaces[fileIdx.int].converters.add LazySym(sym: x.sym)
of MethodEntry:
discard "todo"
of EnumToStrEntry:
discard "todo"
of GenericInstEntry:
raiseAssert "GenericInstEntry should not be in the NIF index"
# 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)
discard "todo"
cachedModules.add fileIdx
proc configComplete*(g: ModuleGraph) =

View File

@@ -52,7 +52,7 @@ 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)
result = cgen.newModule(BModuleList(g.backend), module, g.config, idGeneratorFromModule(module))
proc generateCodeForModule(g: ModuleGraph; module: PSym) =
## Generate C code for a single module.
@@ -82,14 +82,13 @@ proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
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 system module first - it's always needed and contains essential hooks
var cachedModules: seq[FileIndex] = @[]
if g.config.m.systemFileIdx != InvalidFileIdx:
g.systemModule = moduleFromNifFile(g, g.config.m.systemFileIdx, cachedModules)
# Load all modules in dependency order using stack traversal
# This must happen BEFORE any code generation so that hooks are loaded into loadedOps
let modules = loadModuleDependencies(g, mainFileIdx)
if modules.len == 0:
rawMessage(g.config, errGenerated,
@@ -100,10 +99,17 @@ proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
for module in modules:
discard setupNifBackendModule(g, module)
# Also ensure system module is set up and generated first 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)
# 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:
if module != mainModule and module != g.systemModule:
generateCodeForModule(g, module)
# Generate main module last (so all init procs are registered)

View File

@@ -254,41 +254,8 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
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)
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog, replayActions)
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.

View File

@@ -450,6 +450,10 @@ proc generateInstance(c: PContext, fn: PSym, pt: LayeredIdTable,
entry.compilesId = c.compilesContextId
addToGenericProcCache(c, fn, entry)
c.generics.add(makeInstPair(fn, entry))
# Log the generic instance so it gets written to the NIF file.
# This is needed for cyclic module dependencies where generic instances
# may be created in one module but referenced from another.
logGenericInstance(c.graph, result)
# bug #12985 bug #22913
# TODO: use the context of the declaration of generic functions instead
# TODO: consider fixing options as well

View File

@@ -65,16 +65,14 @@ proc newTransNode(a: PNode): PNode {.inline.} =
proc newTransNode(kind: TNodeKind, info: TLineInfo,
sons: int): PNode {.inline.} =
var x = newNodeI(kind, info)
newSeq(x.sons, sons)
result = x
result = newNodeI(kind, info)
newSeq(result.sons, sons)
proc newTransNode(kind: TNodeKind, n: PNode,
sons: int): PNode {.inline.} =
var x = newNodeIT(kind, n.info, n.typ)
newSeq(x.sons, sons)
# x.flags = n.flags
result = x
result = newNodeIT(kind, n.info, n.typ)
newSeq(result.sons, sons)
# x.flags = n.flags
proc newTransCon(owner: PSym): PTransCon =
assert owner != nil
@@ -247,6 +245,7 @@ proc hasContinue(n: PNode): bool =
case n.kind
of nkEmpty..nkNilLit, nkForStmt, nkParForStmt, nkWhileStmt: result = false
of nkContinueStmt: result = true
of routineDefs: result = false
else:
result = false
for i in 0..<n.len:

285
compiler/typekeys.nim Normal file
View File

@@ -0,0 +1,285 @@
#
#
# The Nim Compiler
# (c) Copyright 2025 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## Based on sighashes.nim but works on astdef directly as we need it in ast2nif.nim.
## Also produces more readable names thanks to treemangler.
import std/assertions
import "../dist/nimony/src/lib" / [treemangler]
import "../dist/nimony/src/gear2" / modnames
import astdef, idents, options, lineinfos, msgs
import ic / [enum2nif]
# -------------- Module name handling --------------------------------------------
proc cachedModuleSuffix*(config: ConfigRef; fileIdx: FileIndex): string =
## Gets or computes the module suffix for a FileIndex.
## For NIF modules, the suffix is already stored in the file info.
## For source files, computes it from the path.
let fullPath = toFullPath(config, fileIdx)
if fileInfoKind(config, fileIdx) == fikNifModule:
result = fullPath # Already a suffix
else:
result = moduleSuffix(fullPath, cast[seq[string]](config.searchPaths))
proc modname*(module: int; conf: ConfigRef): string =
cachedModuleSuffix(conf, module.FileIndex)
proc modname*(module: PSym; conf: ConfigRef): string =
assert module.kindImpl == skModule
modname(module.positionImpl, conf)
# --------------- Type key generation --------------------------------------------
type
ConsiderFlag = enum
CoProc
CoType
CoIgnoreRange
CoConsiderOwned
CoDistinct
CoHashTypeInsideNode
TypeLoader* = proc (t: PType) {.nimcall.}
SymLoader* = proc (s: PSym) {.nimcall.}
Context = object
m: Mangler
tl: TypeLoader
sl: SymLoader
proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef)
proc symKey(c: var Context; s: PSym; conf: ConfigRef) =
if s.state == Partial:
assert c.sl != nil
c.sl(s)
if sfAnon in s.flagsImpl or s.kindImpl == skGenericParam:
c.m.addIdent("´anon")
else:
var name = s.name.s
name.add '.'
name.addInt s.disamb
let it =
if s.kindImpl == skModule:
s
elif s.kindImpl in skProcKinds and sfFromGeneric in s.flagsImpl and s.ownerFieldImpl.kindImpl != skModule:
s.ownerFieldImpl.ownerFieldImpl
else:
s.ownerFieldImpl
if it.kindImpl == skModule:
name.add '.'
name.add modname(it, conf)
c.m.addSymbol(name)
proc treeKey(c: var Context; n: PNode; flags: set[ConsiderFlag]; conf: ConfigRef) =
if n == nil:
c.m.addEmpty()
return
let k = n.kind
case k
of nkEmpty, nkNilLit, nkType: discard
of nkIdent:
c.m.addIdent(n.ident.s)
of nkSym:
symKey(c, n.sym, conf)
if CoHashTypeInsideNode in flags and n.sym.typImpl != nil:
typeKey(c, n.sym.typImpl, flags, conf)
of nkCharLit..nkUInt64Lit:
let v = n.intVal
c.m.addIntLit v
of nkFloatLit..nkFloat64Lit:
let v = n.floatVal
c.m.addFloatLit v
of nkStrLit..nkTripleStrLit:
c.m.addStrLit n.strVal
else:
withTree c.m, toNifTag(k):
for i in 0..<n.len: treeKey(c, n[i], flags, conf)
proc skipModifierB(n: PType): PType {.inline.} =
n.sonsImpl[^1]
proc skipTypesB(t: PType, kinds: TTypeKinds): PType =
result = t
while result.kind in kinds: result = result.sonsImpl[^1]
proc isGenericAlias(t: PType): bool =
result = t.kind == tyGenericInst and t.skipModifierB.skipTypesB({tyAlias}).kind == tyGenericInst
proc skipGenericAlias(t: PType): PType =
result = t.skipTypesB({tyAlias})
if result.isGenericAlias:
result = result.skipModifierB.skipTypesB({tyAlias})
proc maybeImported(c: var Context; s: PSym; conf: ConfigRef) {.inline.} =
if s != nil and {sfImportc, sfExportc} * s.flagsImpl != {}:
c.symKey(s, conf)
proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) =
if t == nil:
c.m.addEmpty()
return
if t.state == Partial:
assert c.tl != nil
c.tl(t)
case t.kind
of tyGenericInvocation:
for a in t.sonsImpl:
c.typeKey a, flags, conf
of tyDistinct:
if CoDistinct in flags:
if t.symImpl != nil: symKey(c, t.symImpl, conf)
if t.symImpl == nil or tfFromGeneric in t.flagsImpl:
c.typeKey t.sonsImpl[^1], flags, conf
elif CoType in flags or t.symImpl == nil:
c.typeKey t.sonsImpl[^1], flags, conf
else:
symKey(c, t.symImpl, conf)
of tyGenericInst:
if sfInfixCall in t.sonsImpl[0].symImpl.flagsImpl:
# This is an imported C++ generic type.
# We cannot trust the `lastSon` to hold a properly populated and unique
# value for each instantiation, so we hash the generic parameters here:
let normalizedType = t.skipGenericAlias
c.typeKey normalizedType.sonsImpl[0], flags, conf
for i in 1..<t.sonsImpl.len-1:
c.typeKey t.sonsImpl[i], flags, conf
else:
c.typeKey t.skipModifierB, flags, conf
of tyAlias, tySink, tyUserTypeClasses, tyInferred:
c.typeKey t.skipModifierB, flags, conf
of tyOwned:
if CoConsiderOwned in flags:
withTree c.m, toNifTag(t.kind):
c.typeKey t.skipModifierB, flags, conf
else:
c.typeKey t.skipModifierB, flags, conf
of tyBool:
withTree c.m, "bool":
maybeImported(c, t.symImpl, conf)
of tyChar:
withTree c.m, "c":
c.m.addIntLit 8 # char is always 8 bits
maybeImported(c, t.symImpl, conf)
of tyInt:
withTree c.m, "i":
c.m.addIntLit -1
maybeImported(c, t.symImpl, conf)
of tyInt8:
withTree c.m, "i":
c.m.addIntLit 8
maybeImported(c, t.symImpl, conf)
of tyInt16:
withTree c.m, "i":
c.m.addIntLit 16
maybeImported(c, t.symImpl, conf)
of tyInt32:
withTree c.m, "i":
c.m.addIntLit 32
maybeImported(c, t.symImpl, conf)
of tyInt64:
withTree c.m, "i":
c.m.addIntLit 64
maybeImported(c, t.symImpl, conf)
of tyUInt:
withTree c.m, "u":
c.m.addIntLit -1
maybeImported(c, t.symImpl, conf)
of tyUInt8:
withTree c.m, "u":
c.m.addIntLit 8
maybeImported(c, t.symImpl, conf)
of tyUInt16:
withTree c.m, "u":
c.m.addIntLit 16
maybeImported(c, t.symImpl, conf)
of tyUInt32:
withTree c.m, "u":
c.m.addIntLit 32
maybeImported(c, t.symImpl, conf)
of tyUInt64:
withTree c.m, "u":
c.m.addIntLit 64
maybeImported(c, t.symImpl, conf)
of tyObject, tyEnum:
if t.typeInstImpl != nil:
# prevent against infinite recursions here, see bug #8883:
let inst = t.typeInstImpl
t.typeInstImpl = nil # IC: spurious writes are ok since we set it back immediately
assert inst.kind == tyGenericInst
c.typeKey inst.sonsImpl[0], flags, conf
for i in 1..<inst.sonsImpl.len-1:
c.typeKey inst.sonsImpl[i], flags, conf
t.typeInstImpl = inst
elif t.symImpl != nil:
c.symKey(t.symImpl, conf)
else:
c.m.addIdent "`bug"
of tyFromExpr:
withTree c.m, toNifTag(t.kind):
c.treeKey(t.nImpl, flags, conf)
of tyTuple:
withTree c.m, toNifTag(t.kind):
if t.nImpl != nil and CoType notin flags:
for i in 0..<t.nImpl.len:
withTree c.m, "kv":
assert(t.nImpl[i].kind == nkSym)
c.symKey(t.nImpl[i].sym, conf)
c.typeKey(t.nImpl[i].sym.typImpl, flags+{CoIgnoreRange}, conf)
else:
for i in 1..<t.sonsImpl.len:
c.typeKey t.sonsImpl[i], flags+{CoIgnoreRange}, conf
of tyRange:
if CoIgnoreRange notin flags:
withTree c.m, toNifTag(t.kind):
c.treeKey(t.nImpl, {}, conf)
c.typeKey(t.sonsImpl[^1], flags, conf)
else:
c.typeKey(t.sonsImpl[^1], flags, conf)
of tyStatic:
withTree c.m, toNifTag(t.kind):
c.treeKey(t.nImpl, {}, conf)
if t.sonsImpl.len > 0:
c.typeKey(t.skipModifierB, flags, conf)
of tyProc:
withTree c.m, (if tfIterator in t.flagsImpl: "itertype" else: "proctype"):
if CoProc in flags and t.nImpl != nil:
let params = t.nImpl
for i in 1..<params.len:
let param = params[i].sym
c.symKey(param, conf)
c.typeKey(param.typImpl, flags, conf)
else:
for i in 1..<t.sonsImpl.len:
c.typeKey(t.sonsImpl[i], flags, conf)
if t.sonsImpl.len > 0:
c.typeKey(t.sonsImpl[0], flags, conf)
c.m.addIdent toNifTag(t.callConvImpl)
if tfVarargs in t.flagsImpl: c.m.addIdent "´varargs"
of tyArray:
withTree c.m, toNifTag(t.kind):
c.typeKey(t.sonsImpl[^1], flags-{CoIgnoreRange}, conf)
c.typeKey(t.sonsImpl[0], flags-{CoIgnoreRange}, conf)
else:
withTree c.m, toNifTag(t.kind):
for i in 1..<t.sonsImpl.len:
c.typeKey t.sonsImpl[i], flags, conf
if tfNotNil in t.flagsImpl and CoType notin flags:
c.m.addIdent "´notnil"
proc typeKey*(t: PType; conf: ConfigRef; tl: TypeLoader; sl: SymLoader): string =
var c: Context = Context(m: createMangler(30, -1), tl: tl, sl: sl)
typeKey(c, t, {}, conf)
result = c.m.extract()