github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/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/go-asm/go/cmd/compile/base"
     9  	"github.com/go-asm/go/cmd/compile/ir"
    10  	"github.com/go-asm/go/cmd/compile/typecheck"
    11  	"github.com/go-asm/go/cmd/compile/types"
    12  	"github.com/go-asm/go/cmd/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  	argument := func(k hole, arg ir.Node) {
    20  		// TODO(mdempsky): Should be "call argument".
    21  		e.expr(k.note(call, "call parameter"), arg)
    22  	}
    23  
    24  	switch call.Op() {
    25  	default:
    26  		ir.Dump("esc", call)
    27  		base.Fatalf("unexpected call op: %v", call.Op())
    28  
    29  	case ir.OCALLFUNC, ir.OCALLINTER:
    30  		call := call.(*ir.CallExpr)
    31  		typecheck.AssertFixedCall(call)
    32  
    33  		// Pick out the function callee, if statically known.
    34  		//
    35  		// TODO(mdempsky): Change fn from *ir.Name to *ir.Func, but some
    36  		// functions (e.g., runtime builtins, method wrappers, generated
    37  		// eq/hash functions) don't have it set. Investigate whether
    38  		// that's a concern.
    39  		var fn *ir.Name
    40  		switch call.Op() {
    41  		case ir.OCALLFUNC:
    42  			v := ir.StaticValue(call.Fun)
    43  			fn = ir.StaticCalleeName(v)
    44  		}
    45  
    46  		fntype := call.Fun.Type()
    47  		if fn != nil {
    48  			fntype = fn.Type()
    49  		}
    50  
    51  		if ks != nil && fn != nil && e.inMutualBatch(fn) {
    52  			for i, result := range fn.Type().Results() {
    53  				e.expr(ks[i], result.Nname.(*ir.Name))
    54  			}
    55  		}
    56  
    57  		var recvArg ir.Node
    58  		if call.Op() == ir.OCALLFUNC {
    59  			// Evaluate callee function expression.
    60  			calleeK := e.discardHole()
    61  			if fn == nil { // unknown callee
    62  				for _, k := range ks {
    63  					if k.dst != &e.blankLoc {
    64  						// The results flow somewhere, but we don't statically
    65  						// know the callee function. If a closure flows here, we
    66  						// need to conservatively assume its results might flow to
    67  						// the heap.
    68  						calleeK = e.calleeHole().note(call, "callee operand")
    69  						break
    70  					}
    71  				}
    72  			}
    73  			e.expr(calleeK, call.Fun)
    74  		} else {
    75  			recvArg = call.Fun.(*ir.SelectorExpr).X
    76  		}
    77  
    78  		// argumentParam handles escape analysis of assigning a call
    79  		// argument to its corresponding parameter.
    80  		argumentParam := func(param *types.Field, arg ir.Node) {
    81  			e.rewriteArgument(arg, call, fn)
    82  			argument(e.tagHole(ks, fn, param), arg)
    83  		}
    84  
    85  		args := call.Args
    86  		if recvParam := fntype.Recv(); recvParam != nil {
    87  			if recvArg == nil {
    88  				// Function call using method expression. Receiver argument is
    89  				// at the front of the regular arguments list.
    90  				recvArg, args = args[0], args[1:]
    91  			}
    92  
    93  			argumentParam(recvParam, recvArg)
    94  		}
    95  
    96  		for i, param := range fntype.Params() {
    97  			argumentParam(param, args[i])
    98  		}
    99  
   100  	case ir.OINLCALL:
   101  		call := call.(*ir.InlinedCallExpr)
   102  		e.stmts(call.Body)
   103  		for i, result := range call.ReturnVars {
   104  			k := e.discardHole()
   105  			if ks != nil {
   106  				k = ks[i]
   107  			}
   108  			e.expr(k, result)
   109  		}
   110  
   111  	case ir.OAPPEND:
   112  		call := call.(*ir.CallExpr)
   113  		args := call.Args
   114  
   115  		// Appendee slice may flow directly to the result, if
   116  		// it has enough capacity. Alternatively, a new heap
   117  		// slice might be allocated, and all slice elements
   118  		// might flow to heap.
   119  		appendeeK := e.teeHole(ks[0], e.mutatorHole())
   120  		if args[0].Type().Elem().HasPointers() {
   121  			appendeeK = e.teeHole(appendeeK, e.heapHole().deref(call, "appendee slice"))
   122  		}
   123  		argument(appendeeK, args[0])
   124  
   125  		if call.IsDDD {
   126  			appendedK := e.discardHole()
   127  			if args[1].Type().IsSlice() && args[1].Type().Elem().HasPointers() {
   128  				appendedK = e.heapHole().deref(call, "appended slice...")
   129  			}
   130  			argument(appendedK, args[1])
   131  		} else {
   132  			for i := 1; i < len(args); i++ {
   133  				argument(e.heapHole(), args[i])
   134  			}
   135  		}
   136  		e.discard(call.RType)
   137  
   138  	case ir.OCOPY:
   139  		call := call.(*ir.BinaryExpr)
   140  		argument(e.mutatorHole(), call.X)
   141  
   142  		copiedK := e.discardHole()
   143  		if call.Y.Type().IsSlice() && call.Y.Type().Elem().HasPointers() {
   144  			copiedK = e.heapHole().deref(call, "copied slice")
   145  		}
   146  		argument(copiedK, call.Y)
   147  		e.discard(call.RType)
   148  
   149  	case ir.OPANIC:
   150  		call := call.(*ir.UnaryExpr)
   151  		argument(e.heapHole(), call.X)
   152  
   153  	case ir.OCOMPLEX:
   154  		call := call.(*ir.BinaryExpr)
   155  		e.discard(call.X)
   156  		e.discard(call.Y)
   157  
   158  	case ir.ODELETE, ir.OPRINT, ir.OPRINTLN, ir.ORECOVERFP:
   159  		call := call.(*ir.CallExpr)
   160  		for _, arg := range call.Args {
   161  			e.discard(arg)
   162  		}
   163  		e.discard(call.RType)
   164  
   165  	case ir.OMIN, ir.OMAX:
   166  		call := call.(*ir.CallExpr)
   167  		for _, arg := range call.Args {
   168  			argument(ks[0], arg)
   169  		}
   170  		e.discard(call.RType)
   171  
   172  	case ir.OLEN, ir.OCAP, ir.OREAL, ir.OIMAG, ir.OCLOSE:
   173  		call := call.(*ir.UnaryExpr)
   174  		e.discard(call.X)
   175  
   176  	case ir.OCLEAR:
   177  		call := call.(*ir.UnaryExpr)
   178  		argument(e.mutatorHole(), call.X)
   179  
   180  	case ir.OUNSAFESTRINGDATA, ir.OUNSAFESLICEDATA:
   181  		call := call.(*ir.UnaryExpr)
   182  		argument(ks[0], call.X)
   183  
   184  	case ir.OUNSAFEADD, ir.OUNSAFESLICE, ir.OUNSAFESTRING:
   185  		call := call.(*ir.BinaryExpr)
   186  		argument(ks[0], call.X)
   187  		e.discard(call.Y)
   188  		e.discard(call.RType)
   189  	}
   190  }
   191  
   192  // goDeferStmt analyzes a "go" or "defer" statement.
   193  func (e *escape) goDeferStmt(n *ir.GoDeferStmt) {
   194  	k := e.heapHole()
   195  	if n.Op() == ir.ODEFER && e.loopDepth == 1 && n.DeferAt == nil {
   196  		// Top-level defer arguments don't escape to the heap,
   197  		// but they do need to last until they're invoked.
   198  		k = e.later(e.discardHole())
   199  
   200  		// force stack allocation of defer record, unless
   201  		// open-coded defers are used (see ssa.go)
   202  		n.SetEsc(ir.EscNever)
   203  	}
   204  
   205  	// If the function is already a zero argument/result function call,
   206  	// just escape analyze it normally.
   207  	//
   208  	// Note that the runtime is aware of this optimization for
   209  	// "go" statements that start in reflect.makeFuncStub or
   210  	// reflect.methodValueCall.
   211  
   212  	call, ok := n.Call.(*ir.CallExpr)
   213  	if !ok || call.Op() != ir.OCALLFUNC {
   214  		base.FatalfAt(n.Pos(), "expected function call: %v", n.Call)
   215  	}
   216  	if sig := call.Fun.Type(); sig.NumParams()+sig.NumResults() != 0 {
   217  		base.FatalfAt(n.Pos(), "expected signature without parameters or results: %v", sig)
   218  	}
   219  
   220  	if clo, ok := call.Fun.(*ir.ClosureExpr); ok && n.Op() == ir.OGO {
   221  		clo.IsGoWrap = true
   222  	}
   223  
   224  	e.expr(k, call.Fun)
   225  }
   226  
   227  // rewriteArgument rewrites the argument arg of the given call expression.
   228  // fn is the static callee function, if known.
   229  func (e *escape) rewriteArgument(arg ir.Node, call *ir.CallExpr, fn *ir.Name) {
   230  	if fn == nil || fn.Func == nil {
   231  		return
   232  	}
   233  	pragma := fn.Func.Pragma
   234  	if pragma&(ir.UintptrKeepAlive|ir.UintptrEscapes) == 0 {
   235  		return
   236  	}
   237  
   238  	// unsafeUintptr rewrites "uintptr(ptr)" arguments to syscall-like
   239  	// functions, so that ptr is kept alive and/or escaped as
   240  	// appropriate. unsafeUintptr also reports whether it modified arg0.
   241  	unsafeUintptr := func(arg ir.Node) {
   242  		// If the argument is really a pointer being converted to uintptr,
   243  		// arrange for the pointer to be kept alive until the call
   244  		// returns, by copying it into a temp and marking that temp still
   245  		// alive when we pop the temp stack.
   246  		conv, ok := arg.(*ir.ConvExpr)
   247  		if !ok || conv.Op() != ir.OCONVNOP {
   248  			return // not a conversion
   249  		}
   250  		if !conv.X.Type().IsUnsafePtr() || !conv.Type().IsUintptr() {
   251  			return // not an unsafe.Pointer->uintptr conversion
   252  		}
   253  
   254  		// Create and declare a new pointer-typed temp variable.
   255  		//
   256  		// TODO(mdempsky): This potentially violates the Go spec's order
   257  		// of evaluations, by evaluating arg.X before any other
   258  		// operands.
   259  		tmp := e.copyExpr(conv.Pos(), conv.X, call.PtrInit())
   260  		conv.X = tmp
   261  
   262  		k := e.mutatorHole()
   263  		if pragma&ir.UintptrEscapes != 0 {
   264  			k = e.heapHole().note(conv, "//go:uintptrescapes")
   265  		}
   266  		e.flow(k, e.oldLoc(tmp))
   267  
   268  		if pragma&ir.UintptrKeepAlive != 0 {
   269  			tmp.SetAddrtaken(true) // ensure SSA keeps the tmp variable
   270  			call.KeepAlive = append(call.KeepAlive, tmp)
   271  		}
   272  	}
   273  
   274  	// For variadic functions, the compiler has already rewritten:
   275  	//
   276  	//     f(a, b, c)
   277  	//
   278  	// to:
   279  	//
   280  	//     f([]T{a, b, c}...)
   281  	//
   282  	// So we need to look into slice elements to handle uintptr(ptr)
   283  	// arguments to variadic syscall-like functions correctly.
   284  	if arg.Op() == ir.OSLICELIT {
   285  		list := arg.(*ir.CompLitExpr).List
   286  		for _, el := range list {
   287  			if el.Op() == ir.OKEY {
   288  				el = el.(*ir.KeyExpr).Value
   289  			}
   290  			unsafeUintptr(el)
   291  		}
   292  	} else {
   293  		unsafeUintptr(arg)
   294  	}
   295  }
   296  
   297  // copyExpr creates and returns a new temporary variable within fn;
   298  // appends statements to init to declare and initialize it to expr;
   299  // and escape analyzes the data flow.
   300  func (e *escape) copyExpr(pos src.XPos, expr ir.Node, init *ir.Nodes) *ir.Name {
   301  	if ir.HasUniquePos(expr) {
   302  		pos = expr.Pos()
   303  	}
   304  
   305  	tmp := typecheck.TempAt(pos, e.curfn, expr.Type())
   306  
   307  	stmts := []ir.Node{
   308  		ir.NewDecl(pos, ir.ODCL, tmp),
   309  		ir.NewAssignStmt(pos, tmp, expr),
   310  	}
   311  	typecheck.Stmts(stmts)
   312  	init.Append(stmts...)
   313  
   314  	e.newLoc(tmp, true)
   315  	e.stmts(stmts)
   316  
   317  	return tmp
   318  }
   319  
   320  // tagHole returns a hole for evaluating an argument passed to param.
   321  // ks should contain the holes representing where the function
   322  // callee's results flows. fn is the statically-known callee function,
   323  // if any.
   324  func (e *escape) tagHole(ks []hole, fn *ir.Name, param *types.Field) hole {
   325  	// If this is a dynamic call, we can't rely on param.Note.
   326  	if fn == nil {
   327  		return e.heapHole()
   328  	}
   329  
   330  	if e.inMutualBatch(fn) {
   331  		if param.Nname == nil {
   332  			return e.discardHole()
   333  		}
   334  		return e.addr(param.Nname.(*ir.Name))
   335  	}
   336  
   337  	// Call to previously tagged function.
   338  
   339  	var tagKs []hole
   340  	esc := parseLeaks(param.Note)
   341  
   342  	if x := esc.Heap(); x >= 0 {
   343  		tagKs = append(tagKs, e.heapHole().shift(x))
   344  	}
   345  	if x := esc.Mutator(); x >= 0 {
   346  		tagKs = append(tagKs, e.mutatorHole().shift(x))
   347  	}
   348  	if x := esc.Callee(); x >= 0 {
   349  		tagKs = append(tagKs, e.calleeHole().shift(x))
   350  	}
   351  
   352  	if ks != nil {
   353  		for i := 0; i < numEscResults; i++ {
   354  			if x := esc.Result(i); x >= 0 {
   355  				tagKs = append(tagKs, ks[i].shift(x))
   356  			}
   357  		}
   358  	}
   359  
   360  	return e.teeHole(tagKs...)
   361  }