123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- #
- #
- # 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 Sqlite database.
- ##
- ## The caching of modules is critical for 'nimsuggest' and is tricky to get
- ## right. If module E is being edited, we need autocompletion (and type
- ## checking) for E but we don't want to recompile depending
- ## modules right away for faster turnaround times. Instead we mark the module's
- ## dependencies as 'dirty'. Let D be a dependency of E. If D is dirty, we
- ## need to recompile it and all of its dependencies that are marked as 'dirty'.
- ## 'nimsuggest sug' actually is invoked for the file being edited so we know
- ## its content changed and there is no need to compute any checksums.
- ## Instead of a recursive algorithm, we use an iterative algorithm:
- ##
- ## - If a module gets recompiled, its dependencies need to be updated.
- ## - Its dependent module stays the same.
- ##
- import ast, intsets, tables, options, lineinfos, hashes, idents,
- incremental, btrees, md5
- type
- SigHash* = distinct MD5Digest
- ModuleGraph* = ref object
- modules*: seq[PSym] ## indexed by int32 fileIdx
- 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
- 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.
- doStopCompile*: proc(): bool {.closure.}
- usageSym*: PSym # for nimsuggest
- owners*: seq[PSym]
- methods*: seq[tuple[methods: seq[PSym], dispatcher: PSym]] # needs serialization!
- systemModule*: PSym
- sysTypes*: array[TTypeKind, PType]
- compilerprocs*: TStrTable
- exposed*: TStrTable
- intTypeCache*: array[-5..64, PType]
- opContains*, opNot*: PSym
- emptyNode*: PNode
- incr*: IncrementalCtx
- 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.}
- recordStmt*: proc (graph: ModuleGraph; m: PSym; n: PNode) {.nimcall.}
- cacheSeqs*: Table[string, PNode] # state that is shared to support the 'macrocache' API
- cacheCounters*: Table[string, BiggestInt]
- cacheTables*: Table[string, BTree[string, PNode]]
- passes*: seq[TPass]
- 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.}
- TPassContext* = object of RootObj # the pass's context
- PPassContext* = ref TPassContext
- TPassOpen* = proc (graph: ModuleGraph; module: PSym): 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]
- 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]
- proc `$`*(u: SigHash): string =
- toBase64a(cast[cstring](unsafeAddr u), sizeof(u))
- proc `==`*(a, b: SigHash): bool =
- result = equalMem(unsafeAddr a, unsafeAddr b, sizeof(a))
- proc hash*(u: SigHash): Hash =
- result = 0
- for x in 0..3:
- result = (result shl 8) or u.MD5Digest[x].int
- proc hash*(x: FileIndex): Hash {.borrow.}
- when defined(nimfind):
- template onUse*(info: TLineInfo; s: PSym) =
- when compiles(c.c.graph):
- if c.c.graph.onUsage != nil: c.c.graph.onUsage(c.c.graph, s, info)
- else:
- if c.graph.onUsage != nil: c.graph.onUsage(c.graph, s, info)
- template onDef*(info: TLineInfo; s: PSym) =
- when compiles(c.c.graph):
- if c.c.graph.onDefinition != nil: c.c.graph.onDefinition(c.c.graph, s, info)
- else:
- if c.graph.onDefinition != nil: c.graph.onDefinition(c.graph, s, info)
- template onDefResolveForward*(info: TLineInfo; s: PSym) =
- when compiles(c.c.graph):
- if c.c.graph.onDefinitionResolveForward != nil:
- c.c.graph.onDefinitionResolveForward(c.c.graph, s, info)
- else:
- if c.graph.onDefinitionResolveForward != nil:
- c.graph.onDefinitionResolveForward(c.graph, s, info)
- else:
- template onUse*(info: TLineInfo; s: PSym) = discard
- template onDef*(info: TLineInfo; s: PSym) = discard
- template onDefResolveForward*(info: TLineInfo; s: PSym) = discard
- proc stopCompile*(g: ModuleGraph): bool {.inline.} =
- result = g.doStopCompile != nil and g.doStopCompile()
- proc createMagic*(g: ModuleGraph; name: string, m: TMagic): PSym =
- result = newSym(skProc, getIdent(g.cache, name), nil, unknownLineInfo, {})
- result.magic = m
- result.flags = {sfNeverRaises}
- proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
- result = ModuleGraph()
- initStrTable(result.packageSyms)
- result.deps = initIntSet()
- result.importDeps = initTable[FileIndex, seq[FileIndex]]()
- result.modules = @[]
- result.importStack = @[]
- result.inclToMod = initTable[FileIndex, FileIndex]()
- result.config = config
- result.cache = cache
- result.owners = @[]
- result.methods = @[]
- initStrTable(result.compilerprocs)
- initStrTable(result.exposed)
- result.opNot = createMagic(result, "not", mNot)
- result.opContains = createMagic(result, "contains", mInSet)
- result.emptyNode = newNode(nkEmpty)
- init(result.incr)
- result.recordStmt = proc (graph: ModuleGraph; m: PSym; n: PNode) {.nimcall.} =
- discard
- 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]()
- proc resetAllModules*(g: ModuleGraph) =
- initStrTable(g.packageSyms)
- g.deps = initIntSet()
- g.modules = @[]
- g.importStack = @[]
- g.inclToMod = initTable[FileIndex, FileIndex]()
- g.usageSym = nil
- g.owners = @[]
- g.methods = @[]
- initStrTable(g.compilerprocs)
- initStrTable(g.exposed)
- proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
- if fileIdx.int32 >= 0 and fileIdx.int32 < g.modules.len:
- result = g.modules[fileIdx.int32]
- 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
- addModuleDep(g.incr, g.config, m.info.fileIndex, dep, isIncludeFile = false)
- 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) =
- addModuleDep(g.incr, g.config, module, includeFile, isIncludeFile = true)
- 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.modules.len and g.modules[fileIdx.int32] != nil:
- result = fileIdx
- else:
- result = g.inclToMod.getOrDefault(fileIdx)
- proc transitiveClosure(g: var IntSet; n: int) =
- # warshall's algorithm
- for k in 0..<n:
- for i in 0..<n:
- for j in 0..<n:
- if i != j and not g.contains(i.dependsOn(j)):
- if g.contains(i.dependsOn(k)) and g.contains(k.dependsOn(j)):
- g.incl i.dependsOn(j)
- proc markDirty*(g: ModuleGraph; fileIdx: FileIndex) =
- let m = g.getModule fileIdx
- if m != nil: incl m.flags, sfDirty
- proc markClientsDirty*(g: ModuleGraph; fileIdx: FileIndex) =
- # we need to mark its dependent modules D as dirty right away because after
- # nimsuggest is done with this module, the module's dirty flag will be
- # cleared but D still needs to be remembered as 'dirty'.
- if g.invalidTransitiveClosure:
- g.invalidTransitiveClosure = false
- transitiveClosure(g.deps, g.modules.len)
- # every module that *depends* on this file is also dirty:
- for i in 0i32..<g.modules.len.int32:
- let m = g.modules[i]
- if m != nil and g.deps.contains(i.dependsOn(fileIdx.int)):
- incl m.flags, sfDirty
- proc isDirty*(g: ModuleGraph; m: PSym): bool =
- result = g.suggestMode and sfDirty in m.flags
|