Golang 编译器代码浅析
  • 0. Golang 编译器代码浅析
  • 1. golang 编译器 - 前言
    • 1.1 编译器简介
    • 1.2 Golang 编译器
    • 1.3 Go 语言版本
    • 1.4 项目设置
    • 1.5 约定
    • 1.6 写作目的
  • 2. golang 编译器 - 词法分析
    • 2.1 简介
    • 2.2 代码结构
    • 2.3 处理字符
    • 2.4 扫描Token
    • 2.5 总结
  • 3.a 语法分析理论知识
    • 3A.1 语法分析简介
    • 3A.2 文法
    • 3A.3 语法解析
    • 3A.3.1 自顶向下(Top-Down)
    • 3A.3.2 自顶向下 - 递归下降
    • 3A.3.3 自顶向下 - LL(1)文法
    • 3A.3.4 自底向上(Bottom-Up)
    • 3A.3.5 自底向上 - LR(0)项集及SLR预测表
    • 3A.3.6 自底向上 - LR(1)、LALR
    • 3A.4 语法分析工具
    • 3A.5 总结
  • 3B. golang 编译器 - 语法分析
    • 3B.1 简介
    • 3B.2 代码结构
    • 3B.3 数据结构
    • 3B.4 构造语法树
    • 3B.5 Unit Test及AST可视化
  • 4. Golang 编译器 - 类型检查
    • 4.1 简介
    • 4.2 代码结构
    • 4.3 符号解析
    • 4.4.1 数据结构 - 作用域
    • 4.4.2 数据结构 - Package
    • 4.4.3 数据结构 - Object 对象
    • 4.4.4-1 类型数据结构 - 简介
    • 4.4.4-2 类型接口
    • 4.4.4-3 基础类型
    • 4.4.4-4 内置复合类型
    • 4.4.4-5 Struct 类型
    • 4.4.4-6 Interface 类型
    • 4.4.4-7 Named 类型
    • 4.4.4-8 Tuple 类型
    • 4.4.4-9 Sum 类型
    • 4.4.4-10 Function & Method 类型
    • 4.4.4-11 泛型类型
    • 4.4.4-12 类型的等价规则
    • 4.4.4-13 类型的比较规则
    • 4.4.4-14 总结
    • 4.4.5 类型检查器
    • 4.4.6 总结
    • 4.5.1 类型检查逻辑 - 包加载器
    • 4.5.2 类型检查逻辑 - 初始化
    • 4.5.2-1 全局作用域
    • 4.5.2-2 类型检查器
    • 4.5.3 类型检查逻辑 - 流程分析
    • 4.5.3-1.1 总体流程
    • 4.5.3-1.2 类型检查准备工作
    • 4.5.3-1.3 类型检查核心逻辑
    • 4.5.3-1.3a 总体介绍
    • 4.5.3-1.3b 类型表达式的类型检查
    • 4.5.3-1.3c 求值表达式的类型检查
    • 4.5.3-1.3d 类型兼容性检查
    • 4.5.3-1.3e 处理delayed队列
    • 4.5.3-1.4 构建初始化顺序
    • 4.5.3-1.5 总结
    • 4.5.3-2 特定问题分析
    • 4.5.3-2a 对象循环依赖检查
    • 4.5.3-2b 方法与属性查找
    • 4.5.3-2c Underlying Type
    • 4.6 如何测试
    • 4.7 总结
  • 5. Golang 编译器 - IR Tree
    • 5.1 简介
    • 5.2 代码结构
    • 5.3 数据结构
    • 5.4 处理逻辑
    • 5.5 编译日志
    • 5.6 Unit Test
    • 5.7 总结
  • 6. golang 编译器 - 初始化任务
    • 6.1 简介
    • 6.2 代码结构
    • 6.3 总体逻辑
    • 6.4 赋值语句
    • 6.5 编译日志
    • 6.6 Unit Test
    • 6.7 总结
  • 7. golang 编译器 - 清除无效代码
    • 7.1 简介
    • 7.2 处理逻辑
    • 7.3 Unit Test
  • 8. golang 编译器 - Inline
    • 8.1 简介
    • 8.2 Inline的问题
    • 8.3 代码结构
    • 8.4 处理逻辑
    • 8.4.1 遍历调用链
    • 8.4.2 内联判断
    • 8.4.3 内联操作
    • 8.4.4 编译日志
    • 8.4.5 Unit Test
    • 8.4.6 总结
  • 9. golang 编译器 - 逃逸分析
    • 9.1 什么是逃逸分析
    • 9.2 Go 的逃逸分析
    • 9.3 算法思路
    • 9.4 代码结构
    • 9.5 处理逻辑
    • 9.5.1总体逻辑
    • 9.5.2 数据结构
    • 9.5.3 构建数据流有向图
    • 9.5.4 逃逸分析
    • 9.6 编译日志
    • 9.7 Unit Test
    • 9.8 总结
  • 10. golang 编译器 - 函数编译及导出
    • 10.1 简介
    • 10.2 编译函数
    • 10.2.1 SSA
    • 10.2.2 ABI
    • 10.2.3 并发控制
    • 10.3 导出对象文件
    • 10.4 总结
  • 11. Golang 编译器 - 写在最后
