modulegraphs.nim 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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 ROD file. The ROD file mechanism is not yet integrated here.
  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
  27. type
  28. ModuleGraph* = ref object
  29. modules*: seq[PSym] ## indexed by int32 fileIdx
  30. packageSyms*: TStrTable
  31. deps*: IntSet # the dependency graph or potentially its transitive closure.
  32. suggestMode*: bool # whether we are in nimsuggest mode or not.
  33. invalidTransitiveClosure: bool
  34. inclToMod*: Table[int32, int32] # mapping of include file to the
  35. # first module that included it
  36. importStack*: seq[int32] # The current import stack. Used for detecting recursive
  37. # module dependencies.
  38. backend*: RootRef # minor hack so that a backend can extend this easily
  39. config*: ConfigRef
  40. doStopCompile*: proc(): bool {.closure.}
  41. usageSym*: PSym # for nimsuggest
  42. owners*: seq[PSym]
  43. methods*: seq[tuple[methods: TSymSeq, dispatcher: PSym]]
  44. {.this: g.}
  45. proc stopCompile*(g: ModuleGraph): bool {.inline.} =
  46. result = doStopCompile != nil and doStopCompile()
  47. proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph =
  48. result = ModuleGraph()
  49. initStrTable(result.packageSyms)
  50. result.deps = initIntSet()
  51. result.modules = @[]
  52. result.importStack = @[]
  53. result.inclToMod = initTable[int32, int32]()
  54. if config.isNil:
  55. result.config = newConfigRef()
  56. else:
  57. result.config = config
  58. result.owners = @[]
  59. result.methods = @[]
  60. proc resetAllModules*(g: ModuleGraph) =
  61. initStrTable(packageSyms)
  62. deps = initIntSet()
  63. modules = @[]
  64. importStack = @[]
  65. inclToMod = initTable[int32, int32]()
  66. usageSym = nil
  67. owners = @[]
  68. methods = @[]
  69. proc getModule*(g: ModuleGraph; fileIdx: int32): PSym =
  70. if fileIdx >= 0 and fileIdx < modules.len:
  71. result = modules[fileIdx]
  72. proc dependsOn(a, b: int): int {.inline.} = (a shl 15) + b
  73. proc addDep*(g: ModuleGraph; m: PSym, dep: int32) =
  74. if suggestMode:
  75. deps.incl m.position.dependsOn(dep)
  76. # we compute the transitive closure later when quering the graph lazily.
  77. # this improve efficiency quite a lot:
  78. #invalidTransitiveClosure = true
  79. proc addIncludeDep*(g: ModuleGraph; module, includeFile: int32) =
  80. discard hasKeyOrPut(inclToMod, includeFile, module)
  81. proc parentModule*(g: ModuleGraph; fileIdx: int32): int32 =
  82. ## returns 'fileIdx' if the file belonging to this index is
  83. ## directly used as a module or else the module that first
  84. ## references this include file.
  85. if fileIdx >= 0 and fileIdx < modules.len and modules[fileIdx] != nil:
  86. result = fileIdx
  87. else:
  88. result = inclToMod.getOrDefault(fileIdx)
  89. proc transitiveClosure(g: var IntSet; n: int) =
  90. # warshall's algorithm
  91. for k in 0..<n:
  92. for i in 0..<n:
  93. for j in 0..<n:
  94. if i != j and not g.contains(i.dependsOn(j)):
  95. if g.contains(i.dependsOn(k)) and g.contains(k.dependsOn(j)):
  96. g.incl i.dependsOn(j)
  97. proc markDirty*(g: ModuleGraph; fileIdx: int32) =
  98. let m = getModule fileIdx
  99. if m != nil: incl m.flags, sfDirty
  100. proc markClientsDirty*(g: ModuleGraph; fileIdx: int32) =
  101. # we need to mark its dependent modules D as dirty right away because after
  102. # nimsuggest is done with this module, the module's dirty flag will be
  103. # cleared but D still needs to be remembered as 'dirty'.
  104. if invalidTransitiveClosure:
  105. invalidTransitiveClosure = false
  106. transitiveClosure(deps, modules.len)
  107. # every module that *depends* on this file is also dirty:
  108. for i in 0i32..<modules.len.int32:
  109. let m = modules[i]
  110. if m != nil and deps.contains(i.dependsOn(fileIdx)):
  111. incl m.flags, sfDirty
  112. proc isDirty*(g: ModuleGraph; m: PSym): bool =
  113. result = suggestMode and sfDirty in m.flags