是否可以创建独立于原始解释程序的“自举”解释程序?


21

根据Wikipedia的说法,在编写编译器的上下文中,“引导”一词的含义是

在计算机科学中,引导程序是使用要编译的源编程语言编写编译器(或汇编器)的过程。应用此技术将导致自托管编译器。

我可以理解这将如何工作。但是,对于口译员来说,故事似乎有些不同。现在,当然可以编写自托管解释器了。那不是我要的 我实际上要问的是:是否可以使自托管翻译器独立于原始的第一翻译器。为了解释我的意思,请考虑以下示例:

你写在你的语言翻译的第一版本X和解释是为正在创建一个新的语言,称为ÿ。首先,您使用X语言的编译器来创建可执行文件。现在,您可以使用以语言X编写的解释器来解释以您的新语言Y编写的文件。

现在,据我所知,要能够“引导”您使用语言X编写的解释器,您需要使用语言Y重写该解释器。但这很重要:即使您确实使用语言Y重写了整个解释器,仍然需要使用语言X编写的原始解释器。因为要使用语言Y运行解释器,所以您将不得不解释源文件。但是,究竟该如何解释源文件呢?好吧,当然不能没有什么,所以您被迫仍然使用第一个解释器。

不管您用语言Y编写了多少新的解释器,您始终将必须使用用X编写的第一个解释器来解释后续的解释器。仅仅由于口译员的性质,这似乎是一个问题。

但是,另一方面,这篇关于口译员的Wikipedia文章实际上讨论的是自托管口译员。这是一个相关的小摘录:

自解释器是用一种可以解释自身的编程语言编写的编程语言解释器;一个例子是用BASIC编写的BASIC解释器。自解释器与自托管编译器有关。

如果不存在用于要解释的语言的编译器,则创建自解释器需要使用宿主语言(可以是另一种编程语言或汇编程序)来实现该语言。通过拥有这样的第一个解释器,系统将被引导,并且可以使用语言本身开发新版本的解释器

不过,对于我来说仍然不清楚如何完成此操作。看来,无论如何,您总是被迫使用以宿主语言编写的解释器的第一个版本。

现在,上面提到的文章链接到另一篇文章,其中Wikipedia提供了一些假定的自我托管解释器的示例。经过仔细检查,似乎许多这些自托管解释器(尤其是一些较常见的解释器,例如PyPy或Rubinius)的主要“解释”部分实际上是用其他语言编写的,例如C ++或C。

那我上面描述的可能吗?自托管口译员可以独立于其原始主机吗?如果是这样,将如何精确地做到这一点?

Answers:


24

简短的答案是:您猜对了,总是需要另一个用X编写的解释器,或者是从Y到已经具有解释器的某种其他语言的编译器。解释器执行,编译器只能从一种语言翻译成另一种语言,在系统中的某个时候,必须有一个解释器…即使是CPU。

不管您用语言Y编写了多少新的解释器,您始终将必须使用用X编写的第一个解释器来解释后续的解释器。仅仅由于口译员的性质,这似乎是一个问题。

正确。您可以做的是将编译器从Y编写为X(或您拥有解释器的另一种语言),甚至可以在Y中进行编译。然后你可以运行你的Ÿ编译器写在ŸŸ解释写在X(或在ÿ解释写在Ÿ在运行Ÿ解释写在X,或在ÿ写在解释Ÿ在运行Ÿ解释写在Ÿ在运行ÿX编写的解释器,或…ad infinitum)将用Y编写的Y解释器编译为X,以便您可以在X解释器上执行它。这样,您就摆脱了用X编写的Y解释器,但是现在您需要X解释器(但是我们知道我们已经有了一个,因为否则我们将无法运行用Y编写的X解释器),并且您必须先编写一个YX的编译器。

但是,另一方面,Wikipedia上有关口译员的文章实际上讨论的是自托管口译员。这是一个相关的小摘录:

自解释器是用一种可以解释自身的编程语言编写的编程语言解释器;一个例子是用BASIC编写的BASIC解释器。自解释器与自托管编译器有关。

如果不存在用于要解释的语言的编译器,则创建自解释器需要使用宿主语言(可以是另一种编程语言或汇编程序)来实现该语言。通过拥有这样的第一个解释器,系统将被引导,并且可以使用语言本身开发新版本的解释器

不过,对于我来说仍然不清楚如何完成此操作。看来,无论如何,您总是被迫使用以宿主语言编写的解释器的第一个版本。

正确。请注意,Wikipedia文章明确指出您需要语言的第二种实现,但并没有说您可以摆脱第一种实现。

现在,上面提到的文章链接到另一篇文章,其中Wikipedia提供了一些假定的自我托管解释器的示例。经过仔细检查,似乎许多这些自托管解释器(尤其是一些较常见的解释器,例如PyPy或Rubinius)的主要“解释”部分实际上是用其他语言编写的,例如C ++或C。

同样,正确。这些确实是不好的例子。以Rubinius为例。是的,Rubinius的Ruby部分是自托管的,但这确实是编译器,而不是解释器:它可以将Ruby源代码编译为Rubinius字节码。解释器部分OTOH并不是自托管的:它解释Rubinius字节码,但是它是用C ++编写的。因此,将Rubinius称为“自托管解释器”是错误的:自托管部分不是解释器,而解释器部分也不是自托管

PyPy与之类似,但更不正确:它最初不是用Python编写的,而是用另一种语言的RPython编写的。它在语法上类似于Python,在语义上是“扩展子集”,但实际上是一种与Java处于相同抽象级别的静态类型语言,其实现是具有多个后端的编译器,可将RPython编译为C源代码ECMAScript。源代码,CIL字节代码,JVM字节代码或Python源代码。

