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 循环用来设置返回值变量的索引值,此处不用太关注。

注意此处创建的顶点仅是函数内的命名局部变量,对于通过函数 newmake 创建的顶点,以及各种字面量初始化创建的顶点,需要在构造有向图边的时候才能创建。

创建有向边

构造有向边的逻辑更加复杂,因为只有对函数的所有语句进行分析之后,才能够构建出完整的赋值关系,从而构建出完整的数据流有向图。

分析赋值关系的逻辑入口是 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) 建立从 srch.dst 两个顶点的有向边。

读者可以通过Unit Test部分介绍的方式对总体构建逻辑进行调试。

最后更新于