}
var e Escape
+ e.heapLoc.escapes = true
// Construct data-flow graph from syntax trees.
for _, fn := range fns {
// its address to k, and returns a hole that flows values to it. It's
// intended for use with most expressions that allocate storage.
func (e *Escape) spill(k EscHole, n *Node) EscHole {
- // TODO(mdempsky): Optimize. E.g., if k is the heap or blank,
- // then we already know whether n leaks, and we can return a
- // more optimized hole.
loc := e.newLoc(n, true)
e.flow(k.addr(n, "spill"), loc)
return loc.asHole()
}
n.SetOpt(loc)
- // TODO(mdempsky): Perhaps set n.Esc and then just return &HeapLoc?
if mustHeapAlloc(n) && !loc.isName(PPARAM) && !loc.isName(PPARAMOUT) {
- e.flow(e.heapHole().addr(nil, ""), loc)
+ loc.escapes = true
}
}
return loc
if dst == &e.blankLoc {
return
}
- if dst == src && k.derefs >= 0 {
+ if dst == src && k.derefs >= 0 { // dst = dst, dst = *dst, ...
+ return
+ }
+ if dst.escapes && k.derefs < 0 { // dst = &src
+ src.escapes = true
return
}
- // TODO(mdempsky): More optimizations?
// TODO(mdempsky): Deduplicate edges?
dst.edges = append(dst.edges, EscEdge{src: src, derefs: k.derefs})
func (e *Escape) 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.
var todo []*EscLocation // LIFO queue
enqueue := func(loc *EscLocation) {
}
}
- enqueue(&e.heapLoc)
for _, loc := range e.allLocs {
enqueue(loc)
}
+ enqueue(&e.heapLoc)
var walkgen uint32
for len(todo) > 0 {
}
if e.outlives(root, l) {
- // If l's address flows somewhere that
- // outlives it, then l needs to be heap
- // allocated.
- if addressOf && !l.escapes {
- l.escapes = true
-
- // If l is heap allocated, then any
- // values stored into it flow to the
- // heap too.
- // TODO(mdempsky): Better way to handle this?
- if root != &e.heapLoc {
- e.flow(e.heapHole(), l)
- enqueue(&e.heapLoc)
- }
- }
-
// l's value flows to root. If l is a function
// parameter and root is the heap or a
// corresponding result parameter, then record
if l.isName(PPARAM) {
l.leakTo(root, base)
}
+
+ // If l's address flows somewhere that
+ // outlives it, then l needs to be heap
+ // allocated.
+ if addressOf && !l.escapes {
+ l.escapes = true
+ enqueue(l)
+ continue
+ }
}
for _, edge := range l.edges {
+ if edge.src.escapes {
+ continue
+ }
derefs := base + edge.derefs
if edge.src.walkgen != walkgen || edge.src.derefs > derefs {
edge.src.walkgen = walkgen
// other's lifetime if stack allocated.
func (e *Escape) outlives(l, other *EscLocation) bool {
// The heap outlives everything.
- if l == &e.heapLoc {
+ if l.escapes {
return true
}