1// Copyright 2018 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package escape67import (8 "cmd/compile/internal/base"9 "cmd/compile/internal/ir"10 "cmd/compile/internal/logopt"11 "cmd/internal/src"12 "fmt"13 "math/bits"14 "strings"15)1617// walkState contains the root properties used by a walk. Roots with equal18// states can be analyzed together.19type walkState struct {20 sink *location // canonical leak sink; not necessarily a walk root21 curfn *ir.Func22 loopDepth int23 attrs locAttr24}2526func (s walkState) hasAttr(attr locAttr) bool { return s.attrs&attr != 0 }2728// walkPath is an immutable path from a location to one of the roots of the29// current walk.30type walkPath struct {31 root *location // root reached by this path32 dst *location // destination of the first edge33 edgeIdx int // index of the first edge in dst.edges34 next *walkPath // path from dst to root35}3637// walkState returns the normalized walk state for loc.38func (b *batch) walkState(loc *location) walkState {39 s := walkState{40 sink: &b.heapLoc,41 curfn: loc.curfn,42 loopDepth: loc.loopDepth,43 attrs: loc.attrs,44 }45 if loc.paramOut || loc == &b.mutatorLoc || loc == &b.calleeLoc {46 s.sink = loc47 return s48 }49 if loc.hasAttr(attrEscapes) {50 // outlives returns true for all escaping roots.51 s.curfn = nil52 s.loopDepth = 053 }54 return s55}5657// walkAll computes the minimal dereferences from each group of roots to58// all other locations.59func (b *batch) walkAll() {60 // We use a work queue to keep track of locations that we need61 // to visit, and repeatedly walk until we reach a fixed point.62 //63 // We walk once from each group of locations with the same state, since64 // their effects depend only on the minimum dereference count from the65 // group. We re-enqueue locations when their attributes change, grouping66 // them using their new state.6768 // Queue of locations to walk. Has enough room for b.allLocs69 // plus b.heapLoc, b.mutatorLoc, b.calleeLoc.70 todo := newQueue(len(b.allLocs) + 3)7172 enqueue := func(loc *location) {73 if !loc.queuedWalkAll {74 loc.queuedWalkAll = true75 if loc.hasAttr(attrEscapes) {76 // Favor locations that escape to the heap,77 // which in some cases allows attrEscape to78 // propagate faster.79 todo.pushFront(loc)80 } else {81 todo.pushBack(loc)82 }83 }84 }8586 for _, loc := range b.allLocs {87 todo.pushFront(loc)88 // TODO(thepudds): clean up setting queuedWalkAll.89 loc.queuedWalkAll = true90 }91 todo.pushFront(&b.mutatorLoc)92 todo.pushFront(&b.calleeLoc)93 todo.pushFront(&b.heapLoc)9495 b.mutatorLoc.queuedWalkAll = true96 b.calleeLoc.queuedWalkAll = true97 b.heapLoc.queuedWalkAll = true9899 var walkgen uint32100 walkTodo := newQueue(len(b.allLocs) + 3)101 groups := make(map[walkState][]*location)102 var states []walkState103 for todo.len() > 0 {104 // Process the queue in rounds. At the start of a round, group roots105 // whose walk states are equal. Each walk uses the captured state rather106 // than the roots' live attributes, so the roots only need to agree when107 // they are grouped.108 //109 // An earlier walk may add attributes to a root scheduled for a later110 // group. The root is then re-enqueued to be walked with its new state111 // in the next round. Walking it here with its old state is still safe,112 // because attributes and the effects propagated from them only grow.113 clear(groups)114 states = states[:0]115 for todo.len() > 0 {116 root := todo.popFront()117 root.queuedWalkAll = false118 state := b.walkState(root)119 if _, ok := groups[state]; !ok {120 states = append(states, state)121 }122 groups[state] = append(groups[state], root)123 }124 for _, state := range states {125 walkgen++126 b.walk(state, groups[state], walkgen, enqueue, walkTodo)127 }128 }129}130131// walk computes the minimal number of dereferences from roots that were132// scheduled with state s to all other locations. A root's live attributes may133// have grown since it was scheduled; the resulting new state is walked in a134// later round.135func (b *batch) walk(s walkState, roots []*location, walkgen uint32, enqueue func(*location), todo *queue) {136 // The data flow graph has negative edges (from addressing137 // operations), so we use the Bellman-Ford algorithm. However,138 // we don't have to worry about infinite negative cycles since139 // we bound intermediate dereference counts to 0.140141 diagnose := base.Flag.LowerM >= 2 || logopt.Enabled()142 var paths map[*location]*walkPath143 if diagnose {144 paths = make(map[*location]*walkPath)145 }146 todo.reset()147 for _, r := range roots {148 r.walkgen = walkgen149 r.derefs = 0150 r.queuedWalk = walkgen151 todo.pushBack(r)152153 if s.hasAttr(attrCalls) {154 if clo, ok := r.n.(*ir.ClosureExpr); ok {155 if fn := clo.Func; b.inMutualBatch(fn.Nname) && !fn.ClosureResultsLost() {156 fn.SetClosureResultsLost(true)157158 // Re-flow from the closure's results, now that we're aware159 // we lost track of them.160 for _, result := range fn.Type().Results() {161 enqueue(b.oldLoc(result.Nname.(*ir.Name)))162 }163 }164 }165 }166 }167168 for todo.len() > 0 {169 l := todo.popFront()170 l.queuedWalk = 0 // no longer queued for walk171172 derefs := l.derefs173 var newAttrs locAttr174175 // If l.derefs < 0, then l's address flows to root.176 addressOf := derefs < 0177 if addressOf {178 // For a flow path like "root = &l; l = x",179 // l's address flows to root, but x's does180 // not. We recognize this by lower bounding181 // derefs at 0.182 derefs = 0183184 // If l's address flows somewhere that185 // outlives it, then l needs to be heap186 // allocated.187 if s.outlives(b, l) {188 if !l.hasAttr(attrEscapes) && diagnose {189 if base.Flag.LowerM >= 2 {190 fmt.Printf("%s: %v escapes to heap in %v:\n", base.FmtPos(l.n.Pos()), l.n, ir.FuncName(l.curfn))191 }192 root := walkRoot(l, paths)193 explanation := b.explainPath(root, l, paths[l])194 if logopt.Enabled() {195 var e_curfn *ir.Func // TODO(mdempsky): Fix.196 logopt.LogOpt(l.n.Pos(), "escape", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", l.n), explanation)197 }198 }199 newAttrs |= attrEscapes | attrPersists | attrMutates | attrCalls200 } else201 // If l's address flows to a persistent location, then l needs202 // to persist too.203 if s.hasAttr(attrPersists) {204 newAttrs |= attrPersists205 }206 }207208 if derefs == 0 {209 newAttrs |= s.attrs & (attrMutates | attrCalls)210 }211212 // l's value flows to root. If l is a function213 // parameter and root is the heap or a214 // corresponding result parameter, then record215 // that value flow for tagging the function216 // later.217 if l.param {218 if s.outlives(b, l) {219 if !l.hasAttr(attrEscapes) && diagnose {220 root := walkRoot(l, paths)221 if base.Flag.LowerM >= 2 {222 fmt.Printf("%s: parameter %v leaks to %s for %v with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, b.explainLoc(root), ir.FuncName(l.curfn), derefs)223 }224 explanation := b.explainPath(root, l, paths[l])225 if logopt.Enabled() {226 var e_curfn *ir.Func // TODO(mdempsky): Fix.227 logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e_curfn),228 fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, b.explainLoc(root), derefs), explanation)229 }230 }231 l.leakTo(s.sink, derefs)232 }233 if s.hasAttr(attrMutates) {234 l.paramEsc.AddMutator(derefs)235 }236 if s.hasAttr(attrCalls) {237 l.paramEsc.AddCallee(derefs)238 }239 }240241 if newAttrs&^l.attrs != 0 {242 l.attrs |= newAttrs243 enqueue(l)244 if l.attrs&attrEscapes != 0 {245 continue246 }247 }248249 for i, edge := range l.edges {250 if edge.src.hasAttr(attrEscapes) {251 continue252 }253 d := derefs + edge.derefs254 if edge.src.walkgen != walkgen || edge.src.derefs > d {255 edge.src.walkgen = walkgen256 edge.src.derefs = d257 if diagnose {258 paths[edge.src] = &walkPath{259 root: walkRoot(l, paths),260 dst: l,261 edgeIdx: i,262 next: paths[l],263 }264 }265 // Check if already queued in todo.266 if edge.src.queuedWalk != walkgen {267 edge.src.queuedWalk = walkgen // Mark queued for this walkgen.268269 // Place at the back to possibly give time for270 // other possible attribute changes to src.271 todo.pushBack(edge.src)272 }273 }274 }275 }276}277278func walkRoot(l *location, paths map[*location]*walkPath) *location {279 if path := paths[l]; path != nil {280 return path.root281 }282 return l283}284285// explainPath prints an explanation of how src flows to the walk root.286func (b *batch) explainPath(root, src *location, path *walkPath) []*logopt.LoggedOpt {287 visited := make(map[*location]bool)288 pos := base.FmtPos(src.n.Pos())289 var explanation []*logopt.LoggedOpt290 for ; path != nil; path = path.next {291 // Prevent infinite loop.292 if visited[src] {293 if base.Flag.LowerM >= 2 {294 fmt.Printf("%s: warning: truncated explanation due to assignment cycle; see golang.org/issue/35518\n", pos)295 }296 return explanation297 }298 visited[src] = true299 dst := path.dst300 edge := &dst.edges[path.edgeIdx]301 if edge.src != src {302 base.Fatalf("path inconsistency: %v != %v", edge.src, src)303 }304305 explanation = b.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation)306307 src = dst308 }309 if src != root {310 base.Fatalf("path root inconsistency: %v != %v", src, root)311 }312313 return explanation314}315316func (b *batch) explainFlow(pos string, dst, srcloc *location, derefs int, notes *note, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt {317 ops := "&"318 if derefs >= 0 {319 ops = strings.Repeat("*", derefs)320 }321 print := base.Flag.LowerM >= 2322323 flow := fmt.Sprintf(" flow: %s ← %s%v:", b.explainLoc(dst), ops, b.explainLoc(srcloc))324 if print {325 fmt.Printf("%s:%s\n", pos, flow)326 }327 if logopt.Enabled() {328 var epos src.XPos329 if notes != nil {330 epos = notes.where.Pos()331 } else if srcloc != nil && srcloc.n != nil {332 epos = srcloc.n.Pos()333 }334 var e_curfn *ir.Func // TODO(mdempsky): Fix.335 explanation = append(explanation, logopt.NewLoggedOpt(epos, epos, "escflow", "escape", ir.FuncName(e_curfn), flow))336 }337338 for note := notes; note != nil; note = note.next {339 if print {340 fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, base.FmtPos(note.where.Pos()))341 }342 if logopt.Enabled() {343 var e_curfn *ir.Func // TODO(mdempsky): Fix.344 notePos := note.where.Pos()345 explanation = append(explanation, logopt.NewLoggedOpt(notePos, notePos, "escflow", "escape", ir.FuncName(e_curfn),346 fmt.Sprintf(" from %v (%v)", note.where, note.why)))347 }348 }349 return explanation350}351352func (b *batch) explainLoc(l *location) string {353 if l == &b.heapLoc {354 return "{heap}"355 }356 if l.n == nil {357 // TODO(mdempsky): Omit entirely.358 return "{temp}"359 }360 if l.n.Op() == ir.ONAME {361 return fmt.Sprintf("%v", l.n)362 }363 return fmt.Sprintf("{storage for %v}", l.n)364}365366// outlives reports whether values stored in roots with state s may survive367// beyond other's lifetime if stack allocated.368func (s walkState) outlives(b *batch, other *location) bool {369 // The heap outlives everything.370 if s.hasAttr(attrEscapes) {371 return true372 }373374 // Pseudo-locations that don't really exist.375 if s.sink == &b.mutatorLoc || s.sink == &b.calleeLoc {376 return false377 }378379 // We don't know what callers do with returned values, so380 // pessimistically we need to assume they flow to the heap and381 // outlive everything too.382 if s.sink != nil && s.sink.paramOut {383 // Exception: Closures can return locations allocated outside of384 // them without forcing them to the heap, if we can statically385 // identify all call sites. For example:386 //387 // var u int // okay to stack allocate388 // fn := func() *int { return &u }()389 // *fn() = 42390 if ir.ContainsClosure(other.curfn, s.curfn) && !s.curfn.ClosureResultsLost() {391 return false392 }393394 return true395 }396397 // If root and other are within the same function, then root398 // outlives other if it was declared outside other's loop399 // scope. For example:400 //401 // var l *int402 // for {403 // l = new(int) // must heap allocate: outlives for loop404 // }405 if s.curfn == other.curfn && s.loopDepth < other.loopDepth {406 return true407 }408409 // If other is declared within a child closure of where root is410 // declared, then root outlives it. For example:411 //412 // var l *int413 // func() {414 // l = new(int) // must heap allocate: outlives call frame (if not inlined)415 // }()416 if ir.ContainsClosure(s.curfn, other.curfn) {417 return true418 }419420 return false421}422423// queue implements a queue of locations for use in walkAll and walk.424// It supports pushing to front & back, and popping from front.425// TODO(thepudds): does cmd/compile have a deque or similar somewhere?426type queue struct {427 locs []*location428 head int // index of front element429 tail int // next back element430 elems int431}432433func newQueue(capacity int) *queue {434 capacity = max(capacity, 2)435 capacity = 1 << bits.Len64(uint64(capacity-1)) // round up to a power of 2436 return &queue{locs: make([]*location, capacity)}437}438439func (q *queue) reset() {440 q.head = 0441 q.tail = 0442 q.elems = 0443}444445// pushFront adds an element to the front of the queue.446func (q *queue) pushFront(loc *location) {447 if q.elems == len(q.locs) {448 q.grow()449 }450 q.head = q.wrap(q.head - 1)451 q.locs[q.head] = loc452 q.elems++453}454455// pushBack adds an element to the back of the queue.456func (q *queue) pushBack(loc *location) {457 if q.elems == len(q.locs) {458 q.grow()459 }460 q.locs[q.tail] = loc461 q.tail = q.wrap(q.tail + 1)462 q.elems++463}464465// popFront removes the front of the queue.466func (q *queue) popFront() *location {467 if q.elems == 0 {468 return nil469 }470 loc := q.locs[q.head]471 q.head = q.wrap(q.head + 1)472 q.elems--473 return loc474}475476// grow doubles the capacity.477func (q *queue) grow() {478 newLocs := make([]*location, len(q.locs)*2)479 for i := range q.elems {480 // Copy over our elements in order.481 newLocs[i] = q.locs[q.wrap(q.head+i)]482 }483 q.locs = newLocs484 q.head = 0485 q.tail = q.elems486}487488func (q *queue) len() int { return q.elems }489func (q *queue) wrap(i int) int { return i & (len(q.locs) - 1) }
Findings
✓ No findings reported for this file.