reorder.nim 13 KB

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