thavlak.nim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. discard """
  2. output: '''Welcome to LoopTesterApp, Nim edition
  3. Constructing Simple CFG...
  4. 5000 dummy loops
  5. Constructing CFG...
  6. Performing Loop Recognition
  7. 1 Iteration
  8. Another 3 iterations...
  9. ...
  10. Found 1 loops (including artificial root node) (3)'''
  11. retries: 2
  12. """
  13. # bug #3184
  14. import tables, sets
  15. when not declared(withScratchRegion):
  16. template withScratchRegion(body: untyped) = body
  17. type
  18. BasicBlock = ref object
  19. inEdges: seq[BasicBlock]
  20. outEdges: seq[BasicBlock]
  21. name: int
  22. proc newBasicBlock(name: int): BasicBlock =
  23. result = BasicBlock(
  24. inEdges: newSeq[BasicBlock](),
  25. outEdges: newSeq[BasicBlock](),
  26. name: name
  27. )
  28. proc hash(x: BasicBlock): int {.inline.} =
  29. result = x.name
  30. type
  31. BasicBlockEdge = object
  32. fr: BasicBlock
  33. to: BasicBlock
  34. Cfg = object
  35. basicBlockMap: Table[int, BasicBlock]
  36. edgeList: seq[BasicBlockEdge]
  37. startNode: BasicBlock
  38. proc newCfg(): Cfg =
  39. result = Cfg(
  40. basicBlockMap: initTable[int, BasicBlock](),
  41. edgeList: newSeq[BasicBlockEdge](),
  42. startNode: nil)
  43. proc createNode(self: var Cfg, name: int): BasicBlock =
  44. result = self.basicBlockMap.getOrDefault(name)
  45. if result == nil:
  46. result = newBasicBlock(name)
  47. self.basicBlockMap.add name, result
  48. if self.startNode == nil:
  49. self.startNode = result
  50. proc newBasicBlockEdge(cfg: var Cfg, fromName, toName: int) =
  51. var result = BasicBlockEdge(
  52. fr: cfg.createNode(fromName),
  53. to: cfg.createNode(toName)
  54. )
  55. result.fr.outEdges.add(result.to)
  56. result.to.inEdges.add(result.fr)
  57. cfg.edgeList.add(result)
  58. type
  59. SimpleLoop = ref object
  60. basicBlocks: seq[BasicBlock] # TODO: set here
  61. children: seq[SimpleLoop] # TODO: set here
  62. parent: SimpleLoop
  63. header: BasicBlock
  64. isRoot, isReducible: bool
  65. counter, nestingLevel, depthLevel: int
  66. proc setParent(self: SimpleLoop, parent: SimpleLoop) =
  67. self.parent = parent
  68. self.parent.children.add self
  69. proc setHeader(self: SimpleLoop, bb: BasicBlock) =
  70. self.basicBlocks.add(bb)
  71. self.header = bb
  72. proc setNestingLevel(self: SimpleLoop, level: int) =
  73. self.nestingLevel = level
  74. if level == 0: self.isRoot = true
  75. var loopCounter: int = 0
  76. type
  77. Lsg = object
  78. loops: seq[SimpleLoop]
  79. root: SimpleLoop
  80. proc createNewLoop(self: var Lsg): SimpleLoop =
  81. result = SimpleLoop(
  82. basicBlocks: newSeq[BasicBlock](),
  83. children: newSeq[SimpleLoop](),
  84. isReducible: true)
  85. loopCounter += 1
  86. result.counter = loopCounter
  87. proc addLoop(self: var Lsg, l: SimpleLoop) =
  88. self.loops.add l
  89. proc newLsg(): Lsg =
  90. result = Lsg(loops: newSeq[SimpleLoop](),
  91. root: result.createNewLoop())
  92. result.root.setNestingLevel(0)
  93. result.addLoop(result.root)
  94. type
  95. UnionFindNode = ref object
  96. parent {.cursor.}: UnionFindNode
  97. bb: BasicBlock
  98. l: SimpleLoop
  99. dfsNumber: int
  100. proc initNode(self: UnionFindNode, bb: BasicBlock, dfsNumber: int) =
  101. self.parent = self
  102. self.bb = bb
  103. self.dfsNumber = dfsNumber
  104. proc findSet(self: UnionFindNode): UnionFindNode =
  105. var nodeList = newSeq[UnionFindNode]()
  106. var it {.cursor.} = self
  107. while it != it.parent:
  108. var parent {.cursor.} = it.parent
  109. if parent != parent.parent: nodeList.add it
  110. it = parent
  111. for iter in nodeList: iter.parent = it.parent
  112. result = it
  113. proc union(self: UnionFindNode, unionFindNode: UnionFindNode) =
  114. self.parent = unionFindNode
  115. const
  116. BB_NONHEADER = 1 # a regular BB
  117. BB_REDUCIBLE = 2 # reducible loop
  118. BB_SELF = 3 # single BB loop
  119. BB_IRREDUCIBLE = 4 # irreducible loop
  120. BB_DEAD = 5 # a dead BB
  121. # # Marker for uninitialized nodes.
  122. UNVISITED = -1
  123. # # Safeguard against pathologic algorithm behavior.
  124. MAXNONBACKPREDS = (32 * 1024)
  125. type
  126. HavlakLoopFinder = object
  127. cfg: Cfg
  128. lsg: Lsg
  129. proc newHavlakLoopFinder(cfg: Cfg, lsg: sink Lsg): HavlakLoopFinder =
  130. result = HavlakLoopFinder(cfg: cfg, lsg: lsg)
  131. proc isAncestor(w, v: int, last: seq[int]): bool =
  132. w <= v and v <= last[w]
  133. proc dfs(currentNode: BasicBlock, nodes: var seq[UnionFindNode],
  134. number: var Table[BasicBlock, int],
  135. last: var seq[int], current: int) =
  136. var stack = @[(currentNode, current)]
  137. while stack.len > 0:
  138. let (currentNode, current) = stack.pop()
  139. nodes[current].initNode(currentNode, current)
  140. number[currentNode] = current
  141. for target in currentNode.outEdges:
  142. if number[target] == UNVISITED:
  143. stack.add((target, current+1))
  144. #result = dfs(target, nodes, number, last, result + 1)
  145. last[number[currentNode]] = current
  146. proc findLoops(self: var HavlakLoopFinder): int =
  147. var startNode = self.cfg.startNode
  148. if startNode == nil: return 0
  149. var size = self.cfg.basicBlockMap.len
  150. var nonBackPreds = newSeq[HashSet[int]]()
  151. var backPreds = newSeq[seq[int]]()
  152. var number = initTable[BasicBlock, int]()
  153. var header = newSeq[int](size)
  154. var types = newSeq[int](size)
  155. var last = newSeq[int](size)
  156. var nodes = newSeq[UnionFindNode]()
  157. for i in 1..size:
  158. nonBackPreds.add initHashSet[int](1)
  159. backPreds.add newSeq[int]()
  160. nodes.add(UnionFindNode())
  161. # Step a:
  162. # - initialize all nodes as unvisited.
  163. # - depth-first traversal and numbering.
  164. # - unreached BB's are marked as dead.
  165. #
  166. for v in self.cfg.basicBlockMap.values: number[v] = UNVISITED
  167. dfs(startNode, nodes, number, last, 0)
  168. # Step b:
  169. # - iterate over all nodes.
  170. #
  171. # A backedge comes from a descendant in the DFS tree, and non-backedges
  172. # from non-descendants (following Tarjan).
  173. #
  174. # - check incoming edges 'v' and add them to either
  175. # - the list of backedges (backPreds) or
  176. # - the list of non-backedges (nonBackPreds)
  177. #
  178. for w in 0 ..< size:
  179. header[w] = 0
  180. types[w] = BB_NONHEADER
  181. var nodeW = nodes[w].bb
  182. if nodeW != nil:
  183. for nodeV in nodeW.inEdges:
  184. var v = number[nodeV]
  185. if v != UNVISITED:
  186. if isAncestor(w, v, last):
  187. backPreds[w].add v
  188. else:
  189. nonBackPreds[w].incl v
  190. else:
  191. types[w] = BB_DEAD
  192. # Start node is root of all other loops.
  193. header[0] = 0
  194. # Step c:
  195. #
  196. # The outer loop, unchanged from Tarjan. It does nothing except
  197. # for those nodes which are the destinations of backedges.
  198. # For a header node w, we chase backward from the sources of the
  199. # backedges adding nodes to the set P, representing the body of
  200. # the loop headed by w.
  201. #
  202. # By running through the nodes in reverse of the DFST preorder,
  203. # we ensure that inner loop headers will be processed before the
  204. # headers for surrounding loops.
  205. for w in countdown(size - 1, 0):
  206. # this is 'P' in Havlak's paper
  207. var nodePool = newSeq[UnionFindNode]()
  208. var nodeW = nodes[w].bb
  209. if nodeW != nil: # dead BB
  210. # Step d:
  211. for v in backPreds[w]:
  212. if v != w:
  213. nodePool.add nodes[v].findSet
  214. else:
  215. types[w] = BB_SELF
  216. # Copy nodePool to workList.
  217. #
  218. var workList = newSeq[UnionFindNode]()
  219. for x in nodePool: workList.add x
  220. if nodePool.len != 0: types[w] = BB_REDUCIBLE
  221. # work the list...
  222. #
  223. while workList.len > 0:
  224. let x = workList[0]
  225. workList.del(0)
  226. # Step e:
  227. #
  228. # Step e represents the main difference from Tarjan's method.
  229. # Chasing upwards from the sources of a node w's backedges. If
  230. # there is a node y' that is not a descendant of w, w is marked
  231. # the header of an irreducible loop, there is another entry
  232. # into this loop that avoids w.
  233. #
  234. # The algorithm has degenerated. Break and
  235. # return in this case.
  236. #
  237. var nonBackSize = nonBackPreds[x.dfsNumber].len
  238. if nonBackSize > MAXNONBACKPREDS: return 0
  239. for iter in nonBackPreds[x.dfsNumber]:
  240. var y = nodes[iter]
  241. var ydash = y.findSet
  242. if not isAncestor(w, ydash.dfsNumber, last):
  243. types[w] = BB_IRREDUCIBLE
  244. nonBackPreds[w].incl ydash.dfsNumber
  245. else:
  246. if ydash.dfsNumber != w and not nodePool.contains(ydash):
  247. workList.add ydash
  248. nodePool.add ydash
  249. # Collapse/Unionize nodes in a SCC to a single node
  250. # For every SCC found, create a loop descriptor and link it in.
  251. #
  252. if nodePool.len > 0 or types[w] == BB_SELF:
  253. var l = self.lsg.createNewLoop
  254. l.setHeader(nodeW)
  255. l.isReducible = types[w] != BB_IRREDUCIBLE
  256. # At this point, one can set attributes to the loop, such as:
  257. #
  258. # the bottom node:
  259. # iter = backPreds(w).begin();
  260. # loop bottom is: nodes(iter).node;
  261. #
  262. # the number of backedges:
  263. # backPreds(w).size()
  264. #
  265. # whether this loop is reducible:
  266. # types(w) != BB_IRREDUCIBLE
  267. #
  268. nodes[w].l = l
  269. for node in nodePool:
  270. # Add nodes to loop descriptor.
  271. header[node.dfsNumber] = w
  272. node.union(nodes[w])
  273. # Nested loops are not added, but linked together.
  274. var nodeL = node.l
  275. if nodeL != nil:
  276. nodeL.setParent(l)
  277. else:
  278. l.basicBlocks.add node.bb
  279. self.lsg.addLoop(l)
  280. result = self.lsg.loops.len
  281. type
  282. LoopTesterApp = object
  283. cfg: Cfg
  284. lsg: Lsg
  285. proc newLoopTesterApp(): LoopTesterApp =
  286. result.cfg = newCfg()
  287. result.lsg = newLsg()
  288. proc buildDiamond(self: var LoopTesterApp, start: int): int =
  289. newBasicBlockEdge(self.cfg, start, start + 1)
  290. newBasicBlockEdge(self.cfg, start, start + 2)
  291. newBasicBlockEdge(self.cfg, start + 1, start + 3)
  292. newBasicBlockEdge(self.cfg, start + 2, start + 3)
  293. result = start + 3
  294. proc buildConnect(self: var LoopTesterApp, start1, end1: int) =
  295. newBasicBlockEdge(self.cfg, start1, end1)
  296. proc buildStraight(self: var LoopTesterApp, start, n: int): int =
  297. for i in 0..n-1:
  298. self.buildConnect(start + i, start + i + 1)
  299. result = start + n
  300. proc buildBaseLoop(self: var LoopTesterApp, from1: int): int =
  301. let header = self.buildStraight(from1, 1)
  302. let diamond1 = self.buildDiamond(header)
  303. let d11 = self.buildStraight(diamond1, 1)
  304. let diamond2 = self.buildDiamond(d11)
  305. let footer = self.buildStraight(diamond2, 1)
  306. self.buildConnect(diamond2, d11)
  307. self.buildConnect(diamond1, header)
  308. self.buildConnect(footer, from1)
  309. result = self.buildStraight(footer, 1)
  310. proc run(self: var LoopTesterApp) =
  311. echo "Welcome to LoopTesterApp, Nim edition"
  312. echo "Constructing Simple CFG..."
  313. discard self.cfg.createNode(0)
  314. discard self.buildBaseLoop(0)
  315. discard self.cfg.createNode(1)
  316. self.buildConnect(0, 2)
  317. echo "5000 dummy loops"
  318. for i in 1..5000:
  319. withScratchRegion:
  320. var h = newHavlakLoopFinder(self.cfg, newLsg())
  321. discard h.findLoops
  322. echo "Constructing CFG..."
  323. var n = 2
  324. when true: # not defined(gcOrc):
  325. # currently cycle detection is so slow that we disable this part
  326. for parlooptrees in 1..10:
  327. discard self.cfg.createNode(n + 1)
  328. self.buildConnect(2, n + 1)
  329. n += 1
  330. for i in 1..100:
  331. var top = n
  332. n = self.buildStraight(n, 1)
  333. for j in 1..25: n = self.buildBaseLoop(n)
  334. var bottom = self.buildStraight(n, 1)
  335. self.buildConnect n, top
  336. n = bottom
  337. self.buildConnect(n, 1)
  338. echo "Performing Loop Recognition\n1 Iteration"
  339. var h = newHavlakLoopFinder(self.cfg, newLsg())
  340. var loops = h.findLoops
  341. echo "Another 3 iterations..."
  342. var sum = 0
  343. for i in 1..3:
  344. withScratchRegion:
  345. write stdout, "."
  346. flushFile(stdout)
  347. var hlf = newHavlakLoopFinder(self.cfg, newLsg())
  348. sum += hlf.findLoops
  349. #echo getOccupiedMem()
  350. echo "\nFound ", loops, " loops (including artificial root node) (", sum, ")"
  351. when false:
  352. echo("Total memory available: " & formatSize(getTotalMem()) & " bytes")
  353. echo("Free memory: " & formatSize(getFreeMem()) & " bytes")
  354. proc main =
  355. var l = newLoopTesterApp()
  356. l.run
  357. let mem = getOccupiedMem()
  358. main()
  359. when defined(gcOrc):
  360. GC_fullCollect()
  361. doAssert getOccupiedMem() == mem