go/callgraph: fix asymptote trap in DeleteSyntheticNodes.

The previous implementation would cause the graph to contain
many duplicate edges resulting in very large cross products,
so that for some inputs (and random map iteration orders) the
running time of DeleteSyntheticNodes was many minutes---more
than the pointer analysis!

Duplicate edges can arise from an interface call that
dispatches to several different wrapper functions each
wrapping the same declared method.

For example, in the callgraph for go/types, a call to
Object.Pos() dispatches to the synthetic functions (*Type).Pos
and (*Var).Pos, each of which wrap (*object).Pos().  After
DeleteSyntheticNodes, Object.Pos() appeared to call
(*object).Pos() twice.

This change builds the set of all edges and avoids adding
edges already in the set.

Also, document findings.

LGTM=crawshaw
R=crawshaw
CC=golang-codereviews
https://golang.org/cl/96100043
This commit is contained in:
Alan Donovan 2014-05-08 14:03:06 -04:00
parent c212b356b8
commit 4700b7a612
1 changed files with 26 additions and 5 deletions

View File

@ -80,19 +80,40 @@ func PathSearch(start *Node, isEnd func(*Node) bool) []*Edge {
// DeleteSyntheticNodes removes from call graph g all nodes for // DeleteSyntheticNodes removes from call graph g all nodes for
// synthetic functions (except g.Root and package initializers), // synthetic functions (except g.Root and package initializers),
// preserving the topology. // preserving the topology. In effect, calls to synthetic wrappers
// are "inlined".
//
func (g *Graph) DeleteSyntheticNodes() { func (g *Graph) DeleteSyntheticNodes() {
// TODO(adonovan): opt: this step results in duplicate // Measurements on the standard library and go.tools show that
// edges---approx 10% of the total. I suspect this is due to // resulting graph has ~15% fewer nodes and 4-8% fewer edges
// some interface method calls dispatching to both (C).f and // than the input.
// (*C).f where the latter is a wrapper. //
// Inlining a wrapper of in-degree m, out-degree n adds m*n
// and removes m+n edges. Since most wrappers are monomorphic
// (n=1) this results in a slight reduction. Polymorphic
// wrappers (n>1), e.g. from embedding an interface value
// inside a struct to satisfy some interface, cause an
// increase in the graph, but they seem to be uncommon.
// Hash all existing edges to avoid creating duplicates.
edges := make(map[Edge]bool)
for _, cgn := range g.Nodes {
for _, e := range cgn.Out {
edges[*e] = true
}
}
for fn, cgn := range g.Nodes { for fn, cgn := range g.Nodes {
if cgn == g.Root || fn.Synthetic == "" || isInit(cgn.Func) { if cgn == g.Root || fn.Synthetic == "" || isInit(cgn.Func) {
continue // keep continue // keep
} }
for _, eIn := range cgn.In { for _, eIn := range cgn.In {
for _, eOut := range cgn.Out { for _, eOut := range cgn.Out {
newEdge := Edge{eIn.Caller, eIn.Site, eOut.Callee}
if edges[newEdge] {
continue // don't add duplicate
}
AddEdge(eIn.Caller, eIn.Site, eOut.Callee) AddEdge(eIn.Caller, eIn.Site, eOut.Callee)
edges[newEdge] = true
} }
} }
g.DeleteNode(cgn) g.DeleteNode(cgn)