// 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)] == '.' }