github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/escape/escape.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  	"fmt"
     9  
    10  	"github.com/bir3/gocompiler/src/cmd/compile/internal/base"
    11  	"github.com/bir3/gocompiler/src/cmd/compile/internal/ir"
    12  	"github.com/bir3/gocompiler/src/cmd/compile/internal/logopt"
    13  	"github.com/bir3/gocompiler/src/cmd/compile/internal/typecheck"
    14  	"github.com/bir3/gocompiler/src/cmd/compile/internal/types"
    15  )
    16  
    17  // Escape analysis.
    18  //
    19  // Here we analyze functions to determine which Go variables
    20  // (including implicit allocations such as calls to "new" or "make",
    21  // composite literals, etc.) can be allocated on the stack. The two
    22  // key invariants we have to ensure are: (1) pointers to stack objects
    23  // cannot be stored in the heap, and (2) pointers to a stack object
    24  // cannot outlive that object (e.g., because the declaring function
    25  // returned and destroyed the object's stack frame, or its space is
    26  // reused across loop iterations for logically distinct variables).
    27  //
    28  // We implement this with a static data-flow analysis of the AST.
    29  // First, we construct a directed weighted graph where vertices
    30  // (termed "locations") represent variables allocated by statements
    31  // and expressions, and edges represent assignments between variables
    32  // (with weights representing addressing/dereference counts).
    33  //
    34  // Next we walk the graph looking for assignment paths that might
    35  // violate the invariants stated above. If a variable v's address is
    36  // stored in the heap or elsewhere that may outlive it, then v is
    37  // marked as requiring heap allocation.
    38  //
    39  // To support interprocedural analysis, we also record data-flow from
    40  // each function's parameters to the heap and to its result
    41  // parameters. This information is summarized as "parameter tags",
    42  // which are used at static call sites to improve escape analysis of
    43  // function arguments.
    44  
    45  // Constructing the location graph.
    46  //
    47  // Every allocating statement (e.g., variable declaration) or
    48  // expression (e.g., "new" or "make") is first mapped to a unique
    49  // "location."
    50  //
    51  // We also model every Go assignment as a directed edges between
    52  // locations. The number of dereference operations minus the number of
    53  // addressing operations is recorded as the edge's weight (termed
    54  // "derefs"). For example:
    55  //
    56  //     p = &q    // -1
    57  //     p = q     //  0
    58  //     p = *q    //  1
    59  //     p = **q   //  2
    60  //
    61  //     p = **&**&q  // 2
    62  //
    63  // Note that the & operator can only be applied to addressable
    64  // expressions, and the expression &x itself is not addressable, so
    65  // derefs cannot go below -1.
    66  //
    67  // Every Go language construct is lowered into this representation,
    68  // generally without sensitivity to flow, path, or context; and
    69  // without distinguishing elements within a compound variable. For
    70  // example:
    71  //
    72  //     var x struct { f, g *int }
    73  //     var u []*int
    74  //
    75  //     x.f = u[0]
    76  //
    77  // is modeled simply as
    78  //
    79  //     x = *u
    80  //
    81  // That is, we don't distinguish x.f from x.g, or u[0] from u[1],
    82  // u[2], etc. However, we do record the implicit dereference involved
    83  // in indexing a slice.
    84  
    85  // A batch holds escape analysis state that's shared across an entire
    86  // batch of functions being analyzed at once.
    87  type batch struct {
    88  	allLocs  []*location
    89  	closures []closure
    90  
    91  	heapLoc  location
    92  	blankLoc location
    93  }
    94  
    95  // A closure holds a closure expression and its spill hole (i.e.,
    96  // where the hole representing storing into its closure record).
    97  type closure struct {
    98  	k   hole
    99  	clo *ir.ClosureExpr
   100  }
   101  
   102  // An escape holds state specific to a single function being analyzed
   103  // within a batch.
   104  type escape struct {
   105  	*batch
   106  
   107  	curfn *ir.Func // function being analyzed
   108  
   109  	labels map[*types.Sym]labelState // known labels
   110  
   111  	// loopDepth counts the current loop nesting depth within
   112  	// curfn. It increments within each "for" loop and at each
   113  	// label with a corresponding backwards "goto" (i.e.,
   114  	// unstructured loop).
   115  	loopDepth int
   116  }
   117  
   118  func Funcs(all []ir.Node) {
   119  	ir.VisitFuncsBottomUp(all, Batch)
   120  }
   121  
   122  // Batch performs escape analysis on a minimal batch of
   123  // functions.
   124  func Batch(fns []*ir.Func, recursive bool) {
   125  	for _, fn := range fns {
   126  		if fn.Op() != ir.ODCLFUNC {
   127  			base.Fatalf("unexpected node: %v", fn)
   128  		}
   129  	}
   130  
   131  	var b batch
   132  	b.heapLoc.escapes = true
   133  
   134  	// Construct data-flow graph from syntax trees.
   135  	for _, fn := range fns {
   136  		if base.Flag.W > 1 {
   137  			s := fmt.Sprintf("\nbefore escape %v", fn)
   138  			ir.Dump(s, fn)
   139  		}
   140  		b.initFunc(fn)
   141  	}
   142  	for _, fn := range fns {
   143  		if !fn.IsHiddenClosure() {
   144  			b.walkFunc(fn)
   145  		}
   146  	}
   147  
   148  	// We've walked the function bodies, so we've seen everywhere a
   149  	// variable might be reassigned or have it's address taken. Now we
   150  	// can decide whether closures should capture their free variables
   151  	// by value or reference.
   152  	for _, closure := range b.closures {
   153  		b.flowClosure(closure.k, closure.clo)
   154  	}
   155  	b.closures = nil
   156  
   157  	for _, loc := range b.allLocs {
   158  		if why := HeapAllocReason(loc.n); why != "" {
   159  			b.flow(b.heapHole().addr(loc.n, why), loc)
   160  		}
   161  	}
   162  
   163  	b.walkAll()
   164  	b.finish(fns)
   165  }
   166  
   167  func (b *batch) with(fn *ir.Func) *escape {
   168  	return &escape{
   169  		batch:     b,
   170  		curfn:     fn,
   171  		loopDepth: 1,
   172  	}
   173  }
   174  
   175  func (b *batch) initFunc(fn *ir.Func) {
   176  	e := b.with(fn)
   177  	if fn.Esc() != escFuncUnknown {
   178  		base.Fatalf("unexpected node: %v", fn)
   179  	}
   180  	fn.SetEsc(escFuncPlanned)
   181  	if base.Flag.LowerM > 3 {
   182  		ir.Dump("escAnalyze", fn)
   183  	}
   184  
   185  	// Allocate locations for local variables.
   186  	for _, n := range fn.Dcl {
   187  		e.newLoc(n, false)
   188  	}
   189  
   190  	// Also for hidden parameters (e.g., the ".this" parameter to a
   191  	// method value wrapper).
   192  	if fn.OClosure == nil {
   193  		for _, n := range fn.ClosureVars {
   194  			e.newLoc(n.Canonical(), false)
   195  		}
   196  	}
   197  
   198  	// Initialize resultIndex for result parameters.
   199  	for i, f := range fn.Type().Results().FieldSlice() {
   200  		e.oldLoc(f.Nname.(*ir.Name)).resultIndex = 1 + i
   201  	}
   202  }
   203  
   204  func (b *batch) walkFunc(fn *ir.Func) {
   205  	e := b.with(fn)
   206  	fn.SetEsc(escFuncStarted)
   207  
   208  	// Identify labels that mark the head of an unstructured loop.
   209  	ir.Visit(fn, func(n ir.Node) {
   210  		switch n.Op() {
   211  		case ir.OLABEL:
   212  			n := n.(*ir.LabelStmt)
   213  			if n.Label.IsBlank() {
   214  				break
   215  			}
   216  			if e.labels == nil {
   217  				e.labels = make(map[*types.Sym]labelState)
   218  			}
   219  			e.labels[n.Label] = nonlooping
   220  
   221  		case ir.OGOTO:
   222  			// If we visited the label before the goto,
   223  			// then this is a looping label.
   224  			n := n.(*ir.BranchStmt)
   225  			if e.labels[n.Label] == nonlooping {
   226  				e.labels[n.Label] = looping
   227  			}
   228  		}
   229  	})
   230  
   231  	e.block(fn.Body)
   232  
   233  	if len(e.labels) != 0 {
   234  		base.FatalfAt(fn.Pos(), "leftover labels after walkFunc")
   235  	}
   236  }
   237  
   238  func (b *batch) flowClosure(k hole, clo *ir.ClosureExpr) {
   239  	for _, cv := range clo.Func.ClosureVars {
   240  		n := cv.Canonical()
   241  		loc := b.oldLoc(cv)
   242  		if !loc.captured {
   243  			base.FatalfAt(cv.Pos(), "closure variable never captured: %v", cv)
   244  		}
   245  
   246  		// Capture by value for variables <= 128 bytes that are never reassigned.
   247  		n.SetByval(!loc.addrtaken && !loc.reassigned && n.Type().Size() <= 128)
   248  		if !n.Byval() {
   249  			n.SetAddrtaken(true)
   250  			if n.Sym().Name == typecheck.LocalDictName {
   251  				base.FatalfAt(n.Pos(), "dictionary variable not captured by value")
   252  			}
   253  		}
   254  
   255  		if base.Flag.LowerM > 1 {
   256  			how := "ref"
   257  			if n.Byval() {
   258  				how = "value"
   259  			}
   260  			base.WarnfAt(n.Pos(), "%v capturing by %s: %v (addr=%v assign=%v width=%d)", n.Curfn, how, n, loc.addrtaken, loc.reassigned, n.Type().Size())
   261  		}
   262  
   263  		// Flow captured variables to closure.
   264  		k := k
   265  		if !cv.Byval() {
   266  			k = k.addr(cv, "reference")
   267  		}
   268  		b.flow(k.note(cv, "captured by a closure"), loc)
   269  	}
   270  }
   271  
   272  func (b *batch) finish(fns []*ir.Func) {
   273  	// Record parameter tags for package export data.
   274  	for _, fn := range fns {
   275  		fn.SetEsc(escFuncTagged)
   276  
   277  		narg := 0
   278  		for _, fs := range &types.RecvsParams {
   279  			for _, f := range fs(fn.Type()).Fields().Slice() {
   280  				narg++
   281  				f.Note = b.paramTag(fn, narg, f)
   282  			}
   283  		}
   284  	}
   285  
   286  	for _, loc := range b.allLocs {
   287  		n := loc.n
   288  		if n == nil {
   289  			continue
   290  		}
   291  		if n.Op() == ir.ONAME {
   292  			n := n.(*ir.Name)
   293  			n.Opt = nil
   294  		}
   295  
   296  		// Update n.Esc based on escape analysis results.
   297  
   298  		// Omit escape diagnostics for go/defer wrappers, at least for now.
   299  		// Historically, we haven't printed them, and test cases don't expect them.
   300  		// TODO(mdempsky): Update tests to expect this.
   301  		goDeferWrapper := n.Op() == ir.OCLOSURE && n.(*ir.ClosureExpr).Func.Wrapper()
   302  
   303  		if loc.escapes {
   304  			if n.Op() == ir.ONAME {
   305  				if base.Flag.CompilingRuntime {
   306  					base.ErrorfAt(n.Pos(), "%v escapes to heap, not allowed in runtime", n)
   307  				}
   308  				if base.Flag.LowerM != 0 {
   309  					base.WarnfAt(n.Pos(), "moved to heap: %v", n)
   310  				}
   311  			} else {
   312  				if base.Flag.LowerM != 0 && !goDeferWrapper {
   313  					base.WarnfAt(n.Pos(), "%v escapes to heap", n)
   314  				}
   315  				if logopt.Enabled() {
   316  					var e_curfn *ir.Func // TODO(mdempsky): Fix.
   317  					logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e_curfn))
   318  				}
   319  			}
   320  			n.SetEsc(ir.EscHeap)
   321  		} else {
   322  			if base.Flag.LowerM != 0 && n.Op() != ir.ONAME && !goDeferWrapper {
   323  				base.WarnfAt(n.Pos(), "%v does not escape", n)
   324  			}
   325  			n.SetEsc(ir.EscNone)
   326  			if loc.transient {
   327  				switch n.Op() {
   328  				case ir.OCLOSURE:
   329  					n := n.(*ir.ClosureExpr)
   330  					n.SetTransient(true)
   331  				case ir.OMETHVALUE:
   332  					n := n.(*ir.SelectorExpr)
   333  					n.SetTransient(true)
   334  				case ir.OSLICELIT:
   335  					n := n.(*ir.CompLitExpr)
   336  					n.SetTransient(true)
   337  				}
   338  			}
   339  		}
   340  	}
   341  }
   342  
   343  // inMutualBatch reports whether function fn is in the batch of
   344  // mutually recursive functions being analyzed. When this is true,
   345  // fn has not yet been analyzed, so its parameters and results
   346  // should be incorporated directly into the flow graph instead of
   347  // relying on its escape analysis tagging.
   348  func (e *escape) inMutualBatch(fn *ir.Name) bool {
   349  	if fn.Defn != nil && fn.Defn.Esc() < escFuncTagged {
   350  		if fn.Defn.Esc() == escFuncUnknown {
   351  			base.Fatalf("graph inconsistency: %v", fn)
   352  		}
   353  		return true
   354  	}
   355  	return false
   356  }
   357  
   358  const (
   359  	escFuncUnknown = 0 + iota
   360  	escFuncPlanned
   361  	escFuncStarted
   362  	escFuncTagged
   363  )
   364  
   365  // Mark labels that have no backjumps to them as not increasing e.loopdepth.
   366  type labelState int
   367  
   368  const (
   369  	looping labelState = 1 + iota
   370  	nonlooping
   371  )
   372  
   373  func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
   374  	name := func() string {
   375  		if f.Sym != nil {
   376  			return f.Sym.Name
   377  		}
   378  		return fmt.Sprintf("arg#%d", narg)
   379  	}
   380  
   381  	// Only report diagnostics for user code;
   382  	// not for wrappers generated around them.
   383  	// TODO(mdempsky): Generalize this.
   384  	diagnose := base.Flag.LowerM != 0 && !(fn.Wrapper() || fn.Dupok())
   385  
   386  	if len(fn.Body) == 0 {
   387  		// Assume that uintptr arguments must be held live across the call.
   388  		// This is most important for syscall.Syscall.
   389  		// See golang.org/issue/13372.
   390  		// This really doesn't have much to do with escape analysis per se,
   391  		// but we are reusing the ability to annotate an individual function
   392  		// argument and pass those annotations along to importing code.
   393  		fn.Pragma |= ir.UintptrKeepAlive
   394  
   395  		if f.Type.IsUintptr() {
   396  			if diagnose {
   397  				base.WarnfAt(f.Pos, "assuming %v is unsafe uintptr", name())
   398  			}
   399  			return ""
   400  		}
   401  
   402  		if !f.Type.HasPointers() { // don't bother tagging for scalars
   403  			return ""
   404  		}
   405  
   406  		var esc leaks
   407  
   408  		// External functions are assumed unsafe, unless
   409  		// //go:noescape is given before the declaration.
   410  		if fn.Pragma&ir.Noescape != 0 {
   411  			if diagnose && f.Sym != nil {
   412  				base.WarnfAt(f.Pos, "%v does not escape", name())
   413  			}
   414  		} else {
   415  			if diagnose && f.Sym != nil {
   416  				base.WarnfAt(f.Pos, "leaking param: %v", name())
   417  			}
   418  			esc.AddHeap(0)
   419  		}
   420  
   421  		return esc.Encode()
   422  	}
   423  
   424  	if fn.Pragma&ir.UintptrEscapes != 0 {
   425  		if f.Type.IsUintptr() {
   426  			if diagnose {
   427  				base.WarnfAt(f.Pos, "marking %v as escaping uintptr", name())
   428  			}
   429  			return ""
   430  		}
   431  		if f.IsDDD() && f.Type.Elem().IsUintptr() {
   432  			// final argument is ...uintptr.
   433  			if diagnose {
   434  				base.WarnfAt(f.Pos, "marking %v as escaping ...uintptr", name())
   435  			}
   436  			return ""
   437  		}
   438  	}
   439  
   440  	if !f.Type.HasPointers() { // don't bother tagging for scalars
   441  		return ""
   442  	}
   443  
   444  	// Unnamed parameters are unused and therefore do not escape.
   445  	if f.Sym == nil || f.Sym.IsBlank() {
   446  		var esc leaks
   447  		return esc.Encode()
   448  	}
   449  
   450  	n := f.Nname.(*ir.Name)
   451  	loc := b.oldLoc(n)
   452  	esc := loc.paramEsc
   453  	esc.Optimize()
   454  
   455  	if diagnose && !loc.escapes {
   456  		if esc.Empty() {
   457  			base.WarnfAt(f.Pos, "%v does not escape", name())
   458  		}
   459  		if x := esc.Heap(); x >= 0 {
   460  			if x == 0 {
   461  				base.WarnfAt(f.Pos, "leaking param: %v", name())
   462  			} else {
   463  				// TODO(mdempsky): Mention level=x like below?
   464  				base.WarnfAt(f.Pos, "leaking param content: %v", name())
   465  			}
   466  		}
   467  		for i := 0; i < numEscResults; i++ {
   468  			if x := esc.Result(i); x >= 0 {
   469  				res := fn.Type().Results().Field(i).Sym
   470  				base.WarnfAt(f.Pos, "leaking param: %v to result %v level=%d", name(), res, x)
   471  			}
   472  		}
   473  	}
   474  
   475  	return esc.Encode()
   476  }