# 8.7 汇编

一旦这些编译阶段完成，编译器就会拥有帧块列表，每个帧块都包含指令列表和指向下一个块的指针。汇编器（`assembler`）对基础帧块执行深度优先搜索（DFS），并将指令合并为单字节码序列。

### 汇编器数据结构

在 `Python/compile.c` 中声明汇编器状态结构（`assembler`），其具有以下字段：

| 字段             | 类型                  | 用途                  |
| -------------- | ------------------- | ------------------- |
| a\_bytecode    | PyObject \* ( str ) | 包含字节码的字符串           |
| a\_lineno      | int                 | 发出指令后的最后 `lineno`   |
| a\_lineno\_off | int                 | 最后的 `lineno` 字节码偏移量 |
| a\_lnotab      | PyObject \* ( str ) | 包含 `innotab` 的字符串   |
| a\_lnotab\_off | int                 | 偏移到 `innotab` 中     |
| a\_nblocks     | int                 | 可达块数                |
| a\_offset      | int                 | 偏移到字节码              |
| a\_postorder   | basicblock \*\*     | DFS 后序遍历中的块列表       |

### 汇编器深度优先搜索算法

汇编器使用深度优先搜索（DFS）遍历基础帧块图中的节点。DFS 算法并不是特定于 CPython 的，但它通常用于图遍历。

CST 和 AST 都是树结构，而编译器状态是一个图结构，其中的节点是包含指令的基础帧块。

基础帧块由两个图链接在一起。一种是基于每个块的 `b_list` 属性按相反顺序来创建。一系列按字母顺序从 A 到 O 命名的基础帧块，看起来如下图所示：

图不翻译

从 `b_list` 创建的图用于顺序访问编译器单元（compiler unit）中的每个块（block）。

第二个图使用每个块的 `b_next` 属性。此列表代表控制流。此图中的顶点是通过调用 `compiler_use_next_block(c, next)` 创建的，其中 `next` 是从当前块绘制顶点的下一个块(`c->u->u_curblock`)。

`for` 循环节点图可能类似于：

图不翻译

顺序图和控制流图都会被用到，但只有控制流图是执行 DFS 使用到的图。

### 汇编器 C API

汇编器 API 有一个入口点：`assemble()` ，它具有以下职责：

* 计算内存分配的块数；
* 确保每个从末端掉落的块都返回 `None`；
* 解析任何被标记为相对的跳转语句偏移；
* 调用 `dfs()` 对块执行深度优先搜索；
* 向编译器发送所有指令；
* 把编译器状态作为入参调用 `makecode()` 以生成 `PyCodeObject`。

代码不翻译

### 深度优先搜索

深度优先搜索由 `Python.compile.c` 中的 `dfs()` 执行，它跟随每个块中的 `b_next` 指针，通过转换 `b_seen` 把块标记为所看到的，然后以相反的顺序将它们添加到汇编器的 `a_postorder` 列表中。

该函数在汇编器的后序列表和每一个块上循环，如果它有跳转操作，则递归调用 `dfs()` 进行该跳转：

代码不翻译

一旦汇编器使用 DFS 将图汇编到 CFG 中，就可以创建 code object。
