1
0
mirror of https://github.com/golang/go synced 2024-09-30 14:28:33 -06:00

cmd/guru: call{ers,stack}: avoid pointer analysis where possible

As an optimization, the callers and callstack queries now avoid the
relatively costly pointer analysis in some common cases:

The callers of a function that is never address-taken can be enumerated
directly from the SSA representation.

Similarly, the callstack can be computed initially by ignoring dynamic
call edges; we run the pointer analysis only if no path is found in this
partial callgraph.  As a bonus, this also causes the tool to
preferentially report all-static callpaths.

A callers query on fmt.Errorf now completes in 3 seconds instead of 8,
and a callstack query completes in 2 seconds instead of 8.

The new code is covered by the existing tests.

Change-Id: I777ea07a1cdb6cadcc2a94952f553b6b036e7382
Reviewed-on: https://go-review.googlesource.com/19496
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Alan Donovan 2016-02-12 15:39:35 -05:00
parent 8da9f8bbd7
commit 608d57b3ae
3 changed files with 115 additions and 18 deletions

View File

@ -7,6 +7,7 @@ package main
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/callgraph"
@ -60,16 +61,20 @@ func callers(q *Query) error {
return fmt.Errorf("no SSA function built for this location (dead code?)")
}
// TODO(adonovan): opt: if function is never address-taken, skip
// the pointer analysis. Just look for direct calls. This can
// be done in a single pass over the SSA.
// Run the pointer analysis, recording each
// call found to originate from target.
ptaConfig.BuildCallGraph = true
cg := ptrAnalysis(ptaConfig).CallGraph
// If the function is never address-taken, all calls are direct
// and can be found quickly by inspecting the whole SSA program.
cg := directCallsTo(target, entryPoints(ptaConfig.Mains))
if cg == nil {
// Run the pointer analysis, recording each
// call found to originate from target.
// (Pointer analysis may return fewer results than
// directCallsTo because it ignores dead code.)
ptaConfig.BuildCallGraph = true
cg = ptrAnalysis(ptaConfig).CallGraph
}
cg.DeleteSyntheticNodes()
edges := cg.CreateNode(target).In
// TODO(adonovan): sort + dedup calls to ensure test determinism.
q.result = &callersResult{
@ -80,6 +85,82 @@ func callers(q *Query) error {
return nil
}
// directCallsTo inspects the whole program and returns a callgraph
// containing edges for all direct calls to the target function.
// directCallsTo returns nil if the function is ever address-taken.
func directCallsTo(target *ssa.Function, entrypoints []*ssa.Function) *callgraph.Graph {
cg := callgraph.New(nil) // use nil as root *Function
targetNode := cg.CreateNode(target)
// Is the function a program entry point?
// If so, add edge from callgraph root.
for _, f := range entrypoints {
if f == target {
callgraph.AddEdge(cg.Root, nil, targetNode)
}
}
// Find receiver type (for methods).
var recvType types.Type
if recv := target.Signature.Recv(); recv != nil {
recvType = recv.Type()
}
// Find all direct calls to function,
// or a place where its address is taken.
var space [32]*ssa.Value // preallocate
for fn := range ssautil.AllFunctions(target.Prog) {
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
// Is this a method (T).f of a concrete type T
// whose runtime type descriptor is address-taken?
// (To be fully sound, we would have to check that
// the type doesn't make it to reflection as a
// subelement of some other address-taken type.)
if recvType != nil {
if mi, ok := instr.(*ssa.MakeInterface); ok {
if types.Identical(mi.X.Type(), recvType) {
return nil // T is address-taken
}
if ptr, ok := mi.X.Type().(*types.Pointer); ok &&
types.Identical(ptr.Elem(), recvType) {
return nil // *T is address-taken
}
}
}
// Direct call to target?
rands := instr.Operands(space[:0])
if site, ok := instr.(ssa.CallInstruction); ok &&
site.Common().Value == target {
callgraph.AddEdge(cg.CreateNode(fn), site, targetNode)
rands = rands[1:] // skip .Value (rands[0])
}
// Address-taken?
for _, rand := range rands {
if rand != nil && *rand == target {
return nil
}
}
}
}
}
return cg
}
func entryPoints(mains []*ssa.Package) []*ssa.Function {
var entrypoints []*ssa.Function
for _, pkg := range mains {
entrypoints = append(entrypoints, pkg.Func("init"))
if main := pkg.Func("main"); main != nil && pkg.Pkg.Name() == "main" {
entrypoints = append(entrypoints, main)
}
}
return entrypoints
}
type callersResult struct {
target *ssa.Function
callgraph *callgraph.Graph

View File

@ -10,6 +10,7 @@ import (
"golang.org/x/tools/cmd/guru/serial"
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/callgraph/static"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
@ -68,16 +69,31 @@ func callstack(q *Query) error {
return fmt.Errorf("no SSA function built for this location (dead code?)")
}
// Run the pointer analysis and build the complete call graph.
ptaConfig.BuildCallGraph = true
cg := ptrAnalysis(ptaConfig).CallGraph
cg.DeleteSyntheticNodes()
// Search for an arbitrary path from a root to the target function.
var callpath []*callgraph.Edge
isEnd := func(n *callgraph.Node) bool { return n.Func == target }
callpath := callgraph.PathSearch(cg.Root, isEnd)
if callpath != nil {
callpath = callpath[1:] // remove synthetic edge from <root>
// First, build a callgraph containing only static call edges,
// and search for an arbitrary path from a root to the target function.
// This is quick, and the user wants a static path if one exists.
cg := static.CallGraph(prog)
cg.DeleteSyntheticNodes()
for _, ep := range entryPoints(ptaConfig.Mains) {
callpath = callgraph.PathSearch(cg.CreateNode(ep), isEnd)
if callpath != nil {
break
}
}
// No fully static path found.
// Run the pointer analysis and build a complete call graph.
if callpath == nil {
ptaConfig.BuildCallGraph = true
cg := ptrAnalysis(ptaConfig).CallGraph
cg.DeleteSyntheticNodes()
callpath = callgraph.PathSearch(cg.Root, isEnd)
if callpath != nil {
callpath = callpath[1:] // remove synthetic edge from <root>
}
}
q.Fset = fset

View File

@ -76,7 +76,7 @@ User manual: http://golang.org/s/oracle-user-manual
Example: describe syntax at offset 530 in this file (an import spec):
$ guru describe src/golang.org/x/tools/cmd/guru/main.go:#530
$ guru describe src/golang.org/x/tools/cmd/guru/main.go:#530
`
func printHelp() {