那我上面描述的可能吗?自宿主解释器可以独立于其原始宿主吗?如果是这样,将如何精确地做到这一点?

不,不是靠自己。您要么需要保留原始解释器,要么编写一个编译器并编译您的自解释器。

这里一些元圆形的虚拟机,如克莱因(写在自我)和玛克辛(用Java编写的)。但是请注意,这里的“元圆”定义是不同的:这些VM并不是它们执行的语言编写的:Klein执行Self字节代码,但以Self编写,Maxine执行JVM字节代码,但以Java编写。但是,VM的Self / Java源代码实际上会被编译为Self / JVM字节码,然后由VM执行,因此,在执行VM时,它使用其执行的语言。ew

还要注意,这与诸如SqueakVMJikes RVM之类的 VM不同。Jikes用Java编写,SqueakVM用Slang(Smalltalk的静态类型化语法和语义子集,与高级汇编程序大致处于同一抽象级别)编写,并且两者都在运行之前被静态编译为本机代码。他们不会在自己内心奔跑。但是,您可以在自己之上(或在另一个Smalltalk VM / JVM 之上)运行它们。但是,从这个意义上讲,这不是“元圆形”。

Maxine和Klein,OTOH 在自己内心奔跑;他们使用自己的实现来执行自己的字节码。这是真正的弯腰!它提供了一些很酷的优化机会,例如,由于VM与用户程序一起执行自身,因此它可以内联从用户程序到VM的调用,反之亦然,例如可以将对垃圾回收器或内存分配器的调用内联到用户中代码,并且用户代码中的反射性回调可以内联到VM中。此外,现代VM会执行所有聪明的优化技巧,它们会监视执行程序并根据实际工作量和数据对其进行优化,因此VM可以在执行用户程序时将这些相同的技巧应用于自身,而用户程序正在执行特定的工作负载。换言之,虚拟机高度专业化的本身特定的程序运行的是特定的工作负载。

但是,请注意,我绕过上面的“解释器”一词的使用,而总是使用“执行”吗?嗯,这些VM不是围绕解释器构建的,而是围绕(JIT)编译器构建的。有加入玛克辛后来的解释,但你总是需要编译器:你必须运行VM 一次上(在玛克辛的情况下,如Oracle的HotSpot)另一个虚拟机之上,以便VM可以(JIT)编译本身。对于Maxine,它将JIT编译自己的启动阶段,然后将编译后的本机代码序列化为引导VM映像,并在前面放置一个非常简单的bootloader(VM用C语言编写的唯一组件,尽管这只是为了方便起见)。 ,也可以在Java中使用)。现在,您可以使用Maxine自行执行。


Yeesh。我从来不知道自托管口译员的世界如此粘人!感谢您提供了不错的概述。
克里斯汀·迪恩

1
哈哈,好吧,为什么这个世界不如这个概念那么引人入胜?;-)
约尔格W¯¯米塔格

3
我想其中的一个问题是,人们经常在所涉及的语言中游刃有余。例如,Rubinius通常被称为“ Ruby in Ruby”,但这仅仅是故事的一半。是的,严格来说,Rubinius中的Ruby编译器是用Ruby编写的,但是执行字节码的VM不是。更糟糕的是:PyPy通常被称为“ Python中的Python”,只是其中实际上没有一行Python。整个内容都是用RPython编写的,RPython旨在让Python程序员熟悉,但不是Python。同样SqueakVM:它不是写在Smalltalk中,它...
约尔格W¯¯米塔格

…用Slang编写,据实际编写过代码的人说,它的抽象能力甚至比C更差。Slang唯一的优点是,它是Smalltalk的适当子集,这意味着您可以开发它(并在一个功能强大的Smalltalk IDE上运行并最重要地调试VM)。
约尔格W¯¯米塔格

2
只是增加了另一个复杂性:某些解释语言(例如FORTH,可能还有TeX)能够将正在运行的系统的可加载内存映像作为可执行文件写入。从这种意义上讲,这样的系统可以在没有原始解释器的情况下运行。例如,我曾经编写过一个FORTH解释器,方法是使用16位版本的FORTH来“交叉解释”另一位CPU的32位版本,并在不同的OS上运行。(请注意,FORTH语言包含其自己的汇编程序,因此“ FORTH VM”(通常只有10或20条机器代码指令)可以用FORTH本身编写。)
alephzero

7

您正确地指出,自托管解释器仍然需要解释器自行运行,并且不能以与编译器相同的方式进行引导。

但是,自托管语言与自托管解释器不是一回事。通常,构建解释器比构建编译器要容易。因此,要实施一种新语言,我们可能首先要使用一种不相关的语言来实现解释器。然后,我们可以使用该解释器为我们的语言开发编译器。由于解释了编译器,因此该语言是自托管的。然后,编译器可以自行编译,然后可以视为完全自举。

这种情况的一个特例是自托管的JIT编译运行时。它可以从宿主语言的解释器开始,然后使用新语言来实现JIT编译,然后JIT编译器可以自行编译。感觉就像是一个自托管的解释器,但是回避了无限解释器的问题。使用了这种方法,但是还不是很普遍。

另一个相关技术是可扩展的解释器,我们可以在其中解释所解释语言的扩展。例如,我们可以用该语言实现新的操作码。只要我们避免循环依赖,就可以将基本解释器变成功能丰富的解释器。

实际上相当普遍的一种情况是语言影响其自身解析的能力,例如作为解析时评估的宏。由于宏语言与正在处理的语言相同,因此与专用或受限宏语言相比,它的功能丰富得多。但是,请正确注意执行扩展的语言与扩展后的语言略有不同。

当使用“真正的”自托管翻译时,通常出于教育或研究的原因。例如,在Scheme内部实现Scheme的解释器是教授编程语言的一种很酷的方法(请参见SICP)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.