由 GitBook 提供支持
在本页

这有帮助吗?

  1. 5. Golang 编译器 - IR Tree

5.5 编译日志

如果查看 irgen.generate() 的完整代码,可以发现如下代码片段:

if base.Flag.W > 1 {
    for _, n := range g.target.Decls {
        s := fmt.Sprintf("\nafter noder2 %v", n)
        ir.Dump(s, n)
    }
}

可见 IR Tree 并没有完全隐藏在编译器内部,而是可以通过编译参数 -W 来查看,并且编译器不仅会 dump 出 IR Tree 的结构,还会 dump 出泛型函数实例化时所做的改变。我们通过一个例子来查看一下。

将下列代码保存为 main.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)
}

通过命令 go tool compile -G=2 -W=2 main.go 进行编译即可看到如下结果:

after noder2 genericFun [0xc0001642c0]
.   DCLFUNC tc(1) Iota:-1 ABI:ABIInternal FUNC-func[T₁](T₁, T₁) # main.go:5
.   DCLFUNC-Dcl
.   .   NAME-main.i tc(1) Class:PPARAM Offset:0 OnStack main.T₁ # main.go:5
.   .   NAME-main.j tc(1) Class:PPARAM Offset:0 OnStack main.T₁ # main.go:5
.   DCLFUNC-body
.   .   CALLFUNC tc(1) Use:3 STRUCT-(int, error) # main.go:6 STRUCT-(int, error)
.   .   .   NAME-fmt.Printf tc(1) Class:PFUNC Offset:0 FUNC-func(string, …interface {}) (int, error) # print.go:212
.   .   CALLFUNC-Args
.   .   .   LITERAL-“i: %v, j: %v\n” tc(1) string # main.go:6
.   .   .   CONVIFACE tc(1) Implicit INTER-interface {} # main.go:6 INTER-interface {}
.   .   .   .   NAME-main.i tc(1) Class:PPARAM Offset:0 OnStack main.T₁ # main.go:5
.   .   .   CONVIFACE tc(1) Implicit INTER-interface {} # main.go:6 INTER-interface {}
.   .   .   .   NAME-main.j tc(1) Class:PPARAM Offset:0 OnStack main.T₁ # main.go:5

after noder2 main [0xc000164580]
.   DCLFUNC tc(1) Iota:-1 ABI:ABIInternal FUNC-func() # main.go:9
.   DCLFUNC-body
.   .   CALL tc(1) Use:3 # main.go:10
.   .   .   FUNCINST tc(1) FUNC-func[T₁](T₁, T₁) # main.go:10 FUNC-func[T₁](T₁, T₁)
.   .   .   .   NAME-main.genericFun tc(1) Class:PFUNC Offset:0 FUNC-func[T₁](T₁, T₁) # main.go:5
.   .   .   FUNCINST-Targs
.   .   .   .   TYPE .int Offset:0 type int
.   .   CALL-Args
.   .   .   LITERAL-5 tc(1) int # main.go:10
.   .   .   LITERAL-6 tc(1) int # main.go:10
.   .   CALL tc(1) Use:3 # main.go:11
.   .   .   FUNCINST tc(1) FUNC-func[T₁](T₁, T₁) # main.go:11 FUNC-func[T₁](T₁, T₁)
.   .   .   .   NAME-main.genericFun tc(1) Class:PFUNC Offset:0 FUNC-func[T₁](T₁, T₁) # main.go:5
.   .   .   FUNCINST-Targs
.   .   .   .   TYPE .float32 Offset:0 type float32
.   .   CALL-Args
.   .   .   LITERAL-10.5 tc(1) float32 # main.go:11
.   .   .   LITERAL-11.8 tc(1) float32 # main.go:11

