9.5.1总体逻辑

逃逸分析以函数为单元对代码进行分析,同内联操作一样,编译器自底向上地遍历源代码中函数调用链形成的有向图,然后依次对各个SCC进行分析。入口函数如下:

// file: cmd/compile/internal/escape/escape.go

func Funcs(all []ir.Node) {
    ir.VisitFuncsBottomUp(all, Batch)
}

函数 Batch 以一个SCC为输入,对该SCC内的所有函数进行逃逸分析,其总体逻辑如下:

  1. 创建静态数据流有向图

  2. 遍历有向图的每个顶点,并分析指向该顶点的所有路径(Path)上的各个顶点是否需要逃逸

  3. 对每个变量设置逃逸标记

函数的主要流程如下,此处仅保留重要步骤的代码:

// file: cmd/compile/internal/escape/escape.go

func Batch(fns []*ir.Func, recursive bool) {
    var b batch
    b.heapLoc.escapes = true

    // 1. 构建数据流有向图,分为三步完成

    // 1.1 遍历函数内的变量,为每个变量创建一个有向图的顶点。顶点用 location 表示,下文中会介绍该数据结构
    for _, fn := range fns {
        b.initFunc(fn)
    }

    // 1.2 遍历函数的 IR Tree, 根据赋值语句创建有向图的边并计算权重。边用 edge 表示,下文中会介绍该数据结构
    for _, fn := range fns {
        // 闭包在下一步处理
        if !fn.IsHiddenClosure() {
            b.walkFunc(fn)
        }
    }

    // 1.3 处理闭包,继续创建有向图的边
    for _, closure := range b.closures {
        b.flowClosure(closure.k, closure.clo)
    }
    b.closures = nil

    // 2. 先检查各个变量的大小,大对象直接逃逸到堆上
    for _, loc := range b.allLocs {
        if why := HeapAllocReason(loc.n); why != "" {
            b.flow(b.heapHole().addr(loc.n, why), loc)
        }
    }

    // 3. 对有向图的各个顶点进行逃逸分析
    b.walkAll()

    // 4. 逃逸分析完成,在 IR Tree 中标记各个变量顶点的结果
    b.finish(fns)
}

最后更新于