@@ -9,30 +9,35 @@ import (
9
9
"cmd/compile/internal/ir"
10
10
"cmd/compile/internal/typecheck"
11
11
"cmd/compile/internal/types"
12
+ "cmd/internal/src"
12
13
)
13
14
14
15
// call evaluates a call expressions, including builtin calls. ks
15
16
// should contain the holes representing where the function callee's
16
17
// results flows.
17
18
func (e * escape ) call (ks []hole , call ir.Node ) {
18
- e .callCommon (ks , call , nil )
19
+ var init ir.Nodes
20
+ e .callCommon (ks , call , & init , nil )
21
+ if len (init ) != 0 {
22
+ call .(* ir.CallExpr ).PtrInit ().Append (init ... )
23
+ }
19
24
}
20
25
21
- func (e * escape ) callCommon (ks []hole , call ir.Node , where * ir.GoDeferStmt ) {
22
- argument := func (k hole , argp * ir.Node ) {
23
- if where != nil {
24
- if where .Esc () == ir .EscNever {
25
- // Top-level defers arguments don't escape to heap,
26
- // but they do need to last until end of function.
27
- k = e .later (k )
28
- } else {
29
- k = e .heapHole ()
30
- }
31
- }
26
+ func (e * escape ) callCommon (ks []hole , call ir.Node , init * ir.Nodes , wrapper * ir.Func ) {
27
+
28
+ // argumentPragma handles escape analysis of argument *argp to the
29
+ // given hole. If the function callee is known, pragma is the
30
+ // function's pragma flags; otherwise 0.
31
+ argumentFunc := func (fn * ir.Name , k hole , argp * ir.Node ) {
32
+ e .rewriteArgument (argp , init , call , fn , wrapper )
32
33
33
34
e .expr (k .note (call , "call parameter" ), * argp )
34
35
}
35
36
37
+ argument := func (k hole , argp * ir.Node ) {
38
+ argumentFunc (nil , k , argp )
39
+ }
40
+
36
41
switch call .Op () {
37
42
default :
38
43
ir .Dump ("esc" , call )
@@ -43,6 +48,11 @@ func (e *escape) callCommon(ks []hole, call ir.Node, where *ir.GoDeferStmt) {
43
48
typecheck .FixVariadicCall (call )
44
49
45
50
// Pick out the function callee, if statically known.
51
+ //
52
+ // TODO(mdempsky): Change fn from *ir.Name to *ir.Func, but some
53
+ // functions (e.g., runtime builtins, method wrappers, generated
54
+ // eq/hash functions) don't have it set. Investigate whether
55
+ // that's a concern.
46
56
var fn * ir.Name
47
57
switch call .Op () {
48
58
case ir .OCALLFUNC :
@@ -68,15 +78,20 @@ func (e *escape) callCommon(ks []hole, call ir.Node, where *ir.GoDeferStmt) {
68
78
}
69
79
70
80
if r := fntype .Recv (); r != nil {
71
- argument (e .tagHole (ks , fn , r ), & call .X .(* ir.SelectorExpr ).X )
81
+ dot := call .X .(* ir.SelectorExpr )
82
+ argumentFunc (fn , e .tagHole (ks , fn , r ), & dot .X )
72
83
} else {
73
84
// Evaluate callee function expression.
85
+ //
86
+ // Note: We use argument and not argumentFunc, because call.X
87
+ // here may be an argument to runtime.{new,defer}proc, but it's
88
+ // not an argument to fn itself.
74
89
argument (e .discardHole (), & call .X )
75
90
}
76
91
77
92
args := call .Args
78
93
for i , param := range fntype .Params ().FieldSlice () {
79
- argument ( e .tagHole (ks , fn , param ), & args [i ])
94
+ argumentFunc ( fn , e .tagHole (ks , fn , param ), & args [i ])
80
95
}
81
96
82
97
case ir .OAPPEND :
@@ -142,16 +157,196 @@ func (e *escape) callCommon(ks []hole, call ir.Node, where *ir.GoDeferStmt) {
142
157
}
143
158
}
144
159
160
+ // goDeferStmt analyzes a "go" or "defer" statement.
161
+ //
162
+ // In the process, it also normalizes the statement to always use a
163
+ // simple function call with no arguments and no results. For example,
164
+ // it rewrites:
165
+ //
166
+ // defer f(x, y)
167
+ //
168
+ // into:
169
+ //
170
+ // x1, y1 := x, y
171
+ // defer func() { f(x1, y1) }()
145
172
func (e * escape ) goDeferStmt (n * ir.GoDeferStmt ) {
146
- topLevelDefer := n .Op () == ir .ODEFER && e .loopDepth == 1
147
- if topLevelDefer {
173
+ k := e .heapHole ()
174
+ if n .Op () == ir .ODEFER && e .loopDepth == 1 {
175
+ // Top-level defer arguments don't escape to the heap,
176
+ // but they do need to last until they're invoked.
177
+ k = e .later (e .discardHole ())
178
+
148
179
// force stack allocation of defer record, unless
149
180
// open-coded defers are used (see ssa.go)
150
181
n .SetEsc (ir .EscNever )
151
182
}
152
183
153
- e .stmts (n .Call .Init ())
154
- e .callCommon (nil , n .Call , n )
184
+ call := n .Call
185
+
186
+ init := n .PtrInit ()
187
+ init .Append (ir .TakeInit (call )... )
188
+ e .stmts (* init )
189
+
190
+ // If the function is already a zero argument/result function call,
191
+ // just escape analyze it normally.
192
+ if call , ok := call .(* ir.CallExpr ); ok && call .Op () == ir .OCALLFUNC {
193
+ if sig := call .X .Type (); sig .NumParams ()+ sig .NumResults () == 0 {
194
+ if clo , ok := call .X .(* ir.ClosureExpr ); ok && n .Op () == ir .OGO {
195
+ clo .IsGoWrap = true
196
+ }
197
+ e .expr (k , call .X )
198
+ return
199
+ }
200
+ }
201
+
202
+ // Create a new no-argument function that we'll hand off to defer.
203
+ fn := ir .NewClosureFunc (n .Pos (), true )
204
+ fn .SetWrapper (true )
205
+ fn .Nname .SetType (types .NewSignature (types .LocalPkg , nil , nil , nil , nil ))
206
+ fn .Body = []ir.Node {call }
207
+
208
+ clo := fn .OClosure
209
+ if n .Op () == ir .OGO {
210
+ clo .IsGoWrap = true
211
+ }
212
+
213
+ e .callCommon (nil , call , init , fn )
214
+ e .closures = append (e .closures , closure {e .spill (k , clo ), clo })
215
+
216
+ // Create new top level call to closure.
217
+ n .Call = ir .NewCallExpr (call .Pos (), ir .OCALL , clo , nil )
218
+ ir .WithFunc (e .curfn , func () {
219
+ typecheck .Stmt (n .Call )
220
+ })
221
+ }
222
+
223
+ // rewriteArgument rewrites the argument *argp of the given call expression.
224
+ // fn is the static callee function, if known.
225
+ // wrapper is the go/defer wrapper function for call, if any.
226
+ func (e * escape ) rewriteArgument (argp * ir.Node , init * ir.Nodes , call ir.Node , fn * ir.Name , wrapper * ir.Func ) {
227
+ var pragma ir.PragmaFlag
228
+ if fn != nil && fn .Func != nil {
229
+ pragma = fn .Func .Pragma
230
+ }
231
+
232
+ // unsafeUintptr rewrites "uintptr(ptr)" arguments to syscall-like
233
+ // functions, so that ptr is kept alive and/or escaped as
234
+ // appropriate. unsafeUintptr also reports whether it modified arg0.
235
+ unsafeUintptr := func (arg0 ir.Node ) bool {
236
+ if pragma & (ir .UintptrKeepAlive | ir .UintptrEscapes ) == 0 {
237
+ return false
238
+ }
239
+
240
+ // If the argument is really a pointer being converted to uintptr,
241
+ // arrange for the pointer to be kept alive until the call returns,
242
+ // by copying it into a temp and marking that temp
243
+ // still alive when we pop the temp stack.
244
+ if arg0 .Op () != ir .OCONVNOP || ! arg0 .Type ().IsUintptr () {
245
+ return false
246
+ }
247
+ arg := arg0 .(* ir.ConvExpr )
248
+
249
+ if ! arg .X .Type ().IsUnsafePtr () {
250
+ return false
251
+ }
252
+
253
+ // Create and declare a new pointer-typed temp variable.
254
+ tmp := e .wrapExpr (arg .Pos (), & arg .X , init , call , wrapper )
255
+
256
+ if pragma & ir .UintptrEscapes != 0 {
257
+ e .flow (e .heapHole ().note (arg , "//go:uintptrescapes" ), e .oldLoc (tmp ))
258
+ }
259
+
260
+ if pragma & ir .UintptrKeepAlive != 0 {
261
+ call := call .(* ir.CallExpr )
262
+
263
+ // SSA implements CallExpr.KeepAlive using OpVarLive, which
264
+ // doesn't support PAUTOHEAP variables. I tried changing it to
265
+ // use OpKeepAlive, but that ran into issues of its own.
266
+ // For now, the easy solution is to explicitly copy to (yet
267
+ // another) new temporary variable.
268
+ keep := tmp
269
+ if keep .Class == ir .PAUTOHEAP {
270
+ keep = e .copyExpr (arg .Pos (), tmp , call .PtrInit (), wrapper , false )
271
+ }
272
+
273
+ keep .SetAddrtaken (true ) // ensure SSA keeps the tmp variable
274
+ call .KeepAlive = append (call .KeepAlive , keep )
275
+ }
276
+
277
+ return true
278
+ }
279
+
280
+ visit := func (pos src.XPos , argp * ir.Node ) {
281
+ if unsafeUintptr (* argp ) {
282
+ return
283
+ }
284
+
285
+ if wrapper != nil {
286
+ e .wrapExpr (pos , argp , init , call , wrapper )
287
+ }
288
+ }
289
+
290
+ // Peel away any slice lits.
291
+ if arg := * argp ; arg .Op () == ir .OSLICELIT {
292
+ list := arg .(* ir.CompLitExpr ).List
293
+ for i := range list {
294
+ visit (arg .Pos (), & list [i ])
295
+ }
296
+ } else {
297
+ visit (call .Pos (), argp )
298
+ }
299
+ }
300
+
301
+ // wrapExpr replaces *exprp with a temporary variable copy. If wrapper
302
+ // is non-nil, the variable will be captured for use within that
303
+ // function.
304
+ func (e * escape ) wrapExpr (pos src.XPos , exprp * ir.Node , init * ir.Nodes , call ir.Node , wrapper * ir.Func ) * ir.Name {
305
+ tmp := e .copyExpr (pos , * exprp , init , e .curfn , true )
306
+
307
+ if wrapper != nil {
308
+ // Currently for "defer i.M()" if i is nil it panics at the point
309
+ // of defer statement, not when deferred function is called. We
310
+ // need to do the nil check outside of the wrapper.
311
+ if call .Op () == ir .OCALLINTER && exprp == & call .(* ir.CallExpr ).X .(* ir.SelectorExpr ).X {
312
+ check := ir .NewUnaryExpr (pos , ir .OCHECKNIL , ir .NewUnaryExpr (pos , ir .OITAB , tmp ))
313
+ init .Append (typecheck .Stmt (check ))
314
+ }
315
+
316
+ e .oldLoc (tmp ).captured = true
317
+
318
+ cv := ir .NewClosureVar (pos , wrapper , tmp )
319
+ cv .SetType (tmp .Type ())
320
+ tmp = typecheck .Expr (cv ).(* ir.Name )
321
+ }
322
+
323
+ * exprp = tmp
324
+ return tmp
325
+ }
326
+
327
+ // copyExpr creates and returns a new temporary variable within fn;
328
+ // appends statements to init to declare and initialize it to expr;
329
+ // and escape analyzes the data flow if analyze is true.
330
+ func (e * escape ) copyExpr (pos src.XPos , expr ir.Node , init * ir.Nodes , fn * ir.Func , analyze bool ) * ir.Name {
331
+ if ir .HasUniquePos (expr ) {
332
+ pos = expr .Pos ()
333
+ }
334
+
335
+ tmp := typecheck .TempAt (pos , fn , expr .Type ())
336
+
337
+ stmts := []ir.Node {
338
+ ir .NewDecl (pos , ir .ODCL , tmp ),
339
+ ir .NewAssignStmt (pos , tmp , expr ),
340
+ }
341
+ typecheck .Stmts (stmts )
342
+ init .Append (stmts ... )
343
+
344
+ if analyze {
345
+ e .newLoc (tmp , false )
346
+ e .stmts (stmts )
347
+ }
348
+
349
+ return tmp
155
350
}
156
351
157
352
// tagHole returns a hole for evaluating an argument passed to param.
@@ -170,12 +365,6 @@ func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole {
170
365
171
366
// Call to previously tagged function.
172
367
173
- if fn .Func != nil && fn .Func .Pragma & ir .UintptrEscapes != 0 && (param .Type .IsUintptr () || param .IsDDD () && param .Type .Elem ().IsUintptr ()) {
174
- k := e .heapHole ()
175
- k .uintptrEscapesHack = true
176
- return k
177
- }
178
-
179
368
var tagKs []hole
180
369
181
370
esc := parseLeaks (param .Note )
0 commit comments