// IRGraph is the key datastrcture that is built from profile. It is essentially a call graph with nodes pointing to IRs of functions and edges carrying weights and callsite information. The graph is bidirectional that helps in removing nodes efficiently.
typeIRGraphstruct{
// Nodes of the graph
IRNodesmap[string]*IRNode
OutEdgesIREdgeMap
InEdgesIREdgeMap
}
// IRNode represents a node in the IRGraph.
typeIRNodestruct{
// Pointer to the IR of the Function represented by this node.
AST*ir.Func
// Flat weight of the IRNode, obtained from profile.
Flatint64
// Cumulative weight of the IRNode.
Cumint64
}
// IREdgeMap maps an IRNode to its successors.
typeIREdgeMapmap[*IRNode][]*IREdge
// IREdge represents a call edge in the IRGraph with source, destination, weight, callsite, and line number information.
typeIREdgestruct{
// Source and destination of the edge in IRNode.
Src,Dst*IRNode
Weightint64
CallSiteint
}
// NodeMapKey represents a hash key to identify unique call-edges in profile and in IR. Used for deduplication of call edges found in profile.
typeNodeMapKeystruct{
CallerNamestring
CalleeNamestring
CallSiteint
}
// Weights capture both node weight and edge weight.
typeWeightsstruct{
NFlatint64
NCumint64
EWeightint64
}
// CallSiteInfo captures call-site information and its caller/callee.
typeCallSiteInfostruct{
Lineint
Caller*ir.Func
Callee*ir.Func
}
var(
// Aggregated NodeWeights and EdgeWeights across profiles. This helps us determine the percentage threshold for hot/cold partitioning.
GlobalTotalNodeWeight=int64(0)
GlobalTotalEdgeWeight=int64(0)
// Global node and their aggregated weight information.
GlobalNodeMap=make(map[NodeMapKey]*Weights)
// WeightedCG represents the IRGraph built from profile, which we will update as part of inlining.
WeightedCG*IRGraph
// Original profile-graph.
ProfileGraph*Graph
// Per-caller data structure to track the list of hot call sites. This gets rewritten every caller leaving it to GC for cleanup.
// BuildProfileGraph generates a profile-graph from the profile.
funcBuildProfileGraph(profileFilestring){
// if possible, we should cache the profile-graph.
ifProfileGraph!=nil{
return
}
// open the profile file.
f,err:=os.Open(profileFile)
iferr!=nil{
log.Fatal("failed to open file "+profileFile)
return
}
deferf.Close()
p,err:=profile.Parse(f)
iferr!=nil{
log.Fatal("failed to Parse profile file.")
return
}
// Build the options.
opt:=&Options{
CallTree:false,
SampleValue:func(v[]int64)int64{returnv[1]},
}
// Build the graph using profile package.
ProfileGraph=New(p,opt)
// Build various global maps from profile.
preprocessProfileGraph()
}
// BuildWeightedCallGraph generates a weighted callgraph from the profile for the current package.
funcBuildWeightedCallGraph(){
// Bail if there is no profile-graph available.
ifProfileGraph==nil{
return
}
// Create package-level call graph with weights from profile and IR.
WeightedCG=createIRGraph()
}
// preprocessProfileGraph builds various maps from the profile-graph. It builds GlobalNodeMap and other information based on the name and callsite to compute node and edge weights which will be used later on to create edges for WeightedCG.
funcpreprocessProfileGraph(){
nFlat:=make(map[string]int64)
nCum:=make(map[string]int64)
// Accummulate weights for the same node.
for_,n:=rangeProfileGraph.Nodes{
canonicalName:=n.Info.Name
nFlat[canonicalName]+=n.FlatValue()
nCum[canonicalName]+=n.CumValue()
}
// Process ProfileGraph and build various node and edge maps which will be consumed by AST walk.
for_,n:=rangeProfileGraph.Nodes{
GlobalTotalNodeWeight+=n.FlatValue()
canonicalName:=n.Info.Name
// Create the key to the NodeMapKey.
nodeinfo:=NodeMapKey{
CallerName:canonicalName,
CallSite:n.Info.Lineno,
}
for_,e:=rangen.Out{
GlobalTotalEdgeWeight+=e.WeightValue()
nodeinfo.CalleeName=e.Dest.Info.Name
ifw,ok:=GlobalNodeMap[nodeinfo];ok{
w.EWeight+=e.WeightValue()
}else{
weights:=new(Weights)
weights.NFlat=nFlat[canonicalName]
weights.NCum=nCum[canonicalName]
weights.EWeight=e.WeightValue()
GlobalNodeMap[nodeinfo]=weights
}
}
}
}
// createIRGraph builds the IRGraph by visting all the ir.Func in decl list of a package.
funccreateIRGraph()*IRGraph{
vargIRGraph
// Bottomup walk over the function to create IRGraph.
// Visit traverses the body of each ir.Func and use GlobalNodeMap to determine if we need to add an edge from ir.Func and any node in the ir.Func body.
func(g*IRGraph)Visit(fn*ir.Func,recursivebool){
ifg.IRNodes==nil{
g.IRNodes=make(map[string]*IRNode)
}
ifg.OutEdges==nil{
g.OutEdges=make(map[*IRNode][]*IREdge)
}
ifg.InEdges==nil{
g.InEdges=make(map[*IRNode][]*IREdge)
}
name:=ir.PkgFuncName(fn)
node:=new(IRNode)
node.AST=fn
ifg.IRNodes[name]==nil{
g.IRNodes[name]=node
}
// Create the key for the NodeMapKey.
nodeinfo:=NodeMapKey{
CallerName:name,
CalleeName:"",
CallSite:-1,
}
// If the node exists, then update its node weight.
ifweights,ok:=GlobalNodeMap[nodeinfo];ok{
g.IRNodes[name].Flat=weights.NFlat
g.IRNodes[name].Cum=weights.NCum
}
// Recursively walk over the body of the function to create IRGraph edges.
g.createIRGraphEdge(fn,g.IRNodes[name],name)
}
// addEdge adds an edge between caller and new node that points to `callee` based on the profile-graph and GlobalNodeMap.
// createIRGraphEdge traverses the nodes in the body of ir.Func and add edges between callernode which points to the ir.Func and the nodes in the body.