stenciled genericFun[int] [0xc0001646e0]
.   DCLFUNC tc(1) Iota:-1 ABI:ABIInternal FUNC-func(int, int) # main.go:5
.   DCLFUNC-Dcl
.   .   NAME-main.i tc(1) Class:PPARAM Offset:0 OnStack int # main.go:5
.   .   NAME-main.j tc(1) Class:PPARAM Offset:0 OnStack int # main.go:5
.   DCLFUNC-body
.   .   CALLFUNC tc(1) Use:3 STRUCT-(int, error) # main.go:6 STRUCT-(int, error)
.   .   .   NAME-fmt.Printf tc(1) Class:PFUNC Offset:0 FUNC-func(string, …interface {}) (int, error) # print.go:212
.   .   CALLFUNC-Args
.   .   .   LITERAL-“i: %v, j: %v\n” tc(1) string # main.go:6
.   .   .   CONVIFACE tc(1) Implicit INTER-interface {} # main.go:6 INTER-interface {}
.   .   .   .   NAME-main.i tc(1) Class:PPARAM Offset:0 OnStack int # main.go:5
.   .   .   CONVIFACE tc(1) Implicit INTER-interface {} # main.go:6 INTER-interface {}
.   .   .   .   NAME-main.j tc(1) Class:PPARAM Offset:0 OnStack int # main.go:5

stenciled genericFun[float32] [0xc000164840]
.   DCLFUNC tc(1) Iota:-1 ABI:ABIInternal FUNC-func(float32, float32) # main.go:5
.   DCLFUNC-Dcl
.   .   NAME-main.i tc(1) Class:PPARAM Offset:0 OnStack float32 # main.go:5
.   .   NAME-main.j tc(1) Class:PPARAM Offset:0 OnStack float32 # main.go:5
.   DCLFUNC-body
.   .   CALLFUNC tc(1) Use:3 STRUCT-(int, error) # main.go:6 STRUCT-(int, error)
.   .   .   NAME-fmt.Printf tc(1) Class:PFUNC Offset:0 FUNC-func(string, …interface {}) (int, error) # print.go:212
.   .   CALLFUNC-Args
.   .   .   LITERAL-“i: %v, j: %v\n” tc(1) string # main.go:6
.   .   .   CONVIFACE tc(1) Implicit INTER-interface {} # main.go:6 INTER-interface {}
.   .   .   .   NAME-main.i tc(1) Class:PPARAM Offset:0 OnStack float32 # main.go:5
.   .   .   CONVIFACE tc(1) Implicit INTER-interface {} # main.go:6 INTER-interface {}
.   .   .   .   NAME-main.j tc(1) Class:PPARAM Offset:0 OnStack float32 # main.go:5

modified main [0xc000164580]
.   DCLFUNC tc(1) Iota:-1 ABI:ABIInternal FUNC-func() # main.go:9
.   DCLFUNC-body
.   .   CALLFUNC tc(1) Use:3 # main.go:10
.   .   .   NAME-main.genericFun[int] tc(1) Class:PFUNC Offset:0 FUNC-func(int, int) # main.go:5
.   .   CALLFUNC-Args
.   .   .   LITERAL-5 tc(1) int # main.go:10
.   .   .   LITERAL-6 tc(1) int # main.go:10
.   .   CALLFUNC tc(1) Use:3 # main.go:11
.   .   .   NAME-main.genericFun[float32] tc(1) Class:PFUNC Offset:0 FUNC-func(float32, float32) # main.go:5
.   .   CALLFUNC-Args
.   .   .   LITERAL-10.5 tc(1) float32 # main.go:11
.   .   .   LITERAL-11.8 tc(1) float32 # main.go:11

前两个以 after noder2 开头的结构分别对应两个函数申明,这是 Step 3 完成之后 IR Tree 的总体结构,可以看到其中还包括泛型信息;而随后的信息是泛型函数实例化的 dump 信息,以 stenciled 开头的两个函数 genericFun[int] 与 genericFun[float32] 便是生成的实例化函数,最后 modified main 是修改之后的 main 函数,可以发现此时 main 函数内部直接调用的是实例化函数,已经抹掉了有关泛型的所有信息。

编译参数加上 -G=2 是为了启用泛型支持以便调用新的类型检查逻辑,并且在创建 IR Tree 完成之后退出编译,否则会 dump 出非常多的信息。可以在 irgen.go 的函数 check2() 中查看该 flag 的使用方式。

上一页5.4 处理逻辑下一页5.6 Unit Test

最后更新于3年前

这有帮助吗?