Source file src/cmd/compile/internal/escape/graph.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/logopt"
    11  	"cmd/compile/internal/types"
    12  	"fmt"
    13  )
    14  
    15  // Below we implement the methods for walking the AST and recording
    16  // data flow edges. Note that because a sub-expression might have
    17  // side-effects, it's important to always visit the entire AST.
    18  //
    19  // For example, write either:
    20  //
    21  //     if x {
    22  //         e.discard(n.Left)
    23  //     } else {
    24  //         e.value(k, n.Left)
    25  //     }
    26  //
    27  // or
    28  //
    29  //     if x {
    30  //         k = e.discardHole()
    31  //     }
    32  //     e.value(k, n.Left)
    33  //
    34  // Do NOT write:
    35  //
    36  //    // BAD: possibly loses side-effects within n.Left
    37  //    if !x {
    38  //        e.value(k, n.Left)
    39  //    }
    40  
    41  // An location represents an abstract location that stores a Go
    42  // variable.
    43  type location struct {
    44  	n         ir.Node  // represented variable or expression, if any
    45  	curfn     *ir.Func // enclosing function
    46  	edges     []edge   // incoming edges
    47  	loopDepth int      // loopDepth at declaration
    48  
    49  	// resultIndex records the tuple index (starting at 1) for
    50  	// PPARAMOUT variables within their function's result type.
    51  	// For non-PPARAMOUT variables it's 0.
    52  	resultIndex int
    53  
    54  	// derefs and walkgen are used during walkOne to track the
    55  	// minimal dereferences from the walk root.
    56  	derefs  int // >= -1
    57  	walkgen uint32
    58  
    59  	// dst and dstEdgeindex track the next immediate assignment
    60  	// destination location during walkone, along with the index
    61  	// of the edge pointing back to this location.
    62  	dst        *location
    63  	dstEdgeIdx int
    64  
    65  	// queued is used by walkAll to track whether this location is
    66  	// in the walk queue.
    67  	queued bool
    68  
    69  	// escapes reports whether the represented variable's address
    70  	// escapes; that is, whether the variable must be heap
    71  	// allocated.
    72  	escapes bool
    73  
    74  	// transient reports whether the represented expression's
    75  	// address does not outlive the statement; that is, whether
    76  	// its storage can be immediately reused.
    77  	transient bool
    78  
    79  	// paramEsc records the represented parameter's leak set.
    80  	paramEsc leaks
    81  
    82  	captured   bool // has a closure captured this variable?
    83  	reassigned bool // has this variable been reassigned?
    84  	addrtaken  bool // has this variable's address been taken?
    85  }
    86  
    87  // An edge represents an assignment edge between two Go variables.
    88  type edge struct {
    89  	src    *location
    90  	derefs int // >= -1
    91  	notes  *note
    92  }
    93  
    94  func (l *location) asHole() hole {
    95  	return hole{dst: l}
    96  }
    97  
    98  // leak records that parameter l leaks to sink.
    99  func (l *location) leakTo(sink *location, derefs int) {
   100  	// If sink is a result parameter that doesn't escape (#44614)
   101  	// and we can fit return bits into the escape analysis tag,
   102  	// then record as a result leak.
   103  	if !sink.escapes && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn {
   104  		ri := sink.resultIndex - 1
   105  		if ri < numEscResults {
   106  			// Leak to result parameter.
   107  			l.paramEsc.AddResult(ri, derefs)
   108  			return
   109  		}
   110  	}
   111  
   112  	// Otherwise, record as heap leak.
   113  	l.paramEsc.AddHeap(derefs)
   114  }
   115  
   116  func (l *location) isName(c ir.Class) bool {
   117  	return l.n != nil && l.n.Op() == ir.ONAME && l.n.(*ir.Name).Class == c
   118  }
   119  
   120  // A hole represents a context for evaluation of a Go
   121  // expression. E.g., when evaluating p in "x = **p", we'd have a hole
   122  // with dst==x and derefs==2.
   123  type hole struct {
   124  	dst    *location
   125  	derefs int // >= -1
   126  	notes  *note
   127  
   128  	// addrtaken indicates whether this context is taking the address of
   129  	// the expression, independent of whether the address will actually
   130  	// be stored into a variable.
   131  	addrtaken bool
   132  }
   133  
   134  type note struct {
   135  	next  *note
   136  	where ir.Node
   137  	why   string
   138  }
   139  
   140  func (k hole) note(where ir.Node, why string) hole {
   141  	if where == nil || why == "" {
   142  		base.Fatalf("note: missing where/why")
   143  	}
   144  	if base.Flag.LowerM >= 2 || logopt.Enabled() {
   145  		k.notes = &note{
   146  			next:  k.notes,
   147  			where: where,
   148  			why:   why,
   149  		}
   150  	}
   151  	return k
   152  }
   153  
   154  func (k hole) shift(delta int) hole {
   155  	k.derefs += delta
   156  	if k.derefs < -1 {
   157  		base.Fatalf("derefs underflow: %v", k.derefs)
   158  	}
   159  	k.addrtaken = delta < 0
   160  	return k
   161  }
   162  
   163  func (k hole) deref(where ir.Node, why string) hole { return k.shift(1).note(where, why) }
   164  func (k hole) addr(where ir.Node, why string) hole  { return k.shift(-1).note(where, why) }
   165  
   166  func (k hole) dotType(t *types.Type, where ir.Node, why string) hole {
   167  	if !t.IsInterface() && !types.IsDirectIface(t) {
   168  		k = k.shift(1)
   169  	}
   170  	return k.note(where, why)
   171  }
   172  
   173  func (b *batch) flow(k hole, src *location) {
   174  	if k.addrtaken {
   175  		src.addrtaken = true
   176  	}
   177  
   178  	dst := k.dst
   179  	if dst == &b.blankLoc {
   180  		return
   181  	}
   182  	if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ...
   183  		return
   184  	}
   185  	if dst.escapes && k.derefs < 0 { // dst = &src
   186  		if base.Flag.LowerM >= 2 || logopt.Enabled() {
   187  			pos := base.FmtPos(src.n.Pos())
   188  			if base.Flag.LowerM >= 2 {
   189  				fmt.Printf("%s: %v escapes to heap:\n", pos, src.n)
   190  			}
   191  			explanation := b.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{})
   192  			if logopt.Enabled() {
   193  				var e_curfn *ir.Func // TODO(mdempsky): Fix.
   194  				logopt.LogOpt(src.n.Pos(), "escapes", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", src.n), explanation)
   195  			}
   196  
   197  		}
   198  		src.escapes = true
   199  		return
   200  	}
   201  
   202  	// TODO(mdempsky): Deduplicate edges?
   203  	dst.edges = append(dst.edges, edge{src: src, derefs: k.derefs, notes: k.notes})
   204  }
   205  
   206  func (b *batch) heapHole() hole    { return b.heapLoc.asHole() }
   207  func (b *batch) discardHole() hole { return b.blankLoc.asHole() }
   208  
   209  func (b *batch) oldLoc(n *ir.Name) *location {
   210  	if n.Canonical().Opt == nil {
   211  		base.Fatalf("%v has no location", n)
   212  	}
   213  	return n.Canonical().Opt.(*location)
   214  }
   215  
   216  func (e *escape) newLoc(n ir.Node, transient bool) *location {
   217  	if e.curfn == nil {
   218  		base.Fatalf("e.curfn isn't set")
   219  	}
   220  	if n != nil && n.Type() != nil && n.Type().NotInHeap() {
   221  		base.ErrorfAt(n.Pos(), "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type())
   222  	}
   223  
   224  	if n != nil && n.Op() == ir.ONAME {
   225  		if canon := n.(*ir.Name).Canonical(); n != canon {
   226  			base.Fatalf("newLoc on non-canonical %v (canonical is %v)", n, canon)
   227  		}
   228  	}
   229  	loc := &location{
   230  		n:         n,
   231  		curfn:     e.curfn,
   232  		loopDepth: e.loopDepth,
   233  		transient: transient,
   234  	}
   235  	e.allLocs = append(e.allLocs, loc)
   236  	if n != nil {
   237  		if n.Op() == ir.ONAME {
   238  			n := n.(*ir.Name)
   239  			if n.Class == ir.PPARAM && n.Curfn == nil {
   240  				// ok; hidden parameter
   241  			} else if n.Curfn != e.curfn {
   242  				base.Fatalf("curfn mismatch: %v != %v for %v", n.Curfn, e.curfn, n)
   243  			}
   244  
   245  			if n.Opt != nil {
   246  				base.Fatalf("%v already has a location", n)
   247  			}
   248  			n.Opt = loc
   249  		}
   250  	}
   251  	return loc
   252  }
   253  
   254  // teeHole returns a new hole that flows into each hole of ks,
   255  // similar to the Unix tee(1) command.
   256  func (e *escape) teeHole(ks ...hole) hole {
   257  	if len(ks) == 0 {
   258  		return e.discardHole()
   259  	}
   260  	if len(ks) == 1 {
   261  		return ks[0]
   262  	}
   263  	// TODO(mdempsky): Optimize if there's only one non-discard hole?
   264  
   265  	// Given holes "l1 = _", "l2 = **_", "l3 = *_", ..., create a
   266  	// new temporary location ltmp, wire it into place, and return
   267  	// a hole for "ltmp = _".
   268  	loc := e.newLoc(nil, true)
   269  	for _, k := range ks {
   270  		// N.B., "p = &q" and "p = &tmp; tmp = q" are not
   271  		// semantically equivalent. To combine holes like "l1
   272  		// = _" and "l2 = &_", we'd need to wire them as "l1 =
   273  		// *ltmp" and "l2 = ltmp" and return "ltmp = &_"
   274  		// instead.
   275  		if k.derefs < 0 {
   276  			base.Fatalf("teeHole: negative derefs")
   277  		}
   278  
   279  		e.flow(k, loc)
   280  	}
   281  	return loc.asHole()
   282  }
   283  
   284  // later returns a new hole that flows into k, but some time later.
   285  // Its main effect is to prevent immediate reuse of temporary
   286  // variables introduced during Order.
   287  func (e *escape) later(k hole) hole {
   288  	loc := e.newLoc(nil, false)
   289  	e.flow(k, loc)
   290  	return loc.asHole()
   291  }
   292  
   293  // Fmt is called from node printing to print information about escape analysis results.
   294  func Fmt(n ir.Node) string {
   295  	text := ""
   296  	switch n.Esc() {
   297  	case ir.EscUnknown:
   298  		break
   299  
   300  	case ir.EscHeap:
   301  		text = "esc(h)"
   302  
   303  	case ir.EscNone:
   304  		text = "esc(no)"
   305  
   306  	case ir.EscNever:
   307  		text = "esc(N)"
   308  
   309  	default:
   310  		text = fmt.Sprintf("esc(%d)", n.Esc())
   311  	}
   312  
   313  	if n.Op() == ir.ONAME {
   314  		n := n.(*ir.Name)
   315  		if loc, ok := n.Opt.(*location); ok && loc.loopDepth != 0 {
   316  			if text != "" {
   317  				text += " "
   318  			}
   319  			text += fmt.Sprintf("ld(%d)", loc.loopDepth)
   320  		}
   321  	}
   322  
   323  	return text
   324  }
   325  

View as plain text