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
5package escape
6
7import (
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// A location represents an abstract location that stores a Go
42// variable.
43type 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	// attrs is a bitset of location attributes.
70	attrs locAttr
71
72	// paramEsc records the represented parameter's leak set.
73	paramEsc leaks
74
75	captured   bool // has a closure captured this variable?
76	reassigned bool // has this variable been reassigned?
77	addrtaken  bool // has this variable's address been taken?
78}
79
80type locAttr uint8
81
82const (
83	// attrEscapes indicates whether the represented variable's address
84	// escapes; that is, whether the variable must be heap allocated.
85	attrEscapes locAttr = 1 << iota
86
87	// attrPersists indicates whether the represented expression's
88	// address outlives the statement; that is, whether its storage
89	// cannot be immediately reused.
90	attrPersists
91
92	// attrMutates indicates whether pointers that are reachable from
93	// this location may have their addressed memory mutated. This is
94	// used to detect string->[]byte conversions that can be safely
95	// optimized away.
96	attrMutates
97
98	// attrCalls indicates whether closures that are reachable from this
99	// location may be called without tracking their results. This is
100	// used to better optimize indirect closure calls.
101	attrCalls
102)
103
104func (l *location) hasAttr(attr locAttr) bool { return l.attrs&attr != 0 }
105
106// An edge represents an assignment edge between two Go variables.
107type edge struct {
108	src    *location
109	derefs int // >= -1
110	notes  *note
111}
112
113func (l *location) asHole() hole {
114	return hole{dst: l}
115}
116
117// leak records that parameter l leaks to sink.
118func (l *location) leakTo(sink *location, derefs int) {
119	// If sink is a result parameter that doesn't escape (#44614)
120	// and we can fit return bits into the escape analysis tag,
121	// then record as a result leak.
122	if !sink.hasAttr(attrEscapes) && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn {
123		ri := sink.resultIndex - 1
124		if ri < numEscResults {
125			// Leak to result parameter.
126			l.paramEsc.AddResult(ri, derefs)
127			return
128		}
129	}
130
131	// Otherwise, record as heap leak.
132	l.paramEsc.AddHeap(derefs)
133}
134
135// leakTo records that parameter l leaks to sink.
136func (b *batch) leakTo(l, sink *location, derefs int) {
137	if (logopt.Enabled() || base.Flag.LowerM >= 2) && !l.hasAttr(attrEscapes) {
138		if base.Flag.LowerM >= 2 {
139			fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, b.explainLoc(sink), derefs)
140		}
141		explanation := b.explainPath(sink, l)
142		if logopt.Enabled() {
143			var e_curfn *ir.Func // TODO(mdempsky): Fix.
144			logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e_curfn),
145				fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, b.explainLoc(sink), derefs), explanation)
146		}
147	}
148
149	// If sink is a result parameter that doesn't escape (#44614)
150	// and we can fit return bits into the escape analysis tag,
151	// then record as a result leak.
152	if !sink.hasAttr(attrEscapes) && sink.isName(ir.PPARAMOUT) && sink.curfn == l.curfn {
153		if ri := sink.resultIndex - 1; ri < numEscResults {
154			// Leak to result parameter.
155			l.paramEsc.AddResult(ri, derefs)
156			return
157		}
158	}
159
160	// Otherwise, record as heap leak.
161	l.paramEsc.AddHeap(derefs)
162}
163
164func (l *location) isName(c ir.Class) bool {
165	return l.n != nil && l.n.Op() == ir.ONAME && l.n.(*ir.Name).Class == c
166}
167
168// A hole represents a context for evaluation of a Go
169// expression. E.g., when evaluating p in "x = **p", we'd have a hole
170// with dst==x and derefs==2.
171type hole struct {
172	dst    *location
173	derefs int // >= -1
174	notes  *note
175
176	// addrtaken indicates whether this context is taking the address of
177	// the expression, independent of whether the address will actually
178	// be stored into a variable.
179	addrtaken bool
180}
181
182type note struct {
183	next  *note
184	where ir.Node
185	why   string
186}
187
188func (k hole) note(where ir.Node, why string) hole {
189	if where == nil || why == "" {
190		base.Fatalf("note: missing where/why")
191	}
192	if base.Flag.LowerM >= 2 || logopt.Enabled() {
193		k.notes = &note{
194			next:  k.notes,
195			where: where,
196			why:   why,
197		}
198	}
199	return k
200}
201
202func (k hole) shift(delta int) hole {
203	k.derefs += delta
204	if k.derefs < -1 {
205		base.Fatalf("derefs underflow: %v", k.derefs)
206	}
207	k.addrtaken = delta < 0
208	return k
209}
210
211func (k hole) deref(where ir.Node, why string) hole { return k.shift(1).note(where, why) }
212func (k hole) addr(where ir.Node, why string) hole  { return k.shift(-1).note(where, why) }
213
214func (k hole) dotType(t *types.Type, where ir.Node, why string) hole {
215	if !t.IsInterface() && !types.IsDirectIface(t) {
216		k = k.shift(1)
217	}
218	return k.note(where, why)
219}
220
221func (b *batch) flow(k hole, src *location) {
222	if k.addrtaken {
223		src.addrtaken = true
224	}
225
226	dst := k.dst
227	if dst == &b.blankLoc {
228		return
229	}
230	if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ...
231		return
232	}
233	if dst.hasAttr(attrEscapes) && k.derefs < 0 { // dst = &src
234		if base.Flag.LowerM >= 2 || logopt.Enabled() {
235			pos := base.FmtPos(src.n.Pos())
236			if base.Flag.LowerM >= 2 {
237				fmt.Printf("%s: %v escapes to heap:\n", pos, src.n)
238			}
239			explanation := b.explainFlow(pos, dst, src, k.derefs, k.notes, []*logopt.LoggedOpt{})
240			if logopt.Enabled() {
241				var e_curfn *ir.Func // TODO(mdempsky): Fix.
242				logopt.LogOpt(src.n.Pos(), "escapes", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", src.n), explanation)
243			}
244
245		}
246		src.attrs |= attrEscapes | attrPersists | attrMutates | attrCalls
247		return
248	}
249
250	// TODO(mdempsky): Deduplicate edges?
251	dst.edges = append(dst.edges, edge{src: src, derefs: k.derefs, notes: k.notes})
252}
253
254func (b *batch) heapHole() hole    { return b.heapLoc.asHole() }
255func (b *batch) mutatorHole() hole { return b.mutatorLoc.asHole() }
256func (b *batch) calleeHole() hole  { return b.calleeLoc.asHole() }
257func (b *batch) discardHole() hole { return b.blankLoc.asHole() }
258
259func (b *batch) oldLoc(n *ir.Name) *location {
260	if n.Canonical().Opt == nil {
261		base.FatalfAt(n.Pos(), "%v has no location", n)
262	}
263	return n.Canonical().Opt.(*location)
264}
265
266func (e *escape) newLoc(n ir.Node, persists bool) *location {
267	if e.curfn == nil {
268		base.Fatalf("e.curfn isn't set")
269	}
270	if n != nil && n.Type() != nil && n.Type().NotInHeap() {
271		base.ErrorfAt(n.Pos(), 0, "%v is incomplete (or unallocatable); stack allocation disallowed", n.Type())
272	}
273
274	if n != nil && n.Op() == ir.ONAME {
275		if canon := n.(*ir.Name).Canonical(); n != canon {
276			base.FatalfAt(n.Pos(), "newLoc on non-canonical %v (canonical is %v)", n, canon)
277		}
278	}
279	loc := &location{
280		n:         n,
281		curfn:     e.curfn,
282		loopDepth: e.loopDepth,
283	}
284	if persists {
285		loc.attrs |= attrPersists
286	}
287	e.allLocs = append(e.allLocs, loc)
288	if n != nil {
289		if n.Op() == ir.ONAME {
290			n := n.(*ir.Name)
291			if n.Class == ir.PPARAM && n.Curfn == nil {
292				// ok; hidden parameter
293			} else if n.Curfn != e.curfn {
294				base.FatalfAt(n.Pos(), "curfn mismatch: %v != %v for %v", n.Curfn, e.curfn, n)
295			}
296
297			if n.Opt != nil {
298				base.FatalfAt(n.Pos(), "%v already has a location", n)
299			}
300			n.Opt = loc
301		}
302	}
303	return loc
304}
305
306// teeHole returns a new hole that flows into each hole of ks,
307// similar to the Unix tee(1) command.
308func (e *escape) teeHole(ks ...hole) hole {
309	if len(ks) == 0 {
310		return e.discardHole()
311	}
312	if len(ks) == 1 {
313		return ks[0]
314	}
315	// TODO(mdempsky): Optimize if there's only one non-discard hole?
316
317	// Given holes "l1 = _", "l2 = **_", "l3 = *_", ..., create a
318	// new temporary location ltmp, wire it into place, and return
319	// a hole for "ltmp = _".
320	loc := e.newLoc(nil, false)
321	for _, k := range ks {
322		// N.B., "p = &q" and "p = &tmp; tmp = q" are not
323		// semantically equivalent. To combine holes like "l1
324		// = _" and "l2 = &_", we'd need to wire them as "l1 =
325		// *ltmp" and "l2 = ltmp" and return "ltmp = &_"
326		// instead.
327		if k.derefs < 0 {
328			base.Fatalf("teeHole: negative derefs")
329		}
330
331		e.flow(k, loc)
332	}
333	return loc.asHole()
334}
335
336// later returns a new hole that flows into k, but some time later.
337// Its main effect is to prevent immediate reuse of temporary
338// variables introduced during Order.
339func (e *escape) later(k hole) hole {
340	loc := e.newLoc(nil, true)
341	e.flow(k, loc)
342	return loc.asHole()
343}
344
345// Fmt is called from node printing to print information about escape analysis results.
346func Fmt(n ir.Node) string {
347	text := ""
348	switch n.Esc() {
349	case ir.EscUnknown:
350		break
351
352	case ir.EscHeap:
353		text = "esc(h)"
354
355	case ir.EscNone:
356		text = "esc(no)"
357
358	case ir.EscNever:
359		text = "esc(N)"
360
361	default:
362		text = fmt.Sprintf("esc(%d)", n.Esc())
363	}
364
365	if n.Op() == ir.ONAME {
366		n := n.(*ir.Name)
367		if loc, ok := n.Opt.(*location); ok && loc.loopDepth != 0 {
368			if text != "" {
369				text += " "
370			}
371			text += fmt.Sprintf("ld(%d)", loc.loopDepth)
372		}
373	}
374
375	return text
376}
377