1 // Copyright 2022 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
10 "cmd/compile/internal/ir"
11 "cmd/compile/internal/typecheck"
12 "cmd/compile/internal/types"
21 // 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.
24 IRNodes map[string]*IRNode
29 // IRNode represents a node in the IRGraph.
31 // Pointer to the IR of the Function represented by this node.
33 // Flat weight of the IRNode, obtained from profile.
35 // Cumulative weight of the IRNode.
39 // IREdgeMap maps an IRNode to its successors.
40 type IREdgeMap map[*IRNode][]*IREdge
42 // IREdge represents a call edge in the IRGraph with source, destination, weight, callsite, and line number information.
44 // Source and destination of the edge in IRNode.
50 // NodeMapKey represents a hash key to identify unique call-edges in profile and in IR. Used for deduplication of call edges found in profile.
51 type NodeMapKey struct {
57 // Weights capture both node weight and edge weight.
64 // CallSiteInfo captures call-site information and its caller/callee.
65 type CallSiteInfo struct {
72 // Aggregated NodeWeights and EdgeWeights across profiles. This helps us determine the percentage threshold for hot/cold partitioning.
73 GlobalTotalNodeWeight = int64(0)
74 GlobalTotalEdgeWeight = int64(0)
76 // Global node and their aggregated weight information.
77 GlobalNodeMap = make(map[NodeMapKey]*Weights)
79 // WeightedCG represents the IRGraph built from profile, which we will update as part of inlining.
82 // Original profile-graph.
85 // Per-caller data structure to track the list of hot call sites. This gets rewritten every caller leaving it to GC for cleanup.
86 ListOfHotCallSites = make(map[CallSiteInfo]struct{})
89 // BuildProfileGraph generates a profile-graph from the profile.
90 func BuildProfileGraph(profileFile string) {
92 // if possible, we should cache the profile-graph.
93 if ProfileGraph != nil {
97 // open the profile file.
98 f, err := os.Open(profileFile)
100 log.Fatal("failed to open file " + profileFile)
104 p, err := profile.Parse(f)
106 log.Fatal("failed to Parse profile file.")
109 // Build the options.
112 SampleValue: func(v []int64) int64 { return v[1] },
114 // Build the graph using profile package.
115 ProfileGraph = New(p, opt)
117 // Build various global maps from profile.
118 preprocessProfileGraph()
122 // BuildWeightedCallGraph generates a weighted callgraph from the profile for the current package.
123 func BuildWeightedCallGraph() {
125 // Bail if there is no profile-graph available.
126 if ProfileGraph == nil {
130 // Create package-level call graph with weights from profile and IR.
131 WeightedCG = createIRGraph()
134 // ConvertLine2Int converts ir.Line string to integer.
135 func ConvertLine2Int(line string) int {
136 splits := strings.Split(line, ":")
137 cs, _ := strconv.ParseInt(splits[len(splits)-2], 0, 64)
141 // 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.
142 func preprocessProfileGraph() {
143 nFlat := make(map[string]int64)
144 nCum := make(map[string]int64)
146 // Accummulate weights for the same node.
147 for _, n := range ProfileGraph.Nodes {
148 canonicalName := n.Info.Name
149 nFlat[canonicalName] += n.FlatValue()
150 nCum[canonicalName] += n.CumValue()
153 // Process ProfileGraph and build various node and edge maps which will be consumed by AST walk.
154 for _, n := range ProfileGraph.Nodes {
155 GlobalTotalNodeWeight += n.FlatValue()
156 canonicalName := n.Info.Name
157 // Create the key to the NodeMapKey.
158 nodeinfo := NodeMapKey{
159 CallerName: canonicalName,
160 CallSite: n.Info.Lineno,
163 for _, e := range n.Out {
164 GlobalTotalEdgeWeight += e.WeightValue()
165 nodeinfo.CalleeName = e.Dest.Info.Name
166 if w, ok := GlobalNodeMap[nodeinfo]; ok {
167 w.EWeight += e.WeightValue()
169 weights := new(Weights)
170 weights.NFlat = nFlat[canonicalName]
171 weights.NCum = nCum[canonicalName]
172 weights.EWeight = e.WeightValue()
173 GlobalNodeMap[nodeinfo] = weights
179 // createIRGraph builds the IRGraph by visting all the ir.Func in decl list of a package.
180 func createIRGraph() *IRGraph {
182 // Bottomup walk over the function to create IRGraph.
183 ir.VisitFuncsBottomUp(typecheck.Target.Decls, func(list []*ir.Func, recursive bool) {
184 for _, n := range list {
185 g.Visit(n, recursive)
191 // 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.
192 func (g *IRGraph) Visit(fn *ir.Func, recursive bool) {
193 if g.IRNodes == nil {
194 g.IRNodes = make(map[string]*IRNode)
196 if g.OutEdges == nil {
197 g.OutEdges = make(map[*IRNode][]*IREdge)
199 if g.InEdges == nil {
200 g.InEdges = make(map[*IRNode][]*IREdge)
202 name := ir.PkgFuncName(fn)
205 if g.IRNodes[name] == nil {
206 g.IRNodes[name] = node
208 // Create the key for the NodeMapKey.
209 nodeinfo := NodeMapKey{
214 // If the node exists, then update its node weight.
215 if weights, ok := GlobalNodeMap[nodeinfo]; ok {
216 g.IRNodes[name].Flat = weights.NFlat
217 g.IRNodes[name].Cum = weights.NCum
220 // Recursively walk over the body of the function to create IRGraph edges.
221 g.createIRGraphEdge(fn, g.IRNodes[name], name)
224 // addEdge adds an edge between caller and new node that points to `callee` based on the profile-graph and GlobalNodeMap.
225 func (g *IRGraph) addEdge(caller *IRNode, callee *ir.Func, n *ir.Node, callername string, line int) {
227 // Create an IRNode for the callee.
228 calleenode := new(IRNode)
229 calleenode.AST = callee
230 calleename := ir.PkgFuncName(callee)
232 // Create key for NodeMapKey.
233 nodeinfo := NodeMapKey{
234 CallerName: callername,
235 CalleeName: calleename,
239 // Create the callee node with node weight.
240 if g.IRNodes[calleename] == nil {
241 g.IRNodes[calleename] = calleenode
242 nodeinfo2 := NodeMapKey{
243 CallerName: calleename,
247 if weights, ok := GlobalNodeMap[nodeinfo2]; ok {
248 g.IRNodes[calleename].Flat = weights.NFlat
249 g.IRNodes[calleename].Cum = weights.NCum
253 if weights, ok := GlobalNodeMap[nodeinfo]; ok {
254 caller.Flat = weights.NFlat
255 caller.Cum = weights.NCum
257 // Add edge in the IRGraph from caller to callee.
258 info := &IREdge{Src: caller, Dst: g.IRNodes[calleename], Weight: weights.EWeight, CallSite: line}
259 g.OutEdges[caller] = append(g.OutEdges[caller], info)
260 g.InEdges[g.IRNodes[calleename]] = append(g.InEdges[g.IRNodes[calleename]], info)
262 nodeinfo.CalleeName = ""
263 nodeinfo.CallSite = -1
264 if weights, ok := GlobalNodeMap[nodeinfo]; ok {
265 caller.Flat = weights.NFlat
266 caller.Cum = weights.NCum
267 info := &IREdge{Src: caller, Dst: g.IRNodes[calleename], Weight: 0, CallSite: line}
268 g.OutEdges[caller] = append(g.OutEdges[caller], info)
269 g.InEdges[g.IRNodes[calleename]] = append(g.InEdges[g.IRNodes[calleename]], info)
271 info := &IREdge{Src: caller, Dst: g.IRNodes[calleename], Weight: 0, CallSite: line}
272 g.OutEdges[caller] = append(g.OutEdges[caller], info)
273 g.InEdges[g.IRNodes[calleename]] = append(g.InEdges[g.IRNodes[calleename]], info)
278 // 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.
279 func (g *IRGraph) createIRGraphEdge(fn *ir.Func, callernode *IRNode, name string) {
280 var doNode func(ir.Node) bool
281 doNode = func(n ir.Node) bool {
284 ir.DoChildren(n, doNode)
286 call := n.(*ir.CallExpr)
287 line := ConvertLine2Int(ir.Line(n))
288 // Find the callee function from the call site and add the edge.
289 f := inlCallee(call.X)
291 g.addEdge(callernode, f, &n, name, line)
294 call := n.(*ir.CallExpr)
295 // Find the callee method from the call site and add the edge.
296 fn2 := ir.MethodExprName(call.X).Func
297 line := ConvertLine2Int(ir.Line(n))
298 g.addEdge(callernode, fn2, &n, name, line)
305 // WeightInPercentage converts profile weights to a percentage.
306 func WeightInPercentage(value int64, total int64) float64 {
309 ratio = (float64(value) / float64(total)) * 100
314 // PrintWeightedCallGraphDOT prints IRGraph in DOT format.
315 func PrintWeightedCallGraphDOT(nodeThreshold float64, edgeThreshold float64) {
316 fmt.Printf("\ndigraph G {\n")
317 fmt.Printf("forcelabels=true;\n")
319 // List of functions in this package.
320 funcs := make(map[string]struct{})
321 ir.VisitFuncsBottomUp(typecheck.Target.Decls, func(list []*ir.Func, recursive bool) {
322 for _, f := range list {
323 name := ir.PkgFuncName(f)
324 funcs[name] = struct{}{}
328 // Determine nodes of DOT.
329 nodes := make(map[string]*ir.Func)
330 for name, _ := range funcs {
331 if n, ok := WeightedCG.IRNodes[name]; ok {
332 for _, e := range WeightedCG.OutEdges[n] {
333 if _, ok := nodes[ir.PkgFuncName(e.Src.AST)]; !ok {
334 nodes[ir.PkgFuncName(e.Src.AST)] = e.Src.AST
336 if _, ok := nodes[ir.PkgFuncName(e.Dst.AST)]; !ok {
337 nodes[ir.PkgFuncName(e.Dst.AST)] = e.Dst.AST
340 if _, ok := nodes[ir.PkgFuncName(n.AST)]; !ok {
341 nodes[ir.PkgFuncName(n.AST)] = n.AST
347 for name, ast := range nodes {
348 if n, ok := WeightedCG.IRNodes[name]; ok {
349 nodeweight := WeightInPercentage(n.Flat, GlobalTotalNodeWeight)
351 if nodeweight > nodeThreshold {
355 fmt.Printf("\"%v\" [color=%v,label=\"%v,freq=%.2f,inl_cost=%d\"];\n", ir.PkgFuncName(ast), color, ir.PkgFuncName(ast), nodeweight, ast.Inl.Cost)
357 fmt.Printf("\"%v\" [color=%v, label=\"%v,freq=%.2f\"];\n", ir.PkgFuncName(ast), color, ir.PkgFuncName(ast), nodeweight)
362 ir.VisitFuncsBottomUp(typecheck.Target.Decls, func(list []*ir.Func, recursive bool) {
363 for _, f := range list {
364 name := ir.PkgFuncName(f)
365 if n, ok := WeightedCG.IRNodes[name]; ok {
366 for _, e := range WeightedCG.OutEdges[n] {
367 edgepercent := WeightInPercentage(e.Weight, GlobalTotalEdgeWeight)
368 if edgepercent > edgeThreshold {
369 fmt.Printf("edge [color=red, style=solid];\n")
371 fmt.Printf("edge [color=black, style=solid];\n")
374 fmt.Printf("\"%v\" -> \"%v\" [label=\"%.2f\"];\n", ir.PkgFuncName(n.AST), ir.PkgFuncName(e.Dst.AST), edgepercent)
382 // redirectEdges deletes the cur node out-edges and redirect them so now these edges are the parent node out-edges.
383 func redirectEdges(g *IRGraph, parent *IRNode, cur *IRNode) {
384 for _, outEdge := range g.OutEdges[cur] {
386 g.OutEdges[parent] = append(g.OutEdges[parent], outEdge)
388 delete(g.OutEdges, cur)
391 // RedirectEdges deletes and redirects out-edges from node cur based on inlining information via inlinedCallSites.
392 func RedirectEdges(cur *IRNode, inlinedCallSites map[CallSiteInfo]struct{}) {
394 for i, outEdge := range g.OutEdges[cur] {
395 if _, found := inlinedCallSites[CallSiteInfo{Line: outEdge.CallSite, Caller: cur.AST}]; !found {
396 for _, InEdge := range g.InEdges[cur] {
397 if _, ok := inlinedCallSites[CallSiteInfo{Line: InEdge.CallSite, Caller: InEdge.Src.AST}]; ok {
398 weight := calculateweight(g, InEdge.Src, cur)
399 redirectEdge(g, InEdge.Src, cur, outEdge, weight, i)
403 remove(g, cur, i, outEdge.Dst.AST.Nname)
409 // calculateweight calculates the weight of the new redirected edge.
410 func calculateweight(g *IRGraph, parent *IRNode, cur *IRNode) int64 {
413 for _, InEdge := range g.InEdges[cur] {
414 sum = sum + InEdge.Weight
415 if InEdge.Src == parent {
428 // redirectEdge deletes the cur-node's out-edges and redirect them so now these edges are the parent node out-edges.
429 func redirectEdge(g *IRGraph, parent *IRNode, cur *IRNode, outEdge *IREdge, weight int64, idx int) {
431 outEdge.Weight = weight * outEdge.Weight
432 g.OutEdges[parent] = append(g.OutEdges[parent], outEdge)
433 remove(g, cur, idx, outEdge.Dst.AST.Nname)
436 // remove deletes the cur-node's out-edges at index idx.
437 func remove(g *IRGraph, cur *IRNode, idx int, name *ir.Name) {
438 if len(g.OutEdges[cur]) >= 2 {
439 g.OutEdges[cur][idx] = &IREdge{CallSite: -1}
441 delete(g.OutEdges, cur)
445 // removeall deletes all cur-node's out-edges that marked to be removed .
446 func removeall(g *IRGraph, cur *IRNode) {
447 for i := len(g.OutEdges[cur]) - 1; i >= 0; i-- {
448 if g.OutEdges[cur][i].CallSite == -1 {
449 g.OutEdges[cur][i] = g.OutEdges[cur][len(g.OutEdges[cur])-1]
450 g.OutEdges[cur] = g.OutEdges[cur][:len(g.OutEdges[cur])-1]
455 // inlCallee is same as the implementation for inl.go with one change. The change is that we do not invoke CanInline on a closure.
456 func inlCallee(fn ir.Node) *ir.Func {
457 fn = ir.StaticValue(fn)
460 fn := fn.(*ir.SelectorExpr)
461 n := ir.MethodExprName(fn)
462 // Check that receiver type matches fn.X.
463 // TODO(mdempsky): Handle implicit dereference
464 // of pointer receiver argument?
465 if n == nil || !types.Identical(n.Type().Recv().Type, fn.X.Type()) {
471 if fn.Class == ir.PFUNC {
475 fn := fn.(*ir.ClosureExpr)