Skip to content

Commit 0a0e3a3

Browse files
committed
[dev.typeparams] cmd/compile: move call logic from order.go to escape
This CL moves two bits of related code from order.go to escape analysis: 1. The recognition of "unsafe uintptr" arguments passed to syscall-like functions. 2. The wrapping of go/defer function calls in parameter-free function literals. As with previous CLs, it would be nice to push this logic even further forward, but for now escape analysis seems most pragmatic. A couple side benefits: 1. It allows getting rid of the uintptrEscapesHack kludge. 2. When inserting wrappers, we can move some expressions into the wrapper and escape analyze them better. For example, the test expectation changes are all due to slice literals in go/defer calls where the slice is now constructed at the call site, and can now be stack allocated. Change-Id: I73679bcad7fa8d61d2fc52d4cea0dc5ff0de8c0c Reviewed-on: https://go-review.googlesource.com/c/go/+/330330 Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Matthew Dempsky <[email protected]> Reviewed-by: Cuong Manh Le <[email protected]>
1 parent 574ec1c commit 0a0e3a3

File tree

10 files changed

+259
-400
lines changed

10 files changed

+259
-400
lines changed

src/cmd/compile/internal/escape/call.go

Lines changed: 213 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,35 @@ import (
99
"cmd/compile/internal/ir"
1010
"cmd/compile/internal/typecheck"
1111
"cmd/compile/internal/types"
12+
"cmd/internal/src"
1213
)
1314

1415
// call evaluates a call expressions, including builtin calls. ks
1516
// should contain the holes representing where the function callee's
1617
// results flows.
1718
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+
}
1924
}
2025

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)
3233

3334
e.expr(k.note(call, "call parameter"), *argp)
3435
}
3536

37+
argument := func(k hole, argp *ir.Node) {
38+
argumentFunc(nil, k, argp)
39+
}
40+
3641
switch call.Op() {
3742
default:
3843
ir.Dump("esc", call)
@@ -43,6 +48,11 @@ func (e *escape) callCommon(ks []hole, call ir.Node, where *ir.GoDeferStmt) {
4348
typecheck.FixVariadicCall(call)
4449

4550
// 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.
4656
var fn *ir.Name
4757
switch call.Op() {
4858
case ir.OCALLFUNC:
@@ -68,15 +78,20 @@ func (e *escape) callCommon(ks []hole, call ir.Node, where *ir.GoDeferStmt) {
6878
}
6979

7080
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)
7283
} else {
7384
// 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.
7489
argument(e.discardHole(), &call.X)
7590
}
7691

7792
args := call.Args
7893
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])
8095
}
8196

8297
case ir.OAPPEND:
@@ -142,16 +157,196 @@ func (e *escape) callCommon(ks []hole, call ir.Node, where *ir.GoDeferStmt) {
142157
}
143158
}
144159

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) }()
145172
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+
148179
// force stack allocation of defer record, unless
149180
// open-coded defers are used (see ssa.go)
150181
n.SetEsc(ir.EscNever)
151182
}
152183

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
155350
}
156351

157352
// 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 {
170365

171366
// Call to previously tagged function.
172367

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-
179368
var tagKs []hole
180369

181370
esc := parseLeaks(param.Note)

src/cmd/compile/internal/escape/escape.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ func (b *batch) finish(fns []*ir.Func) {
282282

283283
// Update n.Esc based on escape analysis results.
284284

285+
// Omit escape diagnostics for go/defer wrappers, at least for now.
286+
// Historically, we haven't printed them, and test cases don't expect them.
287+
// TODO(mdempsky): Update tests to expect this.
288+
goDeferWrapper := n.Op() == ir.OCLOSURE && n.(*ir.ClosureExpr).Func.Wrapper()
289+
285290
if loc.escapes {
286291
if n.Op() == ir.ONAME {
287292
if base.Flag.CompilingRuntime {
@@ -291,7 +296,7 @@ func (b *batch) finish(fns []*ir.Func) {
291296
base.WarnfAt(n.Pos(), "moved to heap: %v", n)
292297
}
293298
} else {
294-
if base.Flag.LowerM != 0 {
299+
if base.Flag.LowerM != 0 && !goDeferWrapper {
295300
base.WarnfAt(n.Pos(), "%v escapes to heap", n)
296301
}
297302
if logopt.Enabled() {
@@ -301,7 +306,7 @@ func (b *batch) finish(fns []*ir.Func) {
301306
}
302307
n.SetEsc(ir.EscHeap)
303308
} else {
304-
if base.Flag.LowerM != 0 && n.Op() != ir.ONAME {
309+
if base.Flag.LowerM != 0 && n.Op() != ir.ONAME && !goDeferWrapper {
305310
base.WarnfAt(n.Pos(), "%v does not escape", n)
306311
}
307312
n.SetEsc(ir.EscNone)

src/cmd/compile/internal/escape/expr.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,7 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
3030
base.Pos = lno
3131
}()
3232

33-
uintptrEscapesHack := k.uintptrEscapesHack
34-
k.uintptrEscapesHack = false
35-
36-
if uintptrEscapesHack && n.Op() == ir.OCONVNOP && n.(*ir.ConvExpr).X.Type().IsUnsafePtr() {
37-
// nop
38-
} else if k.derefs >= 0 && !n.Type().HasPointers() {
33+
if k.derefs >= 0 && !n.Type().HasPointers() {
3934
k.dst = &e.blankLoc
4035
}
4136

@@ -198,7 +193,6 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
198193
case ir.OSLICELIT:
199194
n := n.(*ir.CompLitExpr)
200195
k = e.spill(k, n)
201-
k.uintptrEscapesHack = uintptrEscapesHack // for ...uintptr parameters
202196

203197
for _, elt := range n.List {
204198
if elt.Op() == ir.OKEY {

src/cmd/compile/internal/escape/graph.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,6 @@ type hole struct {
129129
// the expression, independent of whether the address will actually
130130
// be stored into a variable.
131131
addrtaken bool
132-
133-
// uintptrEscapesHack indicates this context is evaluating an
134-
// argument for a //go:uintptrescapes function.
135-
uintptrEscapesHack bool
136132
}
137133

138134
type note struct {

src/cmd/compile/internal/ir/func.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,17 @@ func PkgFuncName(f *Func) string {
278278

279279
var CurFunc *Func
280280

281+
// WithFunc invokes do with CurFunc and base.Pos set to curfn and
282+
// curfn.Pos(), respectively, and then restores their previous values
283+
// before returning.
284+
func WithFunc(curfn *Func, do func()) {
285+
oldfn, oldpos := CurFunc, base.Pos
286+
defer func() { CurFunc, base.Pos = oldfn, oldpos }()
287+
288+
CurFunc, base.Pos = curfn, curfn.Pos()
289+
do()
290+
}
291+
281292
func FuncSymName(s *types.Sym) string {
282293
return s.Name + "·f"
283294
}

0 commit comments

Comments
 (0)