reorder.nim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. import
  2. intsets, ast, idents, algorithm, renderer, strutils,
  3. msgs, modulegraphs, syntaxes, options, modulepaths,
  4. lineinfos
  5. type
  6. DepN = ref object
  7. pnode: PNode
  8. id, idx, lowLink: int
  9. onStack: bool
  10. kids: seq[DepN]
  11. hAQ, hIS, hB, hCmd: int
  12. when defined(debugReorder):
  13. expls: seq[string]
  14. DepG = seq[DepN]
  15. when defined(debugReorder):
  16. var idNames = newTable[int, string]()
  17. proc newDepN(id: int, pnode: PNode): DepN =
  18. new(result)
  19. result.id = id
  20. result.pnode = pnode
  21. result.idx = -1
  22. result.lowLink = -1
  23. result.onStack = false
  24. result.kids = @[]
  25. result.hAQ = -1
  26. result.hIS = -1
  27. result.hB = -1
  28. result.hCmd = -1
  29. when defined(debugReorder):
  30. result.expls = @[]
  31. proc accQuoted(cache: IdentCache; n: PNode): PIdent =
  32. var id = ""
  33. for i in 0..<n.len:
  34. let ident = n[i].getPIdent
  35. if ident != nil: id.add(ident.s)
  36. result = getIdent(cache, id)
  37. proc addDecl(cache: IdentCache; n: PNode; declares: var IntSet) =
  38. case n.kind
  39. of nkPostfix: addDecl(cache, n[1], declares)
  40. of nkPragmaExpr: addDecl(cache, n[0], declares)
  41. of nkIdent:
  42. declares.incl n.ident.id
  43. when defined(debugReorder):
  44. idNames[n.ident.id] = n.ident.s
  45. of nkSym:
  46. declares.incl n.sym.name.id
  47. when defined(debugReorder):
  48. idNames[n.sym.name.id] = n.sym.name.s
  49. of nkAccQuoted:
  50. let a = accQuoted(cache, n)
  51. declares.incl a.id
  52. when defined(debugReorder):
  53. idNames[a.id] = a.s
  54. of nkEnumFieldDef:
  55. addDecl(cache, n[0], declares)
  56. else: discard
  57. proc computeDeps(cache: IdentCache; n: PNode, declares, uses: var IntSet; topLevel: bool) =
  58. template deps(n) = computeDeps(cache, n, declares, uses, false)
  59. template decl(n) =
  60. if topLevel: addDecl(cache, n, declares)
  61. case n.kind
  62. of procDefs, nkMacroDef, nkTemplateDef:
  63. decl(n[0])
  64. for i in 1..bodyPos: deps(n[i])
  65. of nkLetSection, nkVarSection, nkUsingStmt:
  66. for a in n:
  67. if a.kind in {nkIdentDefs, nkVarTuple}:
  68. for j in 0..<a.len-2: decl(a[j])
  69. for j in a.len-2..<a.len: deps(a[j])
  70. of nkConstSection, nkTypeSection:
  71. for a in n:
  72. if a.len >= 3:
  73. decl(a[0])
  74. for i in 1..<a.len:
  75. if a[i].kind == nkEnumTy:
  76. # declare enum members
  77. for b in a[i]:
  78. decl(b)
  79. else:
  80. deps(a[i])
  81. of nkIdentDefs:
  82. for i in 1..<n.len: # avoid members identifiers in object definition
  83. deps(n[i])
  84. of nkIdent: uses.incl n.ident.id
  85. of nkSym: uses.incl n.sym.name.id
  86. of nkAccQuoted: uses.incl accQuoted(cache, n).id
  87. of nkOpenSymChoice, nkClosedSymChoice:
  88. uses.incl n[0].sym.name.id
  89. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
  90. for i in 0..<n.len: computeDeps(cache, n[i], declares, uses, topLevel)
  91. of nkPragma:
  92. let a = n[0]
  93. if a.kind == nkExprColonExpr and a[0].kind == nkIdent and a[0].ident.s == "pragma":
  94. # user defined pragma
  95. decl(a[1])
  96. else:
  97. for i in 0..<n.safeLen: deps(n[i])
  98. of nkMixinStmt, nkBindStmt: discard
  99. else:
  100. # XXX: for callables, this technically adds the return type dep before args
  101. for i in 0..<n.safeLen: deps(n[i])
  102. proc hasIncludes(n:PNode): bool =
  103. for a in n:
  104. if a.kind == nkIncludeStmt:
  105. return true
  106. proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PNode =
  107. result = syntaxes.parseFile(fileIdx, graph.cache, graph.config)
  108. graph.addDep(s, fileIdx)
  109. graph.addIncludeDep(FileIndex s.position, fileIdx)
  110. proc expandIncludes(graph: ModuleGraph, module: PSym, n: PNode,
  111. modulePath: string, includedFiles: var IntSet): PNode =
  112. # Parses includes and injects them in the current tree
  113. if not n.hasIncludes:
  114. return n
  115. result = newNodeI(nkStmtList, n.info)
  116. for a in n:
  117. if a.kind == nkIncludeStmt:
  118. for i in 0..<a.len:
  119. var f = checkModuleName(graph.config, a[i])
  120. if f != InvalidFileIdx:
  121. if containsOrIncl(includedFiles, f.int):
  122. localError(graph.config, a.info, "recursive dependency: '$1'" %
  123. toMsgFilename(graph.config, f))
  124. else:
  125. let nn = includeModule(graph, module, f)
  126. let nnn = expandIncludes(graph, module, nn, modulePath,
  127. includedFiles)
  128. excl(includedFiles, f.int)
  129. for b in nnn:
  130. result.add b
  131. else:
  132. result.add a
  133. proc splitSections(n: PNode): PNode =
  134. # Split typeSections and ConstSections into
  135. # sections that contain only one definition
  136. assert n.kind == nkStmtList
  137. result = newNodeI(nkStmtList, n.info)
  138. for a in n:
  139. if a.kind in {nkTypeSection, nkConstSection} and a.len > 1:
  140. for b in a:
  141. var s = newNode(a.kind)
  142. s.info = b.info
  143. s.add b
  144. result.add s
  145. else:
  146. result.add a
  147. proc haveSameKind(dns: seq[DepN]): bool =
  148. # Check if all the nodes in a strongly connected
  149. # component have the same kind
  150. result = true
  151. let kind = dns[0].pnode.kind
  152. for dn in dns:
  153. if dn.pnode.kind != kind:
  154. return false
  155. proc mergeSections(conf: ConfigRef; comps: seq[seq[DepN]], res: PNode) =
  156. # Merges typeSections and ConstSections when they form
  157. # a strong component (ex: circular type definition)
  158. for c in comps:
  159. assert c.len > 0
  160. if c.len == 1:
  161. res.add c[0].pnode
  162. else:
  163. let fstn = c[0].pnode
  164. let kind = fstn.kind
  165. # always return to the original order when we got circular dependencies
  166. let cs = c.sortedByIt(it.id)
  167. if kind in {nkTypeSection, nkConstSection} and haveSameKind(cs):
  168. # Circular dependency between type or const sections, we just
  169. # need to merge them
  170. var sn = newNode(kind)
  171. for dn in cs:
  172. sn.add dn.pnode[0]
  173. res.add sn
  174. else:
  175. # Problematic circular dependency, we arrange the nodes into
  176. # their original relative order and make sure to re-merge
  177. # consecutive type and const sections
  178. var wmsg = "Circular dependency detected. `codeReordering` pragma may not be able to" &
  179. " reorder some nodes properly"
  180. when defined(debugReorder):
  181. wmsg &= ":\n"
  182. for i in 0..<cs.len-1:
  183. for j in i..<cs.len:
  184. for ci in 0..<cs[i].kids.len:
  185. if cs[i].kids[ci].id == cs[j].id:
  186. wmsg &= "line " & $cs[i].pnode.info.line &
  187. " depends on line " & $cs[j].pnode.info.line &
  188. ": " & cs[i].expls[ci] & "\n"
  189. for j in 0..<cs.len-1:
  190. for ci in 0..<cs[^1].kids.len:
  191. if cs[^1].kids[ci].id == cs[j].id:
  192. wmsg &= "line " & $cs[^1].pnode.info.line &
  193. " depends on line " & $cs[j].pnode.info.line &
  194. ": " & cs[^1].expls[ci] & "\n"
  195. message(conf, cs[0].pnode.info, warnUser, wmsg)
  196. var i = 0
  197. while i < cs.len:
  198. if cs[i].pnode.kind in {nkTypeSection, nkConstSection}:
  199. let ckind = cs[i].pnode.kind
  200. var sn = newNode(ckind)
  201. sn.add cs[i].pnode[0]
  202. inc i
  203. while i < cs.len and cs[i].pnode.kind == ckind:
  204. sn.add cs[i].pnode[0]
  205. inc i
  206. res.add sn
  207. else:
  208. res.add cs[i].pnode
  209. inc i
  210. proc hasImportStmt(n: PNode): bool =
  211. # Checks if the node is an import statement or
  212. # i it contains one
  213. case n.kind
  214. of nkImportStmt, nkFromStmt, nkImportExceptStmt:
  215. return true
  216. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
  217. for a in n:
  218. if a.hasImportStmt:
  219. return true
  220. else:
  221. result = false
  222. proc hasImportStmt(n: DepN): bool =
  223. if n.hIS < 0:
  224. n.hIS = ord(n.pnode.hasImportStmt)
  225. result = bool(n.hIS)
  226. proc hasCommand(n: PNode): bool =
  227. # Checks if the node is a command or a call
  228. # or if it contains one
  229. case n.kind
  230. of nkCommand, nkCall:
  231. result = true
  232. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse,
  233. nkStaticStmt, nkLetSection, nkConstSection, nkVarSection,
  234. nkIdentDefs:
  235. for a in n:
  236. if a.hasCommand:
  237. return true
  238. else:
  239. return false
  240. proc hasCommand(n: DepN): bool =
  241. if n.hCmd < 0:
  242. n.hCmd = ord(n.pnode.hasCommand)
  243. result = bool(n.hCmd)
  244. proc hasAccQuoted(n: PNode): bool =
  245. if n.kind == nkAccQuoted:
  246. return true
  247. for a in n:
  248. if hasAccQuoted(a):
  249. return true
  250. const extendedProcDefs = procDefs + {nkMacroDef, nkTemplateDef}
  251. proc hasAccQuotedDef(n: PNode): bool =
  252. # Checks if the node is a function, macro, template ...
  253. # with a quoted name or if it contains one
  254. case n.kind
  255. of extendedProcDefs:
  256. result = n[0].hasAccQuoted
  257. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
  258. for a in n:
  259. if hasAccQuotedDef(a):
  260. return true
  261. else:
  262. result = false
  263. proc hasAccQuotedDef(n: DepN): bool =
  264. if n.hAQ < 0:
  265. n.hAQ = ord(n.pnode.hasAccQuotedDef)
  266. result = bool(n.hAQ)
  267. proc hasBody(n: PNode): bool =
  268. # Checks if the node is a function, macro, template ...
  269. # with a body or if it contains one
  270. case n.kind
  271. of nkCommand, nkCall:
  272. result = true
  273. of extendedProcDefs:
  274. result = n[^1].kind == nkStmtList
  275. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
  276. for a in n:
  277. if a.hasBody:
  278. return true
  279. else:
  280. result = false
  281. proc hasBody(n: DepN): bool =
  282. if n.hB < 0:
  283. n.hB = ord(n.pnode.hasBody)
  284. result = bool(n.hB)
  285. proc intersects(s1, s2: IntSet): bool =
  286. for a in s1:
  287. if s2.contains(a):
  288. return true
  289. proc buildGraph(n: PNode, deps: seq[(IntSet, IntSet)]): DepG =
  290. # Build a dependency graph
  291. result = newSeqOfCap[DepN](deps.len)
  292. for i in 0..<deps.len:
  293. result.add newDepN(i, n[i])
  294. for i in 0..<deps.len:
  295. var ni = result[i]
  296. let uses = deps[i][1]
  297. let niHasBody = ni.hasBody
  298. let niHasCmd = ni.hasCommand
  299. for j in 0..<deps.len:
  300. if i == j: continue
  301. var nj = result[j]
  302. let declares = deps[j][0]
  303. if j < i and nj.hasCommand and niHasCmd:
  304. # Preserve order for commands and calls
  305. ni.kids.add nj
  306. when defined(debugReorder):
  307. ni.expls.add "both have commands and one comes after the other"
  308. elif j < i and nj.hasImportStmt:
  309. # Every node that comes after an import statement must
  310. # depend on that import
  311. ni.kids.add nj
  312. when defined(debugReorder):
  313. ni.expls.add "parent is, or contains, an import statement and child comes after it"
  314. elif j < i and niHasBody and nj.hasAccQuotedDef:
  315. # Every function, macro, template... with a body depends
  316. # on precedent function declarations that have quoted names.
  317. # That's because it is hard to detect the use of functions
  318. # like "[]=", "[]", "or" ... in their bodies.
  319. ni.kids.add nj
  320. when defined(debugReorder):
  321. ni.expls.add "one declares a quoted identifier and the other has a body and comes after it"
  322. elif j < i and niHasBody and not nj.hasBody and
  323. intersects(deps[i][0], declares):
  324. # Keep function declaration before function definition
  325. ni.kids.add nj
  326. when defined(debugReorder):
  327. for dep in deps[i][0]:
  328. if dep in declares:
  329. ni.expls.add "one declares \"" & idNames[dep] & "\" and the other defines it"
  330. else:
  331. for d in declares:
  332. if uses.contains(d):
  333. ni.kids.add nj
  334. when defined(debugReorder):
  335. ni.expls.add "one declares \"" & idNames[d] & "\" and the other uses it"
  336. proc strongConnect(v: var DepN, idx: var int, s: var seq[DepN],
  337. res: var seq[seq[DepN]]) =
  338. # Recursive part of trajan's algorithm
  339. v.idx = idx
  340. v.lowLink = idx
  341. inc idx
  342. s.add v
  343. v.onStack = true
  344. for w in v.kids.mitems:
  345. if w.idx < 0:
  346. strongConnect(w, idx, s, res)
  347. v.lowLink = min(v.lowLink, w.lowLink)
  348. elif w.onStack:
  349. v.lowLink = min(v.lowLink, w.idx)
  350. if v.lowLink == v.idx:
  351. var comp = newSeq[DepN]()
  352. while true:
  353. var w = s.pop
  354. w.onStack = false
  355. comp.add w
  356. if w.id == v.id: break
  357. res.add comp
  358. proc getStrongComponents(g: var DepG): seq[seq[DepN]] =
  359. ## Tarjan's algorithm. Performs a topological sort
  360. ## and detects strongly connected components.
  361. var s: seq[DepN]
  362. var idx = 0
  363. for v in g.mitems:
  364. if v.idx < 0:
  365. strongConnect(v, idx, s, result)
  366. proc hasForbiddenPragma(n: PNode): bool =
  367. # Checks if the tree node has some pragmas that do not
  368. # play well with reordering, like the push/pop pragma
  369. for a in n:
  370. if a.kind == nkPragma and a[0].kind == nkIdent and
  371. a[0].ident.s == "push":
  372. return true
  373. proc reorder*(graph: ModuleGraph, n: PNode, module: PSym): PNode =
  374. if n.hasForbiddenPragma:
  375. return n
  376. var includedFiles = initIntSet()
  377. let mpath = toFullPath(graph.config, module.fileIdx)
  378. let n = expandIncludes(graph, module, n, mpath,
  379. includedFiles).splitSections
  380. result = newNodeI(nkStmtList, n.info)
  381. var deps = newSeq[(IntSet, IntSet)](n.len)
  382. for i in 0..<n.len:
  383. deps[i][0] = initIntSet()
  384. deps[i][1] = initIntSet()
  385. computeDeps(graph.cache, n[i], deps[i][0], deps[i][1], true)
  386. var g = buildGraph(n, deps)
  387. let comps = getStrongComponents(g)
  388. mergeSections(graph.config, comps, result)