-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
go/callgraph/vta: allow nil initial call graph
When nil is passed as the initial call graph, vta will use a more performant version of CHA. For this purpose, lazyCallees function of CHA is exposed to VTA. This change reduces the time and memory footprint for ~10%, measured on several large real world Go projects. Updates golang/go#57357 Change-Id: Ib5c5edca0026e6902e453fa10fc14f2b763849db Reviewed-on: https://go-review.googlesource.com/c/tools/+/609978 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
- Loading branch information
1 parent
e5ae0a9
commit 09886e0
Showing
8 changed files
with
188 additions
and
127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Copyright 2024 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package chautil provides helper functions related to | ||
// class hierarchy analysis (CHA) for use in x/tools. | ||
package chautil | ||
|
||
import ( | ||
"go/types" | ||
|
||
"golang.org/x/tools/go/ssa" | ||
"golang.org/x/tools/go/types/typeutil" | ||
) | ||
|
||
// LazyCallees returns a function that maps a call site (in a function in fns) | ||
// to its callees within fns. The set of callees is computed using the CHA algorithm, | ||
// i.e., on the entire implements relation between interfaces and concrete types | ||
// in fns. Please see golang.org/x/tools/go/callgraph/cha for more information. | ||
// | ||
// The resulting function is not concurrency safe. | ||
func LazyCallees(fns map[*ssa.Function]bool) func(site ssa.CallInstruction) []*ssa.Function { | ||
// funcsBySig contains all functions, keyed by signature. It is | ||
// the effective set of address-taken functions used to resolve | ||
// a dynamic call of a particular signature. | ||
var funcsBySig typeutil.Map // value is []*ssa.Function | ||
|
||
// methodsByID contains all methods, grouped by ID for efficient | ||
// lookup. | ||
// | ||
// We must key by ID, not name, for correct resolution of interface | ||
// calls to a type with two (unexported) methods spelled the same but | ||
// from different packages. The fact that the concrete type implements | ||
// the interface does not mean the call dispatches to both methods. | ||
methodsByID := make(map[string][]*ssa.Function) | ||
|
||
// An imethod represents an interface method I.m. | ||
// (There's no go/types object for it; | ||
// a *types.Func may be shared by many interfaces due to interface embedding.) | ||
type imethod struct { | ||
I *types.Interface | ||
id string | ||
} | ||
// methodsMemo records, for every abstract method call I.m on | ||
// interface type I, the set of concrete methods C.m of all | ||
// types C that satisfy interface I. | ||
// | ||
// Abstract methods may be shared by several interfaces, | ||
// hence we must pass I explicitly, not guess from m. | ||
// | ||
// methodsMemo is just a cache, so it needn't be a typeutil.Map. | ||
methodsMemo := make(map[imethod][]*ssa.Function) | ||
lookupMethods := func(I *types.Interface, m *types.Func) []*ssa.Function { | ||
id := m.Id() | ||
methods, ok := methodsMemo[imethod{I, id}] | ||
if !ok { | ||
for _, f := range methodsByID[id] { | ||
C := f.Signature.Recv().Type() // named or *named | ||
if types.Implements(C, I) { | ||
methods = append(methods, f) | ||
} | ||
} | ||
methodsMemo[imethod{I, id}] = methods | ||
} | ||
return methods | ||
} | ||
|
||
for f := range fns { | ||
if f.Signature.Recv() == nil { | ||
// Package initializers can never be address-taken. | ||
if f.Name() == "init" && f.Synthetic == "package initializer" { | ||
continue | ||
} | ||
funcs, _ := funcsBySig.At(f.Signature).([]*ssa.Function) | ||
funcs = append(funcs, f) | ||
funcsBySig.Set(f.Signature, funcs) | ||
} else if obj := f.Object(); obj != nil { | ||
id := obj.(*types.Func).Id() | ||
methodsByID[id] = append(methodsByID[id], f) | ||
} | ||
} | ||
|
||
return func(site ssa.CallInstruction) []*ssa.Function { | ||
call := site.Common() | ||
if call.IsInvoke() { | ||
tiface := call.Value.Type().Underlying().(*types.Interface) | ||
return lookupMethods(tiface, call.Method) | ||
} else if g := call.StaticCallee(); g != nil { | ||
return []*ssa.Function{g} | ||
} else if _, ok := call.Value.(*ssa.Builtin); !ok { | ||
fns, _ := funcsBySig.At(call.Signature()).([]*ssa.Function) | ||
return fns | ||
} | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Copyright 2024 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package vta | ||
|
||
import ( | ||
"golang.org/x/tools/go/callgraph" | ||
"golang.org/x/tools/go/callgraph/internal/chautil" | ||
"golang.org/x/tools/go/ssa" | ||
) | ||
|
||
// calleesFunc abstracts call graph in one direction, | ||
// from call sites to callees. | ||
type calleesFunc func(ssa.CallInstruction) []*ssa.Function | ||
|
||
// makeCalleesFunc returns an initial call graph for vta as a | ||
// calleesFunc. If c is not nil, returns callees as given by c. | ||
// Otherwise, it returns chautil.LazyCallees over fs. | ||
func makeCalleesFunc(fs map[*ssa.Function]bool, c *callgraph.Graph) calleesFunc { | ||
if c == nil { | ||
return chautil.LazyCallees(fs) | ||
} | ||
return func(call ssa.CallInstruction) []*ssa.Function { | ||
node := c.Nodes[call.Parent()] | ||
if node == nil { | ||
return nil | ||
} | ||
var cs []*ssa.Function | ||
for _, edge := range node.Out { | ||
if edge.Site == call { | ||
cs = append(cs, edge.Callee.Func) | ||
} | ||
} | ||
return cs | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.