github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/escape/utils.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/ir"
     9  	"github.com/go-asm/go/cmd/compile/typecheck"
    10  	"github.com/go-asm/go/cmd/compile/types"
    11  )
    12  
    13  func isSliceSelfAssign(dst, src ir.Node) bool {
    14  	// Detect the following special case.
    15  	//
    16  	//	func (b *Buffer) Foo() {
    17  	//		n, m := ...
    18  	//		b.buf = b.buf[n:m]
    19  	//	}
    20  	//
    21  	// This assignment is a no-op for escape analysis,
    22  	// it does not store any new pointers into b that were not already there.
    23  	// However, without this special case b will escape, because we assign to OIND/ODOTPTR.
    24  	// Here we assume that the statement will not contain calls,
    25  	// that is, that order will move any calls to init.
    26  	// Otherwise base ONAME value could change between the moments
    27  	// when we evaluate it for dst and for src.
    28  
    29  	// dst is ONAME dereference.
    30  	var dstX ir.Node
    31  	switch dst.Op() {
    32  	default:
    33  		return false
    34  	case ir.ODEREF:
    35  		dst := dst.(*ir.StarExpr)
    36  		dstX = dst.X
    37  	case ir.ODOTPTR:
    38  		dst := dst.(*ir.SelectorExpr)
    39  		dstX = dst.X
    40  	}
    41  	if dstX.Op() != ir.ONAME {
    42  		return false
    43  	}
    44  	// src is a slice operation.
    45  	switch src.Op() {
    46  	case ir.OSLICE, ir.OSLICE3, ir.OSLICESTR:
    47  		// OK.
    48  	case ir.OSLICEARR, ir.OSLICE3ARR:
    49  		// Since arrays are embedded into containing object,
    50  		// slice of non-pointer array will introduce a new pointer into b that was not already there
    51  		// (pointer to b itself). After such assignment, if b contents escape,
    52  		// b escapes as well. If we ignore such OSLICEARR, we will conclude
    53  		// that b does not escape when b contents do.
    54  		//
    55  		// Pointer to an array is OK since it's not stored inside b directly.
    56  		// For slicing an array (not pointer to array), there is an implicit OADDR.
    57  		// We check that to determine non-pointer array slicing.
    58  		src := src.(*ir.SliceExpr)
    59  		if src.X.Op() == ir.OADDR {
    60  			return false
    61  		}
    62  	default:
    63  		return false
    64  	}
    65  	// slice is applied to ONAME dereference.
    66  	var baseX ir.Node
    67  	switch base := src.(*ir.SliceExpr).X; base.Op() {
    68  	default:
    69  		return false
    70  	case ir.ODEREF:
    71  		base := base.(*ir.StarExpr)
    72  		baseX = base.X
    73  	case ir.ODOTPTR:
    74  		base := base.(*ir.SelectorExpr)
    75  		baseX = base.X
    76  	}
    77  	if baseX.Op() != ir.ONAME {
    78  		return false
    79  	}
    80  	// dst and src reference the same base ONAME.
    81  	return dstX.(*ir.Name) == baseX.(*ir.Name)
    82  }
    83  
    84  // isSelfAssign reports whether assignment from src to dst can
    85  // be ignored by the escape analysis as it's effectively a self-assignment.
    86  func isSelfAssign(dst, src ir.Node) bool {
    87  	if isSliceSelfAssign(dst, src) {
    88  		return true
    89  	}
    90  
    91  	// Detect trivial assignments that assign back to the same object.
    92  	//
    93  	// It covers these cases:
    94  	//	val.x = val.y
    95  	//	val.x[i] = val.y[j]
    96  	//	val.x1.x2 = val.x1.y2
    97  	//	... etc
    98  	//
    99  	// These assignments do not change assigned object lifetime.
   100  
   101  	if dst == nil || src == nil || dst.Op() != src.Op() {
   102  		return false
   103  	}
   104  
   105  	// The expression prefix must be both "safe" and identical.
   106  	switch dst.Op() {
   107  	case ir.ODOT, ir.ODOTPTR:
   108  		// Safe trailing accessors that are permitted to differ.
   109  		dst := dst.(*ir.SelectorExpr)
   110  		src := src.(*ir.SelectorExpr)
   111  		return ir.SameSafeExpr(dst.X, src.X)
   112  	case ir.OINDEX:
   113  		dst := dst.(*ir.IndexExpr)
   114  		src := src.(*ir.IndexExpr)
   115  		if mayAffectMemory(dst.Index) || mayAffectMemory(src.Index) {
   116  			return false
   117  		}
   118  		return ir.SameSafeExpr(dst.X, src.X)
   119  	default:
   120  		return false
   121  	}
   122  }
   123  
   124  // mayAffectMemory reports whether evaluation of n may affect the program's
   125  // memory state. If the expression can't affect memory state, then it can be
   126  // safely ignored by the escape analysis.
   127  func mayAffectMemory(n ir.Node) bool {
   128  	// We may want to use a list of "memory safe" ops instead of generally
   129  	// "side-effect free", which would include all calls and other ops that can
   130  	// allocate or change global state. For now, it's safer to start with the latter.
   131  	//
   132  	// We're ignoring things like division by zero, index out of range,
   133  	// and nil pointer dereference here.
   134  
   135  	// TODO(rsc): It seems like it should be possible to replace this with
   136  	// an ir.Any looking for any op that's not the ones in the case statement.
   137  	// But that produces changes in the compiled output detected by buildall.
   138  	switch n.Op() {
   139  	case ir.ONAME, ir.OLITERAL, ir.ONIL:
   140  		return false
   141  
   142  	case ir.OADD, ir.OSUB, ir.OOR, ir.OXOR, ir.OMUL, ir.OLSH, ir.ORSH, ir.OAND, ir.OANDNOT, ir.ODIV, ir.OMOD:
   143  		n := n.(*ir.BinaryExpr)
   144  		return mayAffectMemory(n.X) || mayAffectMemory(n.Y)
   145  
   146  	case ir.OINDEX:
   147  		n := n.(*ir.IndexExpr)
   148  		return mayAffectMemory(n.X) || mayAffectMemory(n.Index)
   149  
   150  	case ir.OCONVNOP, ir.OCONV:
   151  		n := n.(*ir.ConvExpr)
   152  		return mayAffectMemory(n.X)
   153  
   154  	case ir.OLEN, ir.OCAP, ir.ONOT, ir.OBITNOT, ir.OPLUS, ir.ONEG:
   155  		n := n.(*ir.UnaryExpr)
   156  		return mayAffectMemory(n.X)
   157  
   158  	case ir.ODOT, ir.ODOTPTR:
   159  		n := n.(*ir.SelectorExpr)
   160  		return mayAffectMemory(n.X)
   161  
   162  	case ir.ODEREF:
   163  		n := n.(*ir.StarExpr)
   164  		return mayAffectMemory(n.X)
   165  
   166  	default:
   167  		return true
   168  	}
   169  }
   170  
   171  // HeapAllocReason returns the reason the given Node must be heap
   172  // allocated, or the empty string if it doesn't.
   173  func HeapAllocReason(n ir.Node) string {
   174  	if n == nil || n.Type() == nil {
   175  		return ""
   176  	}
   177  
   178  	// Parameters are always passed via the stack.
   179  	if n.Op() == ir.ONAME {
   180  		n := n.(*ir.Name)
   181  		if n.Class == ir.PPARAM || n.Class == ir.PPARAMOUT {
   182  			return ""
   183  		}
   184  	}
   185  
   186  	if n.Type().Size() > ir.MaxStackVarSize {
   187  		return "too large for stack"
   188  	}
   189  	if n.Type().Alignment() > int64(types.PtrSize) {
   190  		return "too aligned for stack"
   191  	}
   192  
   193  	if (n.Op() == ir.ONEW || n.Op() == ir.OPTRLIT) && n.Type().Elem().Size() > ir.MaxImplicitStackVarSize {
   194  		return "too large for stack"
   195  	}
   196  	if (n.Op() == ir.ONEW || n.Op() == ir.OPTRLIT) && n.Type().Elem().Alignment() > int64(types.PtrSize) {
   197  		return "too aligned for stack"
   198  	}
   199  
   200  	if n.Op() == ir.OCLOSURE && typecheck.ClosureType(n.(*ir.ClosureExpr)).Size() > ir.MaxImplicitStackVarSize {
   201  		return "too large for stack"
   202  	}
   203  	if n.Op() == ir.OMETHVALUE && typecheck.MethodValueType(n.(*ir.SelectorExpr)).Size() > ir.MaxImplicitStackVarSize {
   204  		return "too large for stack"
   205  	}
   206  
   207  	if n.Op() == ir.OMAKESLICE {
   208  		n := n.(*ir.MakeExpr)
   209  		r := n.Cap
   210  		if r == nil {
   211  			r = n.Len
   212  		}
   213  		if !ir.IsSmallIntConst(r) {
   214  			return "non-constant size"
   215  		}
   216  		if t := n.Type(); t.Elem().Size() != 0 && ir.Int64Val(r) > ir.MaxImplicitStackVarSize/t.Elem().Size() {
   217  			return "too large for stack"
   218  		}
   219  	}
   220  
   221  	return ""
   222  }