Skip to content

Commit 937957b

Browse files
committed
vta: adds VTA graph propagation functionality
The propagation consists of two steps. First, VTA graph is converted to a DAG by computing SCCs (resolving any graph loops) and then types and higher-order functions are propagated through the DAG. The results of this propagation are used in the final step to create a call graph. That CL comes after this one. Change-Id: Ib66a9401885d4105f3b6a68d5a0007f306882144 Reviewed-on: https://go-review.googlesource.com/c/tools/+/323050 Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Go Bot <gobot@golang.org> Trust: Zvonimir Pavlinovic <zpavlinovic@google.com> Reviewed-by: Roland Shoemaker <roland@golang.org>
1 parent e0b9cf7 commit 937957b

File tree

2 files changed

+517
-0
lines changed

2 files changed

+517
-0
lines changed

go/callgraph/vta/propagation.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2021 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.
4+
5+
package vta
6+
7+
import (
8+
"go/types"
9+
10+
"golang.org/x/tools/go/ssa"
11+
12+
"golang.org/x/tools/go/types/typeutil"
13+
)
14+
15+
// scc computes strongly connected components (SCCs) of `g` using the
16+
// classical Tarjan's algorithm for SCCs. The result is a pair <m, id>
17+
// where m is a map from nodes to unique id of their SCC in the range
18+
// [0, id). The SCCs are sorted in reverse topological order: for SCCs
19+
// with ids X and Y s.t. X < Y, Y comes before X in the topological order.
20+
func scc(g vtaGraph) (map[node]int, int) {
21+
// standard data structures used by Tarjan's algorithm.
22+
var index uint64
23+
var stack []node
24+
indexMap := make(map[node]uint64)
25+
lowLink := make(map[node]uint64)
26+
onStack := make(map[node]bool)
27+
28+
nodeToSccID := make(map[node]int)
29+
sccID := 0
30+
31+
var doSCC func(node)
32+
doSCC = func(n node) {
33+
indexMap[n] = index
34+
lowLink[n] = index
35+
index = index + 1
36+
onStack[n] = true
37+
stack = append(stack, n)
38+
39+
for s := range g[n] {
40+
if _, ok := indexMap[s]; !ok {
41+
// Analyze successor s that has not been visited yet.
42+
doSCC(s)
43+
lowLink[n] = min(lowLink[n], lowLink[s])
44+
} else if onStack[s] {
45+
// The successor is on the stack, meaning it has to be
46+
// in the current SCC.
47+
lowLink[n] = min(lowLink[n], indexMap[s])
48+
}
49+
}
50+
51+
// if n is a root node, pop the stack and generate a new SCC.
52+
if lowLink[n] == indexMap[n] {
53+
for {
54+
w := stack[len(stack)-1]
55+
stack = stack[:len(stack)-1]
56+
onStack[w] = false
57+
nodeToSccID[w] = sccID
58+
if w == n {
59+
break
60+
}
61+
}
62+
sccID++
63+
}
64+
}
65+
66+
index = 0
67+
for n := range g {
68+
if _, ok := indexMap[n]; !ok {
69+
doSCC(n)
70+
}
71+
}
72+
73+
return nodeToSccID, sccID
74+
}
75+
76+
func min(x, y uint64) uint64 {
77+
if x < y {
78+
return x
79+
}
80+
return y
81+
}
82+
83+
// propType represents type information being propagated
84+
// over the vta graph. f != nil only for function nodes
85+
// and nodes reachable from function nodes. There, we also
86+
// remember the actual *ssa.Function in order to more
87+
// precisely model higher-order flow.
88+
type propType struct {
89+
typ types.Type
90+
f *ssa.Function
91+
}
92+
93+
// propTypeMap is an auxiliary structure that serves
94+
// the role of a map from nodes to a set of propTypes.
95+
type propTypeMap struct {
96+
nodeToScc map[node]int
97+
sccToTypes map[int]map[propType]bool
98+
}
99+
100+
// propTypes returns a set of propTypes associated with
101+
// node `n`. If `n` is not in the map `ptm`, nil is returned.
102+
//
103+
// Note: for performance reasons, the returned set is a
104+
// reference to existing set in the map `ptm`, so any updates
105+
// to it will affect `ptm` as well.
106+
func (ptm propTypeMap) propTypes(n node) map[propType]bool {
107+
id, ok := ptm.nodeToScc[n]
108+
if !ok {
109+
return nil
110+
}
111+
return ptm.sccToTypes[id]
112+
}
113+
114+
// propagate reduces the `graph` based on its SCCs and
115+
// then propagates type information through the reduced
116+
// graph. The result is a map from nodes to a set of types
117+
// and functions, stemming from higher-order data flow,
118+
// reaching the node. `canon` is used for type uniqueness.
119+
func propagate(graph vtaGraph, canon *typeutil.Map) propTypeMap {
120+
nodeToScc, sccID := scc(graph)
121+
// Initialize sccToTypes to avoid repeated check
122+
// for initialization later.
123+
sccToTypes := make(map[int]map[propType]bool, sccID)
124+
for i := 0; i <= sccID; i++ {
125+
sccToTypes[i] = make(map[propType]bool)
126+
}
127+
128+
// We also need the reverse map, from ids to SCCs.
129+
sccs := make(map[int][]node, sccID)
130+
for n, id := range nodeToScc {
131+
sccs[id] = append(sccs[id], n)
132+
}
133+
134+
for i := len(sccs) - 1; i >= 0; i-- {
135+
nodes := sccs[i]
136+
// Save the types induced by the nodes of the SCC.
137+
mergeTypes(sccToTypes[i], nodeTypes(nodes, canon))
138+
nextSccs := make(map[int]bool)
139+
for _, node := range nodes {
140+
for succ := range graph[node] {
141+
nextSccs[nodeToScc[succ]] = true
142+
}
143+
}
144+
// Propagate types to all successor SCCs.
145+
for nextScc := range nextSccs {
146+
mergeTypes(sccToTypes[nextScc], sccToTypes[i])
147+
}
148+
}
149+
150+
return propTypeMap{nodeToScc: nodeToScc, sccToTypes: sccToTypes}
151+
}
152+
153+
// nodeTypes returns a set of propTypes for `nodes`. These are the
154+
// propTypes stemming from the type of each node in `nodes` plus.
155+
func nodeTypes(nodes []node, canon *typeutil.Map) map[propType]bool {
156+
types := make(map[propType]bool)
157+
for _, n := range nodes {
158+
if hasInitialTypes(n) {
159+
types[getPropType(n, canon)] = true
160+
}
161+
}
162+
return types
163+
}
164+
165+
// getPropType creates a propType for `node` based on its type.
166+
// propType.typ is always node.Type(). If node is function, then
167+
// propType.val is the underlying function; nil otherwise.
168+
func getPropType(node node, canon *typeutil.Map) propType {
169+
t := canonicalize(node.Type(), canon)
170+
if fn, ok := node.(function); ok {
171+
return propType{f: fn.f, typ: t}
172+
}
173+
return propType{f: nil, typ: t}
174+
}
175+
176+
// mergeTypes merges propTypes in `rhs` to `lhs`.
177+
func mergeTypes(lhs, rhs map[propType]bool) {
178+
for typ := range rhs {
179+
lhs[typ] = true
180+
}
181+
}

0 commit comments

Comments
 (0)