8.6 核心编译过程
现在的 PyAST_CompileObject()
具有编译器状态、symtable
和 AST 形式的模块,实际的编译过程可以开始了。
核心编译器有两个目的:
将状态、
symtable
和 AST 转换成控制流图(CFG
);通过捕获逻辑或代码错误来保护执行阶段免受运行时异常的影响。
从 Python 访问编译器
你可以通过调用内置函数 compile()
来调用 Python 中的编译器。它会返回一个 code object
:
与 symtable()
API一样,简单表达式应具有 “eval” 的模式,模块、函数或类应具有 “exec” 的模式。
编译后的代码可以在 code object 的 co_code
属性中找到:
标准库还包括一个 dis
模块,它可以反汇编字节码指令。你可以在屏幕上打印它们或获取指令(instruction
)实例列表。
注
dis
模块中的 instruction
类型映射了 C API 中的 instr
类型。
如果你导入 dis
模块并将 code object 的 co_code
属性传入 dis()
函数,则函数将其反汇编并在交互式解释器上打印指令。
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 和字节码。
编译器 C API
AST 模块编译的入口点是 compiler_mod()
函数,此函数根据模块类型切换到不同的编译器函数。如果你假设 mod
是 Module
,则模块将作为编译器单元编译到 c_stack
属性中。然后运行 assemble()
从编译器单元堆栈中创建 PyCodeObject
。
新返回的 code object 由解释器发送出去执行,或者以 .pyc
文件的形式存储在磁盘上:
代码不翻译
compiler_body()
循环遍历模块中的每个语句并访问它:
代码不翻译
语句类型是通过调用 asdl_seq_GET()
确定的, 它会查看 AST 节点类型。
VISIT
通过宏为每个语句类型调用 Python/compile.c
中的函数:
对于 stmt
(泛型类型语句),编译器将调用 compiler_visit_stmt()
并切换至能在 Parser /Python.asdl
中找到的所有潜在语句类型。
代码不翻译
例如,这是 Python 中的 for 语句:
你可以在铁路图中可视化 for
语句:
图片不翻译
如果语句是 for
类型,那么 compiler_visit_stmt()
会调用 compile_for()
。所有语句和表达式类型都有一个等效的 compiler_*()
函数。更直接的类型会创建内联字节码指令,而一些更复杂的语句类型会调用其他函数。
指令
许多语句可以有子语句。for
循环有一个执行体,但你也可以在赋值和迭代器中具有复杂的表达式。
编译器将 block
传递给编译器状态。这些块包含指令序列。指令数据结构有操作码、参数、目标块(如果这是跳转指令,你将在下面了解到)和语句的行号。
指令类型
指令类型 instr
具有如下字段:
字段 | 类型 | 用途 |
---|---|---|
i_jabs | unsigned | 指定此跳转为绝对跳转的标志 |
i_jrel | unsigned | 指定此跳转为绝对跳转指令的标志 |
i_lineno | int | 创建此指令的行号 |
i_opcode | unsigned char | 此指令表示的操作码编号(请参见 |
i_oparg | int | 操作码参数 |
i_target | basicblock* |
|
跳转指令
跳转指令用于从一个指令跳转到另一个指令,它们可以是绝对的,也可以是相对的。
绝对跳转指令
指定编译 code object 中的确切指令编号,而相对跳转指令指定相对于另一条指令的跳转目标。
基础帧块
基础帧块(类型为 basicblock
)包含以下字段:
字段 | 类型 | 用途 |
---|---|---|
b_ialloc | int | 指令数组长度( |
b_instr | instr * | 指向指令数组的指针 |
b_iused | int | 使用的指令数( |
b_list | basicblock * | 此编译单元中的 block 列表(倒序) |
b_next | basicblock* | 指向正常控制流到达的下一个 block 的指针 |
b_offset | int | 块的指令偏移量,由 |
b_return | unsigned | 如果插入了 |
b_seen | unsigned | 用于执行基础块的深度优先搜索(请参阅“汇编”章节) |
b_startdepth | int | 进入块时的堆栈深度,由 |
操作和参数
不同类型的操作需要不同的参数。例如,ADDOP_JREL
和 ADDOP_JABS
分别指“add operation with jump to a relative position” 和 “add operation with jump to an absolute position"。
还有其他宏:ADDOP_I
调用 compiler_addop_i()
,它添加了一个带有整数参数的操作。ADDOP_O
调用 compiler_addop_o()
,它添加了一个带有 PyObject
参数的操作。
Last updated