modulegraphs.nim 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2017 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements the module graph data structure. The module graph
  10. ## represents a complete Nim project. Single modules can either be kept in RAM
  11. ## or stored in a Sqlite database.
  12. ##
  13. ## The caching of modules is critical for 'nimsuggest' and is tricky to get
  14. ## right. If module E is being edited, we need autocompletion (and type
  15. ## checking) for E but we don't want to recompile depending
  16. ## modules right away for faster turnaround times. Instead we mark the module's
  17. ## dependencies as 'dirty'. Let D be a dependency of E. If D is dirty, we
  18. ## need to recompile it and all of its dependencies that are marked as 'dirty'.
  19. ## 'nimsuggest sug' actually is invoked for the file being edited so we know
  20. ## its content changed and there is no need to compute any checksums.
  21. ## Instead of a recursive algorithm, we use an iterative algorithm:
  22. ##
  23. ## - If a module gets recompiled, its dependencies need to be updated.
  24. ## - Its dependent module stays the same.
  25. ##
  26. import ast, intsets, tables, options, lineinfos, hashes, idents,
  27. incremental, btrees
  28. type
  29. ModuleGraph* = ref object
  30. modules*: seq[PSym] ## indexed by int32 fileIdx
  31. packageSyms*: TStrTable
  32. deps*: IntSet # the dependency graph or potentially its transitive closure.
  33. suggestMode*: bool # whether we are in nimsuggest mode or not.
  34. invalidTransitiveClosure: bool
  35. inclToMod*: Table[FileIndex, FileIndex] # mapping of include file to the
  36. # first module that included it
  37. importStack*: seq[FileIndex] # The current import stack. Used for detecting recursive
  38. # module dependencies.
  39. backend*: RootRef # minor hack so that a backend can extend this easily
  40. config*: ConfigRef
  41. cache*: IdentCache
  42. vm*: RootRef # unfortunately the 'vm' state is shared project-wise, this will
  43. # be clarified in later compiler implementations.
  44. doStopCompile*: proc(): bool {.closure.}
  45. usageSym*: PSym # for nimsuggest
  46. owners*: seq[PSym]
  47. methods*: seq[tuple[methods: TSymSeq, dispatcher: PSym]] # needs serialization!
  48. systemModule*: PSym
  49. sysTypes*: array[TTypeKind, PType]
  50. compilerprocs*: TStrTable
  51. exposed*: TStrTable
  52. intTypeCache*: array[-5..64, PType]
  53. opContains*, opNot*: PSym
  54. emptyNode*: PNode
  55. incr*: IncrementalCtx
  56. importModuleCallback*: proc (graph: ModuleGraph; m: PSym, fileIdx: FileIndex): PSym {.nimcall.}
  57. includeFileCallback*: proc (graph: ModuleGraph; m: PSym, fileIdx: FileIndex): PNode {.nimcall.}
  58. recordStmt*: proc (graph: ModuleGraph; m: PSym; n: PNode) {.nimcall.}
  59. cacheSeqs*: Table[string, PNode] # state that is shared to suppor the 'macrocache' API
  60. cacheCounters*: Table[string, BiggestInt]
  61. cacheTables*: Table[string, BTree[string, PNode]]
  62. passes*: seq[TPass]
  63. TPassContext* = object of RootObj # the pass's context
  64. PPassContext* = ref TPassContext
  65. TPassOpen* = proc (graph: ModuleGraph; module: PSym): PPassContext {.nimcall.}
  66. TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.}
  67. TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.}
  68. TPass* = tuple[open: TPassOpen,
  69. process: TPassProcess,
  70. close: TPassClose,
  71. isFrontend: bool]
  72. proc hash*(x: FileIndex): Hash {.borrow.}
  73. proc stopCompile*(g: ModuleGraph): bool {.inline.} =
  74. result = g.doStopCompile != nil and g.doStopCompile()
  75. proc createMagic*(g: ModuleGraph; name: string, m: TMagic): PSym =
  76. result = newSym(skProc, getIdent(g.cache, name), nil, unknownLineInfo(), {})
  77. result.magic = m
  78. proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
  79. result = ModuleGraph()
  80. initStrTable(result.packageSyms)
  81. result.deps = initIntSet()
  82. result.modules = @[]
  83. result.importStack = @[]
  84. result.inclToMod = initTable[FileIndex, FileIndex]()
  85. result.config = config
  86. result.cache = cache
  87. result.owners = @[]
  88. result.methods = @[]
  89. initStrTable(result.compilerprocs)
  90. initStrTable(result.exposed)
  91. result.opNot = createMagic(result, "not", mNot)
  92. result.opContains = createMagic(result, "contains", mInSet)
  93. result.emptyNode = newNode(nkEmpty)
  94. init(result.incr)
  95. result.recordStmt = proc (graph: ModuleGraph; m: PSym; n: PNode) {.nimcall.} =
  96. discard
  97. result.cacheSeqs = initTable[string, PNode]()
  98. result.cacheCounters = initTable[string, BiggestInt]()
  99. result.cacheTables = initTable[string, BTree[string, PNode]]()
  100. proc resetAllModules*(g: ModuleGraph) =
  101. initStrTable(g.packageSyms)
  102. g.deps = initIntSet()
  103. g.modules = @[]
  104. g.importStack = @[]
  105. g.inclToMod = initTable[FileIndex, FileIndex]()
  106. g.usageSym = nil
  107. g.owners = @[]
  108. g.methods = @[]
  109. initStrTable(g.compilerprocs)
  110. initStrTable(g.exposed)
  111. proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
  112. if fileIdx.int32 >= 0 and fileIdx.int32 < g.modules.len:
  113. result = g.modules[fileIdx.int32]
  114. proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b
  115. proc addDep*(g: ModuleGraph; m: PSym, dep: FileIndex) =
  116. assert m.position == m.info.fileIndex.int32
  117. addModuleDep(g.incr, g.config, m.info.fileIndex, dep, isIncludeFile = false)
  118. if g.suggestMode:
  119. g.deps.incl m.position.dependsOn(dep.int)
  120. # we compute the transitive closure later when quering the graph lazily.
  121. # this improves efficiency quite a lot:
  122. #invalidTransitiveClosure = true
  123. proc addIncludeDep*(g: ModuleGraph; module, includeFile: FileIndex) =
  124. addModuleDep(g.incr, g.config, module, includeFile, isIncludeFile = true)
  125. discard hasKeyOrPut(g.inclToMod, includeFile, module)
  126. proc parentModule*(g: ModuleGraph; fileIdx: FileIndex): FileIndex =
  127. ## returns 'fileIdx' if the file belonging to this index is
  128. ## directly used as a module or else the module that first
  129. ## references this include file.
  130. if fileIdx.int32 >= 0 and fileIdx.int32 < g.modules.len and g.modules[fileIdx.int32] != nil:
  131. result = fileIdx
  132. else:
  133. result = g.inclToMod.getOrDefault(fileIdx)
  134. proc transitiveClosure(g: var IntSet; n: int) =
  135. # warshall's algorithm
  136. for k in 0..<n:
  137. for i in 0..<n:
  138. for j in 0..<n:
  139. if i != j and not g.contains(i.dependsOn(j)):
  140. if g.contains(i.dependsOn(k)) and g.contains(k.dependsOn(j)):
  141. g.incl i.dependsOn(j)
  142. proc markDirty*(g: ModuleGraph; fileIdx: FileIndex) =
  143. let m = g.getModule fileIdx
  144. if m != nil: incl m.flags, sfDirty
  145. proc markClientsDirty*(g: ModuleGraph; fileIdx: FileIndex) =
  146. # we need to mark its dependent modules D as dirty right away because after
  147. # nimsuggest is done with this module, the module's dirty flag will be
  148. # cleared but D still needs to be remembered as 'dirty'.
  149. if g.invalidTransitiveClosure:
  150. g.invalidTransitiveClosure = false
  151. transitiveClosure(g.deps, g.modules.len)
  152. # every module that *depends* on this file is also dirty:
  153. for i in 0i32..<g.modules.len.int32:
  154. let m = g.modules[i]
  155. if m != nil and g.deps.contains(i.dependsOn(fileIdx.int)):
  156. incl m.flags, sfDirty
  157. proc isDirty*(g: ModuleGraph; m: PSym): bool =
  158. result = g.suggestMode and sfDirty in m.flags