9.5.3 构建数据流有向图
对于函数中的每个局部变量,都需要为其创建一个顶点,该逻辑由函数
initFunc()
完成:func (b *batch) initFunc(fn *ir.Func) {
e := b.with(fn)
if fn.Esc() != escFuncUnknown {
base.Fatalf("unexpected node: %v", fn)
}
fn.SetEsc(escFuncPlanned)
// Allocate locations for local variables.
for _, n := range fn.Dcl {
if n.Op() == ir.ONAME {
e.newLoc(n, false)
}
}
// Initialize resultIndex for result parameters.
for i, f := range fn.Type().Results().FieldSlice() {
e.oldLoc(f.Nname.(*ir.Name)).resultIndex = 1 + i
}
}
创建顶点的逻辑由第一个 for 循环完成,其遍历 函数所有的局部变量,为其创建一个 location 对象,并将其添加到
b.allLocs
属性中,该逻辑由 e.newLoc()
完成,此处不再贴出代码。第二个 for 循环用来设置返回值变量的索引值,此处不用太关注。注意此处创建的顶点仅是函数内的命名局部变量,对于通过函数
new
与 make
创建的顶点,以及各种字面量初始化创建的顶点,需要在构造有向图边的时候才能创建。构造有向边的逻辑更加复杂,因为只有对函数的所有语句进行分析之后,才能够构建出完整的赋值关系,从而构建出完整的数据流有向图。
分析赋值关系的逻辑入口是
batch.walkFunc(ir.Func)
, 该方法对目标函数的所有语句递归遍历并进行分析,如果语句中存在赋值关系,则为其创建有向边。该逻辑涉及到的代码很多,但主体框架可以由如下几个函数体现:- escape.stmt(ir.Node) 对单个语句进行分析的方法,该函数通过一个巨大的
switch...case
对各种类型的语句进行处理。例如对于赋值语句,该函数最终会为赋值语句两边的顶点建立一条有向边。 - escape.expr(h hole, n ir.Node) 该函数模拟对表达式
n
进行求值,并将求值策略保存在参数h
中,可以将该方法理解为为节点n
所表示的表达式构建对应的hole
实例。 - batch.flow(h hole, src location) 建立从
src
到h.dst
两个顶点的有向边。