> For the complete documentation index, see [llms.txt](https://gocompiler.shizhz.me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gocompiler.shizhz.me/9.-golang-bian-yi-qi-tao-yi-fen-xi/9.5.3-gou-jian-shu-ju-liu-you-xiang-tu.md).

# 9.5.3 构建数据流有向图

在[总体逻辑](/9.-golang-bian-yi-qi-tao-yi-fen-xi/9.5.1-zong-ti-luo-ji.md)中我们提到构建数据流有向图总共分为三步，但要做的事情实际上只有两类：创建顶点、创建边。

### 创建顶点

对于函数中的每个局部变量，都需要为其创建一个顶点，该逻辑由函数 `initFunc()` 完成：

```go
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` 两个顶点的有向边。

&#x20;读者可以通过[Unit Test](/9.-golang-bian-yi-qi-tao-yi-fen-xi/9.7-unit-test.md)部分介绍的方式对总体构建逻辑进行调试。
