mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-03 11:42:33 +00:00
* cycle collector: new implementation * cycle collector: make self-adaptive based on its previous effectiveness * cycle collector: added Lins's jump stack to improve traversal from 3*N to 2*N * cycle collector: make tests green * API extensions and bugfixes * code cleanup and use --gc:orc for tasyncawait
442 lines
12 KiB
Nim
442 lines
12 KiB
Nim
discard """
|
|
output: '''Welcome to LoopTesterApp, Nim edition
|
|
Constructing Simple CFG...
|
|
5000 dummy loops
|
|
Constructing CFG...
|
|
Performing Loop Recognition
|
|
1 Iteration
|
|
Another 3 iterations...
|
|
...
|
|
Found 1 loops (including artificial root node) (3)'''
|
|
"""
|
|
|
|
# bug #3184
|
|
|
|
import tables, sets
|
|
|
|
when not declared(withScratchRegion):
|
|
template withScratchRegion(body: untyped) = body
|
|
|
|
type
|
|
BasicBlock = ref object
|
|
inEdges: seq[BasicBlock]
|
|
outEdges: seq[BasicBlock]
|
|
name: int
|
|
|
|
proc newBasicBlock(name: int): BasicBlock =
|
|
result = BasicBlock(
|
|
inEdges: newSeq[BasicBlock](),
|
|
outEdges: newSeq[BasicBlock](),
|
|
name: name
|
|
)
|
|
|
|
proc hash(x: BasicBlock): int {.inline.} =
|
|
result = x.name
|
|
|
|
type
|
|
BasicBlockEdge = object
|
|
fr: BasicBlock
|
|
to: BasicBlock
|
|
|
|
Cfg = object
|
|
basicBlockMap: Table[int, BasicBlock]
|
|
edgeList: seq[BasicBlockEdge]
|
|
startNode: BasicBlock
|
|
|
|
proc newCfg(): Cfg =
|
|
result = Cfg(
|
|
basicBlockMap: initTable[int, BasicBlock](),
|
|
edgeList: newSeq[BasicBlockEdge](),
|
|
startNode: nil)
|
|
|
|
proc createNode(self: var Cfg, name: int): BasicBlock =
|
|
result = self.basicBlockMap.getOrDefault(name)
|
|
if result == nil:
|
|
result = newBasicBlock(name)
|
|
self.basicBlockMap.add name, result
|
|
|
|
if self.startNode == nil:
|
|
self.startNode = result
|
|
|
|
proc newBasicBlockEdge(cfg: var Cfg, fromName, toName: int) =
|
|
var result = BasicBlockEdge(
|
|
fr: cfg.createNode(fromName),
|
|
to: cfg.createNode(toName)
|
|
)
|
|
result.fr.outEdges.add(result.to)
|
|
result.to.inEdges.add(result.fr)
|
|
cfg.edgeList.add(result)
|
|
|
|
type
|
|
SimpleLoop = ref object
|
|
basicBlocks: seq[BasicBlock] # TODO: set here
|
|
children: seq[SimpleLoop] # TODO: set here
|
|
parent: SimpleLoop
|
|
header: BasicBlock
|
|
isRoot, isReducible: bool
|
|
counter, nestingLevel, depthLevel: int
|
|
|
|
proc setParent(self: SimpleLoop, parent: SimpleLoop) =
|
|
self.parent = parent
|
|
self.parent.children.add self
|
|
|
|
proc setHeader(self: SimpleLoop, bb: BasicBlock) =
|
|
self.basicBlocks.add(bb)
|
|
self.header = bb
|
|
|
|
proc setNestingLevel(self: SimpleLoop, level: int) =
|
|
self.nestingLevel = level
|
|
if level == 0: self.isRoot = true
|
|
|
|
var loopCounter: int = 0
|
|
|
|
type
|
|
Lsg = object
|
|
loops: seq[SimpleLoop]
|
|
root: SimpleLoop
|
|
|
|
proc createNewLoop(self: var Lsg): SimpleLoop =
|
|
result = SimpleLoop(
|
|
basicBlocks: newSeq[BasicBlock](),
|
|
children: newSeq[SimpleLoop](),
|
|
isReducible: true)
|
|
loopCounter += 1
|
|
result.counter = loopCounter
|
|
|
|
proc addLoop(self: var Lsg, l: SimpleLoop) =
|
|
self.loops.add l
|
|
|
|
proc newLsg(): Lsg =
|
|
result = Lsg(loops: newSeq[SimpleLoop](),
|
|
root: result.createNewLoop())
|
|
result.root.setNestingLevel(0)
|
|
result.addLoop(result.root)
|
|
|
|
type
|
|
UnionFindNode = ref object
|
|
parent {.cursor.}: UnionFindNode
|
|
bb: BasicBlock
|
|
l: SimpleLoop
|
|
dfsNumber: int
|
|
|
|
proc initNode(self: UnionFindNode, bb: BasicBlock, dfsNumber: int) =
|
|
self.parent = self
|
|
self.bb = bb
|
|
self.dfsNumber = dfsNumber
|
|
|
|
proc findSet(self: UnionFindNode): UnionFindNode =
|
|
var nodeList = newSeq[UnionFindNode]()
|
|
var it {.cursor.} = self
|
|
|
|
while it != it.parent:
|
|
var parent {.cursor.} = it.parent
|
|
if parent != parent.parent: nodeList.add it
|
|
it = parent
|
|
|
|
for iter in nodeList: iter.parent = it.parent
|
|
result = it
|
|
|
|
proc union(self: UnionFindNode, unionFindNode: UnionFindNode) =
|
|
self.parent = unionFindNode
|
|
|
|
|
|
const
|
|
BB_NONHEADER = 1 # a regular BB
|
|
BB_REDUCIBLE = 2 # reducible loop
|
|
BB_SELF = 3 # single BB loop
|
|
BB_IRREDUCIBLE = 4 # irreducible loop
|
|
BB_DEAD = 5 # a dead BB
|
|
|
|
# # Marker for uninitialized nodes.
|
|
UNVISITED = -1
|
|
|
|
# # Safeguard against pathologic algorithm behavior.
|
|
MAXNONBACKPREDS = (32 * 1024)
|
|
|
|
type
|
|
HavlakLoopFinder = object
|
|
cfg: Cfg
|
|
lsg: Lsg
|
|
|
|
proc newHavlakLoopFinder(cfg: Cfg, lsg: sink Lsg): HavlakLoopFinder =
|
|
result = HavlakLoopFinder(cfg: cfg, lsg: lsg)
|
|
|
|
proc isAncestor(w, v: int, last: seq[int]): bool =
|
|
w <= v and v <= last[w]
|
|
|
|
proc dfs(currentNode: BasicBlock, nodes: var seq[UnionFindNode],
|
|
number: var Table[BasicBlock, int],
|
|
last: var seq[int], current: int) =
|
|
var stack = @[(currentNode, current)]
|
|
while stack.len > 0:
|
|
let (currentNode, current) = stack.pop()
|
|
nodes[current].initNode(currentNode, current)
|
|
number[currentNode] = current
|
|
|
|
for target in currentNode.outEdges:
|
|
if number[target] == UNVISITED:
|
|
stack.add((target, current+1))
|
|
#result = dfs(target, nodes, number, last, result + 1)
|
|
last[number[currentNode]] = current
|
|
|
|
proc findLoops(self: var HavlakLoopFinder): int =
|
|
var startNode = self.cfg.startNode
|
|
if startNode == nil: return 0
|
|
var size = self.cfg.basicBlockMap.len
|
|
|
|
var nonBackPreds = newSeq[HashSet[int]]()
|
|
var backPreds = newSeq[seq[int]]()
|
|
var number = initTable[BasicBlock, int]()
|
|
var header = newSeq[int](size)
|
|
var types = newSeq[int](size)
|
|
var last = newSeq[int](size)
|
|
var nodes = newSeq[UnionFindNode]()
|
|
|
|
for i in 1..size:
|
|
nonBackPreds.add initHashSet[int](1)
|
|
backPreds.add newSeq[int]()
|
|
nodes.add(UnionFindNode())
|
|
|
|
# Step a:
|
|
# - initialize all nodes as unvisited.
|
|
# - depth-first traversal and numbering.
|
|
# - unreached BB's are marked as dead.
|
|
#
|
|
for v in self.cfg.basicBlockMap.values: number[v] = UNVISITED
|
|
dfs(startNode, nodes, number, last, 0)
|
|
|
|
# Step b:
|
|
# - iterate over all nodes.
|
|
#
|
|
# A backedge comes from a descendant in the DFS tree, and non-backedges
|
|
# from non-descendants (following Tarjan).
|
|
#
|
|
# - check incoming edges 'v' and add them to either
|
|
# - the list of backedges (backPreds) or
|
|
# - the list of non-backedges (nonBackPreds)
|
|
#
|
|
for w in 0 ..< size:
|
|
header[w] = 0
|
|
types[w] = BB_NONHEADER
|
|
|
|
var nodeW = nodes[w].bb
|
|
if nodeW != nil:
|
|
for nodeV in nodeW.inEdges:
|
|
var v = number[nodeV]
|
|
if v != UNVISITED:
|
|
if isAncestor(w, v, last):
|
|
backPreds[w].add v
|
|
else:
|
|
nonBackPreds[w].incl v
|
|
else:
|
|
types[w] = BB_DEAD
|
|
|
|
# Start node is root of all other loops.
|
|
header[0] = 0
|
|
|
|
# Step c:
|
|
#
|
|
# The outer loop, unchanged from Tarjan. It does nothing except
|
|
# for those nodes which are the destinations of backedges.
|
|
# For a header node w, we chase backward from the sources of the
|
|
# backedges adding nodes to the set P, representing the body of
|
|
# the loop headed by w.
|
|
#
|
|
# By running through the nodes in reverse of the DFST preorder,
|
|
# we ensure that inner loop headers will be processed before the
|
|
# headers for surrounding loops.
|
|
|
|
for w in countdown(size - 1, 0):
|
|
# this is 'P' in Havlak's paper
|
|
var nodePool = newSeq[UnionFindNode]()
|
|
|
|
var nodeW = nodes[w].bb
|
|
if nodeW != nil: # dead BB
|
|
# Step d:
|
|
for v in backPreds[w]:
|
|
if v != w:
|
|
nodePool.add nodes[v].findSet
|
|
else:
|
|
types[w] = BB_SELF
|
|
|
|
# Copy nodePool to workList.
|
|
#
|
|
var workList = newSeq[UnionFindNode]()
|
|
for x in nodePool: workList.add x
|
|
|
|
if nodePool.len != 0: types[w] = BB_REDUCIBLE
|
|
|
|
# work the list...
|
|
#
|
|
while workList.len > 0:
|
|
let x = workList[0]
|
|
workList.del(0)
|
|
|
|
# Step e:
|
|
#
|
|
# Step e represents the main difference from Tarjan's method.
|
|
# Chasing upwards from the sources of a node w's backedges. If
|
|
# there is a node y' that is not a descendant of w, w is marked
|
|
# the header of an irreducible loop, there is another entry
|
|
# into this loop that avoids w.
|
|
#
|
|
|
|
# The algorithm has degenerated. Break and
|
|
# return in this case.
|
|
#
|
|
var nonBackSize = nonBackPreds[x.dfsNumber].len
|
|
if nonBackSize > MAXNONBACKPREDS: return 0
|
|
|
|
for iter in nonBackPreds[x.dfsNumber]:
|
|
var y = nodes[iter]
|
|
var ydash = y.findSet
|
|
|
|
if not isAncestor(w, ydash.dfsNumber, last):
|
|
types[w] = BB_IRREDUCIBLE
|
|
nonBackPreds[w].incl ydash.dfsNumber
|
|
else:
|
|
if ydash.dfsNumber != w and not nodePool.contains(ydash):
|
|
workList.add ydash
|
|
nodePool.add ydash
|
|
|
|
# Collapse/Unionize nodes in a SCC to a single node
|
|
# For every SCC found, create a loop descriptor and link it in.
|
|
#
|
|
if nodePool.len > 0 or types[w] == BB_SELF:
|
|
var l = self.lsg.createNewLoop
|
|
|
|
l.setHeader(nodeW)
|
|
l.isReducible = types[w] != BB_IRREDUCIBLE
|
|
|
|
# At this point, one can set attributes to the loop, such as:
|
|
#
|
|
# the bottom node:
|
|
# iter = backPreds(w).begin();
|
|
# loop bottom is: nodes(iter).node;
|
|
#
|
|
# the number of backedges:
|
|
# backPreds(w).size()
|
|
#
|
|
# whether this loop is reducible:
|
|
# types(w) != BB_IRREDUCIBLE
|
|
#
|
|
nodes[w].l = l
|
|
|
|
for node in nodePool:
|
|
# Add nodes to loop descriptor.
|
|
header[node.dfsNumber] = w
|
|
node.union(nodes[w])
|
|
|
|
# Nested loops are not added, but linked together.
|
|
var nodeL = node.l
|
|
if nodeL != nil:
|
|
nodeL.setParent(l)
|
|
else:
|
|
l.basicBlocks.add node.bb
|
|
|
|
self.lsg.addLoop(l)
|
|
|
|
result = self.lsg.loops.len
|
|
|
|
|
|
type
|
|
LoopTesterApp = object
|
|
cfg: Cfg
|
|
lsg: Lsg
|
|
|
|
proc newLoopTesterApp(): LoopTesterApp =
|
|
result.cfg = newCfg()
|
|
result.lsg = newLsg()
|
|
|
|
proc buildDiamond(self: var LoopTesterApp, start: int): int =
|
|
newBasicBlockEdge(self.cfg, start, start + 1)
|
|
newBasicBlockEdge(self.cfg, start, start + 2)
|
|
newBasicBlockEdge(self.cfg, start + 1, start + 3)
|
|
newBasicBlockEdge(self.cfg, start + 2, start + 3)
|
|
result = start + 3
|
|
|
|
proc buildConnect(self: var LoopTesterApp, start1, end1: int) =
|
|
newBasicBlockEdge(self.cfg, start1, end1)
|
|
|
|
proc buildStraight(self: var LoopTesterApp, start, n: int): int =
|
|
for i in 0..n-1:
|
|
self.buildConnect(start + i, start + i + 1)
|
|
result = start + n
|
|
|
|
proc buildBaseLoop(self: var LoopTesterApp, from1: int): int =
|
|
let header = self.buildStraight(from1, 1)
|
|
let diamond1 = self.buildDiamond(header)
|
|
let d11 = self.buildStraight(diamond1, 1)
|
|
let diamond2 = self.buildDiamond(d11)
|
|
let footer = self.buildStraight(diamond2, 1)
|
|
|
|
self.buildConnect(diamond2, d11)
|
|
self.buildConnect(diamond1, header)
|
|
self.buildConnect(footer, from1)
|
|
result = self.buildStraight(footer, 1)
|
|
|
|
proc run(self: var LoopTesterApp) =
|
|
echo "Welcome to LoopTesterApp, Nim edition"
|
|
echo "Constructing Simple CFG..."
|
|
|
|
discard self.cfg.createNode(0)
|
|
discard self.buildBaseLoop(0)
|
|
discard self.cfg.createNode(1)
|
|
self.buildConnect(0, 2)
|
|
|
|
echo "5000 dummy loops"
|
|
|
|
for i in 1..5000:
|
|
withScratchRegion:
|
|
var h = newHavlakLoopFinder(self.cfg, newLsg())
|
|
discard h.findLoops
|
|
|
|
echo "Constructing CFG..."
|
|
var n = 2
|
|
|
|
when true: # not defined(gcOrc):
|
|
# currently cycle detection is so slow that we disable this part
|
|
for parlooptrees in 1..10:
|
|
discard self.cfg.createNode(n + 1)
|
|
self.buildConnect(2, n + 1)
|
|
n += 1
|
|
for i in 1..100:
|
|
var top = n
|
|
n = self.buildStraight(n, 1)
|
|
for j in 1..25: n = self.buildBaseLoop(n)
|
|
var bottom = self.buildStraight(n, 1)
|
|
self.buildConnect n, top
|
|
n = bottom
|
|
self.buildConnect(n, 1)
|
|
|
|
echo "Performing Loop Recognition\n1 Iteration"
|
|
|
|
var h = newHavlakLoopFinder(self.cfg, newLsg())
|
|
var loops = h.findLoops
|
|
|
|
echo "Another 3 iterations..."
|
|
|
|
var sum = 0
|
|
for i in 1..3:
|
|
withScratchRegion:
|
|
write stdout, "."
|
|
flushFile(stdout)
|
|
var hlf = newHavlakLoopFinder(self.cfg, newLsg())
|
|
sum += hlf.findLoops
|
|
#echo getOccupiedMem()
|
|
echo "\nFound ", loops, " loops (including artificial root node) (", sum, ")"
|
|
|
|
when false:
|
|
echo("Total memory available: " & formatSize(getTotalMem()) & " bytes")
|
|
echo("Free memory: " & formatSize(getFreeMem()) & " bytes")
|
|
|
|
proc main =
|
|
var l = newLoopTesterApp()
|
|
l.run
|
|
|
|
let mem = getOccupiedMem()
|
|
main()
|
|
when defined(gcOrc):
|
|
GC_fullCollect()
|
|
doAssert getOccupiedMem() == mem
|