@@ -9,25 +9,34 @@ import (
9
9
"cmd/compile/internal/pgo"
10
10
"fmt"
11
11
"os"
12
+ "strings"
12
13
)
13
14
14
15
type callSiteAnalyzer struct {
15
- cstab CallSiteTab
16
- nstack []ir.Node
16
+ cstab CallSiteTab
17
+ fn * ir.Func
18
+ ptab map [ir.Node ]pstate
19
+ nstack []ir.Node
20
+ loopNest int
21
+ isInit bool
17
22
}
18
23
19
- func makeCallSiteAnalyzer (fn * ir.Func ) * callSiteAnalyzer {
24
+ func makeCallSiteAnalyzer (fn * ir.Func , ptab map [ir.Node ]pstate ) * callSiteAnalyzer {
25
+ isInit := fn .IsPackageInit () || strings .HasPrefix (fn .Sym ().Name , "init." )
20
26
return & callSiteAnalyzer {
21
- cstab : make (CallSiteTab ),
27
+ fn : fn ,
28
+ cstab : make (CallSiteTab ),
29
+ ptab : ptab ,
30
+ isInit : isInit ,
22
31
}
23
32
}
24
33
25
- func computeCallSiteTable (fn * ir.Func ) CallSiteTab {
26
- if debugTrace & debugTraceCalls != 0 {
34
+ func computeCallSiteTable (fn * ir.Func , ptab map [ir. Node ] pstate ) CallSiteTab {
35
+ if debugTrace != 0 {
27
36
fmt .Fprintf (os .Stderr , "=-= making callsite table for func %v:\n " ,
28
37
fn .Sym ().Name )
29
38
}
30
- csa := makeCallSiteAnalyzer (fn )
39
+ csa := makeCallSiteAnalyzer (fn , ptab )
31
40
var doNode func (ir.Node ) bool
32
41
doNode = func (n ir.Node ) bool {
33
42
csa .nodeVisitPre (n )
@@ -40,7 +49,74 @@ func computeCallSiteTable(fn *ir.Func) CallSiteTab {
40
49
}
41
50
42
51
func (csa * callSiteAnalyzer ) flagsForNode (call * ir.CallExpr ) CSPropBits {
43
- return 0
52
+ var r CSPropBits
53
+
54
+ if debugTrace & debugTraceCalls != 0 {
55
+ fmt .Fprintf (os .Stderr , "=-= analyzing call at %s\n " ,
56
+ fmtFullPos (call .Pos ()))
57
+ }
58
+
59
+ // Set a bit if this call is within a loop.
60
+ if csa .loopNest > 0 {
61
+ r |= CallSiteInLoop
62
+ }
63
+
64
+ // Set a bit if the call is within an init function (either
65
+ // compiler-generated or user-written).
66
+ if csa .isInit {
67
+ r |= CallSiteInInitFunc
68
+ }
69
+
70
+ // Decide whether to apply the panic path heuristic. Hack: don't
71
+ // apply this heuristic in the function "main.main" (mostly just
72
+ // to avoid annoying users).
73
+ if ! isMainMain (csa .fn ) {
74
+ r = csa .determinePanicPathBits (call , r )
75
+ }
76
+
77
+ return r
78
+ }
79
+
80
+ // determinePanicPathBits updates the CallSiteOnPanicPath bit within
81
+ // "r" if we think this call is on an unconditional path to
82
+ // panic/exit. Do this by walking back up the node stack to see if we
83
+ // can find either A) an enclosing panic, or B) a statement node that
84
+ // we've determined leads to a panic/exit.
85
+ func (csa * callSiteAnalyzer ) determinePanicPathBits (call ir.Node , r CSPropBits ) CSPropBits {
86
+ csa .nstack = append (csa .nstack , call )
87
+ defer func () {
88
+ csa .nstack = csa .nstack [:len (csa .nstack )- 1 ]
89
+ }()
90
+
91
+ for ri := range csa .nstack [:len (csa .nstack )- 1 ] {
92
+ i := len (csa .nstack ) - ri - 1
93
+ n := csa .nstack [i ]
94
+ _ , isCallExpr := n .(* ir.CallExpr )
95
+ _ , isStmt := n .(ir.Stmt )
96
+ if isCallExpr {
97
+ isStmt = false
98
+ }
99
+
100
+ if debugTrace & debugTraceCalls != 0 {
101
+ ps , inps := csa .ptab [n ]
102
+ fmt .Fprintf (os .Stderr , "=-= callpar %d op=%s ps=%s inptab=%v stmt=%v\n " , i , n .Op ().String (), ps .String (), inps , isStmt )
103
+ }
104
+
105
+ if n .Op () == ir .OPANIC {
106
+ r |= CallSiteOnPanicPath
107
+ break
108
+ }
109
+ if v , ok := csa .ptab [n ]; ok {
110
+ if v == psCallsPanic {
111
+ r |= CallSiteOnPanicPath
112
+ break
113
+ }
114
+ if isStmt {
115
+ break
116
+ }
117
+ }
118
+ }
119
+ return r
44
120
}
45
121
46
122
func (csa * callSiteAnalyzer ) addCallSite (callee * ir.Func , call * ir.CallExpr ) {
@@ -68,6 +144,10 @@ func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
68
144
69
145
func (csa * callSiteAnalyzer ) nodeVisitPre (n ir.Node ) {
70
146
switch n .Op () {
147
+ case ir .ORANGE , ir .OFOR :
148
+ if ! hasTopLevelLoopBodyReturnOrBreak (loopBody (n )) {
149
+ csa .loopNest ++
150
+ }
71
151
case ir .OCALLFUNC :
72
152
ce := n .(* ir.CallExpr )
73
153
callee := pgo .DirectCallee (ce .X )
@@ -80,6 +160,47 @@ func (csa *callSiteAnalyzer) nodeVisitPre(n ir.Node) {
80
160
81
161
func (csa * callSiteAnalyzer ) nodeVisitPost (n ir.Node ) {
82
162
csa .nstack = csa .nstack [:len (csa .nstack )- 1 ]
163
+ switch n .Op () {
164
+ case ir .ORANGE , ir .OFOR :
165
+ if ! hasTopLevelLoopBodyReturnOrBreak (loopBody (n )) {
166
+ csa .loopNest --
167
+ }
168
+ }
169
+ }
170
+
171
+ func loopBody (n ir.Node ) ir.Nodes {
172
+ if forst , ok := n .(* ir.ForStmt ); ok {
173
+ return forst .Body
174
+ }
175
+ if rst , ok := n .(* ir.RangeStmt ); ok {
176
+ return rst .Body
177
+ }
178
+ return nil
179
+ }
180
+
181
+ // hasTopLevelLoopBodyReturnOrBreak examines the body of a "for" or
182
+ // "range" loop to try to verify that it is a real loop, as opposed to
183
+ // a construct that is syntactically loopy but doesn't actually iterate
184
+ // multiple times, like:
185
+ //
186
+ // for {
187
+ // blah()
188
+ // return 1
189
+ // }
190
+ //
191
+ // [Remark: the pattern above crops up quite a bit in the source code
192
+ // for the compiler itself, e.g. the auto-generated rewrite code]
193
+ //
194
+ // Note that we don't look for GOTO statements here, so it's possible
195
+ // we'll get the wrong result for a loop with complicated control
196
+ // jumps via gotos.
197
+ func hasTopLevelLoopBodyReturnOrBreak (loopBody ir.Nodes ) bool {
198
+ for _ , n := range loopBody {
199
+ if n .Op () == ir .ORETURN || n .Op () == ir .OBREAK {
200
+ return true
201
+ }
202
+ }
203
+ return false
83
204
}
84
205
85
206
// containingAssignment returns the top-level assignment statement
0 commit comments