parent
ded4a6b87d
commit
3f443379c8
@ -0,0 +1,727 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
@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.
|
||||
//
|
||||
|
||||
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 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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
@ -0,0 +1,401 @@
|
||||
#include <stdio.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Block {
|
||||
public:
|
||||
Block(int n) : name(n) {}
|
||||
|
||||
int name;
|
||||
vector<Block*> in;
|
||||
vector<Block*> 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*> block;
|
||||
vector<Edge> 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*> block;
|
||||
vector<Loop*> child;
|
||||
Loop *parent;
|
||||
Block *head;
|
||||
|
||||
bool isRoot;
|
||||
bool isReducible;
|
||||
int counter;
|
||||
int nesting;
|
||||
int depth;
|
||||
|
||||
};
|
||||
|
||||
class LoopGraph {
|
||||
public:
|
||||
Loop root;
|
||||
vector<Loop*> 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<LoopBlock*> backPred;
|
||||
vector<LoopBlock*> nonBackPred;
|
||||
LoopBlock *unionf;
|
||||
|
||||
void Init(Block*);
|
||||
LoopBlock *Find();
|
||||
bool IsAncestor(LoopBlock*);
|
||||
|
||||
};
|
||||
|
||||
class LoopFinder {
|
||||
public:
|
||||
vector<LoopBlock> loopBlock;
|
||||
vector<LoopBlock*> depthFirst;
|
||||
vector<LoopBlock*> 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();
|
||||
}
|
@ -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()
|
||||
}
|
@ -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()
|
||||
}
|
Loading…
Reference in new issue