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

     1  // Copyright 2018 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 escape
     6  
     7  import (
     8  	"cmd/compile/internal/base"
     9  	"cmd/compile/internal/ir"
    10  	"cmd/compile/internal/typecheck"
    11  	"cmd/compile/internal/types"
    12  	"cmd/internal/src"
    13  )
    14  
    15  // call evaluates a call expressions, including builtin calls. ks
    16  // should contain the holes representing where the function callee's
    17  // results flows.
    18  func (e *escape) call(ks []hole, call ir.Node) {
    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  	}
    24  }
    25  
    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)
    33  
    34  		e.expr(k.note(call, "call parameter"), *argp)
    35  	}
    36  
    37  	argument := func(k hole, argp *ir.Node) {
    38  		argumentFunc(nil, k, argp)
    39  	}
    40  
    41  	switch call.Op() {
    42  	default:
    43  		ir.Dump("esc", call)
    44  		base.Fatalf("unexpected call op: %v", call.Op())
    45  
    46  	case ir.OCALLFUNC, ir.OCALLMETH, ir.OCALLINTER:
    47  		call := call.(*ir.CallExpr)
    48  		typecheck.FixVariadicCall(call)
    49  		typecheck.FixMethodCall(call)
    50  
    51  		// Pick out the function callee, if statically known.
    52  		//
    53  		// TODO(mdempsky): Change fn from *ir.Name to *ir.Func, but some
    54  		// functions (e.g., runtime builtins, method wrappers, generated
    55  		// eq/hash functions) don't have it set. Investigate whether
    56  		// that's a concern.
    57  		var fn *ir.Name
    58  		switch call.Op() {
    59  		case ir.OCALLFUNC:
    60  			// If we have a direct call to a closure (not just one we were
    61  			// able to statically resolve with ir.StaticValue), mark it as
    62  			// such so batch.outlives can optimize the flow results.
    63  			if call.X.Op() == ir.OCLOSURE {
    64  				call.X.(*ir.ClosureExpr).Func.SetClosureCalled(true)
    65  			}
    66  
    67  			switch v := ir.StaticValue(call.X); v.Op() {
    68  			case ir.ONAME:
    69  				if v := v.(*ir.Name); v.Class == ir.PFUNC {
    70  					fn = v
    71  				}
    72  			case ir.OCLOSURE:
    73  				fn = v.(*ir.ClosureExpr).Func.Nname
    74  			case ir.OMETHEXPR:
    75  				fn = ir.MethodExprName(v)
    76  			}
    77  		case ir.OCALLMETH:
    78  			base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
    79  		}
    80  
    81  		fntype := call.X.Type()
    82  		if fn != nil {
    83  			fntype = fn.Type()
    84  		}
    85  
    86  		if ks != nil && fn != nil && e.inMutualBatch(fn) {
    87  			for i, result := range fn.Type().Results().FieldSlice() {
    88  				e.expr(ks[i], ir.AsNode(result.Nname))
    89  			}
    90  		}
    91  
    92  		var recvp *ir.Node
    93  		if call.Op() == ir.OCALLFUNC {
    94  			// Evaluate callee function expression.
    95  			//
    96  			// Note: We use argument and not argumentFunc, because while
    97  			// call.X here may be an argument to runtime.{new,defer}proc,
    98  			// it's not an argument to fn itself.
    99  			argument(e.discardHole(), &call.X)
   100  		} else {
   101  			recvp = &call.X.(*ir.SelectorExpr).X
   102  		}
   103  
   104  		args := call.Args
   105  		if recv := fntype.Recv(); recv != nil {
   106  			if recvp == nil {
   107  				// Function call using method expression. Recevier argument is
   108  				// at the front of the regular arguments list.
   109  				recvp = &args[0]
   110  				args = args[1:]
   111  			}
   112  
   113  			argumentFunc(fn, e.tagHole(ks, fn, recv), recvp)
   114  		}
   115  
   116  		for i, param := range fntype.Params().FieldSlice() {
   117  			argumentFunc(fn, e.tagHole(ks, fn, param), &args[i])
   118  		}
   119  
   120  	case ir.OINLCALL:
   121  		call := call.(*ir.InlinedCallExpr)
   122  		e.stmts(call.Body)
   123  		for i, result := range call.ReturnVars {
   124  			k := e.discardHole()
   125  			if ks != nil {
   126  				k = ks[i]
   127  			}
   128  			e.expr(k, result)
   129  		}
   130  
   131  	case ir.OAPPEND:
   132  		call := call.(*ir.CallExpr)
   133  		args := call.Args
   134  
   135  		// Appendee slice may flow directly to the result, if
   136  		// it has enough capacity. Alternatively, a new heap
   137  		// slice might be allocated, and all slice elements
   138  		// might flow to heap.
   139  		appendeeK := ks[0]
   140  		if args[0].Type().Elem().HasPointers() {
   141  			appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice"))
   142  		}
   143  		argument(appendeeK, &args[0])
   144  
   145  		if call.IsDDD {
   146  			appendedK := e.discardHole()
   147  			if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() {
   148  				appendedK = e.heapHole().deref(call, "appended slice...")
   149  			}
   150  			argument(appendedK, &args[1])
   151  		} else {
   152  			for i := 1; i < len(args); i++ {
   153  				argument(e.heapHole(), &args[i])
   154  			}
   155  		}
   156  
   157  	case ir.OCOPY:
   158  		call := call.(*ir.BinaryExpr)
   159  		argument(e.discardHole(), &call.X)
   160  
   161  		copiedK := e.discardHole()
   162  		if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() {
   163  			copiedK = e.heapHole().deref(call, "copied slice")
   164  		}
   165  		argument(copiedK, &call.Y)
   166  
   167  	case ir.OPANIC:
   168  		call := call.(*ir.UnaryExpr)
   169  		argument(e.heapHole(), &call.X)
   170  
   171  	case ir.OCOMPLEX:
   172  		call := call.(*ir.BinaryExpr)
   173  		argument(e.discardHole(), &call.X)
   174  		argument(e.discardHole(), &call.Y)
   175  
   176  	case ir.ODELETE, ir.OPRINT, ir.OPRINTN, ir.ORECOVER:
   177  		call := call.(*ir.CallExpr)
   178  		fixRecoverCall(call)
   179  		for i := range call.Args {
   180  			argument(e.discardHole(), &call.Args[i])
   181  		}
   182  
   183  	case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE:
   184  		call := call.(*ir.UnaryExpr)
   185  		argument(e.discardHole(), &call.X)
   186  
   187  	case ir.OUNSAFEADD, ir.OUNSAFESLICE:
   188  		call := call.(*ir.BinaryExpr)
   189  		argument(ks[0], &call.X)
   190  		argument(e.discardHole(), &call.Y)
   191  	}
   192  }
   193  
   194  // goDeferStmt analyzes a "go" or "defer" statement.
   195  //
   196  // In the process, it also normalizes the statement to always use a
   197  // simple function call with no arguments and no results. For example,
   198  // it rewrites:
   199  //
   200  //	defer f(x, y)
   201  //
   202  // into:
   203  //
   204  //	x1, y1 := x, y
   205  //	defer func() { f(x1, y1) }()
   206  func (e *escape) goDeferStmt(n *ir.GoDeferStmt) {
   207  	k := e.heapHole()
   208  	if n.Op() == ir.ODEFER && e.loopDepth == 1 {
   209  		// Top-level defer arguments don't escape to the heap,
   210  		// but they do need to last until they're invoked.
   211  		k = e.later(e.discardHole())
   212  
   213  		// force stack allocation of defer record, unless
   214  		// open-coded defers are used (see ssa.go)
   215  		n.SetEsc(ir.EscNever)
   216  	}
   217  
   218  	call := n.Call
   219  
   220  	init := n.PtrInit()
   221  	init.Append(ir.TakeInit(call)...)
   222  	e.stmts(*init)
   223  
   224  	// If the function is already a zero argument/result function call,
   225  	// just escape analyze it normally.
   226  	if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC {
   227  		if sig := call.X.Type(); sig.NumParams()+sig.NumResults() == 0 {
   228  			if clo, ok := call.X.(*ir.ClosureExpr); ok && n.Op() == ir.OGO {
   229  				clo.IsGoWrap = true
   230  			}
   231  			e.expr(k, call.X)
   232  			return
   233  		}
   234  	}
   235  
   236  	// Create a new no-argument function that we'll hand off to defer.
   237  	fn := ir.NewClosureFunc(n.Pos(), true)
   238  	fn.SetWrapper(true)
   239  	fn.Nname.SetType(types.NewSignature(types.LocalPkg, nil, nil, nil, nil))
   240  	fn.Body = []ir.Node{call}
   241  	if call, ok := call.(*ir.CallExpr); ok && call.Op() == ir.OCALLFUNC {
   242  		// If the callee is a named function, link to the original callee.
   243  		x := call.X
   244  		if x.Op() == ir.ONAME && x.(*ir.Name).Class == ir.PFUNC {
   245  			fn.WrappedFunc = call.X.(*ir.Name).Func
   246  		} else if x.Op() == ir.OMETHEXPR && ir.MethodExprFunc(x).Nname != nil {
   247  			fn.WrappedFunc = ir.MethodExprName(x).Func
   248  		}
   249  	}
   250  
   251  	clo := fn.OClosure
   252  	if n.Op() == ir.OGO {
   253  		clo.IsGoWrap = true
   254  	}
   255  
   256  	e.callCommon(nil, call, init, fn)
   257  	e.closures = append(e.closures, closure{e.spill(k, clo), clo})
   258  
   259  	// Create new top level call to closure.
   260  	n.Call = ir.NewCallExpr(call.Pos(), ir.OCALL, clo, nil)
   261  	ir.WithFunc(e.curfn, func() {
   262  		typecheck.Stmt(n.Call)
   263  	})
   264  }
   265  
   266  // rewriteArgument rewrites the argument *argp of the given call expression.
   267  // fn is the static callee function, if known.
   268  // wrapper is the go/defer wrapper function for call, if any.
   269  func (e *escape) rewriteArgument(argp *ir.Node, init *ir.Nodes, call ir.Node, fn *ir.Name, wrapper *ir.Func) {
   270  	var pragma ir.PragmaFlag
   271  	if fn != nil && fn.Func != nil {
   272  		pragma = fn.Func.Pragma
   273  	}
   274  
   275  	// unsafeUintptr rewrites "uintptr(ptr)" arguments to syscall-like
   276  	// functions, so that ptr is kept alive and/or escaped as
   277  	// appropriate. unsafeUintptr also reports whether it modified arg0.
   278  	unsafeUintptr := func(arg0 ir.Node) bool {
   279  		if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 {
   280  			return false
   281  		}
   282  
   283  		// If the argument is really a pointer being converted to uintptr,
   284  		// arrange for the pointer to be kept alive until the call returns,
   285  		// by copying it into a temp and marking that temp
   286  		// still alive when we pop the temp stack.
   287  		if arg0.Op() != ir.OCONVNOP || !arg0.Type().IsUintptr() {
   288  			return false
   289  		}
   290  		arg := arg0.(*ir.ConvExpr)
   291  
   292  		if !arg.X.Type().IsUnsafePtr() {
   293  			return false
   294  		}
   295  
   296  		// Create and declare a new pointer-typed temp variable.
   297  		tmp := e.wrapExpr(arg.Pos(), &arg.X, init, call, wrapper)
   298  
   299  		if pragma&ir.UintptrEscapes != 0 {
   300  			e.flow(e.heapHole().note(arg, "//go:uintptrescapes"), e.oldLoc(tmp))
   301  		}
   302  
   303  		if pragma&ir.UintptrKeepAlive != 0 {
   304  			call := call.(*ir.CallExpr)
   305  
   306  			// SSA implements CallExpr.KeepAlive using OpVarLive, which
   307  			// doesn't support PAUTOHEAP variables. I tried changing it to
   308  			// use OpKeepAlive, but that ran into issues of its own.
   309  			// For now, the easy solution is to explicitly copy to (yet
   310  			// another) new temporary variable.
   311  			keep := tmp
   312  			if keep.Class == ir.PAUTOHEAP {
   313  				keep = e.copyExpr(arg.Pos(), tmp, call.PtrInit(), wrapper, false)
   314  			}
   315  
   316  			keep.SetAddrtaken(true) // ensure SSA keeps the tmp variable
   317  			call.KeepAlive = append(call.KeepAlive, keep)
   318  		}
   319  
   320  		return true
   321  	}
   322  
   323  	visit := func(pos src.XPos, argp *ir.Node) {
   324  		// Optimize a few common constant expressions. By leaving these
   325  		// untouched in the call expression, we let the wrapper handle
   326  		// evaluating them, rather than taking up closure context space.
   327  		switch arg := *argp; arg.Op() {
   328  		case ir.OLITERAL, ir.ONIL, ir.OMETHEXPR:
   329  			return
   330  		case ir.ONAME:
   331  			if arg.(*ir.Name).Class == ir.PFUNC {
   332  				return
   333  			}
   334  		}
   335  
   336  		if unsafeUintptr(*argp) {
   337  			return
   338  		}
   339  
   340  		if wrapper != nil {
   341  			e.wrapExpr(pos, argp, init, call, wrapper)
   342  		}
   343  	}
   344  
   345  	// Peel away any slice literals for better escape analyze
   346  	// them. For example:
   347  	//
   348  	//     go F([]int{a, b})
   349  	//
   350  	// If F doesn't escape its arguments, then the slice can
   351  	// be allocated on the new goroutine's stack.
   352  	//
   353  	// For variadic functions, the compiler has already rewritten:
   354  	//
   355  	//     f(a, b, c)
   356  	//
   357  	// to:
   358  	//
   359  	//     f([]T{a, b, c}...)
   360  	//
   361  	// So we need to look into slice elements to handle uintptr(ptr)
   362  	// arguments to syscall-like functions correctly.
   363  	if arg := *argp; arg.Op() == ir.OSLICELIT {
   364  		list := arg.(*ir.CompLitExpr).List
   365  		for i := range list {
   366  			el := &list[i]
   367  			if list[i].Op() == ir.OKEY {
   368  				el = &list[i].(*ir.KeyExpr).Value
   369  			}
   370  			visit(arg.Pos(), el)
   371  		}
   372  	} else {
   373  		visit(call.Pos(), argp)
   374  	}
   375  }
   376  
   377  // wrapExpr replaces *exprp with a temporary variable copy. If wrapper
   378  // is non-nil, the variable will be captured for use within that
   379  // function.
   380  func (e *escape) wrapExpr(pos src.XPos, exprp *ir.Node, init *ir.Nodes, call ir.Node, wrapper *ir.Func) *ir.Name {
   381  	tmp := e.copyExpr(pos, *exprp, init, e.curfn, true)
   382  
   383  	if wrapper != nil {
   384  		// Currently for "defer i.M()" if i is nil it panics at the point
   385  		// of defer statement, not when deferred function is called.  We
   386  		// need to do the nil check outside of the wrapper.
   387  		if call.Op() == ir.OCALLINTER && exprp == &call.(*ir.CallExpr).X.(*ir.SelectorExpr).X {
   388  			check := ir.NewUnaryExpr(pos, ir.OCHECKNIL, ir.NewUnaryExpr(pos, ir.OITAB, tmp))
   389  			init.Append(typecheck.Stmt(check))
   390  		}
   391  
   392  		e.oldLoc(tmp).captured = true
   393  
   394  		tmp = ir.NewClosureVar(pos, wrapper, tmp)
   395  	}
   396  
   397  	*exprp = tmp
   398  	return tmp
   399  }
   400  
   401  // copyExpr creates and returns a new temporary variable within fn;
   402  // appends statements to init to declare and initialize it to expr;
   403  // and escape analyzes the data flow if analyze is true.
   404  func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes, fn *ir.Func, analyze bool) *ir.Name {
   405  	if ir.HasUniquePos(expr) {
   406  		pos = expr.Pos()
   407  	}
   408  
   409  	tmp := typecheck.TempAt(pos, fn, expr.Type())
   410  
   411  	stmts := []ir.Node{
   412  		ir.NewDecl(pos, ir.ODCL, tmp),
   413  		ir.NewAssignStmt(pos, tmp, expr),
   414  	}
   415  	typecheck.Stmts(stmts)
   416  	init.Append(stmts...)
   417  
   418  	if analyze {
   419  		e.newLoc(tmp, false)
   420  		e.stmts(stmts)
   421  	}
   422  
   423  	return tmp
   424  }
   425  
   426  // tagHole returns a hole for evaluating an argument passed to param.
   427  // ks should contain the holes representing where the function
   428  // callee's results flows. fn is the statically-known callee function,
   429  // if any.
   430  func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole {
   431  	// If this is a dynamic call, we can't rely on param.Note.
   432  	if fn == nil {
   433  		return e.heapHole()
   434  	}
   435  
   436  	if e.inMutualBatch(fn) {
   437  		return e.addr(ir.AsNode(param.Nname))
   438  	}
   439  
   440  	// Call to previously tagged function.
   441  
   442  	var tagKs []hole
   443  
   444  	esc := parseLeaks(param.Note)
   445  	if x := esc.Heap(); x >= 0 {
   446  		tagKs = append(tagKs, e.heapHole().shift(x))
   447  	}
   448  
   449  	if ks != nil {
   450  		for i := 0; i < numEscResults; i++ {
   451  			if x := esc.Result(i); x >= 0 {
   452  				tagKs = append(tagKs, ks[i].shift(x))
   453  			}
   454  		}
   455  	}
   456  
   457  	return e.teeHole(tagKs...)
   458  }
   459  

View as plain text