【Go笔记】编译器
三段式结构
¶前端
- 处理,理解,解析源程序
- 进行精准的语义表达
¶IR(中间阶段)
- 多个IR,多种数据结构表示代码
- 识别冗余代码,识别内存逃逸等代码优化工作
- 需要前端记录的各种细节
¶后端
- 生成特定目标机器的最终产物
- 如可执行文件,obj文件,汇编语言等
语法解析
¶自上而下的递归下降算法 (Top-Down Recursive-Descent)
- https://www.youtube.com/watch?v=iddRD8tJi44
- https://dspmuranchi.ac.in/pdf/Blog/RECURSIVE DESCENT PARSING.pdf
- 舍弃了一些不重要的标识符,比如括号
(
syntax/nodes.go
,syntax/parser.go
抽象语法树 (AST)
- 构造成由
ir/node.go
中定义的OXXXX
的抽象语法树
类型检查
- 遍历节点树,决定节点类型
- 推断语法中未指定的类型
- 对类型最特别的语法语义检查(是否可导出,数组索引是否为自然数,是否超过其长度)
- 计算编译时常量、标识符与声明的绑定
typecheck/typecheck.go
变量捕获
- 针对闭包场景而言的,为值引用或地址引用
- 变量在闭包外变化的时候,使用地址引用
函数内联
- 减少函数调用开销
- 栈空间减小
- 参数和返回值复制时间减少
- 有循环,选择,递归以及新进程的函数不会被内联,
//go:noinline
也不会被内联 - 编译指令中加入
-l
也不会内联 - 内联函数的参数和返回值都会被转换成声明语句
逃逸分析
- 用于标识变量内存应该分配在栈区还是堆区,判断数据是否可能流向函数之外
- 会尽可能将变量放在栈中参与垃圾回收,如果在函数外对指针有调用的话就放在堆区
- golang中不管是字面量还是对象都有可能分配到栈或堆中,但遵循两个原则
- 原则一:指向栈上对象的指针不能存储到堆中(指向对象的对象得死)
- 原则二:指向栈上的对象的指针不能超过该栈对象的生命周期(被指向的对象后死)
- 通过静态数据流分析抽象语法树来实现逃逸分析,构建带权重的有向图
- 当引用时权重-1,当解引用的时候权重+1
- 根据根节点的位置+两个原则来确定权重为负的节点的位置
escape/escape.go
闭包重写
- 分为闭包定义后被立即调用或不被立即调用两种情况
- 在立即调用的情况下,闭包只能调用一次,可以转换为普通函数的调用一次,传参形式参照逃逸分析的结果
- 在不立即调用的情况下,需要创建闭包对象
遍历函数
- 识别声明但是没有使用的变量
- 遍历函数中的声明和表达式
- 将代表操作的节点转换为运行时的具体函数执行
- 对表达式和语句进行一定的重新排序
- 按需引入临时变量以确保形式简单
walk/walk.go
SSA(静态单赋值)
This is a list of possible improvements to the SSA pass of the compiler.
¶Optimizations (better compiled code)
- Reduce register pressure in scheduler
- Make dead store pass inter-block
- If there are a lot of MOVQ $0, …, then load
0 into a register and use the register as the source instead. - Allow large structs to be SSAable (issue 24416)
- Allow arrays of length >1 to be SSAable
- If strings are being passed around without being interpreted (ptr
and len fields being accessed) pass them in xmm registers?
Same for interfaces? - any pointer generated by unsafe arithmetic must be non-nil?
(Of course that may not be true in general, but it is for all uses
in the runtime, and we can play games with unsafe.)
¶Optimizations (better compiler)
- Handle signed division overflow and sign extension earlier
¶Regalloc
- Make liveness analysis non-quadratic
;## 总结
- SSA的生成阶段,每个变量在声明之前都需要被定义
- 每个变量只会被赋值一次
- 保障后续优化,例如常量传播,无效代码清除,消除冗余,强度降低等
- 当遇到循环或判断分支时,临时使用phi函数来选择,生成最终代码的时候替换成合适的赋值或寄存器传递
arm64/ssa.go
,ssa/
汇编器
- 生成指令集相关的obj文件
- 基于plan9汇编器的输入形式
ssa/_gen
,internal/obj/
链接
- 分为动态链接和静态链接,不细说了
【Go笔记】编译器
https://study.0x535a.cn/go-note/go-compiler/