| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package escape |
| |
| import ( |
| "cmd/compile/internal/base" |
| "cmd/compile/internal/ir" |
| "cmd/compile/internal/logopt" |
| "cmd/internal/src" |
| "fmt" |
| "strings" |
| ) |
| |
| // walkAll computes the minimal dereferences between all pairs of |
| // locations. |
| func (b *batch) walkAll() { |
| // We use a work queue to keep track of locations that we need |
| // to visit, and repeatedly walk until we reach a fixed point. |
| // |
| // We walk once from each location (including the heap), and |
| // then re-enqueue each location on its transition from |
| // transient->!transient and !escapes->escapes, which can each |
| // happen at most once. So we take Θ(len(e.allLocs)) walks. |
| |
| // LIFO queue, has enough room for e.allLocs and e.heapLoc. |
| todo := make([]*location, 0, len(b.allLocs)+1) |
| enqueue := func(loc *location) { |
| if !loc.queued { |
| todo = append(todo, loc) |
| loc.queued = true |
| } |
| } |
| |
| for _, loc := range b.allLocs { |
| enqueue(loc) |
| } |
| enqueue(&b.heapLoc) |
| |
| var walkgen uint32 |
| for len(todo) > 0 { |
| root := todo[len(todo)-1] |
| todo = todo[:len(todo)-1] |
| root.queued = false |
| |
| walkgen++ |
| b.walkOne(root, walkgen, enqueue) |
| } |
| } |
| |
| // walkOne computes the minimal number of dereferences from root to |
| // all other locations. |
| func (b *batch) walkOne(root *location, walkgen uint32, enqueue func(*location)) { |
| // The data flow graph has negative edges (from addressing |
| // operations), so we use the Bellman-Ford algorithm. However, |
| // we don't have to worry about infinite negative cycles since |
| // we bound intermediate dereference counts to 0. |
| |
| root.walkgen = walkgen |
| root.derefs = 0 |
| root.dst = nil |
| |
| todo := []*location{root} // LIFO queue |
| for len(todo) > 0 { |
| l := todo[len(todo)-1] |
| todo = todo[:len(todo)-1] |
| |
| derefs := l.derefs |
| |
| // If l.derefs < 0, then l's address flows to root. |
| addressOf := derefs < 0 |
| if addressOf { |
| // For a flow path like "root = &l; l = x", |
| // l's address flows to root, but x's does |
| // not. We recognize this by lower bounding |
| // derefs at 0. |
| derefs = 0 |
| |
| // If l's address flows to a non-transient |
| // location, then l can't be transiently |
| // allocated. |
| if !root.transient && l.transient { |
| l.transient = false |
| enqueue(l) |
| } |
| } |
| |
| if b.outlives(root, l) { |
| // l's value flows to root. If l is a function |
| // parameter and root is the heap or a |
| // corresponding result parameter, then record |
| // that value flow for tagging the function |
| // later. |
| if l.isName(ir.PPARAM) { |
| if (logopt.Enabled() || base.Flag.LowerM >= 2) && !l.escapes { |
| if base.Flag.LowerM >= 2 { |
| fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, b.explainLoc(root), derefs) |
| } |
| explanation := b.explainPath(root, l) |
| if logopt.Enabled() { |
| var e_curfn *ir.Func // TODO(mdempsky): Fix. |
| logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e_curfn), |
| fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, b.explainLoc(root), derefs), explanation) |
| } |
| } |
| l.leakTo(root, derefs) |
| } |
| |
| // If l's address flows somewhere that |
| // outlives it, then l needs to be heap |
| // allocated. |
| if addressOf && !l.escapes { |
| if logopt.Enabled() || base.Flag.LowerM >= 2 { |
| if base.Flag.LowerM >= 2 { |
| fmt.Printf("%s: %v escapes to heap:\n", base.FmtPos(l.n.Pos()), l.n) |
| } |
| explanation := b.explainPath(root, l) |
| if logopt.Enabled() { |
| var e_curfn *ir.Func // TODO(mdempsky): Fix. |
| logopt.LogOpt(l.n.Pos(), "escape", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", l.n), explanation) |
| } |
| } |
| l.escapes = true |
| enqueue(l) |
| continue |
| } |
| } |
| |
| for i, edge := range l.edges { |
| if edge.src.escapes { |
| continue |
| } |
| d := derefs + edge.derefs |
| if edge.src.walkgen != walkgen || edge.src.derefs > d { |
| edge.src.walkgen = walkgen |
| edge.src.derefs = d |
| edge.src.dst = l |
| edge.src.dstEdgeIdx = i |
| todo = append(todo, edge.src) |
| } |
| } |
| } |
| } |
| |
| // explainPath prints an explanation of how src flows to the walk root. |
| func (b *batch) explainPath(root, src *location) []*logopt.LoggedOpt { |
| visited := make(map[*location]bool) |
| pos := base.FmtPos(src.n.Pos()) |
| var explanation []*logopt.LoggedOpt |
| for { |
| // Prevent infinite loop. |
| if visited[src] { |
| if base.Flag.LowerM >= 2 { |
| fmt.Printf("%s: warning: truncated explanation due to assignment cycle; see golang.org/issue/35518\n", pos) |
| } |
| break |
| } |
| visited[src] = true |
| dst := src.dst |
| edge := &dst.edges[src.dstEdgeIdx] |
| if edge.src != src { |
| base.Fatalf("path inconsistency: %v != %v", edge.src, src) |
| } |
| |
| explanation = b.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation) |
| |
| if dst == root { |
| break |
| } |
| src = dst |
| } |
| |
| return explanation |
| } |
| |
| func (b *batch) explainFlow(pos string, dst, srcloc *location, derefs int, notes *note, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt { |
| ops := "&" |
| if derefs >= 0 { |
| ops = strings.Repeat("*", derefs) |
| } |
| print := base.Flag.LowerM >= 2 |
| |
| flow := fmt.Sprintf(" flow: %s = %s%v:", b.explainLoc(dst), ops, b.explainLoc(srcloc)) |
| if print { |
| fmt.Printf("%s:%s\n", pos, flow) |
| } |
| if logopt.Enabled() { |
| var epos src.XPos |
| if notes != nil { |
| epos = notes.where.Pos() |
| } else if srcloc != nil && srcloc.n != nil { |
| epos = srcloc.n.Pos() |
| } |
| var e_curfn *ir.Func // TODO(mdempsky): Fix. |
| explanation = append(explanation, logopt.NewLoggedOpt(epos, "escflow", "escape", ir.FuncName(e_curfn), flow)) |
| } |
| |
| for note := notes; note != nil; note = note.next { |
| if print { |
| fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, base.FmtPos(note.where.Pos())) |
| } |
| if logopt.Enabled() { |
| var e_curfn *ir.Func // TODO(mdempsky): Fix. |
| explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos(), "escflow", "escape", ir.FuncName(e_curfn), |
| fmt.Sprintf(" from %v (%v)", note.where, note.why))) |
| } |
| } |
| return explanation |
| } |
| |
| func (b *batch) explainLoc(l *location) string { |
| if l == &b.heapLoc { |
| return "{heap}" |
| } |
| if l.n == nil { |
| // TODO(mdempsky): Omit entirely. |
| return "{temp}" |
| } |
| if l.n.Op() == ir.ONAME { |
| return fmt.Sprintf("%v", l.n) |
| } |
| return fmt.Sprintf("{storage for %v}", l.n) |
| } |
| |
| // outlives reports whether values stored in l may survive beyond |
| // other's lifetime if stack allocated. |
| func (b *batch) outlives(l, other *location) bool { |
| // The heap outlives everything. |
| if l.escapes { |
| return true |
| } |
| |
| // We don't know what callers do with returned values, so |
| // pessimistically we need to assume they flow to the heap and |
| // outlive everything too. |
| if l.isName(ir.PPARAMOUT) { |
| // Exception: Directly called closures can return |
| // locations allocated outside of them without forcing |
| // them to the heap. For example: |
| // |
| // var u int // okay to stack allocate |
| // *(func() *int { return &u }()) = 42 |
| if containsClosure(other.curfn, l.curfn) && l.curfn.ClosureCalled() { |
| return false |
| } |
| |
| return true |
| } |
| |
| // If l and other are within the same function, then l |
| // outlives other if it was declared outside other's loop |
| // scope. For example: |
| // |
| // var l *int |
| // for { |
| // l = new(int) |
| // } |
| if l.curfn == other.curfn && l.loopDepth < other.loopDepth { |
| return true |
| } |
| |
| // If other is declared within a child closure of where l is |
| // declared, then l outlives it. For example: |
| // |
| // var l *int |
| // func() { |
| // l = new(int) |
| // } |
| if containsClosure(l.curfn, other.curfn) { |
| return true |
| } |
| |
| return false |
| } |
| |
| // containsClosure reports whether c is a closure contained within f. |
| func containsClosure(f, c *ir.Func) bool { |
| // Common case. |
| if f == c { |
| return false |
| } |
| |
| // Closures within function Foo are named like "Foo.funcN..." |
| // TODO(mdempsky): Better way to recognize this. |
| fn := f.Sym().Name |
| cn := c.Sym().Name |
| return len(cn) > len(fn) && cn[:len(fn)] == fn && cn[len(fn)] == '.' |
| } |