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 提供支持
在本页
  • batch
  • location
  • edge
  • hole
  • note

这有帮助吗?

  1. 9. golang 编译器 - 逃逸分析

9.5.2 数据结构

编译器定义了各种数据结构来抽象逃逸分析的各个概念,在详细分析处理逻辑之前,我们先看一下重要的几类:

batch

type batch struct {
    allLocs  []*location
    closures []closure

    heapLoc  location
    blankLoc location
}

batch 保存着 Batch() 函数一次逃逸分析的所有状态。其中 allLocs 代表整个数据流有向图,所有需要在堆上分配内存的变量都有一条边指向 heapLoc 顶点。

location

type location struct {
    n         ir.Node  // represented variable or expression, if any
    curfn     *ir.Func // enclosing function
    edges     []edge   // incoming edges
    loopDepth int      // loopDepth at declaration

    // resultIndex records the tuple index (starting at 1) for
    // PPARAMOUT variables within their function's result type.
    // For non-PPARAMOUT variables it's 0.
    resultIndex int

    // derefs and walkgen are used during walkOne to track the
    // minimal dereferences from the walk root.
    derefs  int // >= -1
    walkgen uint32

    // dst and dstEdgeindex track the next immediate assignment
    // destination location during walkone, along with the index
    // of the edge pointing back to this location.
    dst        *location
    dstEdgeIdx int

    // queued is used by walkAll to track whether this location is
    // in the walk queue.
    queued bool

    // escapes reports whether the represented variable's address
    // escapes; that is, whether the variable must be heap
    // allocated.
    escapes bool

    // transient reports whether the represented expression's
    // address does not outlive the statement; that is, whether
    // its storage can be immediately reused.
    transient bool

    // paramEsc records the represented parameter's leak set.
    paramEsc leaks

    captured   bool // has a closure captured this variable?
    reassigned bool // has this variable been reassigned?
    addrtaken  bool // has this variable's address been taken?
}

可以简单地将 location 理解成函数中的一个变量,但更准确地说:location 代表的是程序中需要使用的一个内存区块,例如赋值语句 c := make(chan string) 右侧并没有变量,但表达式 make(chan string) 依然需要一个 location 来表示,各种字面量的初始化表达式均是如此。

location 同时也是数据流有向图中的一个顶点(Vertex),这是逃逸分析的重要数据结构,包含的属性也比较多,其中需要特别注意的有:

  • edges: 有向图中指向本顶点的所有边

  • derefs: 该属性在逃逸分析时使用,不是前文讨论的权重

  • escapes: 标记该变量是否需要逃逸

edge

type edge struct {
    src    *location
    derefs int // >= -1
    notes  *note
}

edge 代表数据流有向图中的一条边(Edge)以及方向(Direction),属性 derefs 表示该边的权重,一条边代表程序中的一个赋值语句。其中 notes 用来记录编译器逃逸分析时的 dump 信息。

hole

type hole struct {
    dst    *location
    derefs int // >= -1
    notes  *note

    // addrtaken indicates whether this context is taking the address of
    // the expression, independent of whether the address will actually
    // be stored into a variable.
    addrtaken bool

    // uintptrEscapesHack indicates this context is evaluating an
    // argument for a //go:uintptrescapes function.
    uintptrEscapesHack bool
}

hole 封装了赋值语句中对右侧表达式的执行策略。例如对于语句 x = &p, 对应的 hole 实例的内容如下:

hole.dst: location instance of x

hole.derefs: -1

hole.addrtaken: true

除了常量运算,编译阶段并不会真正执行任何表达式,该结构体主要用来记录是对右侧表达式进行 * 与 & 操作的情况。

note

type note struct {
    next  *note
    where ir.Node
    why   string
}

用以记录逃逸分析 dump 信息的辅助数据结构,编译日志一节所打印的内容,便是通过该数据结构记录的信息。

上一页9.5.1总体逻辑下一页9.5.3 构建数据流有向图

最后更新于4年前

这有帮助吗?