PyPy的翻译过程实际上比听起来的概念递归要简单得多。
它实际上只是一个处理Python函数/类/其他对象(不是Python源代码)并输出C代码的Python程序。但当然它不会处理所有的Python对象;它只能处理特定的形式,这种形式是你在RPython中编写待翻译代码时所获得的。
由于翻译工具链是一个Python程序,因此您可以在任何Python解释器上运行它,其中包括PyPy的Python解释器。所以这没什么特别的。
由于它翻译RPython对象,因此您可以使用它来翻译PyPy的Python解释器,后者是用RPython编写的。
但您不能在翻译框架本身上运行它,因为它不是RPython。只有PyPy的Python解释器本身是RPython。
事情之所以变得有趣,是因为RPython代码也是Python代码(但反之不成立),而且RPython永远不存在于源文件中,而是仅存在于工作中的Python进程内存中,必然包括其他非RPython代码(例如,没有“纯RPython”导入或函数定义,因为翻译器操作的是已经被定义和导入的函数)。
请记住,翻译工具链作用于内存中的Python代码对象。Python的执行模型意味着这些对象在某些Python代码运行之前是不存在的。如果您高度简化它,可以想象启动翻译过程类似于这样:
from my_interpreter import main
from pypy import translate
translate(main)
众所周知,仅仅导入
main
就会运行大量的 Python 代码,包括
my_interpreter
导入的所有其他模块。但翻译过程始于分析
function object main
; 它从不关心产生
main
的任何代码,也不在意执行了什么。
一种思考方式是,“使用RPython编程”意味着“编写一个Python程序来生成RPython程序,然后将其提供给翻译过程”。这种方式相对容易理解,并且与许多其他编译器的工作方式有些相似(例如,程序员可以将C编程视为编写一个C预处理器程序,该程序生成一个C程序,然后将其提供给C编译器)。
在PyPy的情况下,事情变得混乱起来,因为所有3个组件(生成RPython程序的Python程序、RPython程序和翻译过程)都加载到
同一个Python解释器中。这意味着很可能有一些函数在某些参数下是RPython,而在另一些参数下则不是;还有可能调用翻译框架的辅助函数来生成你的RPython程序等等,所以在边缘处的情况变得非常模糊,你不能把源代码清晰地分成“要翻译的RPython代码”、“生成我的RPython程序的Python代码”和“将RPython程序交给翻译框架”的部分。
运行在CPython上的PyPy解释器,执行部分自我解释
我认为你这里暗示的是PyPy在翻译过程中使用
流对象空间进行抽象解释。即使这看起来有些疯狂和令人困惑,但实际上并不像初看起来那么神秘。我对PyPy的这部分了解较少,但据我所知:
PyPy通过将所有Python解释器的操作委托给“对象空间”,来实现Python解释器的所有操作。该空间包含所有基本内置操作的实现。但是可以插入不同的对象空间以获得不同的效果,只要它们实现了相同的“对象空间”接口,解释器仍然能够“执行”Python代码。
PyPy翻译工具链处理的RPython代码对象是可以由解释器执行的Python代码。因此,PyPy将其Python解释器的一部分作为翻译工具链的一部分进行重用,通过插入流对象空间。当使用此对象空间“执行”代码时,解释器实际上并不执行代码的操作,而是生成类似于许多其他编译器使用的中间表示形式的流图。这只是一种简单的机器可操纵的代码表示形式,用于进一步处理。这就是如何将常规的(R)Python代码对象转换为翻译过程的其余部分的输入。
由于使用翻译过程进行翻译的通常是PyPy的Python解释器,因此它确实使用流对象空间“自我解释”。但是,这仅仅意味着你有一个处理Python函数的Python程序,包括执行处理的函数。本质上,这并没有比对自身应用装饰器或者使用包装器类封装其自身(或封装类本身)更
dis
接收到的字节码进行抽象解释? - alexgolec