github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/escape/call.go (about)

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