# 5.4 处理逻辑

## 5.4.1 相关代码 <a href="#org45ad279" id="org45ad279"></a>

与构建 IR Tree 密切相关的代码存在于两个地方，一个在文件 `cmd/compiler/internal/noder/irgen.go` 中，是构建逻辑的入口；另一个在 `cmd/compiler/internal/typecheck/target.go` 中，该文件只定义了一个全局变量 `Target`, 而该全局变量保存着 IR Tree 构建的全部结果。

全局变量 `Target` 的类型定义在 `cmd/compiler/internal/ir/package.go` 中，内容如下：

```go
type Package struct {
    Imports []*types.Pkg // <<Imports>>

    // Init functions, listed in source order.
    Inits []*Func

    // Top-level declarations.
    Decls []Node

    // Extern (package global) declarations.
    Externs []Node

    // Assembly function declarations.
    Asms []*Name

    // Cgo directives.
    CgoPragmas [][]string

    // Variables with //go:embed lines.
    Embeds []*Name

    // Exported (or re-exported) symbols.
    Exports []*Name

    // Map from function names of stencils to already-created stencils.
    Stencils map[*types.Sym]*Func
}
```

回忆一下：package 是 Go 编译器一次编译的最大单元！该结构用来封装被编译的包的所有信息。构建 IR Tree 的过程便是填充其中内容的过程，自此往后，编译器的操作都是基于这里面的内容来完成了。

IR Tree 构建逻辑由 `irgen` 驱动，其定义如下：

```go
// file: cmd/compiler/internal/noder/irgen.go
type irgen struct {
    target *ir.Package // 当前对应全局变量 typecheck.Target
    self   *types2.Package
    info   *types2.Info // types2 类型检查的结果

    posMap
    objs   map[types2.Object]*ir.Name
    typs   map[types2.Type]*types.Type
    marker dwarfgen.ScopeMarker

    // Fully-instantiated generic types whose methods should be instantiated
    instTypeList []*types.Type
}
```

构建入口及绝大多数逻辑都由 irgen上的方法来完成。

## 5.4.2 构建入口及步骤 <a href="#orgf0ac657" id="orgf0ac657"></a>

构建逻辑的入口方法是 `irgen.generate()` ，其中包含着总体的处理步骤，我们在此处仅保留处理框架一窥大概：

```go
func (g *irgen) generate(noders []*noder) {
    declLists := make([][]syntax.Decl, len(noders))
Outer:
    // Step 1: 处理 import 语句，将对应的包加载并解析成 types.Pkg 对象，本质上是根据 import 语句实例化 typecheck.Target.Imports 字段
    for i, p := range noders {
        g.pragmaFlags(p.file.Pragma, ir.GoBuildPragma)
        for j, decl := range p.file.DeclList {
            switch decl := decl.(type) {
            case *syntax.ImportDecl:
                // 处理 Import 语句
                g.importDecl(p, decl)
            default:
                // 非 Import 语句保存起来留着后续步骤处理
                declLists[i] = p.file.DeclList[j:]
                continue Outer // no more ImportDecls
            }
        }
    }

    // Step 2: 处理全局的类型申明，对于每个全局类型申明，创建一个对等的 ir.Decl 对象并将其存入 typecheck.Target.Decls 中
    // 1. 创建 Name 对象
    // 2. 将 types2.Type 转换为对应的 types.Type
    // 3. 处理该类型的方法，创建对应的 Field 对象
    for _, declList := range declLists {
        for _, decl := range declList {
            switch decl := decl.(type) {
            case *syntax.TypeDecl:
                g.typeDecl((*ir.Nodes)(&g.target.Decls), decl)
            }
        }
    }

    // Step 3: 处理其他类型的申明，即全局常量申明、全局变量申明、函数申明。为每个申明创建出对应的 ir.Node 并保存在 typecheck.Target.Decls 中
    for _, declList := range declLists {
        // shizhz -
        g.target.Decls = append(g.target.Decls, g.decls(declList)...)
    }

    // Step 4: 注册所有的 builtin 函数到 types.LocalPkg 中
    typecheck.DeclareUniverse()

    // Step 5: 根据泛型函数使用情况创建函数，该过程叫着泛型函数的实例化，后续会专门介绍
    g.stencil()

    // Step 6: 去掉所有的泛型函数申明，详见后续关于第5步的介绍
    j := 0
    for i, decl := range g.target.Decls {
        if decl.Op() != ir.ODCLFUNC || !decl.Type().HasTParam() {
            g.target.Decls[j] = g.target.Decls[i]
            j++
        }
    }
    g.target.Decls = g.target.Decls[:j]
}
```

