【Go笔记】编译器

三段式结构

前端

  • 处理,理解,解析源程序
  • 进行精准的语义表达

IR(中间阶段)

  • 多个IR,多种数据结构表示代码
  • 识别冗余代码,识别内存逃逸等代码优化工作
  • 需要前端记录的各种细节

后端

  • 生成特定目标机器的最终产物
  • 如可执行文件,obj文件,汇编语言等

语法解析

自上而下的递归下降算法 (Top-Down Recursive-Descent)

抽象语法树 (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/
Author
Stephen Zeng
Posted on
August 8, 2025
Licensed under