From 3f443379c823f1f23579c37b63f5117617f2253f Mon Sep 17 00:00:00 2001 From: shenzhuan Date: Wed, 30 Nov 2022 17:50:41 +0800 Subject: [PATCH] update test --- cpu.prof | Bin 2996 -> 2566 bytes cpu1.prof | Bin 0 -> 26061 bytes cpu2.prof | Bin 0 -> 22040 bytes cpu3.prof | Bin 0 -> 3108 bytes cpu4.prof | Bin 0 -> 2441 bytes cpu5.prof | Bin 0 -> 2505 bytes cpu6.prof | Bin 0 -> 2499 bytes havlak/havlak1.go | 727 ++++++++++++++++++++++++++++++++++ havlak/havlak2.go | 727 ++++++++++++++++++++++++++++++++++ havlak/havlak3.go | 714 +++++++++++++++++++++++++++++++++ havlak/havlak4.go | 720 +++++++++++++++++++++++++++++++++ havlak/havlak5.go | 746 ++++++++++++++++++++++++++++++++++ havlak/havlak6.cc.go | 401 +++++++++++++++++++ havlak/havlak6.go | 467 ++++++++++++++++++++++ havlak/havlak7.go | 759 +++++++++++++++++++++++++++++++++++ main.go | 924 ++++++++++++++----------------------------- mem.prof | Bin 2175 -> 2195 bytes mem3.prof | Bin 0 -> 2664 bytes mem4.prof | Bin 0 -> 2074 bytes mem5.prof | Bin 0 -> 2255 bytes mem6.prof | Bin 0 -> 1880 bytes 21 files changed, 5568 insertions(+), 617 deletions(-) create mode 100644 cpu1.prof create mode 100644 cpu2.prof create mode 100644 cpu3.prof create mode 100644 cpu4.prof create mode 100644 cpu5.prof create mode 100644 cpu6.prof create mode 100644 havlak/havlak1.go create mode 100644 havlak/havlak2.go create mode 100644 havlak/havlak3.go create mode 100644 havlak/havlak4.go create mode 100644 havlak/havlak5.go create mode 100644 havlak/havlak6.cc.go create mode 100644 havlak/havlak6.go create mode 100644 havlak/havlak7.go create mode 100644 mem3.prof create mode 100644 mem4.prof create mode 100644 mem5.prof create mode 100644 mem6.prof diff --git a/cpu.prof b/cpu.prof index 68dffda97f620b934fe0068098e729ec319d1974..cf96b4c961b01ad5d6abb346b30a5aebd3961463 100644 GIT binary patch literal 2566 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 literal 2996 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 diff --git a/cpu1.prof b/cpu1.prof new file mode 100644 index 0000000000000000000000000000000000000000..cc5a0ba51652ff01cc5471b2f2b4962e38c219bb GIT binary patch literal 26061 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 y(3I3XT;}0RR6JM9LZig?-1g50(mga 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 HVG;lUWRcIQ literal 0 HcmV?d00001 diff --git a/cpu5.prof b/cpu5.prof new file mode 100644 index 0000000000000000000000000000000000000000..4373326c10561e108c03e9796cb69279bb5f33f6 GIT binary patch literal 2505 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 literal 0 HcmV?d00001 diff --git a/cpu6.prof b/cpu6.prof new file mode 100644 index 0000000000000000000000000000000000000000..ff49217556b4019f7bcc960217ac49614f67b305 GIT binary patch literal 2499 zcmV;!2|V^6iwFP!00004|D;xXY+Lp9|JkwQubqcaoQF?d?#(xS#7=6*H;<<8Hc6AF zPXs>+iGRwCedCz;`g->|gzld;P#9I6rGu?p~_SMu>zR)J6cZo$J{%msto#oVx&yI3WRfB`?yuzEaof@}cat0Wsh8@5TU+|L^D?0ZK=H|yXAGSxvlwo4)2#v1YJ zn~w`q0D|1b>R}akvj+IImHYy*0#`^ZQ{omI0AmCfJ0w>vesAG!8@(u zR^lI@q3{*3;N)%=f^!y~6%fZbWmttL7l>*L{KE^5K&?&Cy|Qyvdx!&DjlX(wtDMRoVYr7+O=|Z8*!y-|s%y4If8qtj(KTns8!@S_y31_UM#bE#kByl!7 zVGsxDwDsW0pOdo}&R2jVP;IlW= zD-gp|SIE``FN;$EtQY@un($Y_y;c>gU3!+oc$!|0DiPk@c5DNC<#8$ zzk-cELv6DD`t->v7?wN5oc!;jq=gFl@q2(q;~9 z1D<^UX<>`N%MO}_2yDSEWZQ^eTcEgZ_^D;*fKt^F0ho$DS)^Dc+0Bjh4_-tXB z&oA!EFm)x!M)1<-fR;gxkRz~ByyFEDFb0<`0b?+NBP8GsTzIh{-~&RMdRsu>HWF}j zM~{a$u`&GqhYyOc$Xr%AQ}IPM_Z0PM!ybgA~^eLo;uC;Ue&ZE4!}?A^D&$J63sjvRi5WR)=bToR z+;BQ?UZ>{&|6op)`<9-2GM(3y97$`VnfYWw$z)`CaLF*bd0;M`P3m)|Kq?N0scMRu zQPrGWV}n!S^1zajh5rG8BY+#qQLaztr>|AAGs{p~fEh}5=72smCGd((7Y4cBMo;Nx zVp>g3Xt_-JW6n@jEtfx_m3Anr&Y5{7F|(ZE7ycr7Q6)_`RC<;Bmn@57YQ=des*+W2 z$&VSTI;NQU(mDzdGpNn$v)S21R;*Nqv~h)&IB6S5pza=luL zwxlSrdC<(On#H>5^K-}n`+X{%P42fAq4;Jdlx$%+KC=d6y0JZ(ECWax>cN6@tqp4C zlR?Dk+AVRjjw7 z^mCKLvr}W4S#vtRoYOxyIXO#9gLd8$5RF5;l(u9kZ|?O*I 0 { + fmt.Printf(" in :") + for _, iter := range bb.InEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + if len(bb.OutEdges) > 0 { + fmt.Print(" out:") + for _, iter := range bb.OutEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + fmt.Printf("\n") +} + +func (bb *BasicBlock) NumPred() int { + return len(bb.InEdges) +} + +func (bb *BasicBlock) NumSucc() int { + return len(bb.OutEdges) +} + +func (bb *BasicBlock) AddInEdge(from *BasicBlock) { + bb.InEdges = append(bb.InEdges, from) +} + +func (bb *BasicBlock) AddOutEdge(to *BasicBlock) { + bb.OutEdges = append(bb.OutEdges, to) +} + +//----------------------------------------------------------- + +type CFG struct { + Blocks []*BasicBlock + Start *BasicBlock +} + +func NewCFG() *CFG { + return &CFG{} +} + +func (cfg *CFG) NumNodes() int { + return len(cfg.Blocks) +} + +func (cfg *CFG) CreateNode(node int) *BasicBlock { + if node < len(cfg.Blocks) { + return cfg.Blocks[node] + } + if node != len(cfg.Blocks) { + println("oops", node, len(cfg.Blocks)) + panic("wtf") + } + bblock := NewBasicBlock(node) + cfg.Blocks = append(cfg.Blocks, bblock) + + if len(cfg.Blocks) == 1 { + cfg.Start = bblock + } + + return bblock +} + +func (cfg *CFG) Dump() { + for _, n := range cfg.Blocks { + n.Dump() + } +} + +//----------------------------------------------------------- + +type BasicBlockEdge struct { + Dst *BasicBlock + Src *BasicBlock +} + +func NewBasicBlockEdge(cfg *CFG, from int, to int) *BasicBlockEdge { + self := new(BasicBlockEdge) + self.Src = cfg.CreateNode(from) + self.Dst = cfg.CreateNode(to) + + self.Src.AddOutEdge(self.Dst) + self.Dst.AddInEdge(self.Src) + + return self +} + +//----------------------------------------------------------- +// Basic Blocks and Loops are being classified as regular, irreducible, +// and so on. This enum contains a symbolic name for all these classifications +// +const ( + _ = iota // Go has an interesting iota concept + bbTop // uninitialized + bbNonHeader // a regular BB + bbReducible // reducible loop + bbSelf // single BB loop + bbIrreducible // irreducible loop + bbDead // a dead BB + bbLast // sentinel +) + +// UnionFindNode is used in the Union/Find algorithm to collapse +// complete loops into a single node. These nodes and the +// corresponding functionality are implemented with this class +// +type UnionFindNode struct { + parent *UnionFindNode + bb *BasicBlock + loop *SimpleLoop + dfsNumber int +} + +// Init explicitly initializes UnionFind nodes. +// +func (u *UnionFindNode) Init(bb *BasicBlock, dfsNumber int) { + u.parent = u + u.bb = bb + u.dfsNumber = dfsNumber + u.loop = nil +} + +// FindSet implements the Find part of the Union/Find Algorithm +// +// Implemented with Path Compression (inner loops are only +// visited and collapsed once, however, deep nests would still +// result in significant traversals). +// +func (u *UnionFindNode) FindSet() *UnionFindNode { + var nodeList []*UnionFindNode + node := u + + for ; node != node.parent; node = node.parent { + if node.parent != node.parent.parent { + nodeList = append(nodeList, node) + } + + } + + // Path Compression, all nodes' parents point to the 1st level parent. + for _, ll := range nodeList { + ll.parent = node.parent + } + + return node +} + +// Union relies on path compression. +// +func (u *UnionFindNode) Union(B *UnionFindNode) { + u.parent = B +} + +// Constants +// +// Marker for uninitialized nodes. +const unvisited = -1 + +// Safeguard against pathological algorithm behavior. +const maxNonBackPreds = 32 * 1024 + +// IsAncestor +// +// As described in the paper, determine whether a node 'w' is a +// "true" ancestor for node 'v'. +// +// Dominance can be tested quickly using a pre-order trick +// for depth-first spanning trees. This is why DFS is the first +// thing we run below. +// +// Go comment: Parameters can be written as w,v int, inlike in C, where +// each parameter needs its own type. +// +func isAncestor(w, v int, last []int) bool { + return ((w <= v) && (v <= last[w])) +} + +// listContainsNode +// +// Check whether a list contains a specific element. +// +func listContainsNode(l []*UnionFindNode, u *UnionFindNode) bool { + for _, ll := range l { + if ll == u { + return true + } + } + return false +} + +// DFS - Depth-First-Search and node numbering. +// +func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number map[*BasicBlock]int, last []int, current int) int { + nodes[current].Init(currentNode, current) + number[currentNode] = current + + lastid := current + for _, target := range currentNode.OutEdges { + if number[target] == unvisited { + lastid = DFS(target, nodes, number, last, lastid+1) + } + } + last[number[currentNode]] = lastid + return lastid +} + +// FindLoops +// +// Find loops and build loop forest using Havlak's algorithm, which +// is derived from Tarjan. Variable names and step numbering has +// been chosen to be identical to the nomenclature in Havlak's +// paper (which, in turn, is similar to the one used by Tarjan). +// +func FindLoops(cfgraph *CFG, lsgraph *LSG) { + if cfgraph.Start == nil { + return + } + + size := cfgraph.NumNodes() + + nonBackPreds := make([]map[int]bool, size) + backPreds := make([][]int, size) + + number := make(map[*BasicBlock]int) + header := make([]int, size, size) + types := make([]int, size, size) + last := make([]int, size, size) + nodes := make([]*UnionFindNode, size, size) + + for i := 0; i < size; i++ { + nodes[i] = new(UnionFindNode) + } + + // Step a: + // - initialize all nodes as unvisited. + // - depth-first traversal and numbering. + // - unreached BB's are marked as dead. + // + for i, bb := range cfgraph.Blocks { + number[bb] = unvisited + nonBackPreds[i] = make(map[int]bool) + } + + DFS(cfgraph.Start, 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 := 0; w < size; w++ { + header[w] = 0 + types[w] = bbNonHeader + + nodeW := nodes[w].bb + if nodeW == nil { + types[w] = bbDead + continue // dead BB + } + + if nodeW.NumPred() > 0 { + for _, nodeV := range nodeW.InEdges { + v := number[nodeV] + if v == unvisited { + continue // dead node + } + + if isAncestor(w, v, last) { + backPreds[w] = append(backPreds[w], v) + } else { + nonBackPreds[w][v] = true + } + } + } + } + + // 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 := size - 1; w >= 0; w-- { + // this is 'P' in Havlak's paper + var nodePool []*UnionFindNode + + nodeW := nodes[w].bb + if nodeW == nil { + continue // dead BB + } + + // Step d: + for _, v := range backPreds[w] { + if v != w { + nodePool = append(nodePool, nodes[v].FindSet()) + } else { + types[w] = bbSelf + } + } + + // Copy nodePool to workList. + // + workList := append([]*UnionFindNode(nil), nodePool...) + + if len(nodePool) != 0 { + types[w] = bbReducible + } + + // work the list... + // + for len(workList) > 0 { + x := workList[0] + workList = workList[1:] + + // 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. + // + nonBackSize := len(nonBackPreds[x.dfsNumber]) + if nonBackSize > maxNonBackPreds { + return + } + + for iter := range nonBackPreds[x.dfsNumber] { + y := nodes[iter] + ydash := y.FindSet() + + if !isAncestor(w, ydash.dfsNumber, last) { + types[w] = bbIrreducible + nonBackPreds[w][ydash.dfsNumber] = true + } else { + if ydash.dfsNumber != w { + if !listContainsNode(nodePool, ydash) { + workList = append(workList, ydash) + nodePool = append(nodePool, ydash) + } + } + } + } + } + + // Collapse/Unionize nodes in a SCC to a single node + // For every SCC found, create a loop descriptor and link it in. + // + if (len(nodePool) > 0) || (types[w] == bbSelf) { + loop := lsgraph.NewLoop() + + loop.SetHeader(nodeW) + if types[w] != bbIrreducible { + loop.IsReducible = true + } + + // 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: + // type[w] != BasicBlockClass.bbIrreducible + // + nodes[w].loop = loop + + for _, node := range nodePool { + // Add nodes to loop descriptor. + header[node.dfsNumber] = w + node.Union(nodes[w]) + + // Nested loops are not added, but linked together. + if node.loop != nil { + node.loop.Parent = loop + } else { + loop.AddNode(node.bb) + } + } + + lsgraph.AddLoop(loop) + } // nodePool.size + } // Step c + +} + +// External entry point. +func FindHavlakLoops(cfgraph *CFG, lsgraph *LSG) int { + FindLoops(cfgraph, lsgraph) + return lsgraph.NumLoops() +} + +//====================================================== +// Scaffold Code +//====================================================== + +// Basic representation of loops, a loop has an entry point, +// one or more exit edges, a set of basic blocks, and potentially +// an outer loop - a "parent" loop. +// +// Furthermore, it can have any set of properties, e.g., +// it can be an irreducible loop, have control flow, be +// a candidate for transformations, and what not. +// +type SimpleLoop struct { + // No set, use map to bool + basicBlocks map[*BasicBlock]bool + Children map[*SimpleLoop]bool + Parent *SimpleLoop + header *BasicBlock + + IsRoot bool + IsReducible bool + Counter int + NestingLevel int + DepthLevel int +} + +func (loop *SimpleLoop) AddNode(bb *BasicBlock) { + loop.basicBlocks[bb] = true +} + +func (loop *SimpleLoop) AddChildLoop(child *SimpleLoop) { + loop.Children[child] = true +} + +func (loop *SimpleLoop) Dump(indent int) { + for i := 0; i < indent; i++ { + fmt.Printf(" ") + } + + // No ? operator ? + fmt.Printf("loop-%d nest: %d depth %d ", + loop.Counter, loop.NestingLevel, loop.DepthLevel) + if !loop.IsReducible { + fmt.Printf("(Irreducible) ") + } + + // must have > 0 + if len(loop.Children) > 0 { + fmt.Printf("Children: ") + for ll := range loop.Children { + fmt.Printf("loop-%d", ll.Counter) + } + } + if len(loop.basicBlocks) > 0 { + fmt.Printf("(") + for bb := range loop.basicBlocks { + fmt.Printf("BB#%06d ", bb.Name) + if loop.header == bb { + fmt.Printf("*") + } + } + fmt.Printf("\b)") + } + fmt.Printf("\n") +} + +func (loop *SimpleLoop) SetParent(parent *SimpleLoop) { + loop.Parent = parent + loop.Parent.AddChildLoop(loop) +} + +func (loop *SimpleLoop) SetHeader(bb *BasicBlock) { + loop.AddNode(bb) + loop.header = bb +} + +//------------------------------------ +// Helper (No templates or such) +// +func max(x, y int) int { + if x > y { + return x + } + return y +} + +// LoopStructureGraph +// +// Maintain loop structure for a given CFG. +// +// Two values are maintained for this loop graph, depth, and nesting level. +// For example: +// +// loop nesting level depth +//---------------------------------------- +// loop-0 2 0 +// loop-1 1 1 +// loop-3 1 1 +// loop-2 0 2 +// +var loopCounter = 0 + +type LSG struct { + root *SimpleLoop + loops []*SimpleLoop +} + +func NewLSG() *LSG { + lsg := new(LSG) + lsg.root = lsg.NewLoop() + lsg.root.NestingLevel = 0 + + return lsg +} + +func (lsg *LSG) NewLoop() *SimpleLoop { + loop := new(SimpleLoop) + loop.basicBlocks = make(map[*BasicBlock]bool) + loop.Children = make(map[*SimpleLoop]bool) + loop.Parent = nil + loop.header = nil + + loop.Counter = loopCounter + loopCounter++ + return loop +} + +func (lsg *LSG) AddLoop(loop *SimpleLoop) { + lsg.loops = append(lsg.loops, loop) +} + +func (lsg *LSG) Dump() { + lsg.dump(lsg.root, 0) +} + +func (lsg *LSG) dump(loop *SimpleLoop, indent int) { + loop.Dump(indent) + + for ll := range loop.Children { + lsg.dump(ll, indent+1) + } +} + +func (lsg *LSG) CalculateNestingLevel() { + for _, sl := range lsg.loops { + if sl.IsRoot { + continue + } + if sl.Parent == nil { + sl.SetParent(lsg.root) + } + } + lsg.calculateNestingLevel(lsg.root, 0) +} + +func (lsg *LSG) calculateNestingLevel(loop *SimpleLoop, depth int) { + loop.DepthLevel = depth + for ll := range loop.Children { + lsg.calculateNestingLevel(ll, depth+1) + + ll.NestingLevel = max(loop.NestingLevel, ll.NestingLevel+1) + } +} + +func (lsg *LSG) NumLoops() int { + return len(lsg.loops) +} + +func (lsg *LSG) Root() *SimpleLoop { + return lsg.root +} + +//====================================================== +// Testing Code +//====================================================== + +func buildDiamond(cfgraph *CFG, start int) int { + bb0 := start + NewBasicBlockEdge(cfgraph, bb0, bb0+1) + NewBasicBlockEdge(cfgraph, bb0, bb0+2) + NewBasicBlockEdge(cfgraph, bb0+1, bb0+3) + NewBasicBlockEdge(cfgraph, bb0+2, bb0+3) + + return bb0 + 3 +} + +func buildConnect(cfgraph *CFG, start int, end int) { + NewBasicBlockEdge(cfgraph, start, end) +} + +func buildStraight(cfgraph *CFG, start int, n int) int { + for i := 0; i < n; i++ { + buildConnect(cfgraph, start+i, start+i+1) + } + return start + n +} + +func buildBaseLoop(cfgraph *CFG, from int) int { + header := buildStraight(cfgraph, from, 1) + diamond1 := buildDiamond(cfgraph, header) + d11 := buildStraight(cfgraph, diamond1, 1) + diamond2 := buildDiamond(cfgraph, d11) + footer := buildStraight(cfgraph, diamond2, 1) + buildConnect(cfgraph, diamond2, d11) + buildConnect(cfgraph, diamond1, header) + + buildConnect(cfgraph, footer, from) + footer = buildStraight(cfgraph, footer, 1) + return footer +} + +var cpuprofile = flag.String("cpuprofile", "cpu1.prof", "write cpu profile to this file") + +func main() { + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + lsgraph := NewLSG() + cfgraph := NewCFG() + + cfgraph.CreateNode(0) // top + cfgraph.CreateNode(1) // bottom + NewBasicBlockEdge(cfgraph, 0, 2) + + for dummyloop := 0; dummyloop < 15000; dummyloop++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + n := 2 + + for parlooptrees := 0; parlooptrees < 10; parlooptrees++ { + cfgraph.CreateNode(n + 1) + buildConnect(cfgraph, 2, n+1) + n = n + 1 + + for i := 0; i < 100; i++ { + top := n + n = buildStraight(cfgraph, n, 1) + for j := 0; j < 25; j++ { + n = buildBaseLoop(cfgraph, n) + } + bottom := buildStraight(cfgraph, n, 1) + buildConnect(cfgraph, n, top) + n = bottom + } + buildConnect(cfgraph, n, 1) + } + + FindHavlakLoops(cfgraph, lsgraph) + + for i := 0; i < 50; i++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + fmt.Printf("# of loops: %d (including 1 artificial root node)\n", lsgraph.NumLoops()) + lsgraph.CalculateNestingLevel() +} diff --git a/havlak/havlak2.go b/havlak/havlak2.go new file mode 100644 index 0000000..fbaecef --- /dev/null +++ b/havlak/havlak2.go @@ -0,0 +1,727 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "runtime/pprof" +) + +/* + @Auth:ShenZ + @Description: +*/ +// Go from multi-language-benchmark/src/havlak/go_pro + +// Copyright 2011 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Test Program for the Havlak loop finder. +// +// This program constructs a fairly large control flow +// graph and performs loop recognition. This is the Go +// version. +// + +type BasicBlock struct { + Name int + InEdges []*BasicBlock + OutEdges []*BasicBlock +} + +func NewBasicBlock(name int) *BasicBlock { + return &BasicBlock{Name: name} +} + +func (bb *BasicBlock) Dump() { + fmt.Printf("BB#%06d:", bb.Name) + if len(bb.InEdges) > 0 { + fmt.Printf(" in :") + for _, iter := range bb.InEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + if len(bb.OutEdges) > 0 { + fmt.Print(" out:") + for _, iter := range bb.OutEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + fmt.Printf("\n") +} + +func (bb *BasicBlock) NumPred() int { + return len(bb.InEdges) +} + +func (bb *BasicBlock) NumSucc() int { + return len(bb.OutEdges) +} + +func (bb *BasicBlock) AddInEdge(from *BasicBlock) { + bb.InEdges = append(bb.InEdges, from) +} + +func (bb *BasicBlock) AddOutEdge(to *BasicBlock) { + bb.OutEdges = append(bb.OutEdges, to) +} + +//----------------------------------------------------------- + +type CFG struct { + Blocks []*BasicBlock + Start *BasicBlock +} + +func NewCFG() *CFG { + return &CFG{} +} + +func (cfg *CFG) NumNodes() int { + return len(cfg.Blocks) +} + +func (cfg *CFG) CreateNode(node int) *BasicBlock { + if node < len(cfg.Blocks) { + return cfg.Blocks[node] + } + if node != len(cfg.Blocks) { + println("oops", node, len(cfg.Blocks)) + panic("wtf") + } + bblock := NewBasicBlock(node) + cfg.Blocks = append(cfg.Blocks, bblock) + + if len(cfg.Blocks) == 1 { + cfg.Start = bblock + } + + return bblock +} + +func (cfg *CFG) Dump() { + for _, n := range cfg.Blocks { + n.Dump() + } +} + +//----------------------------------------------------------- + +type BasicBlockEdge struct { + Dst *BasicBlock + Src *BasicBlock +} + +func NewBasicBlockEdge(cfg *CFG, from int, to int) *BasicBlockEdge { + self := new(BasicBlockEdge) + self.Src = cfg.CreateNode(from) + self.Dst = cfg.CreateNode(to) + + self.Src.AddOutEdge(self.Dst) + self.Dst.AddInEdge(self.Src) + + return self +} + +//----------------------------------------------------------- +// Basic Blocks and Loops are being classified as regular, irreducible, +// and so on. This enum contains a symbolic name for all these classifications +// +const ( + _ = iota // Go has an interesting iota concept + bbTop // uninitialized + bbNonHeader // a regular BB + bbReducible // reducible loop + bbSelf // single BB loop + bbIrreducible // irreducible loop + bbDead // a dead BB + bbLast // sentinel +) + +// UnionFindNode is used in the Union/Find algorithm to collapse +// complete loops into a single node. These nodes and the +// corresponding functionality are implemented with this class +// +type UnionFindNode struct { + parent *UnionFindNode + bb *BasicBlock + loop *SimpleLoop + dfsNumber int +} + +// Init explicitly initializes UnionFind nodes. +// +func (u *UnionFindNode) Init(bb *BasicBlock, dfsNumber int) { + u.parent = u + u.bb = bb + u.dfsNumber = dfsNumber + u.loop = nil +} + +// FindSet implements the Find part of the Union/Find Algorithm +// +// Implemented with Path Compression (inner loops are only +// visited and collapsed once, however, deep nests would still +// result in significant traversals). +// +func (u *UnionFindNode) FindSet() *UnionFindNode { + var nodeList []*UnionFindNode + node := u + + for ; node != node.parent; node = node.parent { + if node.parent != node.parent.parent { + nodeList = append(nodeList, node) + } + + } + + // Path Compression, all nodes' parents point to the 1st level parent. + for _, ll := range nodeList { + ll.parent = node.parent + } + + return node +} + +// Union relies on path compression. +// +func (u *UnionFindNode) Union(B *UnionFindNode) { + u.parent = B +} + +// Constants +// +// Marker for uninitialized nodes. +const unvisited = -1 + +// Safeguard against pathological algorithm behavior. +const maxNonBackPreds = 32 * 1024 + +// IsAncestor +// +// As described in the paper, determine whether a node 'w' is a +// "true" ancestor for node 'v'. +// +// Dominance can be tested quickly using a pre-order trick +// for depth-first spanning trees. This is why DFS is the first +// thing we run below. +// +// Go comment: Parameters can be written as w,v int, inlike in C, where +// each parameter needs its own type. +// +func isAncestor(w, v int, last []int) bool { + return ((w <= v) && (v <= last[w])) +} + +// listContainsNode +// +// Check whether a list contains a specific element. +// +func listContainsNode(l []*UnionFindNode, u *UnionFindNode) bool { + for _, ll := range l { + if ll == u { + return true + } + } + return false +} + +// DFS - Depth-First-Search and node numbering. +// +func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number []int, last []int, current int) int { + nodes[current].Init(currentNode, current) + number[currentNode.Name] = current + + lastid := current + for _, target := range currentNode.OutEdges { + if number[target.Name] == unvisited { + lastid = DFS(target, nodes, number, last, lastid+1) + } + } + last[number[currentNode.Name]] = lastid + return lastid +} + +// FindLoops +// +// Find loops and build loop forest using Havlak's algorithm, which +// is derived from Tarjan. Variable names and step numbering has +// been chosen to be identical to the nomenclature in Havlak's +// paper (which, in turn, is similar to the one used by Tarjan). +// +func FindLoops(cfgraph *CFG, lsgraph *LSG) { + if cfgraph.Start == nil { + return + } + + size := cfgraph.NumNodes() + + nonBackPreds := make([]map[int]bool, size) + backPreds := make([][]int, size) + + number := make([]int, size) + header := make([]int, size, size) + types := make([]int, size, size) + last := make([]int, size, size) + nodes := make([]*UnionFindNode, size, size) + + for i := 0; i < size; i++ { + nodes[i] = new(UnionFindNode) + } + + // Step a: + // - initialize all nodes as unvisited. + // - depth-first traversal and numbering. + // - unreached BB's are marked as dead. + // + for i, bb := range cfgraph.Blocks { + number[bb.Name] = unvisited + nonBackPreds[i] = make(map[int]bool) + } + + DFS(cfgraph.Start, 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 := 0; w < size; w++ { + header[w] = 0 + types[w] = bbNonHeader + + nodeW := nodes[w].bb + if nodeW == nil { + types[w] = bbDead + continue // dead BB + } + + if nodeW.NumPred() > 0 { + for _, nodeV := range nodeW.InEdges { + v := number[nodeV.Name] + if v == unvisited { + continue // dead node + } + + if isAncestor(w, v, last) { + backPreds[w] = append(backPreds[w], v) + } else { + nonBackPreds[w][v] = true + } + } + } + } + + // 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 := size - 1; w >= 0; w-- { + // this is 'P' in Havlak's paper + var nodePool []*UnionFindNode + + nodeW := nodes[w].bb + if nodeW == nil { + continue // dead BB + } + + // Step d: + for _, v := range backPreds[w] { + if v != w { + nodePool = append(nodePool, nodes[v].FindSet()) + } else { + types[w] = bbSelf + } + } + + // Copy nodePool to workList. + // + workList := append([]*UnionFindNode(nil), nodePool...) + + if len(nodePool) != 0 { + types[w] = bbReducible + } + + // work the list... + // + for len(workList) > 0 { + x := workList[0] + workList = workList[1:] + + // 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. + // + nonBackSize := len(nonBackPreds[x.dfsNumber]) + if nonBackSize > maxNonBackPreds { + return + } + + for iter := range nonBackPreds[x.dfsNumber] { + y := nodes[iter] + ydash := y.FindSet() + + if !isAncestor(w, ydash.dfsNumber, last) { + types[w] = bbIrreducible + nonBackPreds[w][ydash.dfsNumber] = true + } else { + if ydash.dfsNumber != w { + if !listContainsNode(nodePool, ydash) { + workList = append(workList, ydash) + nodePool = append(nodePool, ydash) + } + } + } + } + } + + // Collapse/Unionize nodes in a SCC to a single node + // For every SCC found, create a loop descriptor and link it in. + // + if (len(nodePool) > 0) || (types[w] == bbSelf) { + loop := lsgraph.NewLoop() + + loop.SetHeader(nodeW) + if types[w] != bbIrreducible { + loop.IsReducible = true + } + + // 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: + // type[w] != BasicBlockClass.bbIrreducible + // + nodes[w].loop = loop + + for _, node := range nodePool { + // Add nodes to loop descriptor. + header[node.dfsNumber] = w + node.Union(nodes[w]) + + // Nested loops are not added, but linked together. + if node.loop != nil { + node.loop.Parent = loop + } else { + loop.AddNode(node.bb) + } + } + + lsgraph.AddLoop(loop) + } // nodePool.size + } // Step c + +} + +// External entry point. +func FindHavlakLoops(cfgraph *CFG, lsgraph *LSG) int { + FindLoops(cfgraph, lsgraph) + return lsgraph.NumLoops() +} + +//====================================================== +// Scaffold Code +//====================================================== + +// Basic representation of loops, a loop has an entry point, +// one or more exit edges, a set of basic blocks, and potentially +// an outer loop - a "parent" loop. +// +// Furthermore, it can have any set of properties, e.g., +// it can be an irreducible loop, have control flow, be +// a candidate for transformations, and what not. +// +type SimpleLoop struct { + // No set, use map to bool + basicBlocks map[*BasicBlock]bool + Children map[*SimpleLoop]bool + Parent *SimpleLoop + header *BasicBlock + + IsRoot bool + IsReducible bool + Counter int + NestingLevel int + DepthLevel int +} + +func (loop *SimpleLoop) AddNode(bb *BasicBlock) { + loop.basicBlocks[bb] = true +} + +func (loop *SimpleLoop) AddChildLoop(child *SimpleLoop) { + loop.Children[child] = true +} + +func (loop *SimpleLoop) Dump(indent int) { + for i := 0; i < indent; i++ { + fmt.Printf(" ") + } + + // No ? operator ? + fmt.Printf("loop-%d nest: %d depth %d ", + loop.Counter, loop.NestingLevel, loop.DepthLevel) + if !loop.IsReducible { + fmt.Printf("(Irreducible) ") + } + + // must have > 0 + if len(loop.Children) > 0 { + fmt.Printf("Children: ") + for ll := range loop.Children { + fmt.Printf("loop-%d", ll.Counter) + } + } + if len(loop.basicBlocks) > 0 { + fmt.Printf("(") + for bb := range loop.basicBlocks { + fmt.Printf("BB#%06d ", bb.Name) + if loop.header == bb { + fmt.Printf("*") + } + } + fmt.Printf("\b)") + } + fmt.Printf("\n") +} + +func (loop *SimpleLoop) SetParent(parent *SimpleLoop) { + loop.Parent = parent + loop.Parent.AddChildLoop(loop) +} + +func (loop *SimpleLoop) SetHeader(bb *BasicBlock) { + loop.AddNode(bb) + loop.header = bb +} + +//------------------------------------ +// Helper (No templates or such) +// +func max(x, y int) int { + if x > y { + return x + } + return y +} + +// LoopStructureGraph +// +// Maintain loop structure for a given CFG. +// +// Two values are maintained for this loop graph, depth, and nesting level. +// For example: +// +// loop nesting level depth +//---------------------------------------- +// loop-0 2 0 +// loop-1 1 1 +// loop-3 1 1 +// loop-2 0 2 +// +var loopCounter = 0 + +type LSG struct { + root *SimpleLoop + loops []*SimpleLoop +} + +func NewLSG() *LSG { + lsg := new(LSG) + lsg.root = lsg.NewLoop() + lsg.root.NestingLevel = 0 + + return lsg +} + +func (lsg *LSG) NewLoop() *SimpleLoop { + loop := new(SimpleLoop) + loop.basicBlocks = make(map[*BasicBlock]bool) + loop.Children = make(map[*SimpleLoop]bool) + loop.Parent = nil + loop.header = nil + + loop.Counter = loopCounter + loopCounter++ + return loop +} + +func (lsg *LSG) AddLoop(loop *SimpleLoop) { + lsg.loops = append(lsg.loops, loop) +} + +func (lsg *LSG) Dump() { + lsg.dump(lsg.root, 0) +} + +func (lsg *LSG) dump(loop *SimpleLoop, indent int) { + loop.Dump(indent) + + for ll := range loop.Children { + lsg.dump(ll, indent+1) + } +} + +func (lsg *LSG) CalculateNestingLevel() { + for _, sl := range lsg.loops { + if sl.IsRoot { + continue + } + if sl.Parent == nil { + sl.SetParent(lsg.root) + } + } + lsg.calculateNestingLevel(lsg.root, 0) +} + +func (lsg *LSG) calculateNestingLevel(loop *SimpleLoop, depth int) { + loop.DepthLevel = depth + for ll := range loop.Children { + lsg.calculateNestingLevel(ll, depth+1) + + ll.NestingLevel = max(loop.NestingLevel, ll.NestingLevel+1) + } +} + +func (lsg *LSG) NumLoops() int { + return len(lsg.loops) +} + +func (lsg *LSG) Root() *SimpleLoop { + return lsg.root +} + +//====================================================== +// Testing Code +//====================================================== + +func buildDiamond(cfgraph *CFG, start int) int { + bb0 := start + NewBasicBlockEdge(cfgraph, bb0, bb0+1) + NewBasicBlockEdge(cfgraph, bb0, bb0+2) + NewBasicBlockEdge(cfgraph, bb0+1, bb0+3) + NewBasicBlockEdge(cfgraph, bb0+2, bb0+3) + + return bb0 + 3 +} + +func buildConnect(cfgraph *CFG, start int, end int) { + NewBasicBlockEdge(cfgraph, start, end) +} + +func buildStraight(cfgraph *CFG, start int, n int) int { + for i := 0; i < n; i++ { + buildConnect(cfgraph, start+i, start+i+1) + } + return start + n +} + +func buildBaseLoop(cfgraph *CFG, from int) int { + header := buildStraight(cfgraph, from, 1) + diamond1 := buildDiamond(cfgraph, header) + d11 := buildStraight(cfgraph, diamond1, 1) + diamond2 := buildDiamond(cfgraph, d11) + footer := buildStraight(cfgraph, diamond2, 1) + buildConnect(cfgraph, diamond2, d11) + buildConnect(cfgraph, diamond1, header) + + buildConnect(cfgraph, footer, from) + footer = buildStraight(cfgraph, footer, 1) + return footer +} + +var cpuprofile = flag.String("cpuprofile", "cpu2.prof", "write cpu profile to this file") + +func main() { + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + lsgraph := NewLSG() + cfgraph := NewCFG() + + cfgraph.CreateNode(0) // top + cfgraph.CreateNode(1) // bottom + NewBasicBlockEdge(cfgraph, 0, 2) + + for dummyloop := 0; dummyloop < 15000; dummyloop++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + n := 2 + + for parlooptrees := 0; parlooptrees < 10; parlooptrees++ { + cfgraph.CreateNode(n + 1) + buildConnect(cfgraph, 2, n+1) + n = n + 1 + + for i := 0; i < 100; i++ { + top := n + n = buildStraight(cfgraph, n, 1) + for j := 0; j < 25; j++ { + n = buildBaseLoop(cfgraph, n) + } + bottom := buildStraight(cfgraph, n, 1) + buildConnect(cfgraph, n, top) + n = bottom + } + buildConnect(cfgraph, n, 1) + } + + FindHavlakLoops(cfgraph, lsgraph) + + for i := 0; i < 50; i++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + fmt.Printf("# of loops: %d (including 1 artificial root node)\n", lsgraph.NumLoops()) + lsgraph.CalculateNestingLevel() +} diff --git a/havlak/havlak3.go b/havlak/havlak3.go new file mode 100644 index 0000000..dde834b --- /dev/null +++ b/havlak/havlak3.go @@ -0,0 +1,714 @@ +package main + +/* + @Auth:ShenZ + @Description: +*/ +import ( + "flag" + "fmt" + "log" + "os" + "runtime/pprof" +) + +type BasicBlock struct { + Name int + InEdges []*BasicBlock + OutEdges []*BasicBlock +} + +func NewBasicBlock(name int) *BasicBlock { + return &BasicBlock{Name: name} +} + +func (bb *BasicBlock) Dump() { + fmt.Printf("BB#%06d:", bb.Name) + if len(bb.InEdges) > 0 { + fmt.Printf(" in :") + for _, iter := range bb.InEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + if len(bb.OutEdges) > 0 { + fmt.Print(" out:") + for _, iter := range bb.OutEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + fmt.Printf("\n") +} + +func (bb *BasicBlock) NumPred() int { + return len(bb.InEdges) +} + +func (bb *BasicBlock) NumSucc() int { + return len(bb.OutEdges) +} + +func (bb *BasicBlock) AddInEdge(from *BasicBlock) { + bb.InEdges = append(bb.InEdges, from) +} + +func (bb *BasicBlock) AddOutEdge(to *BasicBlock) { + bb.OutEdges = append(bb.OutEdges, to) +} + +//----------------------------------------------------------- + +type CFG struct { + Blocks []*BasicBlock + Start *BasicBlock +} + +func NewCFG() *CFG { + return &CFG{} +} + +func (cfg *CFG) NumNodes() int { + return len(cfg.Blocks) +} + +func (cfg *CFG) CreateNode(node int) *BasicBlock { + if node < len(cfg.Blocks) { + return cfg.Blocks[node] + } + if node != len(cfg.Blocks) { + println("oops", node, len(cfg.Blocks)) + panic("wtf") + } + bblock := NewBasicBlock(node) + cfg.Blocks = append(cfg.Blocks, bblock) + + if len(cfg.Blocks) == 1 { + cfg.Start = bblock + } + + return bblock +} + +func (cfg *CFG) Dump() { + for _, n := range cfg.Blocks { + n.Dump() + } +} + +//----------------------------------------------------------- + +type BasicBlockEdge struct { + Dst *BasicBlock + Src *BasicBlock +} + +func NewBasicBlockEdge(cfg *CFG, from int, to int) *BasicBlockEdge { + self := new(BasicBlockEdge) + self.Src = cfg.CreateNode(from) + self.Dst = cfg.CreateNode(to) + + self.Src.AddOutEdge(self.Dst) + self.Dst.AddInEdge(self.Src) + + return self +} + +//----------------------------------------------------------- +// Basic Blocks and Loops are being classified as regular, irreducible, +// and so on. This enum contains a symbolic name for all these classifications +// +const ( + _ = iota // Go has an interesting iota concept + bbTop // uninitialized + bbNonHeader // a regular BB + bbReducible // reducible loop + bbSelf // single BB loop + bbIrreducible // irreducible loop + bbDead // a dead BB + bbLast // sentinel +) + +// UnionFindNode is used in the Union/Find algorithm to collapse +// complete loops into a single node. These nodes and the +// corresponding functionality are implemented with this class +// +type UnionFindNode struct { + parent *UnionFindNode + bb *BasicBlock + loop *SimpleLoop + dfsNumber int +} + +// Init explicitly initializes UnionFind nodes. +// +func (u *UnionFindNode) Init(bb *BasicBlock, dfsNumber int) { + u.parent = u + u.bb = bb + u.dfsNumber = dfsNumber + u.loop = nil +} + +// FindSet implements the Find part of the Union/Find Algorithm +// +// Implemented with Path Compression (inner loops are only +// visited and collapsed once, however, deep nests would still +// result in significant traversals). +// +func (u *UnionFindNode) FindSet() *UnionFindNode { + var nodeList []*UnionFindNode + node := u + + for ; node != node.parent; node = node.parent { + if node.parent != node.parent.parent { + nodeList = append(nodeList, node) + } + + } + + // Path Compression, all nodes' parents point to the 1st level parent. + for _, ll := range nodeList { + ll.parent = node.parent + } + + return node +} + +// Union relies on path compression. +// +func (u *UnionFindNode) Union(B *UnionFindNode) { + u.parent = B +} + +// Constants +// +// Marker for uninitialized nodes. +const unvisited = -1 + +// Safeguard against pathological algorithm behavior. +const maxNonBackPreds = 32 * 1024 + +// IsAncestor +// +// As described in the paper, determine whether a node 'w' is a +// "true" ancestor for node 'v'. +// +// Dominance can be tested quickly using a pre-order trick +// for depth-first spanning trees. This is why DFS is the first +// thing we run below. +// +// Go comment: Parameters can be written as w,v int, inlike in C, where +// each parameter needs its own type. +// +func isAncestor(w, v int, last []int) bool { + return ((w <= v) && (v <= last[w])) +} + +// listContainsNode +// +// Check whether a list contains a specific element. +// +func listContainsNode(l []*UnionFindNode, u *UnionFindNode) bool { + for _, ll := range l { + if ll == u { + return true + } + } + return false +} + +// DFS - Depth-First-Search and node numbering. +// +func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number []int, last []int, current int) int { + nodes[current].Init(currentNode, current) + number[currentNode.Name] = current + + lastid := current + for _, target := range currentNode.OutEdges { + if number[target.Name] == unvisited { + lastid = DFS(target, nodes, number, last, lastid+1) + } + } + last[number[currentNode.Name]] = lastid + return lastid +} + +// FindLoops +// +// Find loops and build loop forest using Havlak's algorithm, which +// is derived from Tarjan. Variable names and step numbering has +// been chosen to be identical to the nomenclature in Havlak's +// paper (which, in turn, is similar to the one used by Tarjan). +// +func FindLoops(cfgraph *CFG, lsgraph *LSG) { + if cfgraph.Start == nil { + return + } + + size := cfgraph.NumNodes() + + nonBackPreds := make([]map[int]bool, size) + backPreds := make([][]int, size) + + number := make([]int, size) + header := make([]int, size, size) + types := make([]int, size, size) + last := make([]int, size, size) + nodes := make([]*UnionFindNode, size, size) + + for i := 0; i < size; i++ { + nodes[i] = new(UnionFindNode) + } + + // Step a: + // - initialize all nodes as unvisited. + // - depth-first traversal and numbering. + // - unreached BB's are marked as dead. + // + for i, bb := range cfgraph.Blocks { + number[bb.Name] = unvisited + nonBackPreds[i] = make(map[int]bool) + } + + DFS(cfgraph.Start, 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 := 0; w < size; w++ { + header[w] = 0 + types[w] = bbNonHeader + + nodeW := nodes[w].bb + if nodeW == nil { + types[w] = bbDead + continue // dead BB + } + + if nodeW.NumPred() > 0 { + for _, nodeV := range nodeW.InEdges { + v := number[nodeV.Name] + if v == unvisited { + continue // dead node + } + + if isAncestor(w, v, last) { + backPreds[w] = append(backPreds[w], v) + } else { + nonBackPreds[w][v] = true + } + } + } + } + + // 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 := size - 1; w >= 0; w-- { + // this is 'P' in Havlak's paper + var nodePool []*UnionFindNode + + nodeW := nodes[w].bb + if nodeW == nil { + continue // dead BB + } + + // Step d: + for _, v := range backPreds[w] { + if v != w { + nodePool = append(nodePool, nodes[v].FindSet()) + } else { + types[w] = bbSelf + } + } + + // Copy nodePool to workList. + // + workList := append([]*UnionFindNode(nil), nodePool...) + + if len(nodePool) != 0 { + types[w] = bbReducible + } + + // work the list... + // + for len(workList) > 0 { + x := workList[0] + workList = workList[1:] + + // 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. + // + nonBackSize := len(nonBackPreds[x.dfsNumber]) + if nonBackSize > maxNonBackPreds { + return + } + + for iter := range nonBackPreds[x.dfsNumber] { + y := nodes[iter] + ydash := y.FindSet() + + if !isAncestor(w, ydash.dfsNumber, last) { + types[w] = bbIrreducible + nonBackPreds[w][ydash.dfsNumber] = true + } else { + if ydash.dfsNumber != w { + if !listContainsNode(nodePool, ydash) { + workList = append(workList, ydash) + nodePool = append(nodePool, ydash) + } + } + } + } + } + + // Collapse/Unionize nodes in a SCC to a single node + // For every SCC found, create a loop descriptor and link it in. + // + if (len(nodePool) > 0) || (types[w] == bbSelf) { + loop := lsgraph.NewLoop() + + loop.SetHeader(nodeW) + if types[w] != bbIrreducible { + loop.IsReducible = true + } + + // 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: + // type[w] != BasicBlockClass.bbIrreducible + // + nodes[w].loop = loop + + for _, node := range nodePool { + // Add nodes to loop descriptor. + header[node.dfsNumber] = w + node.Union(nodes[w]) + + // Nested loops are not added, but linked together. + if node.loop != nil { + node.loop.Parent = loop + } else { + loop.AddNode(node.bb) + } + } + + lsgraph.AddLoop(loop) + } // nodePool.size + } // Step c + +} + +// External entry point. +func FindHavlakLoops(cfgraph *CFG, lsgraph *LSG) int { + FindLoops(cfgraph, lsgraph) + return lsgraph.NumLoops() +} + +//====================================================== +// Scaffold Code +//====================================================== + +// Basic representation of loops, a loop has an entry point, +// one or more exit edges, a set of basic blocks, and potentially +// an outer loop - a "parent" loop. +// +// Furthermore, it can have any set of properties, e.g., +// it can be an irreducible loop, have control flow, be +// a candidate for transformations, and what not. +// +type SimpleLoop struct { + // No set, use map to bool + basicBlocks map[*BasicBlock]bool + Children map[*SimpleLoop]bool + Parent *SimpleLoop + header *BasicBlock + + IsRoot bool + IsReducible bool + Counter int + NestingLevel int + DepthLevel int +} + +func (loop *SimpleLoop) AddNode(bb *BasicBlock) { + loop.basicBlocks[bb] = true +} + +func (loop *SimpleLoop) AddChildLoop(child *SimpleLoop) { + loop.Children[child] = true +} + +func (loop *SimpleLoop) Dump(indent int) { + for i := 0; i < indent; i++ { + fmt.Printf(" ") + } + + // No ? operator ? + fmt.Printf("loop-%d nest: %d depth %d ", + loop.Counter, loop.NestingLevel, loop.DepthLevel) + if !loop.IsReducible { + fmt.Printf("(Irreducible) ") + } + + // must have > 0 + if len(loop.Children) > 0 { + fmt.Printf("Children: ") + for ll := range loop.Children { + fmt.Printf("loop-%d", ll.Counter) + } + } + if len(loop.basicBlocks) > 0 { + fmt.Printf("(") + for bb := range loop.basicBlocks { + fmt.Printf("BB#%06d ", bb.Name) + if loop.header == bb { + fmt.Printf("*") + } + } + fmt.Printf("\b)") + } + fmt.Printf("\n") +} + +func (loop *SimpleLoop) SetParent(parent *SimpleLoop) { + loop.Parent = parent + loop.Parent.AddChildLoop(loop) +} + +func (loop *SimpleLoop) SetHeader(bb *BasicBlock) { + loop.AddNode(bb) + loop.header = bb +} + +//------------------------------------ +// Helper (No templates or such) +// +func max(x, y int) int { + if x > y { + return x + } + return y +} + +// LoopStructureGraph +// +// Maintain loop structure for a given CFG. +// +// Two values are maintained for this loop graph, depth, and nesting level. +// For example: +// +// loop nesting level depth +//---------------------------------------- +// loop-0 2 0 +// loop-1 1 1 +// loop-3 1 1 +// loop-2 0 2 +// +var loopCounter = 0 + +type LSG struct { + root *SimpleLoop + loops []*SimpleLoop +} + +func NewLSG() *LSG { + lsg := new(LSG) + lsg.root = lsg.NewLoop() + lsg.root.NestingLevel = 0 + + return lsg +} + +func (lsg *LSG) NewLoop() *SimpleLoop { + loop := new(SimpleLoop) + loop.basicBlocks = make(map[*BasicBlock]bool) + loop.Children = make(map[*SimpleLoop]bool) + loop.Parent = nil + loop.header = nil + + loop.Counter = loopCounter + loopCounter++ + return loop +} + +func (lsg *LSG) AddLoop(loop *SimpleLoop) { + lsg.loops = append(lsg.loops, loop) +} + +func (lsg *LSG) Dump() { + lsg.dump(lsg.root, 0) +} + +func (lsg *LSG) dump(loop *SimpleLoop, indent int) { + loop.Dump(indent) + + for ll := range loop.Children { + lsg.dump(ll, indent+1) + } +} + +func (lsg *LSG) CalculateNestingLevel() { + for _, sl := range lsg.loops { + if sl.IsRoot { + continue + } + if sl.Parent == nil { + sl.SetParent(lsg.root) + } + } + lsg.calculateNestingLevel(lsg.root, 0) +} + +func (lsg *LSG) calculateNestingLevel(loop *SimpleLoop, depth int) { + loop.DepthLevel = depth + for ll := range loop.Children { + lsg.calculateNestingLevel(ll, depth+1) + + ll.NestingLevel = max(loop.NestingLevel, ll.NestingLevel+1) + } +} + +func (lsg *LSG) NumLoops() int { + return len(lsg.loops) +} + +func (lsg *LSG) Root() *SimpleLoop { + return lsg.root +} + +//====================================================== +// Testing Code +//====================================================== + +func buildDiamond(cfgraph *CFG, start int) int { + bb0 := start + NewBasicBlockEdge(cfgraph, bb0, bb0+1) + NewBasicBlockEdge(cfgraph, bb0, bb0+2) + NewBasicBlockEdge(cfgraph, bb0+1, bb0+3) + NewBasicBlockEdge(cfgraph, bb0+2, bb0+3) + + return bb0 + 3 +} + +func buildConnect(cfgraph *CFG, start int, end int) { + NewBasicBlockEdge(cfgraph, start, end) +} + +func buildStraight(cfgraph *CFG, start int, n int) int { + for i := 0; i < n; i++ { + buildConnect(cfgraph, start+i, start+i+1) + } + return start + n +} + +func buildBaseLoop(cfgraph *CFG, from int) int { + header := buildStraight(cfgraph, from, 1) + diamond1 := buildDiamond(cfgraph, header) + d11 := buildStraight(cfgraph, diamond1, 1) + diamond2 := buildDiamond(cfgraph, d11) + footer := buildStraight(cfgraph, diamond2, 1) + buildConnect(cfgraph, diamond2, d11) + buildConnect(cfgraph, diamond1, header) + + buildConnect(cfgraph, footer, from) + footer = buildStraight(cfgraph, footer, 1) + return footer +} + +var cpuprofile = flag.String("cpuprofile", "cpu3.prof", "write cpu profile to this file") +var memprofile = flag.String("memprofile", "mem3.prof", "write memory profile to this file") + +func main() { + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + lsgraph := NewLSG() + cfgraph := NewCFG() + + cfgraph.CreateNode(0) // top + cfgraph.CreateNode(1) // bottom + NewBasicBlockEdge(cfgraph, 0, 2) + + for dummyloop := 0; dummyloop < 15000; dummyloop++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + n := 2 + + for parlooptrees := 0; parlooptrees < 10; parlooptrees++ { + cfgraph.CreateNode(n + 1) + buildConnect(cfgraph, 2, n+1) + n = n + 1 + + for i := 0; i < 100; i++ { + top := n + n = buildStraight(cfgraph, n, 1) + for j := 0; j < 25; j++ { + n = buildBaseLoop(cfgraph, n) + } + bottom := buildStraight(cfgraph, n, 1) + buildConnect(cfgraph, n, top) + n = bottom + } + buildConnect(cfgraph, n, 1) + } + + FindHavlakLoops(cfgraph, lsgraph) + if *memprofile != "" { + f, err := os.Create(*memprofile) + if err != nil { + log.Fatal(err) + } + pprof.WriteHeapProfile(f) + f.Close() + return + } + + for i := 0; i < 50; i++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + fmt.Printf("# of loops: %d (including 1 artificial root node)\n", lsgraph.NumLoops()) + lsgraph.CalculateNestingLevel() +} diff --git a/havlak/havlak4.go b/havlak/havlak4.go new file mode 100644 index 0000000..dfaebef --- /dev/null +++ b/havlak/havlak4.go @@ -0,0 +1,720 @@ +package main + +/* + @Auth:ShenZ + @Description: +*/ +import ( + "flag" + "fmt" + "log" + "os" + "runtime/pprof" +) + +type BasicBlock struct { + Name int + InEdges []*BasicBlock + OutEdges []*BasicBlock +} + +func NewBasicBlock(name int) *BasicBlock { + return &BasicBlock{Name: name} +} + +func (bb *BasicBlock) Dump() { + fmt.Printf("BB#%06d:", bb.Name) + if len(bb.InEdges) > 0 { + fmt.Printf(" in :") + for _, iter := range bb.InEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + if len(bb.OutEdges) > 0 { + fmt.Print(" out:") + for _, iter := range bb.OutEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + fmt.Printf("\n") +} + +func (bb *BasicBlock) NumPred() int { + return len(bb.InEdges) +} + +func (bb *BasicBlock) NumSucc() int { + return len(bb.OutEdges) +} + +func (bb *BasicBlock) AddInEdge(from *BasicBlock) { + bb.InEdges = append(bb.InEdges, from) +} + +func (bb *BasicBlock) AddOutEdge(to *BasicBlock) { + bb.OutEdges = append(bb.OutEdges, to) +} + +//----------------------------------------------------------- + +type CFG struct { + Blocks []*BasicBlock + Start *BasicBlock +} + +func NewCFG() *CFG { + return &CFG{} +} + +func (cfg *CFG) NumNodes() int { + return len(cfg.Blocks) +} + +func (cfg *CFG) CreateNode(node int) *BasicBlock { + if node < len(cfg.Blocks) { + return cfg.Blocks[node] + } + if node != len(cfg.Blocks) { + println("oops", node, len(cfg.Blocks)) + panic("wtf") + } + bblock := NewBasicBlock(node) + cfg.Blocks = append(cfg.Blocks, bblock) + + if len(cfg.Blocks) == 1 { + cfg.Start = bblock + } + + return bblock +} + +func (cfg *CFG) Dump() { + for _, n := range cfg.Blocks { + n.Dump() + } +} + +//----------------------------------------------------------- + +type BasicBlockEdge struct { + Dst *BasicBlock + Src *BasicBlock +} + +func NewBasicBlockEdge(cfg *CFG, from int, to int) *BasicBlockEdge { + self := new(BasicBlockEdge) + self.Src = cfg.CreateNode(from) + self.Dst = cfg.CreateNode(to) + + self.Src.AddOutEdge(self.Dst) + self.Dst.AddInEdge(self.Src) + + return self +} + +//----------------------------------------------------------- +// Basic Blocks and Loops are being classified as regular, irreducible, +// and so on. This enum contains a symbolic name for all these classifications +// +const ( + _ = iota // Go has an interesting iota concept + bbTop // uninitialized + bbNonHeader // a regular BB + bbReducible // reducible loop + bbSelf // single BB loop + bbIrreducible // irreducible loop + bbDead // a dead BB + bbLast // sentinel +) + +// UnionFindNode is used in the Union/Find algorithm to collapse +// complete loops into a single node. These nodes and the +// corresponding functionality are implemented with this class +// +type UnionFindNode struct { + parent *UnionFindNode + bb *BasicBlock + loop *SimpleLoop + dfsNumber int +} + +// Init explicitly initializes UnionFind nodes. +// +func (u *UnionFindNode) Init(bb *BasicBlock, dfsNumber int) { + u.parent = u + u.bb = bb + u.dfsNumber = dfsNumber + u.loop = nil +} + +// FindSet implements the Find part of the Union/Find Algorithm +// +// Implemented with Path Compression (inner loops are only +// visited and collapsed once, however, deep nests would still +// result in significant traversals). +// +func (u *UnionFindNode) FindSet() *UnionFindNode { + var nodeList []*UnionFindNode + node := u + + for ; node != node.parent; node = node.parent { + if node.parent != node.parent.parent { + nodeList = append(nodeList, node) + } + + } + + // Path Compression, all nodes' parents point to the 1st level parent. + for _, ll := range nodeList { + ll.parent = node.parent + } + + return node +} + +// Union relies on path compression. +// +func (u *UnionFindNode) Union(B *UnionFindNode) { + u.parent = B +} + +// Constants +// +// Marker for uninitialized nodes. +const unvisited = -1 + +// Safeguard against pathological algorithm behavior. +const maxNonBackPreds = 32 * 1024 + +// IsAncestor +// +// As described in the paper, determine whether a node 'w' is a +// "true" ancestor for node 'v'. +// +// Dominance can be tested quickly using a pre-order trick +// for depth-first spanning trees. This is why DFS is the first +// thing we run below. +// +// Go comment: Parameters can be written as w,v int, inlike in C, where +// each parameter needs its own type. +// +func isAncestor(w, v int, last []int) bool { + return ((w <= v) && (v <= last[w])) +} + +// listContainsNode +// +// Check whether a list contains a specific element. +// +func listContainsNode(l []*UnionFindNode, u *UnionFindNode) bool { + for _, ll := range l { + if ll == u { + return true + } + } + return false +} + +// DFS - Depth-First-Search and node numbering. +// +func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number []int, last []int, current int) int { + nodes[current].Init(currentNode, current) + number[currentNode.Name] = current + + lastid := current + for _, target := range currentNode.OutEdges { + if number[target.Name] == unvisited { + lastid = DFS(target, nodes, number, last, lastid+1) + } + } + last[number[currentNode.Name]] = lastid + return lastid +} + +func appendUnique(a []int, x int) []int { + for _, y := range a { + if x == y { + return a + } + } + return append(a, x) +} + +// FindLoops +// +// Find loops and build loop forest using Havlak's algorithm, which +// is derived from Tarjan. Variable names and step numbering has +// been chosen to be identical to the nomenclature in Havlak's +// paper (which, in turn, is similar to the one used by Tarjan). +// +func FindLoops(cfgraph *CFG, lsgraph *LSG) { + if cfgraph.Start == nil { + return + } + + size := cfgraph.NumNodes() + + nonBackPreds := make([][]int, size) + backPreds := make([][]int, size) + + number := make([]int, size) + header := make([]int, size, size) + types := make([]int, size, size) + last := make([]int, size, size) + nodes := make([]*UnionFindNode, size, size) + + for i := 0; i < size; i++ { + nodes[i] = new(UnionFindNode) + } + + // Step a: + // - initialize all nodes as unvisited. + // - depth-first traversal and numbering. + // - unreached BB's are marked as dead. + // + for _, bb := range cfgraph.Blocks { + number[bb.Name] = unvisited + } + + DFS(cfgraph.Start, 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 := 0; w < size; w++ { + header[w] = 0 + types[w] = bbNonHeader + + nodeW := nodes[w].bb + if nodeW == nil { + types[w] = bbDead + continue // dead BB + } + + if nodeW.NumPred() > 0 { + for _, nodeV := range nodeW.InEdges { + v := number[nodeV.Name] + if v == unvisited { + continue // dead node + } + + if isAncestor(w, v, last) { + backPreds[w] = append(backPreds[w], v) + } else { + nonBackPreds[w] = appendUnique(nonBackPreds[w], v) + } + } + } + } + + // 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 := size - 1; w >= 0; w-- { + // this is 'P' in Havlak's paper + var nodePool []*UnionFindNode + + nodeW := nodes[w].bb + if nodeW == nil { + continue // dead BB + } + + // Step d: + for _, v := range backPreds[w] { + if v != w { + nodePool = append(nodePool, nodes[v].FindSet()) + } else { + types[w] = bbSelf + } + } + + // Copy nodePool to workList. + // + workList := append([]*UnionFindNode(nil), nodePool...) + + if len(nodePool) != 0 { + types[w] = bbReducible + } + + // work the list... + // + for len(workList) > 0 { + x := workList[0] + workList = workList[1:] + + // 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. + // + nonBackSize := len(nonBackPreds[x.dfsNumber]) + if nonBackSize > maxNonBackPreds { + return + } + + for _, iter := range nonBackPreds[x.dfsNumber] { + y := nodes[iter] + ydash := y.FindSet() + + if !isAncestor(w, ydash.dfsNumber, last) { + types[w] = bbIrreducible + nonBackPreds[w] = appendUnique(nonBackPreds[w], ydash.dfsNumber) + } else { + if ydash.dfsNumber != w { + if !listContainsNode(nodePool, ydash) { + workList = append(workList, ydash) + nodePool = append(nodePool, ydash) + } + } + } + } + } + + // Collapse/Unionize nodes in a SCC to a single node + // For every SCC found, create a loop descriptor and link it in. + // + if (len(nodePool) > 0) || (types[w] == bbSelf) { + loop := lsgraph.NewLoop() + + loop.SetHeader(nodeW) + if types[w] != bbIrreducible { + loop.IsReducible = true + } + + // 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: + // type[w] != BasicBlockClass.bbIrreducible + // + nodes[w].loop = loop + + for _, node := range nodePool { + // Add nodes to loop descriptor. + header[node.dfsNumber] = w + node.Union(nodes[w]) + + // Nested loops are not added, but linked together. + if node.loop != nil { + node.loop.Parent = loop + } else { + loop.AddNode(node.bb) + } + } + + lsgraph.AddLoop(loop) + } // nodePool.size + } // Step c + +} + +// External entry point. +func FindHavlakLoops(cfgraph *CFG, lsgraph *LSG) int { + FindLoops(cfgraph, lsgraph) + return lsgraph.NumLoops() +} + +//====================================================== +// Scaffold Code +//====================================================== + +// Basic representation of loops, a loop has an entry point, +// one or more exit edges, a set of basic blocks, and potentially +// an outer loop - a "parent" loop. +// +// Furthermore, it can have any set of properties, e.g., +// it can be an irreducible loop, have control flow, be +// a candidate for transformations, and what not. +// +type SimpleLoop struct { + // No set, use map to bool + basicBlocks []*BasicBlock + Children []*SimpleLoop + Parent *SimpleLoop + header *BasicBlock + + IsRoot bool + IsReducible bool + Counter int + NestingLevel int + DepthLevel int +} + +func (loop *SimpleLoop) AddNode(bb *BasicBlock) { + loop.basicBlocks = append(loop.basicBlocks, bb) +} + +func (loop *SimpleLoop) AddChildLoop(child *SimpleLoop) { + loop.Children = append(loop.Children, child) +} + +func (loop *SimpleLoop) Dump(indent int) { + for i := 0; i < indent; i++ { + fmt.Printf(" ") + } + + // No ? operator ? + fmt.Printf("loop-%d nest: %d depth %d ", + loop.Counter, loop.NestingLevel, loop.DepthLevel) + if !loop.IsReducible { + fmt.Printf("(Irreducible) ") + } + + // must have > 0 + if len(loop.Children) > 0 { + fmt.Printf("Children: ") + for _, ll := range loop.Children { + fmt.Printf("loop-%d", ll.Counter) + } + } + if len(loop.basicBlocks) > 0 { + fmt.Printf("(") + for _, bb := range loop.basicBlocks { + fmt.Printf("BB#%06d ", bb.Name) + if loop.header == bb { + fmt.Printf("*") + } + } + fmt.Printf("\b)") + } + fmt.Printf("\n") +} + +func (loop *SimpleLoop) SetParent(parent *SimpleLoop) { + loop.Parent = parent + loop.Parent.AddChildLoop(loop) +} + +func (loop *SimpleLoop) SetHeader(bb *BasicBlock) { + loop.AddNode(bb) + loop.header = bb +} + +//------------------------------------ +// Helper (No templates or such) +// +func max(x, y int) int { + if x > y { + return x + } + return y +} + +// LoopStructureGraph +// +// Maintain loop structure for a given CFG. +// +// Two values are maintained for this loop graph, depth, and nesting level. +// For example: +// +// loop nesting level depth +//---------------------------------------- +// loop-0 2 0 +// loop-1 1 1 +// loop-3 1 1 +// loop-2 0 2 +// +var loopCounter = 0 + +type LSG struct { + root *SimpleLoop + loops []*SimpleLoop +} + +func NewLSG() *LSG { + lsg := new(LSG) + lsg.root = lsg.NewLoop() + lsg.root.NestingLevel = 0 + + return lsg +} + +func (lsg *LSG) NewLoop() *SimpleLoop { + loop := new(SimpleLoop) + loop.Parent = nil + loop.header = nil + + loop.Counter = loopCounter + loopCounter++ + return loop +} + +func (lsg *LSG) AddLoop(loop *SimpleLoop) { + lsg.loops = append(lsg.loops, loop) +} + +func (lsg *LSG) Dump() { + lsg.dump(lsg.root, 0) +} + +func (lsg *LSG) dump(loop *SimpleLoop, indent int) { + loop.Dump(indent) + + for _, ll := range loop.Children { + lsg.dump(ll, indent+1) + } +} + +func (lsg *LSG) CalculateNestingLevel() { + for _, sl := range lsg.loops { + if sl.IsRoot { + continue + } + if sl.Parent == nil { + sl.SetParent(lsg.root) + } + } + lsg.calculateNestingLevel(lsg.root, 0) +} + +func (lsg *LSG) calculateNestingLevel(loop *SimpleLoop, depth int) { + loop.DepthLevel = depth + for _, ll := range loop.Children { + lsg.calculateNestingLevel(ll, depth+1) + + ll.NestingLevel = max(loop.NestingLevel, ll.NestingLevel+1) + } +} + +func (lsg *LSG) NumLoops() int { + return len(lsg.loops) +} + +func (lsg *LSG) Root() *SimpleLoop { + return lsg.root +} + +//====================================================== +// Testing Code +//====================================================== + +func buildDiamond(cfgraph *CFG, start int) int { + bb0 := start + NewBasicBlockEdge(cfgraph, bb0, bb0+1) + NewBasicBlockEdge(cfgraph, bb0, bb0+2) + NewBasicBlockEdge(cfgraph, bb0+1, bb0+3) + NewBasicBlockEdge(cfgraph, bb0+2, bb0+3) + + return bb0 + 3 +} + +func buildConnect(cfgraph *CFG, start int, end int) { + NewBasicBlockEdge(cfgraph, start, end) +} + +func buildStraight(cfgraph *CFG, start int, n int) int { + for i := 0; i < n; i++ { + buildConnect(cfgraph, start+i, start+i+1) + } + return start + n +} + +func buildBaseLoop(cfgraph *CFG, from int) int { + header := buildStraight(cfgraph, from, 1) + diamond1 := buildDiamond(cfgraph, header) + d11 := buildStraight(cfgraph, diamond1, 1) + diamond2 := buildDiamond(cfgraph, d11) + footer := buildStraight(cfgraph, diamond2, 1) + buildConnect(cfgraph, diamond2, d11) + buildConnect(cfgraph, diamond1, header) + + buildConnect(cfgraph, footer, from) + footer = buildStraight(cfgraph, footer, 1) + return footer +} + +var cpuprofile = flag.String("cpuprofile", "cpu4.prof", "write cpu profile to this file") +var memprofile = flag.String("memprofile", "mem4.prof", "write memory profile to this file") + +func main() { + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + lsgraph := NewLSG() + cfgraph := NewCFG() + + cfgraph.CreateNode(0) // top + cfgraph.CreateNode(1) // bottom + NewBasicBlockEdge(cfgraph, 0, 2) + + for dummyloop := 0; dummyloop < 15000; dummyloop++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + n := 2 + + for parlooptrees := 0; parlooptrees < 10; parlooptrees++ { + cfgraph.CreateNode(n + 1) + buildConnect(cfgraph, 2, n+1) + n = n + 1 + + for i := 0; i < 100; i++ { + top := n + n = buildStraight(cfgraph, n, 1) + for j := 0; j < 25; j++ { + n = buildBaseLoop(cfgraph, n) + } + bottom := buildStraight(cfgraph, n, 1) + buildConnect(cfgraph, n, top) + n = bottom + } + buildConnect(cfgraph, n, 1) + } + + FindHavlakLoops(cfgraph, lsgraph) + if *memprofile != "" { + f, err := os.Create(*memprofile) + if err != nil { + log.Fatal(err) + } + pprof.WriteHeapProfile(f) + f.Close() + return + } + + for i := 0; i < 50; i++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + fmt.Printf("# of loops: %d (including 1 artificial root node)\n", lsgraph.NumLoops()) + lsgraph.CalculateNestingLevel() +} diff --git a/havlak/havlak5.go b/havlak/havlak5.go new file mode 100644 index 0000000..a0fba0e --- /dev/null +++ b/havlak/havlak5.go @@ -0,0 +1,746 @@ +package main + +/* + @Auth:ShenZ + @Description: +*/ +import ( + "flag" + "fmt" + "log" + "os" + "runtime/pprof" +) + +type BasicBlock struct { + Name int + InEdges []*BasicBlock + OutEdges []*BasicBlock +} + +func NewBasicBlock(name int) *BasicBlock { + return &BasicBlock{Name: name} +} + +func (bb *BasicBlock) Dump() { + fmt.Printf("BB#%06d:", bb.Name) + if len(bb.InEdges) > 0 { + fmt.Printf(" in :") + for _, iter := range bb.InEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + if len(bb.OutEdges) > 0 { + fmt.Print(" out:") + for _, iter := range bb.OutEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + fmt.Printf("\n") +} + +func (bb *BasicBlock) NumPred() int { + return len(bb.InEdges) +} + +func (bb *BasicBlock) NumSucc() int { + return len(bb.OutEdges) +} + +func (bb *BasicBlock) AddInEdge(from *BasicBlock) { + bb.InEdges = append(bb.InEdges, from) +} + +func (bb *BasicBlock) AddOutEdge(to *BasicBlock) { + bb.OutEdges = append(bb.OutEdges, to) +} + +//----------------------------------------------------------- + +type CFG struct { + Blocks []*BasicBlock + Start *BasicBlock +} + +func NewCFG() *CFG { + return &CFG{} +} + +func (cfg *CFG) NumNodes() int { + return len(cfg.Blocks) +} + +func (cfg *CFG) CreateNode(node int) *BasicBlock { + if node < len(cfg.Blocks) { + return cfg.Blocks[node] + } + if node != len(cfg.Blocks) { + println("oops", node, len(cfg.Blocks)) + panic("wtf") + } + bblock := NewBasicBlock(node) + cfg.Blocks = append(cfg.Blocks, bblock) + + if len(cfg.Blocks) == 1 { + cfg.Start = bblock + } + + return bblock +} + +func (cfg *CFG) Dump() { + for _, n := range cfg.Blocks { + n.Dump() + } +} + +//----------------------------------------------------------- + +type BasicBlockEdge struct { + Dst *BasicBlock + Src *BasicBlock +} + +func NewBasicBlockEdge(cfg *CFG, from int, to int) *BasicBlockEdge { + self := new(BasicBlockEdge) + self.Src = cfg.CreateNode(from) + self.Dst = cfg.CreateNode(to) + + self.Src.AddOutEdge(self.Dst) + self.Dst.AddInEdge(self.Src) + + return self +} + +//----------------------------------------------------------- +// Basic Blocks and Loops are being classified as regular, irreducible, +// and so on. This enum contains a symbolic name for all these classifications +// +const ( + _ = iota // Go has an interesting iota concept + bbTop // uninitialized + bbNonHeader // a regular BB + bbReducible // reducible loop + bbSelf // single BB loop + bbIrreducible // irreducible loop + bbDead // a dead BB + bbLast // sentinel +) + +// UnionFindNode is used in the Union/Find algorithm to collapse +// complete loops into a single node. These nodes and the +// corresponding functionality are implemented with this class +// +type UnionFindNode struct { + parent *UnionFindNode + bb *BasicBlock + loop *SimpleLoop + dfsNumber int +} + +// Init explicitly initializes UnionFind nodes. +// +func (u *UnionFindNode) Init(bb *BasicBlock, dfsNumber int) { + u.parent = u + u.bb = bb + u.dfsNumber = dfsNumber + u.loop = nil +} + +// FindSet implements the Find part of the Union/Find Algorithm +// +// Implemented with Path Compression (inner loops are only +// visited and collapsed once, however, deep nests would still +// result in significant traversals). +// +func (u *UnionFindNode) FindSet() *UnionFindNode { + var nodeList []*UnionFindNode + node := u + + for ; node != node.parent; node = node.parent { + if node.parent != node.parent.parent { + nodeList = append(nodeList, node) + } + + } + + // Path Compression, all nodes' parents point to the 1st level parent. + for _, ll := range nodeList { + ll.parent = node.parent + } + + return node +} + +// Union relies on path compression. +// +func (u *UnionFindNode) Union(B *UnionFindNode) { + u.parent = B +} + +// Constants +// +// Marker for uninitialized nodes. +const unvisited = -1 + +// Safeguard against pathological algorithm behavior. +const maxNonBackPreds = 32 * 1024 + +// IsAncestor +// +// As described in the paper, determine whether a node 'w' is a +// "true" ancestor for node 'v'. +// +// Dominance can be tested quickly using a pre-order trick +// for depth-first spanning trees. This is why DFS is the first +// thing we run below. +// +// Go comment: Parameters can be written as w,v int, inlike in C, where +// each parameter needs its own type. +// +func isAncestor(w, v int, last []int) bool { + return ((w <= v) && (v <= last[w])) +} + +// listContainsNode +// +// Check whether a list contains a specific element. +// +func listContainsNode(l []*UnionFindNode, u *UnionFindNode) bool { + for _, ll := range l { + if ll == u { + return true + } + } + return false +} + +// DFS - Depth-First-Search and node numbering. +// +func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number []int, last []int, current int) int { + nodes[current].Init(currentNode, current) + number[currentNode.Name] = current + + lastid := current + for _, target := range currentNode.OutEdges { + if number[target.Name] == unvisited { + lastid = DFS(target, nodes, number, last, lastid+1) + } + } + last[number[currentNode.Name]] = lastid + return lastid +} + +func appendUnique(a []int, x int) []int { + for _, y := range a { + if x == y { + return a + } + } + return append(a, x) +} + +var cache struct { + size int + nonBackPreds [][]int + backPreds [][]int + number []int + header []int + types []int + last []int + nodes []*UnionFindNode +} + +// FindLoops +// +// Find loops and build loop forest using Havlak's algorithm, which +// is derived from Tarjan. Variable names and step numbering has +// been chosen to be identical to the nomenclature in Havlak's +// paper (which, in turn, is similar to the one used by Tarjan). +// +func FindLoops(cfgraph *CFG, lsgraph *LSG) { + if cfgraph.Start == nil { + return + } + + size := cfgraph.NumNodes() + + if cache.size < size { + cache.size = size + cache.nonBackPreds = make([][]int, size) + cache.backPreds = make([][]int, size) + cache.number = make([]int, size) + cache.header = make([]int, size) + cache.types = make([]int, size) + cache.last = make([]int, size) + cache.nodes = make([]*UnionFindNode, size) + for i := range cache.nodes { + cache.nodes[i] = new(UnionFindNode) + } + } + + nonBackPreds := cache.nonBackPreds[:size] + for i := range nonBackPreds { + nonBackPreds[i] = nonBackPreds[i][:0] + } + backPreds := cache.backPreds[:size] + for i := range nonBackPreds { + backPreds[i] = backPreds[i][:0] + } + number := cache.number[:size] + header := cache.header[:size] + types := cache.types[:size] + last := cache.last[:size] + nodes := cache.nodes[:size] + + // Step a: + // - initialize all nodes as unvisited. + // - depth-first traversal and numbering. + // - unreached BB's are marked as dead. + // + for _, bb := range cfgraph.Blocks { + number[bb.Name] = unvisited + } + + DFS(cfgraph.Start, 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 := 0; w < size; w++ { + header[w] = 0 + types[w] = bbNonHeader + + nodeW := nodes[w].bb + if nodeW == nil { + types[w] = bbDead + continue // dead BB + } + + if nodeW.NumPred() > 0 { + for _, nodeV := range nodeW.InEdges { + v := number[nodeV.Name] + if v == unvisited { + continue // dead node + } + + if isAncestor(w, v, last) { + backPreds[w] = append(backPreds[w], v) + } else { + nonBackPreds[w] = appendUnique(nonBackPreds[w], v) + } + } + } + } + + // 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 := size - 1; w >= 0; w-- { + // this is 'P' in Havlak's paper + var nodePool []*UnionFindNode + + nodeW := nodes[w].bb + if nodeW == nil { + continue // dead BB + } + + // Step d: + for _, v := range backPreds[w] { + if v != w { + nodePool = append(nodePool, nodes[v].FindSet()) + } else { + types[w] = bbSelf + } + } + + // Copy nodePool to workList. + // + workList := append([]*UnionFindNode(nil), nodePool...) + + if len(nodePool) != 0 { + types[w] = bbReducible + } + + // work the list... + // + for len(workList) > 0 { + x := workList[0] + workList = workList[1:] + + // 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. + // + nonBackSize := len(nonBackPreds[x.dfsNumber]) + if nonBackSize > maxNonBackPreds { + return + } + + for _, iter := range nonBackPreds[x.dfsNumber] { + y := nodes[iter] + ydash := y.FindSet() + + if !isAncestor(w, ydash.dfsNumber, last) { + types[w] = bbIrreducible + nonBackPreds[w] = appendUnique(nonBackPreds[w], ydash.dfsNumber) + } else { + if ydash.dfsNumber != w { + if !listContainsNode(nodePool, ydash) { + workList = append(workList, ydash) + nodePool = append(nodePool, ydash) + } + } + } + } + } + + // Collapse/Unionize nodes in a SCC to a single node + // For every SCC found, create a loop descriptor and link it in. + // + if (len(nodePool) > 0) || (types[w] == bbSelf) { + loop := lsgraph.NewLoop() + + loop.SetHeader(nodeW) + if types[w] != bbIrreducible { + loop.IsReducible = true + } + + // 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: + // type[w] != BasicBlockClass.bbIrreducible + // + nodes[w].loop = loop + + for _, node := range nodePool { + // Add nodes to loop descriptor. + header[node.dfsNumber] = w + node.Union(nodes[w]) + + // Nested loops are not added, but linked together. + if node.loop != nil { + node.loop.Parent = loop + } else { + loop.AddNode(node.bb) + } + } + + lsgraph.AddLoop(loop) + } // nodePool.size + } // Step c + +} + +// External entry point. +func FindHavlakLoops(cfgraph *CFG, lsgraph *LSG) int { + FindLoops(cfgraph, lsgraph) + return lsgraph.NumLoops() +} + +//====================================================== +// Scaffold Code +//====================================================== + +// Basic representation of loops, a loop has an entry point, +// one or more exit edges, a set of basic blocks, and potentially +// an outer loop - a "parent" loop. +// +// Furthermore, it can have any set of properties, e.g., +// it can be an irreducible loop, have control flow, be +// a candidate for transformations, and what not. +// +type SimpleLoop struct { + // No set, use map to bool + basicBlocks []*BasicBlock + Children []*SimpleLoop + Parent *SimpleLoop + header *BasicBlock + + IsRoot bool + IsReducible bool + Counter int + NestingLevel int + DepthLevel int +} + +func (loop *SimpleLoop) AddNode(bb *BasicBlock) { + loop.basicBlocks = append(loop.basicBlocks, bb) +} + +func (loop *SimpleLoop) AddChildLoop(child *SimpleLoop) { + loop.Children = append(loop.Children, child) +} + +func (loop *SimpleLoop) Dump(indent int) { + for i := 0; i < indent; i++ { + fmt.Printf(" ") + } + + // No ? operator ? + fmt.Printf("loop-%d nest: %d depth %d ", + loop.Counter, loop.NestingLevel, loop.DepthLevel) + if !loop.IsReducible { + fmt.Printf("(Irreducible) ") + } + + // must have > 0 + if len(loop.Children) > 0 { + fmt.Printf("Children: ") + for _, ll := range loop.Children { + fmt.Printf("loop-%d", ll.Counter) + } + } + if len(loop.basicBlocks) > 0 { + fmt.Printf("(") + for _, bb := range loop.basicBlocks { + fmt.Printf("BB#%06d ", bb.Name) + if loop.header == bb { + fmt.Printf("*") + } + } + fmt.Printf("\b)") + } + fmt.Printf("\n") +} + +func (loop *SimpleLoop) SetParent(parent *SimpleLoop) { + loop.Parent = parent + loop.Parent.AddChildLoop(loop) +} + +func (loop *SimpleLoop) SetHeader(bb *BasicBlock) { + loop.AddNode(bb) + loop.header = bb +} + +//------------------------------------ +// Helper (No templates or such) +// +func max(x, y int) int { + if x > y { + return x + } + return y +} + +// LoopStructureGraph +// +// Maintain loop structure for a given CFG. +// +// Two values are maintained for this loop graph, depth, and nesting level. +// For example: +// +// loop nesting level depth +//---------------------------------------- +// loop-0 2 0 +// loop-1 1 1 +// loop-3 1 1 +// loop-2 0 2 +// +var loopCounter = 0 + +type LSG struct { + root *SimpleLoop + loops []*SimpleLoop +} + +func NewLSG() *LSG { + lsg := new(LSG) + lsg.root = lsg.NewLoop() + lsg.root.NestingLevel = 0 + + return lsg +} + +func (lsg *LSG) NewLoop() *SimpleLoop { + loop := new(SimpleLoop) + loop.Parent = nil + loop.header = nil + + loop.Counter = loopCounter + loopCounter++ + return loop +} + +func (lsg *LSG) AddLoop(loop *SimpleLoop) { + lsg.loops = append(lsg.loops, loop) +} + +func (lsg *LSG) Dump() { + lsg.dump(lsg.root, 0) +} + +func (lsg *LSG) dump(loop *SimpleLoop, indent int) { + loop.Dump(indent) + + for _, ll := range loop.Children { + lsg.dump(ll, indent+1) + } +} + +func (lsg *LSG) CalculateNestingLevel() { + for _, sl := range lsg.loops { + if sl.IsRoot { + continue + } + if sl.Parent == nil { + sl.SetParent(lsg.root) + } + } + lsg.calculateNestingLevel(lsg.root, 0) +} + +func (lsg *LSG) calculateNestingLevel(loop *SimpleLoop, depth int) { + loop.DepthLevel = depth + for _, ll := range loop.Children { + lsg.calculateNestingLevel(ll, depth+1) + + ll.NestingLevel = max(loop.NestingLevel, ll.NestingLevel+1) + } +} + +func (lsg *LSG) NumLoops() int { + return len(lsg.loops) +} + +func (lsg *LSG) Root() *SimpleLoop { + return lsg.root +} + +//====================================================== +// Testing Code +//====================================================== + +func buildDiamond(cfgraph *CFG, start int) int { + bb0 := start + NewBasicBlockEdge(cfgraph, bb0, bb0+1) + NewBasicBlockEdge(cfgraph, bb0, bb0+2) + NewBasicBlockEdge(cfgraph, bb0+1, bb0+3) + NewBasicBlockEdge(cfgraph, bb0+2, bb0+3) + + return bb0 + 3 +} + +func buildConnect(cfgraph *CFG, start int, end int) { + NewBasicBlockEdge(cfgraph, start, end) +} + +func buildStraight(cfgraph *CFG, start int, n int) int { + for i := 0; i < n; i++ { + buildConnect(cfgraph, start+i, start+i+1) + } + return start + n +} + +func buildBaseLoop(cfgraph *CFG, from int) int { + header := buildStraight(cfgraph, from, 1) + diamond1 := buildDiamond(cfgraph, header) + d11 := buildStraight(cfgraph, diamond1, 1) + diamond2 := buildDiamond(cfgraph, d11) + footer := buildStraight(cfgraph, diamond2, 1) + buildConnect(cfgraph, diamond2, d11) + buildConnect(cfgraph, diamond1, header) + + buildConnect(cfgraph, footer, from) + footer = buildStraight(cfgraph, footer, 1) + return footer +} + +var cpuprofile = flag.String("cpuprofile", "cpu5.prof", "write cpu profile to this file") +var memprofile = flag.String("memprofile", "mem5.prof", "write memory profile to this file") + +func main() { + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + lsgraph := NewLSG() + cfgraph := NewCFG() + + cfgraph.CreateNode(0) // top + cfgraph.CreateNode(1) // bottom + NewBasicBlockEdge(cfgraph, 0, 2) + + for dummyloop := 0; dummyloop < 15000; dummyloop++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + n := 2 + + for parlooptrees := 0; parlooptrees < 10; parlooptrees++ { + cfgraph.CreateNode(n + 1) + buildConnect(cfgraph, 2, n+1) + n = n + 1 + + for i := 0; i < 100; i++ { + top := n + n = buildStraight(cfgraph, n, 1) + for j := 0; j < 25; j++ { + n = buildBaseLoop(cfgraph, n) + } + bottom := buildStraight(cfgraph, n, 1) + buildConnect(cfgraph, n, top) + n = bottom + } + buildConnect(cfgraph, n, 1) + } + + FindHavlakLoops(cfgraph, lsgraph) + if *memprofile != "" { + f, err := os.Create(*memprofile) + if err != nil { + log.Fatal(err) + } + pprof.WriteHeapProfile(f) + f.Close() + return + } + + for i := 0; i < 50; i++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + fmt.Printf("# of loops: %d (including 1 artificial root node)\n", lsgraph.NumLoops()) + lsgraph.CalculateNestingLevel() +} diff --git a/havlak/havlak6.cc.go b/havlak/havlak6.cc.go new file mode 100644 index 0000000..5790b66 --- /dev/null +++ b/havlak/havlak6.cc.go @@ -0,0 +1,401 @@ +#include +#include +#include +#include + +using namespace std; + +class Block { +public: +Block(int n) : name(n) {} + +int name; +vector in; +vector out; + +string String(); +void Dump(FILE*); +}; + +string Block::String() { +char buf[20]; +snprintf(buf, sizeof buf, "b%d", this->name); +return buf; +} + +void Block::Dump(FILE *f) { +fprintf(f, "%s: [", this->String().c_str()); +for (int i = 0; i < this->in.size(); i++) +fprintf(f, "%s%s", i > 0 ? " " : "", this->in[i]->String().c_str()); +fprintf(f, "] ["); +for (int i = 0; i < this->out.size(); i++) +fprintf(f, "%s%s", i > 0 ? " " : "", this->out[i]->String().c_str()); +fprintf(f, "]\n"); +} + +struct Edge { +Edge(int s, int d) : src(s), dst(d) {} +int src, dst; +}; + +class CFG { +public: +vector block; +vector edge; + +Block *NewBlock(); +void Connect(Block *src, Block *dst); +Block *Path(Block *from); +Block *Diamond(Block *from); +Block *BaseLoop(Block *from); +void Dump(FILE*); +}; + +Block *CFG::NewBlock() { +Block *b = new Block(this->block.size()); +this->block.push_back(b); +return b; +} + +void CFG::Dump(FILE *f) { +for (int i = 0; i < this->block.size(); i++) +this->block[i]->Dump(f); +} + +void CFG::Connect(Block *src, Block *dst) { +src->out.push_back(dst); +dst->in.push_back(src); +this->edge.push_back(Edge(src->name, dst->name)); +} + +Block *CFG::Path(Block *from) { +Block *n = this->NewBlock(); +this->Connect(from, n); +return n; +} + +Block *CFG::Diamond(Block *from) { +Block *x = this->Path(from); +Block *y = this->Path(from); +Block *z = this->Path(x); +this->Connect(y, z); +this->Connect(z, from); +return z; +} + +Block *CFG::BaseLoop(Block *from) { +Block *z = this->Path(this->Diamond(this->Path(this->Diamond(this->Path(from))))); +this->Connect(z, from); +return this->Path(z); +} + +CFG *BuildGraph() { +CFG *g = new CFG; + +Block *n0 = g->NewBlock(); +Block *n1 = g->NewBlock(); +Block *n2 = g->NewBlock(); +g->Connect(n0, n2); + +for (int i = 0; i < 10; i++) { +Block *n = g->NewBlock(); +g->Connect(n2, n); + +for (int j = 0; j < 100; j++) { +Block *top = n; +n = g->Path(n); +for (int k = 0; k < 25; k++) { +n = g->BaseLoop(n); +} +Block *bottom = g->Path(n); +g->Connect(n, top); +n = bottom; +} +g->Connect(n, n1); +} +return g; +} + +// Basic representation of loop graph. + +class Loop { +public: +vector block; +vector child; +Loop *parent; +Block *head; + +bool isRoot; +bool isReducible; +int counter; +int nesting; +int depth; + +}; + +class LoopGraph { +public: +Loop root; +vector loop; +~LoopGraph(); + +Loop *NewLoop(int cap); +void CalculateNesting(); +void calculateNesting(Loop* l, int depth); +}; + +LoopGraph::~LoopGraph() { +for (int i = 0; i < this->loop.size(); i++) +delete this->loop[i]; +} + +static int loopCounter = 0; + +Loop *LoopGraph::NewLoop(int cap) { +loopCounter++; +Loop *l = new Loop; +l->counter = loopCounter; +l->block.reserve(cap); +this->loop.push_back(l); +return l; +} + +void LoopGraph::CalculateNesting() { +for (int i = 0; i < this->loop.size(); i++) { +Loop *l = this->loop[i]; +if (l->isRoot) +continue; +if (l->parent == NULL) { +l->parent = &this->root; +this->root.child.push_back(l); +} +} +this->calculateNesting(&this->root, 0); +} + +void LoopGraph::calculateNesting(Loop *l, int depth) { +l->depth = depth; +for (int i = 0; i < l->child.size(); i++) { +Loop *child = l->child[i]; +this->calculateNesting(child, depth+1); +int n = child->nesting + 1; +if (l->nesting < n) +l->nesting = n; +} +} + +// TODO: Dump, String + +// Loop finding state, generated or reused on each iteration. + +class LoopBlock { +public: +enum Type { +NonHeader, +Reducible, +Self, +Irreducible, +Dead, +}; + +Block *block; +Loop *loop; +int first; +int last; +LoopBlock *header; // TODO: head +Type type; +vector backPred; +vector nonBackPred; +LoopBlock *unionf; + +void Init(Block*); +LoopBlock *Find(); +bool IsAncestor(LoopBlock*); + +}; + +class LoopFinder { +public: +vector loopBlock; +vector depthFirst; +vector pool; + +void Search(Block*); +void FindLoops(CFG*, LoopGraph*); +}; + +const int Unvisited = -1; + +void LoopBlock::Init(Block *b) { +this->block = b; +this->loop = NULL; +this->first = Unvisited; +this->last = Unvisited; +this->header = NULL; +this->type = LoopBlock::NonHeader; +this->backPred.clear(); +this->nonBackPred.clear(); +this->unionf = this; +} + +LoopBlock *LoopBlock::Find() { +if (this->unionf != this) { +this->unionf = this->unionf->Find(); +} +return this->unionf; +} + +// Depth first search to number blocks. + +void LoopFinder::Search(Block *b) { +LoopBlock *lb = &this->loopBlock[b->name]; +this->depthFirst.push_back(lb); +lb->first = this->depthFirst.size(); +for (int i = 0; i < b->out.size(); i++) { +Block *out = b->out[i]; +if (this->loopBlock[out->name].first == Unvisited) +this->Search(out); +} +lb->last = this->depthFirst.size(); +} + +bool LoopBlock::IsAncestor(LoopBlock *p) { +return this->first <= p->first && p->first <= this->last; +} + +void LoopFinder::FindLoops(CFG *g, LoopGraph *lsg) { +int size = g->block.size(); +if (size == 0) +return; + +// Step A: Initialize nodes, depth first numbering, mark dead nodes. +this->loopBlock.resize(size); +this->depthFirst.reserve(size); +this->depthFirst.clear(); +for (int i = 0; i < size; i++) +this->loopBlock[i].Init(g->block[i]); +this->Search(g->block[0]); +for (int i = 0; i < size; i++ ){ +LoopBlock *lb = &this->loopBlock[i]; // TODO +if (lb->first == Unvisited) +lb->type = LoopBlock::Dead; +} + +// Step B: Classify back edges as coming from descendents or not. +for (int i = 0; i < this->depthFirst.size(); i++) { +LoopBlock *lb = this->depthFirst[i]; +for (int j = 0; j < lb->block->in.size(); j++) { +Block *b = lb->block->in[j]; +LoopBlock *lbb = &this->loopBlock[b->name]; // TODO +if (lb->IsAncestor(lbb)) +lb->backPred.push_back(lbb); +else +lb->nonBackPred.push_back(lbb); +} +} + +// Start node is root of all other loops. +this->loopBlock[0].header = &this->loopBlock[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 (int i = this->depthFirst.size() - 1; i >= 0; i--) { +LoopBlock *w = this->depthFirst[i]; + +this->pool.clear(); + +// Step D. +for (int i = 0; i < w->backPred.size(); i++) { +LoopBlock* pred = w->backPred[i]; +if (w == pred) { +w->type = LoopBlock::Self; +continue; +} +this->pool.push_back(pred->Find()); +} + +// Process node pool in order as work list. +for (int i = 0; i < this->pool.size(); i++) { +LoopBlock *x = this->pool[i]; + +// 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-> +for (int j = 0; j < x->nonBackPred.size(); j++) { +LoopBlock *y = x->nonBackPred[j]; +LoopBlock *ydash = y->Find(); +if (!w->IsAncestor(ydash)) { +w->type = LoopBlock::Irreducible; +if (find(w->nonBackPred.begin(), w->nonBackPred.end(), y) == w->nonBackPred.end()) +w->nonBackPred.push_back(y); +} else if (ydash != w) { +if (find(this->pool.begin(), this->pool.end(), ydash) == this->pool.end()) +this->pool.push_back(ydash); +} +} +} + +// Collapse/Unionize nodes in a SCC to a single node +// For every SCC found, create a loop descriptor and link it in. +if (this->pool.size() > 0 || w->type == LoopBlock::Self) { +Loop *l = lsg->NewLoop(1 + pool.size()); +l->head = w->block; +l->block.push_back(w->block); +l->isReducible = w->type != LoopBlock::Irreducible; +w->loop = l; + +// 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() +for (int i = 0; i < pool.size(); i++) { +LoopBlock *node = pool[i]; +// Add nodes to loop descriptor. +node->header = w; +node->unionf = w; + +// Nested loops are not added, but linked together. +if (node->loop != NULL) { +node->loop->parent = l; +} else { +l->block.push_back(node->block); +} +} +} +} +} + +// Main program. + +int main() { +LoopFinder f; + +CFG *g = BuildGraph(); +LoopGraph lsg; +f.FindLoops(g, &lsg); + +for (int i = 0; i < 50; i++) { +LoopGraph lsg; +f.FindLoops(g, &lsg); +} + +printf("# of loops: %d (including 1 artificial root node)\n", (int)lsg.loop.size()); +lsg.CalculateNesting(); +} \ No newline at end of file diff --git a/havlak/havlak6.go b/havlak/havlak6.go new file mode 100644 index 0000000..01c68c5 --- /dev/null +++ b/havlak/havlak6.go @@ -0,0 +1,467 @@ +package main + +/* + @Auth:ShenZ + @Description: +*/ +import ( + "flag" + "fmt" + "io" + "log" + "os" + "runtime/pprof" +) + +// Control flow graph, created once. + +type Block struct { + Name int + In []*Block + Out []*Block +} + +func (b *Block) String() string { + return fmt.Sprintf("b%d", b.Name) +} + +func (b *Block) Dump(w io.Writer) { + fmt.Fprintf(w, "%s: %v %v\n", b, b.In, b.Out) +} + +type CFG struct { + Block []*Block + Edge []Edge +} + +type Edge struct { + Src, Dst int +} + +func (g *CFG) NewBlock() *Block { + b := &Block{Name: len(g.Block)} + g.Block = append(g.Block, b) + return b +} + +func (g *CFG) Dump(w io.Writer) { + for _, b := range g.Block { + b.Dump(w) + } +} + +func (g *CFG) Connect(src, dst *Block) { + src.Out = append(src.Out, dst) + dst.In = append(dst.In, src) + g.Edge = append(g.Edge, Edge{src.Name, dst.Name}) +} + +func (g *CFG) Path(from *Block) *Block { + n := g.NewBlock() + g.Connect(from, n) + return n +} + +func (g *CFG) Diamond(from *Block) *Block { + x := g.Path(from) + y := g.Path(from) + z := g.Path(x) + g.Connect(y, z) + g.Connect(z, from) + return z +} + +func (g *CFG) BaseLoop(from *Block) *Block { + z := g.Path(g.Diamond(g.Path(g.Diamond(g.Path(from))))) + g.Connect(z, from) + return g.Path(z) +} + +func buildGraph() *CFG { + g := new(CFG) + + n0 := g.NewBlock() + n1 := g.NewBlock() + n2 := g.NewBlock() + g.Connect(n0, n2) + + for i := 0; i < 10; i++ { + n := g.NewBlock() + g.Connect(n2, n) + + for j := 0; j < 100; j++ { + top := n + n = g.Path(n) + for k := 0; k < 25; k++ { + n = g.BaseLoop(n) + } + bottom := g.Path(n) + g.Connect(n, top) + n = bottom + } + g.Connect(n, n1) + } + return g +} + +// Basic representation of loop graph. + +type LoopGraph struct { + Root Loop + Loop []*Loop +} + +type Loop struct { + Block []*Block + Child []*Loop + Parent *Loop + Head *Block + + IsRoot bool + IsReducible bool + Counter int + Nesting int + Depth int +} + +var loopCounter = 0 + +func (g *LoopGraph) Clear() { + g.Root.Child = g.Root.Child[:0] + g.Loop = g.Loop[:0] +} + +func (g *LoopGraph) NewLoop(lcap int) *Loop { + // If there's a cached loop, use that. + if n := len(g.Loop); n < cap(g.Loop) && g.Loop[:n+1][n] != nil { + g.Loop = g.Loop[:n+1] + l := g.Loop[n] + l.Block = l.Block[:0] + l.Child = l.Child[:0] + l.Parent = nil + l.Head = nil + l.IsRoot = false + l.IsReducible = false + l.Nesting = 0 + l.Depth = 0 + return l + } + + loopCounter++ + l := &Loop{Counter: loopCounter} + g.Loop = append(g.Loop, l) + l.Block = make([]*Block, 0, lcap) + return l +} + +func (g *LoopGraph) CalculateNesting() { + for _, l := range g.Loop { + if l == nil { + panic("nil l") + } + if l.IsRoot { + continue + } + if l.Parent == nil { + l.Parent = &g.Root + g.Root.Child = append(g.Root.Child, l) + } + } + g.calculateNesting(&g.Root, 0) +} + +func (g *LoopGraph) calculateNesting(l *Loop, depth int) { + l.Depth = depth + for _, child := range l.Child { + g.calculateNesting(child, depth+1) + if n := child.Nesting + 1; l.Nesting < n { + l.Nesting = n + } + } +} + +func (g *LoopGraph) Dump(w io.Writer) { + g.dump(w, &g.Root, 0) +} + +func (g *LoopGraph) dump(w io.Writer, l *Loop, indent int) { + l.Dump(w, indent) + + for _, child := range l.Child { + g.dump(w, child, indent+1) + } +} + +func (l *Loop) String() string { + return fmt.Sprintf("loop-%d", l.Counter) +} + +func (l *Loop) Dump(w io.Writer, indent int) { + fmt.Fprintf(w, "%*sloop-%d nest: %d depth %d", + 2*indent, l.Counter, l.Nesting, l.Depth) + if !l.IsReducible { + fmt.Fprintf(w, " (Irreducible)") + } + if len(l.Child) > 0 { + fmt.Fprintf(w, " Children: %v", l.Child) + } + if len(l.Block) > 0 { + fmt.Fprintf(w, "(") + sep := "" + for _, b := range l.Block { + fmt.Fprint(w, sep, b) + if b == l.Head { + fmt.Fprint(w, "*") + } + sep = " " + } + fmt.Fprintf(w, ")") + } + fmt.Fprintf(w, "\n") +} + +// Loop finding state, generated or reused on each iteration. + +type LoopFinder struct { + LoopBlock []LoopBlock + DepthFirst []*LoopBlock + Pool []*LoopBlock +} + +const Unvisited = -1 + +type LoopType int + +const ( + bbNonHeader LoopType = 1 + iota // a regular BB + bbReducible // reducible loop + bbSelf // single BB loop + bbIrreducible // irreducible loop + bbDead // a dead BB +) + +type LoopBlock struct { + Block *Block + Loop *Loop + First int + Last int + Header *LoopBlock + Type LoopType + BackPred []*LoopBlock + NonBackPred []*LoopBlock + + Union *LoopBlock // union find +} + +func (lb *LoopBlock) Init(b *Block) { + lb.Block = b + lb.Loop = nil + lb.First = Unvisited + lb.Last = Unvisited + lb.Header = nil + lb.Type = bbNonHeader + lb.BackPred = lb.BackPred[:0] + lb.NonBackPred = lb.NonBackPred[:0] + lb.Union = lb +} + +func (lb *LoopBlock) Find() *LoopBlock { + if lb.Union != lb { + lb.Union = lb.Union.Find() + } + return lb.Union +} + +// Depth first search to number blocks. + +func (f *LoopFinder) Search(b *Block) { + lb := &f.LoopBlock[b.Name] + f.DepthFirst = append(f.DepthFirst, lb) + lb.First = len(f.DepthFirst) + for _, out := range b.Out { + if f.LoopBlock[out.Name].First == Unvisited { + f.Search(out) + } + } + lb.Last = len(f.DepthFirst) +} + +func (lb *LoopBlock) IsAncestor(p *LoopBlock) bool { + return lb.First <= p.First && p.First <= lb.Last +} + +func (f *LoopFinder) FindLoops(g *CFG, lsg *LoopGraph) { + size := len(g.Block) + if size == 0 { + return + } + + // Step A: Initialize nodes, depth first numbering, mark dead nodes. + if size <= cap(f.LoopBlock) { + f.LoopBlock = f.LoopBlock[:size] + f.DepthFirst = f.DepthFirst[:0] + } else { + f.LoopBlock = make([]LoopBlock, size) + f.DepthFirst = make([]*LoopBlock, 0, size) + } + for i := range f.LoopBlock { + f.LoopBlock[i].Init(g.Block[i]) + } + f.Search(g.Block[0]) + for i := range f.LoopBlock { + lb := &f.LoopBlock[i] + if lb.First == Unvisited { + lb.Type = bbDead + } + } + + // Step B: Classify back edges as coming from descendents or not. + for _, lb := range f.DepthFirst { + for _, b := range lb.Block.In { + lbb := &f.LoopBlock[b.Name] + if lb.IsAncestor(lbb) { + lb.BackPred = append(lb.BackPred, lbb) + } else { + lb.NonBackPred = append(lb.NonBackPred, lbb) + } + } + } + + // Start node is root of all other loops. + f.LoopBlock[0].Header = &f.LoopBlock[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 i := len(f.DepthFirst) - 1; i >= 0; i-- { + w := f.DepthFirst[i] + + pool := f.Pool[:0] + + // Step D. + for _, pred := range w.BackPred { + if w == pred { + w.Type = bbSelf + continue + } + pool = append(pool, pred.Find()) + } + + // Process node pool in order as work list. + for i := 0; i < len(pool); i++ { + x := pool[i] + + // 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. + for _, y := range x.NonBackPred { + ydash := y.Find() + if !w.IsAncestor(ydash) { + w.Type = bbIrreducible + w.NonBackPred = appendUnique(w.NonBackPred, y) + } else if ydash != w { + pool = appendUnique(pool, ydash) + } + } + } + + // Collapse/Unionize nodes in a SCC to a single node + // For every SCC found, create a loop descriptor and link it in. + if len(pool) > 0 || w.Type == bbSelf { + l := lsg.NewLoop(1 + len(pool)) + l.Head = w.Block + l.Block = append(l.Block, w.Block) + l.IsReducible = w.Type != bbIrreducible + w.Loop = l + + // 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() + for _, node := range pool { + // Add nodes to loop descriptor. + node.Header = w + node.Union = w + + // Nested loops are not added, but linked together. + if node.Loop != nil { + node.Loop.Parent = l + } else { + l.Block = append(l.Block, node.Block) + } + } + } + + f.Pool = pool + } +} + +func appendUnique(pool []*LoopBlock, b *LoopBlock) []*LoopBlock { + for _, p := range pool { + if b == p { + return pool + } + } + return append(pool, b) +} + +// Main program. + +var cpuprofile = flag.String("cpuprofile", "cpu6.prof", "write cpu profile to this file") +var memprofile = flag.String("memprofile", "mem6.prof", "write memory profile to this file") +var reuseLoopGraph = flag.Bool("reuseloopgraph", true, "reuse loop graph memory") + +func main() { + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + var f LoopFinder + g := buildGraph() + lsg := new(LoopGraph) + f.FindLoops(g, lsg) + + if *memprofile != "" { + f, err := os.Create(*memprofile) + if err != nil { + log.Fatal(err) + } + pprof.WriteHeapProfile(f) + f.Close() + return + } + + for i := 0; i < 50; i++ { + if *reuseLoopGraph { + lsg.Clear() + f.FindLoops(g, lsg) + } else { + f.FindLoops(g, new(LoopGraph)) + } + } + + fmt.Printf("# of loops: %d (including 1 artificial root node)\n", len(lsg.Loop)) + lsg.CalculateNesting() +} diff --git a/havlak/havlak7.go b/havlak/havlak7.go new file mode 100644 index 0000000..8b00fbf --- /dev/null +++ b/havlak/havlak7.go @@ -0,0 +1,759 @@ +package main + +/* + @Auth:ShenZ + @Description: +*/ +import ( + "flag" + "fmt" + "log" + "os" + "runtime/pprof" +) + +type BasicBlock struct { + Name int + InEdges []*BasicBlock + OutEdges []*BasicBlock +} + +func NewBasicBlock(name int) *BasicBlock { + return &BasicBlock{Name: name} +} + +func (bb *BasicBlock) Dump() { + fmt.Printf("BB#%06d:", bb.Name) + if len(bb.InEdges) > 0 { + fmt.Printf(" in :") + for _, iter := range bb.InEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + if len(bb.OutEdges) > 0 { + fmt.Print(" out:") + for _, iter := range bb.OutEdges { + fmt.Printf(" BB#%06d", iter.Name) + } + } + fmt.Printf("\n") +} + +func (bb *BasicBlock) NumPred() int { + return len(bb.InEdges) +} + +func (bb *BasicBlock) NumSucc() int { + return len(bb.OutEdges) +} + +func (bb *BasicBlock) AddInEdge(from *BasicBlock) { + bb.InEdges = append(bb.InEdges, from) +} + +func (bb *BasicBlock) AddOutEdge(to *BasicBlock) { + bb.OutEdges = append(bb.OutEdges, to) +} + +//----------------------------------------------------------- + +type CFG struct { + Blocks []*BasicBlock + Start *BasicBlock +} + +func NewCFG() *CFG { + return &CFG{} +} + +func (cfg *CFG) NumNodes() int { + return len(cfg.Blocks) +} + +func (cfg *CFG) CreateNode(node int) *BasicBlock { + if node < len(cfg.Blocks) { + return cfg.Blocks[node] + } + if node != len(cfg.Blocks) { + println("oops", node, len(cfg.Blocks)) + panic("wtf") + } + bblock := NewBasicBlock(node) + cfg.Blocks = append(cfg.Blocks, bblock) + + if len(cfg.Blocks) == 1 { + cfg.Start = bblock + } + + return bblock +} + +func (cfg *CFG) Dump() { + for _, n := range cfg.Blocks { + n.Dump() + } +} + +//----------------------------------------------------------- + +type BasicBlockEdge struct { + Dst *BasicBlock + Src *BasicBlock +} + +func NewBasicBlockEdge(cfg *CFG, from int, to int) *BasicBlockEdge { + self := new(BasicBlockEdge) + self.Src = cfg.CreateNode(from) + self.Dst = cfg.CreateNode(to) + + self.Src.AddOutEdge(self.Dst) + self.Dst.AddInEdge(self.Src) + + return self +} + +//----------------------------------------------------------- +// Basic Blocks and Loops are being classified as regular, irreducible, +// and so on. This enum contains a symbolic name for all these classifications +// +const ( + _ = iota // Go has an interesting iota concept + bbTop // uninitialized + bbNonHeader // a regular BB + bbReducible // reducible loop + bbSelf // single BB loop + bbIrreducible // irreducible loop + bbDead // a dead BB + bbLast // sentinel +) + +// UnionFindNode is used in the Union/Find algorithm to collapse +// complete loops into a single node. These nodes and the +// corresponding functionality are implemented with this class +// +type UnionFindNode struct { + parent *UnionFindNode + bb *BasicBlock + loop *SimpleLoop + dfsNumber int +} + +// Init explicitly initializes UnionFind nodes. +// +func (u *UnionFindNode) Init(bb *BasicBlock, dfsNumber int) { + u.parent = u + u.bb = bb + u.dfsNumber = dfsNumber + u.loop = nil +} + +// FindSet implements the Find part of the Union/Find Algorithm +// +// Implemented with Path Compression (inner loops are only +// visited and collapsed once, however, deep nests would still +// result in significant traversals). +// +func (u *UnionFindNode) FindSet() *UnionFindNode { + var nodeList []*UnionFindNode + node := u + + for ; node != node.parent; node = node.parent { + if node.parent != node.parent.parent { + nodeList = append(nodeList, node) + } + + } + + // Path Compression, all nodes' parents point to the 1st level parent. + for _, ll := range nodeList { + ll.parent = node.parent + } + + return node +} + +// Union relies on path compression. +// +func (u *UnionFindNode) Union(B *UnionFindNode) { + u.parent = B +} + +// Constants +// +// Marker for uninitialized nodes. +const unvisited = -1 + +// Safeguard against pathological algorithm behavior. +const maxNonBackPreds = 32 * 1024 + +// IsAncestor +// +// As described in the paper, determine whether a node 'w' is a +// "true" ancestor for node 'v'. +// +// Dominance can be tested quickly using a pre-order trick +// for depth-first spanning trees. This is why DFS is the first +// thing we run below. +// +// Go comment: Parameters can be written as w,v int, inlike in C, where +// each parameter needs its own type. +// +func isAncestor(w, v int, last []int) bool { + return ((w <= v) && (v <= last[w])) +} + +// listContainsNode +// +// Check whether a list contains a specific element. +// +func listContainsNode(l []*UnionFindNode, u *UnionFindNode) bool { + for _, ll := range l { + if ll == u { + return true + } + } + return false +} + +// DFS - Depth-First-Search and node numbering. +// Step a: +//func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number map[*BasicBlock]int, last []int, current int) int { +//// step b: +// nodes[current].Init(currentNode, current) +// number[currentNode] = current +// +// lastid := current +// for _, target := range currentNode.OutEdges { +// if number[target] == unvisited { +// lastid = DFS(target, nodes, number, last, lastid+1) +// } +// } +// last[number[currentNode]] = lastid +// return lastid +//} +// Step b: +func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number []int, last []int, current int) int { + nodes[current].Init(currentNode, current) + number[currentNode.Name] = current + + lastid := current + for _, target := range currentNode.OutEdges { + if number[target.Name] == unvisited { + lastid = DFS(target, nodes, number, last, lastid+1) + } + } + last[number[currentNode.Name]] = lastid + return lastid +} + +// step d 所需 +func appendUnique(a []int, x int) []int { + for _, y := range a { + if x == y { + return a + } + } + return append(a, x) +} + +// FindLoops +// +// Find loops and build loop forest using Havlak's algorithm, which +// is derived from Tarjan. Variable names and step numbering has +// been chosen to be identical to the nomenclature in Havlak's +// paper (which, in turn, is similar to the one used by Tarjan). +// +func FindLoops(cfgraph *CFG, lsgraph *LSG) { + if cfgraph.Start == nil { + return + } + + size := cfgraph.NumNodes() + //step a b c + //nonBackPreds := make([]map[int]bool, size) + //step d + nonBackPreds := make([][]int, size) + backPreds := make([][]int, size) + //step a: + //number := make(map[*BasicBlock]int) + //step b: + number := make([]int, size) + header := make([]int, size, size) + types := make([]int, size, size) + last := make([]int, size, size) + nodes := make([]*UnionFindNode, size, size) + + for i := 0; i < size; i++ { + nodes[i] = new(UnionFindNode) + } + + // Step a:for _, bb := range cfgraph.Blocks { + // - initialize all nodes as unvisited. + // - depth-first traversal and numbering. + // - unreached BB's are marked as dead. + // + //step d for i,bb 改成 for _,bb + for _, bb := range cfgraph.Blocks { + // Step a: + //number[bb] = unvisited + // Step b: + number[bb.Name] = unvisited + //step d 注释下面行 + //nonBackPreds[i] = make(map[int]bool) + + } + + //Step a: + DFS(cfgraph.Start, nodes, number, last, 0) + // 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 := 0; w < size; w++ { + header[w] = 0 + types[w] = bbNonHeader + + nodeW := nodes[w].bb + if nodeW == nil { + types[w] = bbDead + continue // dead BB + } + + if nodeW.NumPred() > 0 { + for _, nodeV := range nodeW.InEdges { + //step a: + //v := number[nodeV] + //step b: + v := number[nodeV.Name] + if v == unvisited { + continue // dead node + } + + if isAncestor(w, v, last) { + backPreds[w] = append(backPreds[w], v) + } else { + // step abc + // nonBackPreds[w][v] = true + //step d + nonBackPreds[w] = appendUnique(nonBackPreds[w], v) + } + } + } + } + + // 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 := size - 1; w >= 0; w-- { + // this is 'P' in Havlak's paper + var nodePool []*UnionFindNode + + nodeW := nodes[w].bb + if nodeW == nil { + continue // dead BB + } + + // Step d: + for _, v := range backPreds[w] { + if v != w { + nodePool = append(nodePool, nodes[v].FindSet()) + } else { + types[w] = bbSelf + } + } + + // Copy nodePool to workList. + // + workList := append([]*UnionFindNode(nil), nodePool...) + + if len(nodePool) != 0 { + types[w] = bbReducible + } + + // work the list... + // + for len(workList) > 0 { + x := workList[0] + workList = workList[1:] + + // 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. + // + nonBackSize := len(nonBackPreds[x.dfsNumber]) + if nonBackSize > maxNonBackPreds { + return + } + + for iter := range nonBackPreds[x.dfsNumber] { + y := nodes[iter] + ydash := y.FindSet() + + if !isAncestor(w, ydash.dfsNumber, last) { + types[w] = bbIrreducible + //step abc + //nonBackPreds[w][ydash.dfsNumber] = true + //step d + nonBackPreds[w] = appendUnique(nonBackPreds[w], ydash.dfsNumber) + } else { + if ydash.dfsNumber != w { + if !listContainsNode(nodePool, ydash) { + workList = append(workList, ydash) + nodePool = append(nodePool, ydash) + } + } + } + } + } + + // Collapse/Unionize nodes in a SCC to a single node + // For every SCC found, create a loop descriptor and link it in. + // + if (len(nodePool) > 0) || (types[w] == bbSelf) { + loop := lsgraph.NewLoop() + + loop.SetHeader(nodeW) + if types[w] != bbIrreducible { + loop.IsReducible = true + } + + // 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: + // type[w] != BasicBlockClass.bbIrreducible + // + nodes[w].loop = loop + + for _, node := range nodePool { + // Add nodes to loop descriptor. + header[node.dfsNumber] = w + node.Union(nodes[w]) + + // Nested loops are not added, but linked together. + if node.loop != nil { + node.loop.Parent = loop + } else { + loop.AddNode(node.bb) + } + } + + lsgraph.AddLoop(loop) + } // nodePool.size + } // Step c + +} + +// External entry point. +func FindHavlakLoops(cfgraph *CFG, lsgraph *LSG) int { + FindLoops(cfgraph, lsgraph) + return lsgraph.NumLoops() +} + +//====================================================== +// Scaffold Code +//====================================================== + +// Basic representation of loops, a loop has an entry point, +// one or more exit edges, a set of basic blocks, and potentially +// an outer loop - a "parent" loop. +// +// Furthermore, it can have any set of properties, e.g., +// it can be an irreducible loop, have control flow, be +// a candidate for transformations, and what not. +// +type SimpleLoop struct { + // No set, use map to bool + basicBlocks map[*BasicBlock]bool + Children map[*SimpleLoop]bool + Parent *SimpleLoop + header *BasicBlock + + IsRoot bool + IsReducible bool + Counter int + NestingLevel int + DepthLevel int +} + +func (loop *SimpleLoop) AddNode(bb *BasicBlock) { + loop.basicBlocks[bb] = true +} + +func (loop *SimpleLoop) AddChildLoop(child *SimpleLoop) { + loop.Children[child] = true +} + +func (loop *SimpleLoop) Dump(indent int) { + for i := 0; i < indent; i++ { + fmt.Printf(" ") + } + + // No ? operator ? + fmt.Printf("loop-%d nest: %d depth %d ", + loop.Counter, loop.NestingLevel, loop.DepthLevel) + if !loop.IsReducible { + fmt.Printf("(Irreducible) ") + } + + // must have > 0 + if len(loop.Children) > 0 { + fmt.Printf("Children: ") + for ll := range loop.Children { + fmt.Printf("loop-%d", ll.Counter) + } + } + if len(loop.basicBlocks) > 0 { + fmt.Printf("(") + for bb := range loop.basicBlocks { + fmt.Printf("BB#%06d ", bb.Name) + if loop.header == bb { + fmt.Printf("*") + } + } + fmt.Printf("\b)") + } + fmt.Printf("\n") +} + +func (loop *SimpleLoop) SetParent(parent *SimpleLoop) { + loop.Parent = parent + loop.Parent.AddChildLoop(loop) +} + +func (loop *SimpleLoop) SetHeader(bb *BasicBlock) { + loop.AddNode(bb) + loop.header = bb +} + +//------------------------------------ +// Helper (No templates or such) +// +func max(x, y int) int { + if x > y { + return x + } + return y +} + +// LoopStructureGraph +// +// Maintain loop structure for a given CFG. +// +// Two values are maintained for this loop graph, depth, and nesting level. +// For example: +// +// loop nesting level depth +//---------------------------------------- +// loop-0 2 0 +// loop-1 1 1 +// loop-3 1 1 +// loop-2 0 2 +// +var loopCounter = 0 + +type LSG struct { + root *SimpleLoop + loops []*SimpleLoop +} + +func NewLSG() *LSG { + lsg := new(LSG) + lsg.root = lsg.NewLoop() + lsg.root.NestingLevel = 0 + + return lsg +} + +func (lsg *LSG) NewLoop() *SimpleLoop { + loop := new(SimpleLoop) + loop.basicBlocks = make(map[*BasicBlock]bool) + loop.Children = make(map[*SimpleLoop]bool) + loop.Parent = nil + loop.header = nil + + loop.Counter = loopCounter + loopCounter++ + return loop +} + +func (lsg *LSG) AddLoop(loop *SimpleLoop) { + lsg.loops = append(lsg.loops, loop) +} + +func (lsg *LSG) Dump() { + lsg.dump(lsg.root, 0) +} + +func (lsg *LSG) dump(loop *SimpleLoop, indent int) { + loop.Dump(indent) + + for ll := range loop.Children { + lsg.dump(ll, indent+1) + } +} + +func (lsg *LSG) CalculateNestingLevel() { + for _, sl := range lsg.loops { + if sl.IsRoot { + continue + } + if sl.Parent == nil { + sl.SetParent(lsg.root) + } + } + lsg.calculateNestingLevel(lsg.root, 0) +} + +func (lsg *LSG) calculateNestingLevel(loop *SimpleLoop, depth int) { + loop.DepthLevel = depth + for ll := range loop.Children { + lsg.calculateNestingLevel(ll, depth+1) + + ll.NestingLevel = max(loop.NestingLevel, ll.NestingLevel+1) + } +} + +func (lsg *LSG) NumLoops() int { + return len(lsg.loops) +} + +func (lsg *LSG) Root() *SimpleLoop { + return lsg.root +} + +//====================================================== +// Testing Code +//====================================================== + +func buildDiamond(cfgraph *CFG, start int) int { + bb0 := start + NewBasicBlockEdge(cfgraph, bb0, bb0+1) + NewBasicBlockEdge(cfgraph, bb0, bb0+2) + NewBasicBlockEdge(cfgraph, bb0+1, bb0+3) + NewBasicBlockEdge(cfgraph, bb0+2, bb0+3) + + return bb0 + 3 +} + +func buildConnect(cfgraph *CFG, start int, end int) { + NewBasicBlockEdge(cfgraph, start, end) +} + +func buildStraight(cfgraph *CFG, start int, n int) int { + for i := 0; i < n; i++ { + buildConnect(cfgraph, start+i, start+i+1) + } + return start + n +} + +func buildBaseLoop(cfgraph *CFG, from int) int { + header := buildStraight(cfgraph, from, 1) + diamond1 := buildDiamond(cfgraph, header) + d11 := buildStraight(cfgraph, diamond1, 1) + diamond2 := buildDiamond(cfgraph, d11) + footer := buildStraight(cfgraph, diamond2, 1) + buildConnect(cfgraph, diamond2, d11) + buildConnect(cfgraph, diamond1, header) + + buildConnect(cfgraph, footer, from) + footer = buildStraight(cfgraph, footer, 1) + return footer +} + +//step a b 接收参数 存储cpu信息 修改成默认存储文件 +var cpuprofile = flag.String("cpuprofile", "cpu.prof", "write cpu profile to this file") + +//step c 存储内存信息 +var memprofile = flag.String("memprofile", "mem.prof", "write memory profile to this file") + +func main() { + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + lsgraph := NewLSG() + cfgraph := NewCFG() + + cfgraph.CreateNode(0) // top + cfgraph.CreateNode(1) // bottom + NewBasicBlockEdge(cfgraph, 0, 2) + + for dummyloop := 0; dummyloop < 15000; dummyloop++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + n := 2 + + for parlooptrees := 0; parlooptrees < 10; parlooptrees++ { + cfgraph.CreateNode(n + 1) + buildConnect(cfgraph, 2, n+1) + n = n + 1 + + for i := 0; i < 100; i++ { + top := n + n = buildStraight(cfgraph, n, 1) + for j := 0; j < 25; j++ { + n = buildBaseLoop(cfgraph, n) + } + bottom := buildStraight(cfgraph, n, 1) + buildConnect(cfgraph, n, top) + n = bottom + } + buildConnect(cfgraph, n, 1) + } + + FindHavlakLoops(cfgraph, lsgraph) + + //step c 记录内存 + if *memprofile != "" { + f, err := os.Create(*memprofile) + if err != nil { + log.Fatal(err) + } + pprof.WriteHeapProfile(f) + f.Close() + return + } + for i := 0; i < 50; i++ { + FindHavlakLoops(cfgraph, NewLSG()) + } + + fmt.Printf("# of loops: %d (including 1 artificial root node)\n", lsgraph.NumLoops()) + lsgraph.CalculateNestingLevel() +} diff --git a/main.go b/main.go index f94725e..01c68c5 100644 --- a/main.go +++ b/main.go @@ -1,371 +1,336 @@ -// Go from multi-language-benchmark/src/havlak/go_pro - -// Copyright 2011 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Test Program for the Havlak loop finder. -// -// This program constructs a fairly large control flow -// graph and performs loop recognition. This is the Go -// version. -// package main +/* + @Auth:ShenZ + @Description: +*/ import ( "flag" "fmt" + "io" "log" "os" "runtime/pprof" ) -type BasicBlock struct { - Name int - InEdges []*BasicBlock - OutEdges []*BasicBlock +// Control flow graph, created once. + +type Block struct { + Name int + In []*Block + Out []*Block } -func NewBasicBlock(name int) *BasicBlock { - return &BasicBlock{Name: name} +func (b *Block) String() string { + return fmt.Sprintf("b%d", b.Name) } -func (bb *BasicBlock) Dump() { - fmt.Printf("BB#%06d:", bb.Name) - if len(bb.InEdges) > 0 { - fmt.Printf(" in :") - for _, iter := range bb.InEdges { - fmt.Printf(" BB#%06d", iter.Name) - } - } - if len(bb.OutEdges) > 0 { - fmt.Print(" out:") - for _, iter := range bb.OutEdges { - fmt.Printf(" BB#%06d", iter.Name) - } - } - fmt.Printf("\n") +func (b *Block) Dump(w io.Writer) { + fmt.Fprintf(w, "%s: %v %v\n", b, b.In, b.Out) } -func (bb *BasicBlock) NumPred() int { - return len(bb.InEdges) +type CFG struct { + Block []*Block + Edge []Edge } -func (bb *BasicBlock) NumSucc() int { - return len(bb.OutEdges) +type Edge struct { + Src, Dst int } -func (bb *BasicBlock) AddInEdge(from *BasicBlock) { - bb.InEdges = append(bb.InEdges, from) +func (g *CFG) NewBlock() *Block { + b := &Block{Name: len(g.Block)} + g.Block = append(g.Block, b) + return b } -func (bb *BasicBlock) AddOutEdge(to *BasicBlock) { - bb.OutEdges = append(bb.OutEdges, to) +func (g *CFG) Dump(w io.Writer) { + for _, b := range g.Block { + b.Dump(w) + } } -//----------------------------------------------------------- +func (g *CFG) Connect(src, dst *Block) { + src.Out = append(src.Out, dst) + dst.In = append(dst.In, src) + g.Edge = append(g.Edge, Edge{src.Name, dst.Name}) +} -type CFG struct { - Blocks []*BasicBlock - Start *BasicBlock +func (g *CFG) Path(from *Block) *Block { + n := g.NewBlock() + g.Connect(from, n) + return n } -func NewCFG() *CFG { - return &CFG{} +func (g *CFG) Diamond(from *Block) *Block { + x := g.Path(from) + y := g.Path(from) + z := g.Path(x) + g.Connect(y, z) + g.Connect(z, from) + return z } -func (cfg *CFG) NumNodes() int { - return len(cfg.Blocks) +func (g *CFG) BaseLoop(from *Block) *Block { + z := g.Path(g.Diamond(g.Path(g.Diamond(g.Path(from))))) + g.Connect(z, from) + return g.Path(z) } -func (cfg *CFG) CreateNode(node int) *BasicBlock { - if node < len(cfg.Blocks) { - return cfg.Blocks[node] - } - if node != len(cfg.Blocks) { - println("oops", node, len(cfg.Blocks)) - panic("wtf") - } - bblock := NewBasicBlock(node) - cfg.Blocks = append(cfg.Blocks, bblock) +func buildGraph() *CFG { + g := new(CFG) - if len(cfg.Blocks) == 1 { - cfg.Start = bblock - } + n0 := g.NewBlock() + n1 := g.NewBlock() + n2 := g.NewBlock() + g.Connect(n0, n2) - return bblock -} + for i := 0; i < 10; i++ { + n := g.NewBlock() + g.Connect(n2, n) -func (cfg *CFG) Dump() { - for _, n := range cfg.Blocks { - n.Dump() + for j := 0; j < 100; j++ { + top := n + n = g.Path(n) + for k := 0; k < 25; k++ { + n = g.BaseLoop(n) + } + bottom := g.Path(n) + g.Connect(n, top) + n = bottom + } + g.Connect(n, n1) } + return g } -//----------------------------------------------------------- +// Basic representation of loop graph. -type BasicBlockEdge struct { - Dst *BasicBlock - Src *BasicBlock +type LoopGraph struct { + Root Loop + Loop []*Loop } -func NewBasicBlockEdge(cfg *CFG, from int, to int) *BasicBlockEdge { - self := new(BasicBlockEdge) - self.Src = cfg.CreateNode(from) - self.Dst = cfg.CreateNode(to) - - self.Src.AddOutEdge(self.Dst) - self.Dst.AddInEdge(self.Src) +type Loop struct { + Block []*Block + Child []*Loop + Parent *Loop + Head *Block - return self + IsRoot bool + IsReducible bool + Counter int + Nesting int + Depth int } -//----------------------------------------------------------- -// Basic Blocks and Loops are being classified as regular, irreducible, -// and so on. This enum contains a symbolic name for all these classifications -// -const ( - _ = iota // Go has an interesting iota concept - bbTop // uninitialized - bbNonHeader // a regular BB - bbReducible // reducible loop - bbSelf // single BB loop - bbIrreducible // irreducible loop - bbDead // a dead BB - bbLast // sentinel -) +var loopCounter = 0 -// UnionFindNode is used in the Union/Find algorithm to collapse -// complete loops into a single node. These nodes and the -// corresponding functionality are implemented with this class -// -type UnionFindNode struct { - parent *UnionFindNode - bb *BasicBlock - loop *SimpleLoop - dfsNumber int -} +func (g *LoopGraph) Clear() { + g.Root.Child = g.Root.Child[:0] + g.Loop = g.Loop[:0] +} + +func (g *LoopGraph) NewLoop(lcap int) *Loop { + // If there's a cached loop, use that. + if n := len(g.Loop); n < cap(g.Loop) && g.Loop[:n+1][n] != nil { + g.Loop = g.Loop[:n+1] + l := g.Loop[n] + l.Block = l.Block[:0] + l.Child = l.Child[:0] + l.Parent = nil + l.Head = nil + l.IsRoot = false + l.IsReducible = false + l.Nesting = 0 + l.Depth = 0 + return l + } -// Init explicitly initializes UnionFind nodes. -// -func (u *UnionFindNode) Init(bb *BasicBlock, dfsNumber int) { - u.parent = u - u.bb = bb - u.dfsNumber = dfsNumber - u.loop = nil + loopCounter++ + l := &Loop{Counter: loopCounter} + g.Loop = append(g.Loop, l) + l.Block = make([]*Block, 0, lcap) + return l } -// FindSet implements the Find part of the Union/Find Algorithm -// -// Implemented with Path Compression (inner loops are only -// visited and collapsed once, however, deep nests would still -// result in significant traversals). -// -func (u *UnionFindNode) FindSet() *UnionFindNode { - var nodeList []*UnionFindNode - node := u - - for ; node != node.parent; node = node.parent { - if node.parent != node.parent.parent { - nodeList = append(nodeList, node) +func (g *LoopGraph) CalculateNesting() { + for _, l := range g.Loop { + if l == nil { + panic("nil l") + } + if l.IsRoot { + continue + } + if l.Parent == nil { + l.Parent = &g.Root + g.Root.Child = append(g.Root.Child, l) } - } + g.calculateNesting(&g.Root, 0) +} - // Path Compression, all nodes' parents point to the 1st level parent. - for _, ll := range nodeList { - ll.parent = node.parent +func (g *LoopGraph) calculateNesting(l *Loop, depth int) { + l.Depth = depth + for _, child := range l.Child { + g.calculateNesting(child, depth+1) + if n := child.Nesting + 1; l.Nesting < n { + l.Nesting = n + } } +} - return node +func (g *LoopGraph) Dump(w io.Writer) { + g.dump(w, &g.Root, 0) } -// Union relies on path compression. -// -func (u *UnionFindNode) Union(B *UnionFindNode) { - u.parent = B +func (g *LoopGraph) dump(w io.Writer, l *Loop, indent int) { + l.Dump(w, indent) + + for _, child := range l.Child { + g.dump(w, child, indent+1) + } } -// Constants -// -// Marker for uninitialized nodes. -const unvisited = -1 - -// Safeguard against pathological algorithm behavior. -const maxNonBackPreds = 32 * 1024 - -// IsAncestor -// -// As described in the paper, determine whether a node 'w' is a -// "true" ancestor for node 'v'. -// -// Dominance can be tested quickly using a pre-order trick -// for depth-first spanning trees. This is why DFS is the first -// thing we run below. -// -// Go comment: Parameters can be written as w,v int, inlike in C, where -// each parameter needs its own type. -// -func isAncestor(w, v int, last []int) bool { - return ((w <= v) && (v <= last[w])) +func (l *Loop) String() string { + return fmt.Sprintf("loop-%d", l.Counter) } -// listContainsNode -// -// Check whether a list contains a specific element. -// -func listContainsNode(l []*UnionFindNode, u *UnionFindNode) bool { - for _, ll := range l { - if ll == u { - return true +func (l *Loop) Dump(w io.Writer, indent int) { + fmt.Fprintf(w, "%*sloop-%d nest: %d depth %d", + 2*indent, l.Counter, l.Nesting, l.Depth) + if !l.IsReducible { + fmt.Fprintf(w, " (Irreducible)") + } + if len(l.Child) > 0 { + fmt.Fprintf(w, " Children: %v", l.Child) + } + if len(l.Block) > 0 { + fmt.Fprintf(w, "(") + sep := "" + for _, b := range l.Block { + fmt.Fprint(w, sep, b) + if b == l.Head { + fmt.Fprint(w, "*") + } + sep = " " } + fmt.Fprintf(w, ")") } - return false + fmt.Fprintf(w, "\n") } -// DFS - Depth-First-Search and node numbering. -// Step a: -//func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number map[*BasicBlock]int, last []int, current int) int { -//// step b: -// nodes[current].Init(currentNode, current) -// number[currentNode] = current -// -// lastid := current -// for _, target := range currentNode.OutEdges { -// if number[target] == unvisited { -// lastid = DFS(target, nodes, number, last, lastid+1) -// } -// } -// last[number[currentNode]] = lastid -// return lastid -//} -// Step b: -func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number []int, last []int, current int) int { - nodes[current].Init(currentNode, current) - number[currentNode.Name] = current - - lastid := current - for _, target := range currentNode.OutEdges { - if number[target.Name] == unvisited { - lastid = DFS(target, nodes, number, last, lastid+1) - } +// Loop finding state, generated or reused on each iteration. + +type LoopFinder struct { + LoopBlock []LoopBlock + DepthFirst []*LoopBlock + Pool []*LoopBlock +} + +const Unvisited = -1 + +type LoopType int + +const ( + bbNonHeader LoopType = 1 + iota // a regular BB + bbReducible // reducible loop + bbSelf // single BB loop + bbIrreducible // irreducible loop + bbDead // a dead BB +) + +type LoopBlock struct { + Block *Block + Loop *Loop + First int + Last int + Header *LoopBlock + Type LoopType + BackPred []*LoopBlock + NonBackPred []*LoopBlock + + Union *LoopBlock // union find +} + +func (lb *LoopBlock) Init(b *Block) { + lb.Block = b + lb.Loop = nil + lb.First = Unvisited + lb.Last = Unvisited + lb.Header = nil + lb.Type = bbNonHeader + lb.BackPred = lb.BackPred[:0] + lb.NonBackPred = lb.NonBackPred[:0] + lb.Union = lb +} + +func (lb *LoopBlock) Find() *LoopBlock { + if lb.Union != lb { + lb.Union = lb.Union.Find() } - last[number[currentNode.Name]] = lastid - return lastid + return lb.Union } -// step d 所需 -func appendUnique(a []int, x int) []int { - for _, y := range a { - if x == y { - return a +// Depth first search to number blocks. + +func (f *LoopFinder) Search(b *Block) { + lb := &f.LoopBlock[b.Name] + f.DepthFirst = append(f.DepthFirst, lb) + lb.First = len(f.DepthFirst) + for _, out := range b.Out { + if f.LoopBlock[out.Name].First == Unvisited { + f.Search(out) } } - return append(a, x) + lb.Last = len(f.DepthFirst) +} + +func (lb *LoopBlock) IsAncestor(p *LoopBlock) bool { + return lb.First <= p.First && p.First <= lb.Last } -// FindLoops -// -// Find loops and build loop forest using Havlak's algorithm, which -// is derived from Tarjan. Variable names and step numbering has -// been chosen to be identical to the nomenclature in Havlak's -// paper (which, in turn, is similar to the one used by Tarjan). -// -func FindLoops(cfgraph *CFG, lsgraph *LSG) { - if cfgraph.Start == nil { +func (f *LoopFinder) FindLoops(g *CFG, lsg *LoopGraph) { + size := len(g.Block) + if size == 0 { return } - size := cfgraph.NumNodes() - //step a b c - //nonBackPreds := make([]map[int]bool, size) - //step d - nonBackPreds := make([][]int, size) - backPreds := make([][]int, size) - //step a: - //number := make(map[*BasicBlock]int) - //step b: - number := make([]int, size) - header := make([]int, size, size) - types := make([]int, size, size) - last := make([]int, size, size) - nodes := make([]*UnionFindNode, size, size) - - for i := 0; i < size; i++ { - nodes[i] = new(UnionFindNode) + // Step A: Initialize nodes, depth first numbering, mark dead nodes. + if size <= cap(f.LoopBlock) { + f.LoopBlock = f.LoopBlock[:size] + f.DepthFirst = f.DepthFirst[:0] + } else { + f.LoopBlock = make([]LoopBlock, size) + f.DepthFirst = make([]*LoopBlock, 0, size) } - - // Step a:for _, bb := range cfgraph.Blocks { - // - initialize all nodes as unvisited. - // - depth-first traversal and numbering. - // - unreached BB's are marked as dead. - // - //step d for i,bb 改成 for _,bb - for _, bb := range cfgraph.Blocks { - // Step a: - //number[bb] = unvisited - // Step b: - number[bb.Name] = unvisited - //step d 注释下面行 - //nonBackPreds[i] = make(map[int]bool) - + for i := range f.LoopBlock { + f.LoopBlock[i].Init(g.Block[i]) } - - //Step a: - DFS(cfgraph.Start, nodes, number, last, 0) - // 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 := 0; w < size; w++ { - header[w] = 0 - types[w] = bbNonHeader - - nodeW := nodes[w].bb - if nodeW == nil { - types[w] = bbDead - continue // dead BB + f.Search(g.Block[0]) + for i := range f.LoopBlock { + lb := &f.LoopBlock[i] + if lb.First == Unvisited { + lb.Type = bbDead } + } - if nodeW.NumPred() > 0 { - for _, nodeV := range nodeW.InEdges { - //step a: - //v := number[nodeV] - //step b: - v := number[nodeV.Name] - if v == unvisited { - continue // dead node - } - - if isAncestor(w, v, last) { - backPreds[w] = append(backPreds[w], v) - } else { - // step abc - // nonBackPreds[w][v] = true - //step d - nonBackPreds[w] = appendUnique(nonBackPreds[w], v) - } + // Step B: Classify back edges as coming from descendents or not. + for _, lb := range f.DepthFirst { + for _, b := range lb.Block.In { + lbb := &f.LoopBlock[b.Name] + if lb.IsAncestor(lbb) { + lb.BackPred = append(lb.BackPred, lbb) + } else { + lb.NonBackPred = append(lb.NonBackPred, lbb) } } } // Start node is root of all other loops. - header[0] = 0 + f.LoopBlock[0].Header = &f.LoopBlock[0] - // Step c: + // Step C: // // The outer loop, unchanged from Tarjan. It does nothing except // for those nodes which are the destinations of backedges. @@ -376,87 +341,50 @@ func FindLoops(cfgraph *CFG, lsgraph *LSG) { // 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 := size - 1; w >= 0; w-- { - // this is 'P' in Havlak's paper - var nodePool []*UnionFindNode + for i := len(f.DepthFirst) - 1; i >= 0; i-- { + w := f.DepthFirst[i] - nodeW := nodes[w].bb - if nodeW == nil { - continue // dead BB - } + pool := f.Pool[:0] - // Step d: - for _, v := range backPreds[w] { - if v != w { - nodePool = append(nodePool, nodes[v].FindSet()) - } else { - types[w] = bbSelf + // Step D. + for _, pred := range w.BackPred { + if w == pred { + w.Type = bbSelf + continue } + pool = append(pool, pred.Find()) } - // Copy nodePool to workList. - // - workList := append([]*UnionFindNode(nil), nodePool...) - - if len(nodePool) != 0 { - types[w] = bbReducible - } - - // work the list... - // - for len(workList) > 0 { - x := workList[0] - workList = workList[1:] + // Process node pool in order as work list. + for i := 0; i < len(pool); i++ { + x := pool[i] - // Step e: + // Step E: // - // Step e represents the main difference from Tarjan's method. + // 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. - // - nonBackSize := len(nonBackPreds[x.dfsNumber]) - if nonBackSize > maxNonBackPreds { - return - } - - for iter := range nonBackPreds[x.dfsNumber] { - y := nodes[iter] - ydash := y.FindSet() - - if !isAncestor(w, ydash.dfsNumber, last) { - types[w] = bbIrreducible - //step abc - //nonBackPreds[w][ydash.dfsNumber] = true - //step d - nonBackPreds[w] = appendUnique(nonBackPreds[w], ydash.dfsNumber) - } else { - if ydash.dfsNumber != w { - if !listContainsNode(nodePool, ydash) { - workList = append(workList, ydash) - nodePool = append(nodePool, ydash) - } - } + for _, y := range x.NonBackPred { + ydash := y.Find() + if !w.IsAncestor(ydash) { + w.Type = bbIrreducible + w.NonBackPred = appendUnique(w.NonBackPred, y) + } else if ydash != w { + pool = appendUnique(pool, ydash) } } } // Collapse/Unionize nodes in a SCC to a single node // For every SCC found, create a loop descriptor and link it in. - // - if (len(nodePool) > 0) || (types[w] == bbSelf) { - loop := lsgraph.NewLoop() - - loop.SetHeader(nodeW) - if types[w] != bbIrreducible { - loop.IsReducible = true - } + if len(pool) > 0 || w.Type == bbSelf { + l := lsg.NewLoop(1 + len(pool)) + l.Head = w.Block + l.Block = append(l.Block, w.Block) + l.IsReducible = w.Type != bbIrreducible + w.Loop = l // At this point, one can set attributes to the loop, such as: // @@ -466,253 +394,38 @@ func FindLoops(cfgraph *CFG, lsgraph *LSG) { // // the number of backedges: // backPreds[w].size() - // - // whether this loop is reducible: - // type[w] != BasicBlockClass.bbIrreducible - // - nodes[w].loop = loop - - for _, node := range nodePool { + for _, node := range pool { // Add nodes to loop descriptor. - header[node.dfsNumber] = w - node.Union(nodes[w]) + node.Header = w + node.Union = w // Nested loops are not added, but linked together. - if node.loop != nil { - node.loop.Parent = loop + if node.Loop != nil { + node.Loop.Parent = l } else { - loop.AddNode(node.bb) + l.Block = append(l.Block, node.Block) } } - - lsgraph.AddLoop(loop) - } // nodePool.size - } // Step c - -} - -// External entry point. -func FindHavlakLoops(cfgraph *CFG, lsgraph *LSG) int { - FindLoops(cfgraph, lsgraph) - return lsgraph.NumLoops() -} - -//====================================================== -// Scaffold Code -//====================================================== - -// Basic representation of loops, a loop has an entry point, -// one or more exit edges, a set of basic blocks, and potentially -// an outer loop - a "parent" loop. -// -// Furthermore, it can have any set of properties, e.g., -// it can be an irreducible loop, have control flow, be -// a candidate for transformations, and what not. -// -type SimpleLoop struct { - // No set, use map to bool - basicBlocks map[*BasicBlock]bool - Children map[*SimpleLoop]bool - Parent *SimpleLoop - header *BasicBlock - - IsRoot bool - IsReducible bool - Counter int - NestingLevel int - DepthLevel int -} - -func (loop *SimpleLoop) AddNode(bb *BasicBlock) { - loop.basicBlocks[bb] = true -} - -func (loop *SimpleLoop) AddChildLoop(child *SimpleLoop) { - loop.Children[child] = true -} - -func (loop *SimpleLoop) Dump(indent int) { - for i := 0; i < indent; i++ { - fmt.Printf(" ") - } - - // No ? operator ? - fmt.Printf("loop-%d nest: %d depth %d ", - loop.Counter, loop.NestingLevel, loop.DepthLevel) - if !loop.IsReducible { - fmt.Printf("(Irreducible) ") - } - - // must have > 0 - if len(loop.Children) > 0 { - fmt.Printf("Children: ") - for ll := range loop.Children { - fmt.Printf("loop-%d", ll.Counter) } - } - if len(loop.basicBlocks) > 0 { - fmt.Printf("(") - for bb := range loop.basicBlocks { - fmt.Printf("BB#%06d ", bb.Name) - if loop.header == bb { - fmt.Printf("*") - } - } - fmt.Printf("\b)") - } - fmt.Printf("\n") -} - -func (loop *SimpleLoop) SetParent(parent *SimpleLoop) { - loop.Parent = parent - loop.Parent.AddChildLoop(loop) -} - -func (loop *SimpleLoop) SetHeader(bb *BasicBlock) { - loop.AddNode(bb) - loop.header = bb -} - -//------------------------------------ -// Helper (No templates or such) -// -func max(x, y int) int { - if x > y { - return x - } - return y -} - -// LoopStructureGraph -// -// Maintain loop structure for a given CFG. -// -// Two values are maintained for this loop graph, depth, and nesting level. -// For example: -// -// loop nesting level depth -//---------------------------------------- -// loop-0 2 0 -// loop-1 1 1 -// loop-3 1 1 -// loop-2 0 2 -// -var loopCounter = 0 - -type LSG struct { - root *SimpleLoop - loops []*SimpleLoop -} - -func NewLSG() *LSG { - lsg := new(LSG) - lsg.root = lsg.NewLoop() - lsg.root.NestingLevel = 0 - - return lsg -} - -func (lsg *LSG) NewLoop() *SimpleLoop { - loop := new(SimpleLoop) - loop.basicBlocks = make(map[*BasicBlock]bool) - loop.Children = make(map[*SimpleLoop]bool) - loop.Parent = nil - loop.header = nil - - loop.Counter = loopCounter - loopCounter++ - return loop -} - -func (lsg *LSG) AddLoop(loop *SimpleLoop) { - lsg.loops = append(lsg.loops, loop) -} - -func (lsg *LSG) Dump() { - lsg.dump(lsg.root, 0) -} -func (lsg *LSG) dump(loop *SimpleLoop, indent int) { - loop.Dump(indent) - - for ll := range loop.Children { - lsg.dump(ll, indent+1) + f.Pool = pool } } -func (lsg *LSG) CalculateNestingLevel() { - for _, sl := range lsg.loops { - if sl.IsRoot { - continue +func appendUnique(pool []*LoopBlock, b *LoopBlock) []*LoopBlock { + for _, p := range pool { + if b == p { + return pool } - if sl.Parent == nil { - sl.SetParent(lsg.root) - } - } - lsg.calculateNestingLevel(lsg.root, 0) -} - -func (lsg *LSG) calculateNestingLevel(loop *SimpleLoop, depth int) { - loop.DepthLevel = depth - for ll := range loop.Children { - lsg.calculateNestingLevel(ll, depth+1) - - ll.NestingLevel = max(loop.NestingLevel, ll.NestingLevel+1) } + return append(pool, b) } -func (lsg *LSG) NumLoops() int { - return len(lsg.loops) -} - -func (lsg *LSG) Root() *SimpleLoop { - return lsg.root -} - -//====================================================== -// Testing Code -//====================================================== - -func buildDiamond(cfgraph *CFG, start int) int { - bb0 := start - NewBasicBlockEdge(cfgraph, bb0, bb0+1) - NewBasicBlockEdge(cfgraph, bb0, bb0+2) - NewBasicBlockEdge(cfgraph, bb0+1, bb0+3) - NewBasicBlockEdge(cfgraph, bb0+2, bb0+3) +// Main program. - return bb0 + 3 -} - -func buildConnect(cfgraph *CFG, start int, end int) { - NewBasicBlockEdge(cfgraph, start, end) -} - -func buildStraight(cfgraph *CFG, start int, n int) int { - for i := 0; i < n; i++ { - buildConnect(cfgraph, start+i, start+i+1) - } - return start + n -} - -func buildBaseLoop(cfgraph *CFG, from int) int { - header := buildStraight(cfgraph, from, 1) - diamond1 := buildDiamond(cfgraph, header) - d11 := buildStraight(cfgraph, diamond1, 1) - diamond2 := buildDiamond(cfgraph, d11) - footer := buildStraight(cfgraph, diamond2, 1) - buildConnect(cfgraph, diamond2, d11) - buildConnect(cfgraph, diamond1, header) - - buildConnect(cfgraph, footer, from) - footer = buildStraight(cfgraph, footer, 1) - return footer -} - -//step a b 接收参数 存储cpu信息 修改成默认存储文件 -var cpuprofile = flag.String("cpuprofile", "cpu.prof", "write cpu profile to this file") - -//step c 存储内存信息 -var memprofile = flag.String("memprofile", "mem.prof", "write memory profile to this file") +var cpuprofile = flag.String("cpuprofile", "cpu6.prof", "write cpu profile to this file") +var memprofile = flag.String("memprofile", "mem6.prof", "write memory profile to this file") +var reuseLoopGraph = flag.Bool("reuseloopgraph", true, "reuse loop graph memory") func main() { flag.Parse() @@ -725,40 +438,11 @@ func main() { defer pprof.StopCPUProfile() } - lsgraph := NewLSG() - cfgraph := NewCFG() - - cfgraph.CreateNode(0) // top - cfgraph.CreateNode(1) // bottom - NewBasicBlockEdge(cfgraph, 0, 2) - - for dummyloop := 0; dummyloop < 15000; dummyloop++ { - FindHavlakLoops(cfgraph, NewLSG()) - } - - n := 2 - - for parlooptrees := 0; parlooptrees < 10; parlooptrees++ { - cfgraph.CreateNode(n + 1) - buildConnect(cfgraph, 2, n+1) - n = n + 1 + var f LoopFinder + g := buildGraph() + lsg := new(LoopGraph) + f.FindLoops(g, lsg) - for i := 0; i < 100; i++ { - top := n - n = buildStraight(cfgraph, n, 1) - for j := 0; j < 25; j++ { - n = buildBaseLoop(cfgraph, n) - } - bottom := buildStraight(cfgraph, n, 1) - buildConnect(cfgraph, n, top) - n = bottom - } - buildConnect(cfgraph, n, 1) - } - - FindHavlakLoops(cfgraph, lsgraph) - - //step c 记录内存 if *memprofile != "" { f, err := os.Create(*memprofile) if err != nil { @@ -768,10 +452,16 @@ func main() { f.Close() return } + for i := 0; i < 50; i++ { - FindHavlakLoops(cfgraph, NewLSG()) + if *reuseLoopGraph { + lsg.Clear() + f.FindLoops(g, lsg) + } else { + f.FindLoops(g, new(LoopGraph)) + } } - fmt.Printf("# of loops: %d (including 1 artificial root node)\n", lsgraph.NumLoops()) - lsgraph.CalculateNestingLevel() + fmt.Printf("# of loops: %d (including 1 artificial root node)\n", len(lsg.Loop)) + lsg.CalculateNesting() } diff --git a/mem.prof b/mem.prof index 3de3a58b70132ca4d81868504da1ec2da89e6f87..b71abcf530a305d06a72b8ce66f951ec2b72048d 100644 GIT binary patch literal 2195 zcmV;E2yFKsiwFP!00004|BO~|Y!l}h&)Ij$>$9Caxj6A-J8>=-ph*Cm9JEkqr3_kN zRmE7Newil0VIQd%|H*7;QNLY)wiMVFttCr4SgR>oTUaa5x^~*6EvnGeX{fCx!6*eZ ztyCHtTcwIhbOJQJ&wCf!fe-!QH@WxsyuaV?`E!51BQptnyB$YYW#$K2m6-xQRc6if ze+&2wU=p7B=dBh)VG=k#kcJqOfFaWZ1~4D~(z(qsAI$p+^MQ)09x$TJk2hYr%`rb* z@f!-0;WLFo{1C*T9vEUwh7g8`NXFS`F7jXnriF+CVGQf7T1bg3LV%%Jr686U|nZv};2FDdcVF14nB5i<(WD_8UF_MepUye|47+!CX!≻68QWTl52;x zBv&N4c8Ft~!NXC^5!&{taabyzRQ9hnM`0kRhHygqtJyBVUX-s-=c8dR01$n~p zX68f3sTxt1#H&jfPH~2~7IGZlK}Q#>DkqXrIW99Fg3-o$@%It=E^I z^5apBiGxi|s?w`>EuIC|hc|^_7*6w0?r#33#eb@TvA(G5ZsEHIFawW!!N20cQH@PI z?4UEkc_T5yqVs zm%S#w!L?(W>WsYV=(MOx|FY$-%7_7M1%CcKbvy(w^5Ce}EPXli1h1E^kKM(x zqMqeGJmZb&Mb3f^l5&6SKGA~_EwQ9ON<#ODfW-fJcq@t5wR_XuU5#|RIe-Cd0B?8+ zpWy0C6OIkI!LGb~fR6`XRV7i^iyp0YpW}0;9m)NpD*c_FyAf_;+G(D1c8gV&6N~K< zX>mq}R#i!me?E7NgVT<*;Eae>Rr;y293)4APpqmEp;6+XD9FdJ4KFQ7Qc%h>oRY8n zdEgl(Gt&#w-<{oJ+<70nVO_Y?oXfTRN5ODV*&tp!O4IN6zBoSM1JtRpb0?%D4nO0EL6IzK5v}Fr zwXcia4tIAa4?cO$H%GU&KkRO!kz_-7dX~uD_jd<;$ltLtX$&#e0o~ZG2egRB&YzG@ zaRN5PgxDHRJMH*SIlG&hRDZ~&)f+r$?(R;}JO0$;y_16OJ{U>15rCK{wy z)=SsYZ+FSi0JaKGU88TV2z=H`5B>=BV2|Ev09%bGZW7iBC))|@gd`>jOW_C4e!{=i zy5P?pgmpnL_7axHuVx5~!s!TMQP5E*Yze(Wb~Exz%ePq~)_yeqyN6R-@I>C3fVEwm4u8AlSU%2usTrPmCb zYvT%k>o6n9*5UlD+fw?fx~rPvj%#XS>)dMcXYBp>^WXiCOLoJLo5@Z$tiToeeFm@x z@J}_w_@56lILrUwRafU??vhc6J8gilq% zk}!yagguD!*D2#(xFr$R3qv@hFV~Wq>d4Lkofb79@?F2-K`&uo8}YS+ms$*k>G115 zI;+D(>g7xym8Y*50jNw{oCI^cJ!~$eE5^uXk2E}2FYwPmCA&bu}x*q(^b1- zhOKu zZ6+Swm@ZWIWJ*~(ZD%KUW$i*KUEpD3C9{>{x8{o3$4jM&iWynF-fsOMZ;gxH0PXU{ z43#H~)m*_AS8E>GnBG|~jg_r}zBQM(E9tRPdaN{@8eX5;kgk+7>9Pnh%G%{dx*hfuKWp~5M>boP zTxN5=lo=GbNTEeIjc}AW=)MkX=}4pA!qLKfuSv1 zw+*JYlx?eOlhL~4TS~>EovGFzAFYs)$tTERz7d8``T@{DurEEA^X6(RK@IZWzrK9<bX9t=qk003lwLdXCB literal 2175 zcmV-_2!Qt=iwFP!00004|BO~^Y!ufO_Uz2qcW3SO@z~xyyKAq319WOkff|{K;w6RG9>aWst@glYk*J{AQT>@cIv*=a>&ZmQ9t(@L!cue4wFb_{SKN zA%FqHZvvC?=p#?^WCdn@l%+sBwi_LKKv!B?T3R$&^2uhHDLDTbRaD`+LWQr0!YTwY zX!z^-icdCysd)Yh<+s9~B73(9}*| zci=}4dGNnDT=%ns@E~6PH+3e#a~!W(7$mq8=d19kCb*Me!2MPD*(MkW?!s#yc;vro zg1ZRT@y#lHvI*7+?#9zq_(T)jO>hs+RpDbza1X(~`1w_@_Ai>?UY~6CvJjsC^J&#o zmq17u-4{F+Z@2b>lcO*`~rFC}+C2tzc#h;JOE zeVVrtfNz6j0_(&1$Egm$GeQGE1S6!OAK$C$d$CDFtgk=9jfvyUzYtp&+)LHJ zOU86T6r;p2fcvWK$D0`91DpYvfge5Ql|RN4!a8$Zo3z?Ddv4l#_8v>|+| zO7}9Si|Os6vRF0M3=cI_UxL@GUYz6U5nXd9o^}mdG&LGt;w4X*z=rWgmHYQRIi|PI zy3&F>Ay%F~Tus-*daE>l!{!Z#C`?@}e0`A@4Qj${DZU^Icw-iXS9qN$@4>gY}N;INhwP9Cjx-6eG{MYM>oQ_I-(mL)5F-k-TNMwFBYyHB7BpBNP%& zomfqTecWy$To~s52npUvsj(Uzm(ipT8+>S$-Z?%_bPXNF%`h9m3yU0G;EqQ5YOYTF-{99ygWJ$5KXP*k5!R<@!OzcC)TeW;E4bAV zkxl$KCb)QEk61Outc|l7W}|rZ2XsHEy`Nm-9rx&MKf3)Hx`D{Oqhc1enQ=CT?;NG{ zHt|^QhQIPhsZ6`Z80&^U>@)m&x88Pg`yAiT#+cZ?Qt#E-$&0=@niyI4SdoV>o{$dm zWKHg_hD~70@HbcKb*Kma5u_VW5AJ%ok9K!dO(US1#!;Y@@ugh3o6Y$ZN*fv^a?A0;dTCYpqe z{}tOK^!XtoQ5MY`c4KCoLLVwtPh*xpLf! zs{HoLnqBFtxe{l$<9zi9=uN8W2)OZdGi_;Mw|mA<=po&I=AXW~y_Y2Z3fbe4ZR7F1 zuCm~|6c2iF!vwYpe|q+}Ty`HEYNeL?U@0y&ZZm=1fv+8=8Ia*md{)9bljio`$3L39 z>W=CIWWoft8jrq9r_>K~0ix-L5gZ|B*5HA+35&zuHNxUBilc*=7Q}6OZrzK9_F5?*@t2fMvLhc-P^P?-4c#kDG)I z!g5?r*j;$)AYmq46#GnAfh!2R8=w7<^bEm6;+=5_5|}WC`GtJV|K_Ygi>BPU?ykE@ z%G2_eb5Fec^Eb{OIvS2DOj;{hEtXm?+9}6UcRg5f%2tP+%@$HS3cJ4Hq$*`gO%5TY1mA8JRcEzDxBEHp`S#Os! zsrA`HYR|^>l;bJdHJ!<(Hx%-DCsna@qM*=ZrDSKO9%zcKx67o+%WI-+q`N0$=L-3> z)lYOIWA(z+%U7l=B%s>PBKY1sp#j>=ZJFF`1*=D5^NGmSk0|fV(qIQc4^O!2A*8X&StGuUw9Jia&Cv6 zORrv)C|mtrnPjn8D(p^7a)}$ZZQoWZ?9OBzYt0uXrTt>%Q`YJ6CUVsE_~O9IdOISc zZd%UHHng92X87ce*M0ILiQUur)VS4MEmVH6Tyb*cik;f?zW@LL|Nmns!9)xY000Km BJz4+& diff --git a/mem3.prof b/mem3.prof new file mode 100644 index 0000000000000000000000000000000000000000..5458d4f0d0c21bda1adf891aabf9cf83447db21c GIT binary patch literal 2664 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 WS1Kn{yZ##h0RR63)*?E~6951iM>#$K literal 0 HcmV?d00001 diff --git a/mem4.prof b/mem4.prof new file mode 100644 index 0000000000000000000000000000000000000000..18cb1e8ce30841d8925ad5b53c0282639e54b219 GIT binary patch literal 2074 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 literal 0 HcmV?d00001 diff --git a/mem6.prof b/mem6.prof new file mode 100644 index 0000000000000000000000000000000000000000..f40d76dd8f222ceaca89e1fbfe86a13504f17b99 GIT binary patch literal 1880 zcmV-e2dDTSiwFP!00004|BO~|Y#V18&;7GspB?+jIgTIulJ@#0?V5D6n_IP3gGo`_ zWoW=uf%t+ZwO8kwn);t?N725WJ36WsLDo%ekTz7Yim+81E2Wz>im;|t2vnv?2(%&b zM@k193??duI$#C8&uiB<+lPM0bH4jM|9-#cxqI_vohhIm2m;5{nFhM4GXqppXCe9> z29*O-@Zz&q!rWjAtSX?)Fs1-Q=0rF!6<2?BCCm+`!hco5R4~ybOv7`pQ=A5uHNrHA zVw5l)Pape^h|{5^5~f28W6rP@v-F{%p&?UOR88l=3_SB3Wl`W;3Mf6?P&K`GC%kX( z?&EPBSO{-?uT$mBK$Vc}B8;#6lyDWUsGwNUPM2C((+6%iFor+4)bYJA;LC_m1pocR z4*cQ(9MN=cGZRm&zZT|V5WBb-#C=?6Vc1O~84$;~6PaNw1PM&guP^|D6X6LK#j7_c zl)-1ooxuo>5ER3&u3ZsXBXEGA2v}&*D2DN$zo33Jctr!n8n;a4vEZ^s#x%@mY*^2R znTkPhS>rZ~<7@w*Dkf~B20zX9TNO=6V$z9Nwq+`Tsfs25URJ-sZdSyU}V+?(8HN&NP|D9V6)ZnZ7zWvy5c zsK*0GT2oJoilR~AHXFsYw?7sr%h^_}73eF$oV2DfN=0R4ily*{>zzok4q=!F#mZP= z<;1b4RMBIK+$FWA1hp~z_1 zx|mfOul=4n=+8$BX{D`@^1;d%L|B@_C}Hp*w;AFGa*a)xA*sZcNX8k<4Cc-T4jI!x ziWGe3c#384)caITjx-GSclbWN@)GrzA$Q{2z9d`t$;o-qLjSZr#ch_wGhHEiEl447 zpanfE#E>1>%*G%8sjq`jXE()a2;Ie2i)wbS54qFhxb`8%_3I>;L+Jj**X|R#kBd@L zraZUV1b*}v3X*iY#r6*F;RL;dGD_^>27F(>=>6i#QsY+qXj?toI;(UproKUxBz80~ zxd29{m$Usa0dK9%3S#{-ZV>A!H(FZL7^OIo4k)TK{|S;qwkYPm5zlwGrYFAK8rpcJ zEjC_Q)7W?`!IRzZPA{V*owZ`Ew(^JIfV8IW3{sZbcUD_w%zr`~ zL_si{<2Kub-+hNB)30%uCNp8h9}Tp2tF3e{rm?9PE8wGn8ngnXb1@C zV>hP;rEW*W!t1}iJ}CnF-pO{D1G^J%wqF(fC*hkR;+BL7oNzX9V0Yna_lUhDj@Y!t zl+~jTia%4k^Dezpca8(g;pyMdxJF?$N@+%6BW@&#@_6!1!cwqq5|#o79m2Nbx%WuE zG5C){*cfcWP0qOGSh1rJtOSa*W<&~@%Wv;02y7PDj-Ly2gQel(CW=l2M^2eteBv8~ zA)MfZAxz>VVRz#TUneXBzmjnon8GQ-cHr}WqmHuhsQmXY3!8DXGcFE*_abk!95SXp z>)ySCS~;tI=E)1syz}@UpFEw;>P*?CxI?bdsCfn7H4Z+~^c(K5S1MHt`>O}P;uo3? z*Cd$q&Uxrh8^ap&6i-5le%@bL+P~nJt9f!?th(d$L&nV)D+_*oIw!6ovf)Mu6~>L#mnzL-*_U_Q_uiY| zSFbMCy|Oc3EcuQ6Vl}^5oz2be%V4YC#lAiY&RUGkfMAn9l8~Rw{m> z=|ca*eO~kMpxfS}SFTnT+|how-Cn~N-MI;RdT^;&S|G-QZV&m(yGzx=5jWm#+!Iok zdqgV!a=C{#nqIwGc89y(%ia;c=BB#0b-&>^8?|DkQmibx(0$w}9QGHMN`5a-&8r{T z-)HQ*u)Rk|dh|pqd!;`56@OXGcebZf=Sl9+Ql&8KCcA|ik2IQoxzY3r SNB$Q80RR8WK%nZH4FCX=yRjw! literal 0 HcmV?d00001