thavlak.nim 13 KB

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