注意，方法 `irgen.generate()` 的参数 `[]*noder` 便是语法分析的结果：AST. 通过上述代码我们基本可以总结出 IR Tree 构建的总体逻辑：遍历 AST 并构建出 IR Tree, 并将所有的处理结果保存在全局变量 `typecheck.Target` 中。比较特别的是 Step 5 与 Step 6, 这两步将泛型函数的调用完全转换成了普通函数的调用，之后所有源代码中关于泛型的语义就消失了。

下面我们详细探索一下上述各步骤的处理逻辑。

## 5.4.3 Import 语句 <a href="#org356f657" id="org356f657"></a>

在类型检查章节，我们已经介绍过新类型检查器中有关包（package）的数据结构以及包加载器（Package Loader）的工作原理，但这部分内容当前还没有应用到编译器后续步骤中来，所以在 IR Tree 的构建阶段依然采用的是之前的加载逻辑，其总体逻辑是将一个 import 语句解析成一个 `types.Pkg` 对象并保存到 `typecheck.Target` 的 [Imports](/golang-bian-yi-qi-ir-tree/untitled-2.md#org45ad279) 属性中。

处理 import 语句对应前面的 Step 1, 代码细节这里不再详细展开。

## 5.4.4 翻译 AST <a href="#org44645ef" id="org44645ef"></a>

将 AST 翻译成 IR Tree 的对应上述 Step 2 与 Step 3. 这里我们先看 Step 3 入口函数的代码：

```go
func (g *irgen) decls(decls []syntax.Decl) []ir.Node {
    var res ir.Nodes
    for _, decl := range decls {
        switch decl := decl.(type) {
        case *syntax.ConstDecl:
            g.constDecl(&res, decl)
        case *syntax.FuncDecl:
            g.funcDecl(&res, decl)
        case *syntax.TypeDecl:
            if ir.CurFunc == nil {
                continue // already handled in irgen.generate
            }
            g.typeDecl(&res, decl)
        case *syntax.VarDecl:
            g.varDecl(&res, decl)
        default:
            g.unhandled("declaration", decl)
        }
    }
    return res
}
```

函数通过 `switch...case...` 对 AST 进行深度优先遍历，并且在遍历过程中根据 AST 的节点构建出对应的 IR Tree 节点。当然 AST 的节点与 IR Tree 的节点并不是一一对应的，例如对于赋值语句 `names := make(chan string)`, AST 与 IR Tree 的结构如下：

![AST vs IR Tree](/files/-MamBT71CuF-Y77GV63X)

> &#x20;上图中椭圆形代表树的节点，线段代表属性名称，矩形代表属性值。图中只显示了重要的属性值。

可见 IR Tree 与 AST 刻画了相同的程序结构，但是 IR Tree 的节点信息更加具体，例如对于 `make(chan string)`, IR Tree 中对应节点的属性就比 AST 中的节点更有表现力。

构建 IR Tree 的逻辑要点可以归结如下：

1. 翻译节点，例如上图中将 AST 中的 `CallExpr` 节点翻译成更加具体的 `MakeExpr` 节点。核心代码如下：
   * `cmd/compile/internal/noder/stmt.go` 中的 `irgen.stmt()` 方法用来翻译语句（Statement）
   * `cmd/compile/internal/noder/expr.go` 中的 `irgen.expr()` 方法用来翻译表达式（Expression）
2. 翻译类型，将新类型检查器中所使用的 `types2` 类型翻译成 `types` 下对应的类型，例如上图中的橙色方框。核心代码如下：
   * `cmd/compile/internal/noder/types.go` 中的 `irgen.typ()` 方法用来将 `types2` 中的类型转换成 `types` 中的类型。

上述的 Step 2 与 Step 3 会构建出完整的 IR Tree, Step 2 先对全局的类型申明（即通过 `type` 关键字申明的类型）进行转换，Step 3 对剩下的全局申明进行转换，例如常量、变量、函数申明等。所有的结果都会保存到[typecheck.Target](/golang-bian-yi-qi-ir-tree/untitled-2.md#org45ad279)的 Decls 属性中，这两步完成之后，该属性保存着完整的 IR Tree.

## 5.4.5 告别泛型 <a href="#org3e1ddf1" id="org3e1ddf1"></a>

在类型检查章节中，我们讨论过泛型的实例化，泛型相当于定义了一个模版，而使用泛型的地方需要根据该模版生成实际对象。对于泛型函数而言，实例函数的生成发生在构建 IR Tree 的最后阶段。我们先了解一下泛型函数的实例化，对于代码：

```go
package main

import "fmt"

func genericFun[T any](i, j T)  {
    fmt.Printf("i: %v, j: %v\n",i, j)
}

func main() {
    genericFun(5,6)
    genericFun[float32](10.5, 11.8)
}
```

这里有两个地方使用到了泛型函数 `genericFun`, 如果不适用泛型的话，上述代码等价于：

```go
package main

import "fmt"

func genericFunInt(i, j int) {
    fmt.Printf("i: %v, j: %v\n", i, j)
}

func genericFunFloat32(i, j float32) {
    fmt.Printf("i: %v, j: %v\n", i, j)
}

func main() {
    genericFunInt(5, 6)
    genericFunFloat32(10.5, 11.8)
}
```

前者代码量更少，对编程人员友好，后者虽然代码量更大，但编译器处理起来更简单，实际上 Go 在不支持泛型时编译的就是这样的代码，而只要明确了使用泛型函数时的类型参数，我们就可以轻松地将前者转换为后者。根据类型参数创建普通函数，便是泛型函数的实例化。

我们知道当编译器完成类型检查后，所有函数调用的类型信息都已经明确了，对泛型的使用也是如此：要么代码中显示指定了类型参数，例如上例中的 `genericFun[float32](10.5, 11.8)` ；要么类型检查器通过类型推导得到类型信息，例如上例中的 `genericFun(5,6)`. 因此在编译阶段我们就可以完成泛型函数的实例化，事实上编译器实例化效果与上述示例代码完全一样，只有生成的函数名不同，对于上述代码，编译器使用的实例化函数名字分别是 `genericFun[int]` 与 `genericFun[float32]`, 这种函数名称在代码中是非法的，仅在编译器内部使用。

泛型函数的实例化会改变 IR Tree 的结构及内容，一是创建出了新的全局函数申明，需要将其添加到 IR Tree 中，二是函数调用的节点需要修改，三是再完成实例化后，泛型函数的申明就不再需要了，即我们可以从 IR Tree 中将其删除。前两点在 Step 5 中通过函数 `irgen.stencil()` 完成，所有的相关逻辑都定义在文件 `cmd/compile/internal/stencil.go` 中。而最后一步 Step 6 则删除了 IR Tree 中所有的泛型函数申明。

自此，我们的代码就告别泛型，又回归最传统的形式了。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gocompiler.shizhz.me/golang-bian-yi-qi-ir-tree/untitled-2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
