# 8.6 核心编译过程

现在的 `PyAST_CompileObject()` 具有编译器状态、`symtable` 和 AST 形式的模块，实际的编译过程可以开始了。

核心编译器有两个目的：

1. 将状态、`symtable` 和 AST 转换成控制流图(`CFG`)；
2. 通过捕获逻辑或代码错误来保护执行阶段免受运行时异常的影响。

### 从 Python 访问编译器

你可以通过调用内置函数 `compile()` 来调用 Python 中的编译器。它会返回一个 `code object`：

```sh
>>> co = compile("b+1", "test.py", mode="eval")
>>> co
<code object <module> at 0x10f222780, file "test.py", line 1>
```

与 `symtable()` API一样，简单表达式应具有 “eval” 的模式，模块、函数或类应具有 “exec” 的模式。

编译后的代码可以在 code object 的 `co_code` 属性中找到：

```sh
>>> co.co_code
b'e\x00d\x00\x17\x00S\x00'
```

标准库还包括一个 `dis` 模块，它可以反汇编字节码指令。你可以在屏幕上打印它们或获取指令(`instruction`)实例列表。

{% hint style="info" %}
**注**

`dis` 模块中的 `instruction` 类型映射了 C API 中的 `instr` 类型。
{% endhint %}

如果你导入 `dis` 模块并将 code object 的 `co_code` 属性传入 `dis()` 函数，则函数将其反汇编并在交互式解释器上打印指令。

```bash
>>> import dis
>>> dis.dis(co.co_code)
0 LOAD_NAME 0 (0)
2 LOAD_CONST 0 (0)
4 BINARY_ADD
6 RETURN_VALUE
```

`LOAD_NAME`、`LOAD_CONT`、`BINARY_ADD` 和 `RETURN_VALUE` 都是字节码指令。它们被称为字节码，因为在二进制形式中，它们都是一个字节长。然而，自从 Python 3.6 以来，存储格式已经更改为 `word`，所以现在从技术上讲，它们是“字码”，而不是字节码。

字节码指令的完整列表可用于 Python 的每个版本，并且它在版本之间会更改。例如，在 Python 3.7 中引入了一些新的字节码指令，以加快特定方法调用的执行速度。

在之前的章节中，你探索了 `instaviz` 包。这包括了通过运行编译器来可视化 code object 类型。`instaviz` 还显示了 code object 内部的字节码操作。

再次执行 `instaviz` 以查看在交互式解释器上定义的函数的 code object 和字节码。

```bash
>>> import instaviz
>>> def example():
a = 1
b = a + 1
return b
>>> instaviz.show(example)
```

### 编译器 C API

AST 模块编译的入口点是 `compiler_mod()` 函数，此函数根据模块类型切换到不同的编译器函数。如果你假设 `mod` 是 `Module`，则模块将作为编译器单元编译到 `c_stack` 属性中。然后运行 `assemble()` 从编译器单元堆栈中创建 `PyCodeObject`。

新返回的 code object 由解释器发送出去执行，或者以 `.pyc` 文件的形式存储在磁盘上：

代码不翻译

`compiler_body()` 循环遍历模块中的每个语句并访问它：

代码不翻译

语句类型是通过调用 `asdl_seq_GET()` 确定的， 它会查看 AST 节点类型。

`VISIT` 通过宏为每个语句类型调用 `Python/compile.c` 中的函数：

```c
#define VISIT(C, TYPE, V) {\
    if (!compiler_visit_ ## TYPE((C), (V))) \
    return 0; \
}
```

对于 `stmt` （泛型类型语句），编译器将调用 `compiler_visit_stmt()` 并切换至能在 `Parser /Python.asdl` 中找到的所有潜在语句类型。

代码不翻译

例如，这是 Python 中的 for 语句：

```python
for i in iterable:
    # block
else: # optional if iterable is False
    # block
```

你可以在铁路图中可视化 `for` 语句：

图片不翻译

如果语句是 `for` 类型，那么 `compiler_visit_stmt()` 会调用 `compile_for()`。所有语句和表达式类型都有一个等效的 `compiler_*()` 函数。更直接的类型会创建内联字节码指令，而一些更复杂的语句类型会调用其他函数。

### 指令

许多语句可以有子语句。`for` 循环有一个执行体，但你也可以在赋值和迭代器中具有复杂的表达式。

编译器将 `block` 传递给编译器状态。这些块包含指令序列。指令数据结构有操作码、参数、目标块（如果这是跳转指令，你将在下面了解到）和语句的行号。

### 指令类型

指令类型 `instr` 具有如下字段：

| 字段        | 类型            | 用途                                     |
| --------- | ------------- | -------------------------------------- |
| i\_jabs   | unsigned      | 指定此跳转为绝对跳转的标志                          |
| i\_jrel   | unsigned      | 指定此跳转为绝对跳转指令的标志                        |
| i\_lineno | int           | 创建此指令的行号                               |
| i\_opcode | unsigned char | 此指令表示的操作码编号（请参见 `Include/Opcode.h`）    |
| i\_oparg  | int           | 操作码参数                                  |
| i\_target | basicblock\*  | `i_jrel` 为 true 时指向目标 `basicblock` 的指针 |

### 跳转指令

跳转指令用于从一个指令跳转到另一个指令，它们可以是绝对的，也可以是相对的。

`绝对跳转指令` 指定编译 code object 中的确切指令编号，而相对跳转指令指定相对于另一条指令的跳转目标。

### 基础帧块

基础帧块（类型为 `basicblock`）包含以下字段：

| 字段            | 类型            | 用途                                       |
| ------------- | ------------- | ---------------------------------------- |
| b\_ialloc     | int           | 指令数组长度(`b_instr`)                        |
| b\_instr      | instr \*      | 指向指令数组的指针                                |
| b\_iused      | int           | 使用的指令数(`b_instr`)                        |
| b\_list       | basicblock \* | 此编译单元中的 block 列表（倒序）                     |
| b\_next       | basicblock\*  | 指向正常控制流到达的下一个 block 的指针                  |
| b\_offset     | int           | 块的指令偏移量，由 `assemble_jump_offsets()` 计算得到 |
| b\_return     | unsigned      | 如果插入了 `RETURN_VALUE` 操作码，则为 true         |
| b\_seen       | unsigned      | 用于执行基础块的深度优先搜索（请参阅“汇编”章节）                |
| b\_startdepth | int           | 进入块时的堆栈深度，由`stackdepth()` 计算得到           |

### 操作和参数

不同类型的操作需要不同的参数。例如，`ADDOP_JREL` 和 `ADDOP_JABS` 分别指“**add o**peration with **j**ump to a **rel**ative position” 和 “**add o**peration with **j**ump to an **abs**olute position"。

还有其他宏：`ADDOP_I` 调用 `compiler_addop_i()`，它添加了一个带有整数参数的操作。`ADDOP_O` 调用 `compiler_addop_o()`，它添加了一个带有 `PyObject` 参数的操作。
