方法 walkOne() 以参数 root 为根节点,逐个分析指向该顶点的路径(Path)上的所有顶点,例如路径:root <- A … <- P … <- Z 中,如果 root 需要逃逸,而 root 实际上持有的是 P 的指针的话,那么 P 也需要逃逸。而此时我们就需要通过参数 enqueue 将顶点 P 再次压入外层队列,因为 walkOne() 只分析 root 与路径上各个顶点的关系,如果我们发现路径上某节点 P 需要逃逸的话,那么就有必要将 P 作为根节点再次进行分析(可能外围队列之前已经遍历过顶点 P 了,而当时 P 可能不需要逃逸,所以这里是再次分析),所以我们需要将顶点 P 再次压入外层队列。
func (b *batch) walkOne(root *location, walkgen uint32, enqueue func(*location)) { root.walkgen = walkgen root.derefs =0 root.dst =nil todo := []*location{root} // LIFO queue// 遍历队列forlen(todo) >0 { l := todo[len(todo)-1] todo = todo[:len(todo)-1]// 删除对 l 进行逃逸分析的代码// 在 for 循环末尾将指向该顶点的所有顶点压入队列for i, edge :=range l.edges {if edge.src.escapes {continue } d := derefs + edge.derefsif 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) } } }}
对每一个顶点的逃逸分析都发生在内层循环中,弄清楚了整个有向图的遍历方式之后,我们可以看看对于根节点 root 路径上的某节点 l 如何进行逃逸分析了。
逃逸分析逻辑
简单来说,对于根节点 root 及其路径上的节点 l, 如果下列两个关系均满足的话,那么 l 就需要逃逸:
root 的生命周期长于 l
判断该逻辑的方法是 batch.outlives(), 其代码如下:
// 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
}