# # # The Nim Compiler # (c) Copyright 2017 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module implements the module graph data structure. The module graph ## represents a complete Nim project. Single modules can either be kept in RAM ## or stored in a rod-file. import std/[intsets, tables, hashes, strtabs, os, strutils, parseutils] import ../dist/checksums/src/checksums/md5 import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages, suggestsymdb when not defined(nimKochBootstrap): import ast2nif import "../dist/nimony/src/lib" / [nifstreams, bitabs] import typekeys when defined(nimPreviewSlimSystem): import std/assertions type SigHash* = distinct MD5Digest Iface* = object ## data we don't want to store directly in the ## ast.PSym type for s.kind == skModule module*: PSym ## module this "Iface" belongs to converters*: seq[PSym] patterns*: seq[PSym] pureEnums*: seq[PSym] interf: TStrTable interfHidden: TStrTable uniqueName*: Rope Operators* = object opNot*, opContains*, opLe*, opLt*, opAnd*, opOr*, opIsNil*, opEq*: PSym opAdd*, opSub*, opMul*, opDiv*, opLen*: PSym PipelinePass* = enum NonePass SemPass JSgenPass CgenPass NifgenPass EvalPass InterpreterPass GenDependPass Docgen2TexPass Docgen2JsonPass Docgen2Pass ModuleGraph* {.acyclic.} = ref object ifaces*: seq[Iface] ## indexed by int32 fileIdx typeInstCache*: Table[ItemId, seq[PType]] # A symbol's ItemId. procInstCache*: Table[ItemId, seq[PInstantiation]] # A symbol's ItemId. attachedOps*: array[TTypeAttachedOp, Table[ItemId, PSym]] # 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, PSym)]] # 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) enumToStringProcs*: Table[ItemId, PSym] emittedTypeInfo*: Table[string, FileIndex] packageSyms*: TStrTable deps*: IntSet # the dependency graph or potentially its transitive closure. importDeps*: Table[FileIndex, seq[FileIndex]] # explicit import module dependencies suggestMode*: bool # whether we are in nimsuggest mode or not. invalidTransitiveClosure: bool interactive*: bool withinSystem*: bool # in system.nim or a module imported by system.nim inclToMod*: Table[FileIndex, FileIndex] # mapping of include file to the # first module that included it importStack*: seq[FileIndex] # The current import stack. Used for detecting recursive # module dependencies. backend*: RootRef # minor hack so that a backend can extend this easily config*: ConfigRef cache*: IdentCache vm*: RootRef # unfortunately the 'vm' state is shared project-wise, this will # be clarified in later compiler implementations. repl*: RootRef # REPL state is shared project-wise. doStopCompile*: proc(): bool {.closure.} usageSym*: PSym # for nimsuggest owners*: seq[PSym] suggestSymbols*: SuggestSymbolDatabase suggestErrors*: Table[FileIndex, seq[Suggest]] methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization! bucketTable*: CountTable[ItemId] objectTree*: Table[ItemId, seq[tuple[depth: int, value: PType]]] methodsPerType*: Table[ItemId, seq[PSym]] dispatchers*: seq[PSym] systemModule*: PSym sysTypes*: array[TTypeKind, PType] compilerprocs*: TStrTable exposed*: TStrTable packageTypes*: TStrTable emptyNode*: PNode canonTypes*: Table[SigHash, PType] symBodyHashes*: Table[int, SigHash] # symId to digest mapping importModuleCallback*: proc (graph: ModuleGraph; m: PSym, fileIdx: FileIndex): PSym {.nimcall.} includeFileCallback*: proc (graph: ModuleGraph; m: PSym, fileIdx: FileIndex): PNode {.nimcall.} cacheSeqs*: Table[string, PNode] # state that is shared to support the 'macrocache' API; IC: implemented cacheCounters*: Table[string, BiggestInt] # IC: implemented cacheTables*: Table[string, BTree[string, PNode]] # IC: implemented passes*: seq[TPass] pipelinePass*: PipelinePass onDefinition*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.} onDefinitionResolveForward*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.} onUsage*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.} globalDestructors*: seq[PNode] strongSemCheck*: proc (graph: ModuleGraph; owner: PSym; body: PNode) {.nimcall.} compatibleProps*: proc (graph: ModuleGraph; formal, actual: PType): bool {.nimcall.} idgen*: IdGenerator operators*: Operators cachedFiles*: StringTableRef procGlobals*: seq[PNode] nifReplayActions*: Table[int32, seq[PNode]] # module position -> replay actions for NIF cachedMods: IntSet TPassContext* = object of RootObj # the pass's context idgen*: IdGenerator PPassContext* = ref TPassContext TPassOpen* = proc (graph: ModuleGraph; module: PSym; idgen: IdGenerator): PPassContext {.nimcall.} TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.} TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.} TPass* = tuple[open: TPassOpen, process: TPassProcess, close: TPassClose, isFrontend: bool] proc resetForBackend*(g: ModuleGraph) = g.compilerprocs = initStrTable() g.typeInstCache.clear() g.procInstCache.clear() for a in mitems(g.attachedOps): a.clear() g.methodsPerGenericType.clear() g.enumToStringProcs.clear() g.dispatchers.setLen(0) g.methodsPerType.clear() for a in mitems(g.loadedOps): a.clear() g.opsLog.setLen(0) const cb64 = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9a", "9b", "9c"] proc toBase64a(s: cstring, len: int): string = ## encodes `s` into base64 representation. result = newStringOfCap(((len + 2) div 3) * 4) result.add "__" var i = 0 while i < len - 2: let a = ord(s[i]) let b = ord(s[i+1]) let c = ord(s[i+2]) result.add cb64[a shr 2] result.add cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)] result.add cb64[((b and 0x0F) shl 2) or ((c and 0xC0) shr 6)] result.add cb64[c and 0x3F] inc(i, 3) if i < len-1: let a = ord(s[i]) let b = ord(s[i+1]) result.add cb64[a shr 2] result.add cb64[((a and 3) shl 4) or ((b and 0xF0) shr 4)] result.add cb64[((b and 0x0F) shl 2)] elif i < len: let a = ord(s[i]) result.add cb64[a shr 2] result.add cb64[(a and 3) shl 4] template interfSelect(iface: Iface, importHidden: bool): TStrTable = var ret = iface.interf.addr # without intermediate ptr, it creates a copy and compiler becomes 15x slower! if importHidden: ret = iface.interfHidden.addr ret[] template semtab(g: ModuleGraph, m: PSym): TStrTable = g.ifaces[m.position].interf template semtabAll*(g: ModuleGraph, m: PSym): TStrTable = g.ifaces[m.position].interfHidden proc initStrTables*(g: ModuleGraph, m: PSym) = semtab(g, m) = initStrTable() semtabAll(g, m) = initStrTable() proc strTableAdds*(g: ModuleGraph, m: PSym, s: PSym) = strTableAdd(semtab(g, m), s) strTableAdd(semtabAll(g, m), s) proc isCachedModule(g: ModuleGraph; module: int): bool {.inline.} = result = module in g.cachedMods proc isCachedModule*(g: ModuleGraph; m: PSym): bool {.inline.} = isCachedModule(g, m.position) type ModuleIter* = object modIndex: int ti: TIdentIter importHidden: bool proc initModuleIter*(mi: var ModuleIter; g: ModuleGraph; m: PSym; name: PIdent): PSym = assert m.kind == skModule mi.modIndex = m.position mi.importHidden = optImportHidden in m.options result = initIdentIter(mi.ti, g.ifaces[mi.modIndex].interfSelect(mi.importHidden), name) proc nextModuleIter*(mi: var ModuleIter; g: ModuleGraph): PSym = result = nextIdentIter(mi.ti, g.ifaces[mi.modIndex].interfSelect(mi.importHidden)) iterator allSyms*(g: ModuleGraph; m: PSym): PSym = let importHidden = optImportHidden in m.options for s in g.ifaces[m.position].interfSelect(importHidden).data: if s != nil: yield s proc someSym*(g: ModuleGraph; m: PSym; name: PIdent): PSym = let importHidden = optImportHidden in m.options result = strTableGet(g.ifaces[m.position].interfSelect(importHidden), name) proc someSymAmb*(g: ModuleGraph; m: PSym; name: PIdent; amb: var bool): PSym = let importHidden = optImportHidden in m.options var ti: TIdentIter = default(TIdentIter) result = initIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden), name) if result != nil and nextIdentIter(ti, g.ifaces[m.position].interfSelect(importHidden)) != nil: # another symbol exists with same name amb = true proc systemModuleSym*(g: ModuleGraph; name: PIdent): PSym = result = someSym(g, g.systemModule, name) iterator systemModuleSyms*(g: ModuleGraph; name: PIdent): PSym = var mi: ModuleIter = default(ModuleIter) var r = initModuleIter(mi, g, g.systemModule, name) while r != nil: yield r r = nextModuleIter(mi, g) iterator typeInstCacheItems*(g: ModuleGraph; s: PSym): PType = if g.typeInstCache.contains(s.itemId): let x = addr(g.typeInstCache[s.itemId]) for t in mitems(x[]): yield t iterator procInstCacheItems*(g: ModuleGraph; s: PSym): PInstantiation = if g.procInstCache.contains(s.itemId): let x = addr(g.procInstCache[s.itemId]) for t in mitems(x[]): yield t proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym = ## returns the requested attached operation for type `t`. Can return nil ## if no such operation exists. if g.attachedOps[op].contains(t.itemId): result = 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] = 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] = 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] = value proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) {.inline.} = discard iterator getDispatchers*(g: ModuleGraph): PSym = for i in g.dispatchers.mitems: yield i proc addDispatchers*(g: ModuleGraph, value: PSym) = # TODO: add it for packed modules g.dispatchers.add value iterator resolveLazySymSeq(g: ModuleGraph, list: var seq[PSym]): PSym = for it in list.mitems: yield it proc setMethodsPerType*(g: ModuleGraph; id: ItemId, methods: seq[PSym]) = # 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]: yield it proc getToStringProc*(g: ModuleGraph; t: PType): PSym = result = g.enumToStringProcs[t.itemId] assert result != nil proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) = g.enumToStringProcs[t.itemId] = 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): for it in mitems g.methodsPerGenericType[t.itemId]: yield (it[0], it[1]) proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) = g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, 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) result = op != nil and sfError in op.flags proc copyTypeProps*(g: ModuleGraph; module: int; dest, src: PType) = for k in low(TTypeAttachedOp)..high(TTypeAttachedOp): let op = getAttachedOp(g, src, k) if op != nil: setAttachedOp(g, module, dest, k, op) proc loadCompilerProc*(g: ModuleGraph; name: string): PSym = result = nil if g.config.symbolFiles == disabledSf and optWithinConfigSystem notin g.config.globalOptions: # For NIF-based compilation, search in loaded NIF modules when not defined(nimKochBootstrap): # Try to resolve from NIF for both cmdNifC and cmdM (which uses NIF files) if g.config.cmd in {cmdNifC, cmdM}: # First try system module (most compilerprocs are there) let systemFileIdx = g.config.m.systemFileIdx if systemFileIdx != InvalidFileIdx and not g.withinSystem: # Only try to load from NIF if the file exists (it may not during initial ic build) 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..= 0: start += pkgs2.len start += skipUntil(rel2, {'/'}, start) if start+1 < rel2.len: rel = "pkg/" & rel2[start+1..= g.ifaces.len: setLen(g.ifaces, m.position + 1) if g.ifaces[m.position].module == nil: g.ifaces[m.position] = Iface(module: m, converters: @[], patterns: @[], uniqueName: rope(uniqueModuleName(g.config, m))) initStrTables(g, m) proc registerModuleById*(g: ModuleGraph; m: FileIndex) = registerModule(g, g.ifaces[int m].module) proc initOperators*(g: ModuleGraph): Operators = # These are safe for IC. # Public because it's used by DrNim. result = Operators( opLe: createMagic(g, "<=", mLeI), opLt: createMagic(g, "<", mLtI), opAnd: createMagic(g, "and", mAnd), opOr: createMagic(g, "or", mOr), opIsNil: createMagic(g, "isnil", mIsNil), opEq: createMagic(g, "==", mEqI), opAdd: createMagic(g, "+", mAddI), opSub: createMagic(g, "-", mSubI), opMul: createMagic(g, "*", mMulI), opDiv: createMagic(g, "div", mDivI), opLen: createMagic(g, "len", mLengthSeq), opNot: createMagic(g, "not", mNot), opContains: createMagic(g, "contains", mInSet) ) proc initModuleGraphFields(result: ModuleGraph) = # A module ID of -1 means that the symbol is not attached to a module at all, # but to the module graph: result.idgen = IdGenerator(module: -1'i32, symId: 0'i32, typeId: 0'i32) result.packageSyms = initStrTable() result.deps = initIntSet() result.importDeps = initTable[FileIndex, seq[FileIndex]]() result.ifaces = @[] result.importStack = @[] result.inclToMod = initTable[FileIndex, FileIndex]() result.owners = @[] result.suggestSymbols = initTable[FileIndex, SuggestFileSymbolDatabase]() result.suggestErrors = initTable[FileIndex, seq[Suggest]]() result.methods = @[] result.compilerprocs = initStrTable() result.exposed = initStrTable() result.packageTypes = initStrTable() result.emptyNode = newNode(nkEmpty) result.cacheSeqs = initTable[string, PNode]() result.cacheCounters = initTable[string, BiggestInt]() result.cacheTables = initTable[string, BTree[string, PNode]]() result.canonTypes = initTable[SigHash, PType]() result.symBodyHashes = initTable[int, SigHash]() result.operators = initOperators(result) result.emittedTypeInfo = initTable[string, FileIndex]() result.cachedFiles = newStringTable() result.cachedMods = initIntSet() proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph = result = ModuleGraph() result.config = config result.cache = cache initModuleGraphFields(result) ast.setupProgram(config, cache) proc resetAllModules*(g: ModuleGraph) = g.packageSyms = initStrTable() g.deps = initIntSet() g.ifaces = @[] g.importStack = @[] g.inclToMod = initTable[FileIndex, FileIndex]() g.usageSym = nil g.owners = @[] g.methods = @[] g.compilerprocs = initStrTable() g.exposed = initStrTable() initModuleGraphFields(g) proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym = if fileIdx.int32 >= 0 and fileIdx.int32 < g.ifaces.len: result = g.ifaces[fileIdx.int32].module else: result = nil proc moduleOpenForCodegen*(g: ModuleGraph; m: FileIndex): bool {.inline.} = result = true proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b proc addDep*(g: ModuleGraph; m: PSym, dep: FileIndex) = assert m.position == m.info.fileIndex.int32 if g.suggestMode: g.deps.incl m.position.dependsOn(dep.int) # we compute the transitive closure later when querying the graph lazily. # this improves efficiency quite a lot: #invalidTransitiveClosure = true proc addIncludeDep*(g: ModuleGraph; module, includeFile: FileIndex) = discard hasKeyOrPut(g.inclToMod, includeFile, module) proc parentModule*(g: ModuleGraph; fileIdx: FileIndex): FileIndex = ## returns 'fileIdx' if the file belonging to this index is ## directly used as a module or else the module that first ## references this include file. if fileIdx.int32 >= 0 and fileIdx.int32 < g.ifaces.len and g.ifaces[fileIdx.int32].module != nil: result = fileIdx else: result = g.inclToMod.getOrDefault(fileIdx) proc transitiveClosure(g: var IntSet; n: int) = # warshall's algorithm for k in 0..