http://www.python.org/dev/peps/pep-0339/
PEP: | 339 |
---|---|
标题: | CPython的编译器设计 |
版本: | 425fc5598ee8 |
最后修改: | 2011-01-18 0时37分50秒+0000(星期二,2011年1月18日) |
作者: | :布雷特大炮<brett python.org> |
状态: | 撤回 |
类型: | 信息 |
内容类型: | 文字/ X-RST |
创建: | 02日,2005 |
历史后: |
注意
这PEP已撤回移动Python开发的指南。
抽象
从历史上看(至2.4),从源代码编译成字节码,涉及两个步骤:
- 解析成语法树(分析器/ pgen.c的源代码)
- 发出解析树(Python / compile.c的的字节码)
从历史上看,这是不是一个标准的编译器是如何工作的。编译通常的步骤是:
- 解析源代码转换成语法树(分析器/ pgen.c的)
- 变换解析成抽象语法树(Python / ast.c的树)
- 成控制流程图(Python / compile.c的的变换AST)
- 控制流图(Python / compile.c的基础上发出的字节码)
与Python 2.5开始,正在使用上述步骤。这种变化打破它分为三个步骤做是为了简化编译。本文件的目的是概述如何后三个步骤的过程工程。
本文件并不触及如何超越什么是需要解释什么是编译所需的解析工作。该怎么整个系统的工作,它也并不详尽。您将最有可能需要阅读一些源有一个确切的了解所有细节。
解析树
Python的解析器是一个LL(1)语法分析器大多是基于实施奠定了龙书[Aho86] 。
为Python语法文件中可以找到的数值语法规则语法/语法在include / graminit.h的存储。类型的标记(文字的令牌,如:数字,等)的数字值被保存在包含/ token.h)。解析树 节点*结构(定义见在include / node.h)。
可以做下面的宏(这是所有定义见在include / token.h),查询数据节点结构:
- 儿童(节点,诠释)
-
返回第n个子节点使用零偏移索引
- RCHILD(节点,诠释)
-
返回第n个孩子从右侧的节点,使用负数!
- NCH(节点*)
-
孩子节点数目
- STR(节点*)
-
节点的字符串表示形式,例如,将返回:冒号令牌
- 类型(节点*)
-
在包括/ graminit.h的指定类型的节点
- REQ(节点*,TYPE)
-
断言该节点是不同的预期
- LINENO(节点*)
-
检索的行号在Python / ast.c的定义导致解析规则创建的源代码;
为了配合这个例子,可以考虑',而'的规则:
while_stmt:'而'测试':'套房['别人的':'套房]
表示此节点类型(Node)== while_stmt中的儿童人数是4或7取决于是否有一个'别人的语句。要访问什么应该是第一个':',并要求它是一个实际的':'令牌(REQ(CHILD(节点2),冒号)`。
抽象语法树(AST)
抽象语法树(AST)是一个高层次的代表的程序结构不包含源代码的必要性,它可以被认为是一个抽象的表示的源代码。规范的AST节点指定和风的抽象语法定义语言(ASDL),[Wang97] 。
(AST)节点为Python的定义的文件分析器/ Python.asdl中。
每个AST节点(相当于语句,表达式,和一些专门的类型,如列表内涵和异常处理)被定义为ASDL。AST中的大多数定义对应一个特定的源结构,如'如果'语句或属性查找。的定义是独立于任何特定的编程语言实现。
Python的ASDL结构下面的代码片段演示的方法和语法:
模块的Python { stmt的=的functionDef(标识符名称,参数实参,stmt的身体, 表达式*装饰) |返回(表达式的值)收益率(表达式的值) 属性(INT LINENO) }
前面的例子介绍了三种不同类型的报表函数定义,返回报表和产量报表。所示,由'|'分隔各种被认为是所有3种类型为stmt。他们都采取不同的种类和数量的参数。
参数类型修饰符指定所需的值;'?' 它是可选的,'*'表示0个或多个,没有修饰符意味着只有一个值的说法,这是需要的。的functionDef,例如,将标识符的名称,ARGS零个或多个stmt的'身体'的论点,和零个或多个expr的参数为'装饰'的'参数'。
注意到,类似的论点,这是一个节点类型,作为一个单一的AST节点,而不是作为stmt的,正如人们所预料的节点序列表示。
所有3种也有一个'属性'的说法,这是表现出一个事实,即“属性”缺乏'|'之前。
上面的语句定义生成下面的C结构类型:
typedef结构_stmt的stmt_ty; 结构_stmt { 枚举{FunctionDef_kind = 1,Return_kind,Yield_kind = 2 = 3}样; 工会{ 结构{ 标识符名称; arguments_ty作为参数; asdl_seq *体; }的functionDef; 结构{ 值expr_ty; }返回; 结构{ 值expr_ty; }产量; } V; LINENO; }
也产生了一系列的构造函数分配(在这种情况下)一个stmt_ty结构与相应的初始化。'那种'字段指定初始化组件工会。的的functionDef()构造函数设置'那种'FunctionDef_kind初始化'名','参数','体','属性'领域。
内存管理
在讨论实际执行的编译器,是为了讨论如何处理内存。为了使内存管理简单,一个舞台。这意味着,存储器汇集在一个单独的位置,便于分配和拆卸。这为我们提供了明确的内存释放去除。因为在编译器中所有需要的内存寄存器,内存分配内存的舞台上,一个单一的呼叫释放竞技场是需要由编译器完全免费使用的所有内存。
在一般情况下,除非你是工作的关键核心的编译器,内存管理可以完全忽略。但如果你是工作在一开始的编译器或结束,你需要关心舞台上是如何工作的。竞技场有关的所有代码是在要么包含/ pyarena.h的或Python / pyarena.c的。
PyArena_New()将创建一个新的舞台。返回的PyArena结构将存储给它的所有内存的指针。这确实簿记内存需要被释放它所占用的内存当编译器完成。这完成用PyArena_Free()释放。这需要只能被称为编译器退出,在战略领域。
诚如上文所述,一般你不应该担心内存管理,编译器工作时。已设计的技术细节,你在大多数情况下被隐藏。
唯一的例外来管理的PyObject时。由于其它的Python使用引用计数,有额外的支持添加到竞技场清理每个PyObject的分配。这些情况是非常罕见的。然而,如果你分配一个PyObject的,你必须告诉它的舞台通过调用PyArena_AddPyObject()。
AST解析树
AST是产生的使用功能PyAST_FromNode()解析树(见的Python / ast.c) 。
函数首先解析树的树步行,创造各种AST节点,因为它一起去。它通过分配它需要所有的新节点,调用适当的AST节点创建函数的任何所需的配套功能,并连接他们需要。
要认识到,有没有自动也不是象征性的语法规范和解析树中的节点之间的连接。没有直接的帮助是在Yacc解析树。
例如,一个必须跟踪哪个节点解析树一中工作(例如,如果您正在使用一个'if'语句,你需要注意的':'令牌找到结束的条件) 。
调用的函数生成的解析树(AST)节点都有的的名称ast_for_xx其中xx是什么样的语法规则的功能处理(alias_for_import_name这是例外)。这些依次调用构造函数所定义的的ASDL语法和包含在Python中/ Python的ast.c的的(这是产生分析器/ asdl_c.py)中创建AST的节点。这一切都导致AST节点存储在asdl_seq结构序列。
功能和宏用于创建和使用中的Python / asdl.c包含/ asdl.h的的发现asdl_seq *类型:
- (asdl_seq_new)
-
分配内存一个asdl_seq的指定长度
- (asdl_seq_GET)
-
规划项目中的特定位置asdl_seq举行
- (asdl_seq_SET)
-
设定为指定的值中的特定索引asdl_seq
- asdl_seq_LEN(asdl_seq *)
-
返回一个asdl_seq的长度
如果您正在使用报表,还必须担心跟踪行号生成的语句。目前的行号的最后一个参数传递每个stmt_ty功能。
控制流图
甲控制流图(常常由它的首字母缩写词,CFG参考)是一个有向图,流建立模型的基本块中包含的中间表示(简称“IR”,在这种情况下,一个程序使用Python字节码)的块内。基本块本身是一个块IR,有一个单一的入口点,但可能有多个出口点。单一的入口点是关键的基本块都有带跳。入口点是东西,改变控制流(如函数调用或跳),而出口点的指示,将改变程序流程(如跳跃和'回报'语句)的目标。这句话的意思是,一个基本块是一大块的代码开始的入口点和出口点或在块结束运行。
作为一个例子,考虑一个'如果'的“else”块表。卫兵'如果'是一个基本的块所指向基本块包含的代码导致的“if”语句。'如果'语句块中包含跳转(退出点)的真身的'如果'和'别人的身体(这可能是NULL),其中每一个都是自己的基本块。两个基本代表整个'if'语句的代码块又指向那些块。
CFGS通常是一步之遥,从最终的代码输出。代码直接生成基本块的(跳转目标调整的基础上输出顺序)做CFG边缘后序深度优先搜索。
AST CFG以字节码
创建的AST,下一个步骤是创建的CFG。第一步是Python字节码,无需跳转目标解决特定的偏移量(这是CFG进入到最后的字节码时计算)的AST转换。本质上,这个转换成Python字节码的AST的CFG的边缘表示的控制流。
转换是分两次完成。首先创建的命名空间(变量可以被归类为/封的细胞,或全球)。做,第二遍基本上并将CFG成一个列表,并最终输出的字节码计算跳转偏移。
转换过程是发起通过调用的的功能 PyAST_Compile() Python / compile.c的。此功能做一个CFG的AST转换和输出最终的字节码从CFG。AST CFG步骤处理大多是由两个函数由PyAST_Compile(); PySymtable_Build()和compiler_mod()。前者是在Python / symtable.c的而后者是Python / compile.c的。
PySymtable_Build()开始进入启动代码块的AST(传递),然后调用的的正确symtable_visit_xx功能(AST节点类型为xx)。接下来,AST树走的各种划定到达一个局部变量的代码块,块使用symtable_enter_block()和symtable_exit_block(进入和退出),分别。
一旦符号表被创建,它是时间为的CFG创造,其代码是在Python / compile.c的。这是由几个功能,将任务分解各种AST节点类型。这些功能都名为compiler_visit_xx的,其中xx为节点类型的名称(如stmt的表达式等)。每个函数接收一个编译* 和xx_ty其中xx是AST节点类型。通常情况下,这些功能包括一个大的'开关'语句,分支节点类型的基础上的那种传递给它。内嵌处理简单的事情更复杂的转换“开关”语句外包给其他功能名为compiler_xx XX正在处理的是一个描述性名称。
在转化任意AST节点时,使用的访问()宏。适当compiler_visit_xx函数被调用时,基于<node TYPE>的传 入的值(所以访问(C,表达式,节点)调用 compiler_visit_expr的(C,节点))。VISIT_SEQ宏是非常相似,但被称为AST节点序列(那些被创建为一个节点,用'*'修饰符的参数值)。还有VISIT_SLICE()只是处理切片。
排放处理字节码是通过以下几个宏:
- (ADDOP)
-
添加一个指定的操作码
- (ADDOP_I)
-
加接受一个参数的操作码
- ADDOP_O(编译C,整数运算的PyObject *类型的PyObject * OBJ)
-
的PyObject PyObject的序列对象指定的位置正确的参数基础上添加一个操作码,但没有处理错位的名称,用于当你需要做的命名对象,如全局consts中,或参数名称重整查找是不可能的,是已知的名称的范围
- (ADDOP_NAME)
-
但就像ADDOP_O,名称重整处理;用于属性或导入基于名称
- (ADDOP_JABS)
-
创建一个绝对跳转到一个基本块
- (ADDOP_JREL)
-
创建一个相对跳转到一个基本块
一些辅助功能,将发出的字节码,并命名为compiler_xx()xx是什么功能有助于(列表,boolop等),。一个比较有用的是compiler_nameop()。此函数查找一个变量的范围,并根据表达上下文,发出适当的操作码来加载,存储,或删除该变量。
至于处理一份声明中定义的行号,处理由compiler_visit_stmt(),因此并非无忧。
除了发光字节码的基础上的AST节点,处理的基本块的创建必须做到的。下面是用于管理基本块的宏和函数:
- (NEW_BLOCK)
-
创建块,并设置它为当前
- (NEXT_BLOCK)
-
基本上NEW_BLOCK()加上从当前块跳
- (compiler_new_block)
-
创建一个块,但不使用它(用于产生跳跃)
在CFG一旦被创建时,它必须被展平,然后字节码发生最终的排放。扁平化使用深度优先搜索后序处理。一旦扁平,跳转偏移backpatched的基础上的平坦化,然后一个PyCodeObject文件创建。所有这一切都可以通过调用汇编()。
引入新的字节码
有时候一个新的功能,需要一个新的操作码。但是,添加新的字节码是不一样简单只是突然引入新的AST中的字节码 - >字节码的编译步骤。几件代码依赖于整个Python的字节码存在什么正确的信息。
首先,你必须选择一个名称和一个唯一的标识号。字节码的正式名单,可以发现在include / opcode.h。如果操作码是一个参数,它必须被赋予更大的比分配(发现在include / opcode.h),HAVE_ARGUMENT的一个唯一的编号 。
一旦名字/号码对已选择并进入在include / opcode.h的,你还必须输入到LIB / opcode.py的和Doc /库/ dis.rst的。
有了一个新的字节码,你也必须改变,什么是所谓的幻数。pyc文件。MAGIC在Python / import.c的变量包含数字。改变这个数字,将导致由解释器进口与旧的魔法被重新编译。pyc文件。
最后,你需要引入新的字节码的使用。改变的Python / compile.c以及Python / ceval.c的的将是主要的改变的地方。但你也将需要改变'编译'包。做到这一点的关键文件是的LIB /编译/ pyassem.py和LIB /编译器/ pycodegen.py器。
如果你做出改变,在这里,可以影响输出的字节码已经存在,你不改变的幻数不断,确保删除旧的。py文件(C | O)!即使你最终会改变一个神奇的数字,如果你改变了字节码,而你正在调试你的工作,你会改变的字节码输出而不断提高的幻数。这意味着你最终与陈旧。pyc文件,将不会重新创建。运行 找到。名称 '*。PY [合作]' -exec的 RM -F {}';'应该全部删除。pyc文件,迫使要创建新的,从而允许你测试出你的新的字节码正确。
代码对象
结果PyAST_Compile()是一个PyCodeObject,在包括/ code.h的定义。你现在有可执行的Python字节码!
对象代码(字节码)执行在Python / ceval.c的。这个文件还需要一个大的switch语句在PyEval_EvalFrameEx()的新的操作码的新的case语句。
重要文件
-
分析器/
-
Python的/
- python-ast.c的
-
创建C的结构的ASDL类型对应。此外,还包含代码编组AST的节点(ASDL类型核心编组代码asdl.c)。“文件自动生成的分析器/ asdl_c.py”。此文件必须分开提交后,每语法变化以来致力于__ version__值设置语法变化的最新修订版本号。
- asdl.c
-
包含代码处理的ASDL序列类型。还具有编组ASDL的核心类型,如号码和标识代码来处理。使用python-ast.c的编组AST节点。
- ast.c
-
Python的解析树转换成抽象语法树。
- ceval.c
-
执行字节代码(又名中,eval循环)。
- compile.c
-
发射出的字节码的基础上的AST。
- symtable.c
-
从AST生成一个符号表。
- pyarena.c
-
实施的舞台内存管理器。
- import.c
-
首页字节码版本的幻数(魔术)
-
包含/
- python-ast.h的
-
包含如Python的/ Python的ast.c的产生的C结构的实际定义。“自动生成分析器/ asdl_c.py”。
- asdl.h
-
头相应的Python / ast.c的。
- ast.h
-
声明PyAST_FromNode()外部(从的Python / ast.c)的。
- code.h
-
为对象/ codeobject.c的头文件包含定义PyCodeObject。
- symtable.h
-
标头Python / symtable.c的。struct symtable的PySTEntryObject这里定义。
- pyarena.h
-
相应的Python / pyarena.c的头文件。
- opcode.h
-
主列表的字节码,如果这个文件被修改,你必须相应地修改其他几个文件(请参阅“ 引入新的字节码 “)
-
对象/
- codeobject.c
-
包含的相关PyCodeObject代码(最初在Python / compile.c的)。
-
LIB /
- opcode.py
-
如果包括/ opcode.h的是必须修改的文件之一。
-
编译器/
- pyassem.py
-
如果包括/ opcode.h的改变必须修改的文件之一。
- pycodegen.py
-
如果包括/ opcode.h的改变必须修改的文件之一。
参考文献
[Aho86] | 阿尔弗雷德·五阿霍,拉维·塞西,杰弗里·乌尔曼。 编译器:原理,技术和工具, http://www.amazon.com/exec/obidos/tg/detail/-/0201100886/104-0162389-6419108 |
[Wang97] | 杰夫•科恩,安德鲁·W.阿佩尔,丹尼尔C.王和克里斯S.塞拉。 西风抽象语法描述语言。 [4] 在重要领域特定语言的会议上,第213 - 227,1997 。 |
[1] | 跳过蒙塔纳罗的窥孔优化纸(http://www.foretec.com/python/workshops/1998-11/proceedings/papers/montanaro/montanaro.html) |
[2] | Bytecodehacks项目(http://bytecodehacks.sourceforge.net/bch-docs/bch/index.html) |
[3] | CALL_ATTR操作码(http://www.python.org/sf/709744) |
[4] | http://www.cs.princeton.edu/〜danwang/Papers/dsl97/dsl97.html |
[5] | (1,2) http://pages.cpsc.ucalgary.ca/〜Aycock通知/火花/ |