自举仍然需要外部支持

Posted

技术标签:

【中文标题】自举仍然需要外部支持【英文标题】:Bootstrapping still requires outside support 【发布时间】:2010-09-06 00:37:27 【问题描述】:

我听说过引导语言的想法,即为语言本身编写编译器/解释器。我想知道这怎么能做到,看了一圈,看到有人说这只能由任何一方完成

用不同的语言编写初始编译器。 在 Assembly 中手动编写初始编译器,这似乎是第一个编译器的特例

对我来说,这两种语言似乎都不是真正引导一种语言,因为它们都需要外部支持。有没有办法用自己的语言实际编写编译器?

【问题讨论】:

我对这类事情不是很有经验,但我认为 initial 编译器必须用另一种语言编写。我相当肯定,关于编译器的“引导”只是指用它要编译的语言为一种语言编写 a 编译器,而不是编写第一个该语言的编译器要编译的语言。 谢谢大家的信息。当以最初编写有限编译器的想法进行解释时,然后在此基础上进行构建,那么引导的想法就更有​​意义了。这学期我要上编译器课程,这个决定很大程度上受Steve Yegge's post on how important a class in Compilers 的影响,我刚从亚马逊链接上买了一本 Dragon 的书,该书在 SO 上被降级了。 另见类似问题:Implementing a compiler in itself 【参考方案1】:

有没有办法用自己的语言实际编写编译器?

必须有一些现有的语言来编写你的新编译器。如果你正在编写一个新的,比如说,C++ 编译器,你只需用 C++ 编写它,然后用现有的编译器编译它第一的。另一方面,如果您正在为一种新语言创建编译器,我们称之为 Yazzleof,您需要先用另一种语言编写新编译器。通常,这将是另一种编程语言,但并非必须如此。它可以是汇编,或者如果需要,可以是机器码。

如果您为 Yazzleof 引导编译器,通常最初不会为完整语言编写编译器。相反,您将为 Yazzle-lite 编写一个编译器,这是 Yazzleof 的最小可能子集(嗯,至少是一个相当小的子集)。然后在 Yazzle-lite 中,您将为完整的语言编写一个编译器。 (显然这可以迭代地发生,而不是一次跳转。)因为 Yazzle-lite 是 Yazzleof 的一个真子集,所以您现在有了一个可以自行编译的编译器。

真的有一篇关于从最低级别(在现代机器上基本上是一个十六进制编辑器)引导编译器的好文章,标题为从无到有引导一个简单的编译器时间>。可以在https://web.archive.org/web/20061108010907/http://www.rano.org/bcompiler.html找到。

【讨论】:

【参考方案2】:

您阅读的解释是正确的。 Compilers: Principles, Techniques, and Tools(龙之书)对此进行了讨论:

用语言 Y 为语言 X 编写编译器 C1 使用编译器 C1 为语言 X 编写语言 X 的编译器 C2 现在 C2 是一个完全自托管的环境。

【讨论】:

【参考方案3】:

一个超级有趣的discussion of this 在Unix 联合创始人Ken Thompson 的Turing Award 讲座中。

他开始说:

我要描述的是当编译器用自己的语言编写时出现的许多“鸡和蛋”问题之一。为了方便起见,我将使用 C 编译器中的一个特定示例。

并继续展示他如何编写一个 Unix C 编译器版本,该版本始终允许他无需密码即可登录,因为 C 编译器会识别登录程序并添加特殊代码。

第二种模式是针对 C 编译器的。替换代码是一个第一阶段的自我复制程序,它将两个特洛伊木马插入编译器。这需要一个学习阶段,就像第二阶段的例子一样。首先,我们用普通的 C 编译器编译修改后的源代码,以生成一个错误的二进制文件。我们将这个二进制文件安装为官方 C。我们现在可以从编译器的源代码中删除错误,新的二进制文件将在编译时重新插入错误。当然,login 命令会一直存在漏洞,并且在任何地方都不会在源代码中留下任何痕迹。

【讨论】:

这是题外话。有趣,但令人困惑,不是问题的答案。【参考方案4】:

我听说的方法是用另一种语言编写一个极其有限的编译器,然后用它来编译一个更复杂的版本,用新语言编写。然后可以使用第二个版本来编译自己和下一个版本。每次编译时都会使用最后一个版本。

这是bootstrapping:的定义

一个简单的系统激活一个服务于相同目的的更复杂系统的过程。

编辑:Wikipedia article on compiler bootstrapping 比我更好地涵盖了这个概念。

【讨论】:

【参考方案5】:

查看播客 Software Engineering Radio episode 61 (2007-07-06),其中讨论了 GCC 编译器内部结构以及 GCC 引导过程。

【讨论】:

【参考方案6】:

Donald E. Knuth实际上是通过在其中编写编译器来构建WEB,然后将其手动编译为汇编或机器码。

【讨论】:

【参考方案7】:

据我了解,第一个 Lisp 解释器是通过手动编译构造函数和令牌读取器来引导的。然后从源代码中读入解释器的其余部分。

您可以通过阅读原始的 McCarthy 论文来检查自己,Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I

【讨论】:

第 2 部分和第 3 部分发生了什么? ...我怎么没有注意到@Wing 在我之前 3 年发布了同样的内容?我是个笨蛋。至少我链接了这篇论文(在帮助下)。【参考方案8】:

另一种选择是为您的语言创建一个字节码机器(或者如果它的功能不是很不寻常,则使用现有的)并编写一个编译器到字节码,无论是在字节码中,还是在您想要的语言中使用另一种中间 -例如解析器工具包,它将 AST 输出为 XML,然后使用 XSLT(或另一种模式匹配语言和基于树的表示)将 XML 编译为字节码。它不会消除对另一种语言的依赖,但可能意味着更多的引导工作最终会出现在最终系统中。

【讨论】:

【参考方案9】:

这是鸡与蛋悖论的计算机科学版本。我想不出不用汇编程序或其他语言编写初始编译器的方法。如果可以做到,我应该 Lisp 可以做到。

实际上,我认为 Lisp 几乎可以胜任。查看its Wikipedia entry。根据这篇文章,Lisp eval 函数可以用机器代码在 IBM 704 上实现,完整的编译器(用 Lisp 本身编写)于 1962 年在 MIT 出现。

【讨论】:

【参考方案10】:

我能想到的每个引导语言的示例(C、PyPy)都是在编译器工作后完成的。你必须从某个地方开始,重新实现一种语言本身需要先用另一种语言编写编译器。

它还能如何工作?我认为它甚至在概念上是不可能的。

【讨论】:

第一个 Lisp 编译器至少是使用现有的 Lisp 解释器 引导的。所以在语义上不是另一种语言,而是另一种语言实现。【参考方案11】:

一些自举编译器或系统将源形式和对象形式都保存在其存储库中:

ocaml 是一种同时具有字节码解释器(即 Ocaml 字节码的编译器)和本机编译器(x86-64 或 ARM 等...汇编器)的语言。它的 svn 存储库包含编译器的源代码(文件*/*.ml,mli)和字节码(文件boot/ocamlc)形式。因此,当您构建它时,首先使用它的字节码(以前版本的编译器)来编译自己。稍后,新编译的字节码能够编译本机编译器。所以 Ocaml svn 存储库包含*.ml[i] 源文件和boot/ocamlc 字节码文件。

rust 编译器会下载(使用wget,因此您需要有效的 Internet 连接)其二进制文件的先前版本以进行自身编译。

MELT 是一种类似 Lisp 的语言,用于自定义和扩展 GCC。它由自举翻译器翻译成 C++ 代码。编译器生成的 C++ 代码是分布式的,所以 svn 存储库包含 *.melt 源文件和 melt/generated/*.cc 翻译器的“对象”文件。

J.Pitrat 的CAIA 人工智能系统完全是自我生成的。它可作为数千个[A-Z]*.c 生成文件的集合(也带有生成的dx.h 头文件)和数千个_[0-9]* 数据文件的集合。

几个 Scheme 编译器也被引导。 Scheme48,鸡计划,...

【讨论】:

以上是关于自举仍然需要外部支持的主要内容,如果未能解决你的问题,请参考以下文章

单击外部链接后悬停效果仍然存在

Perl脚本记录外部可执行文件输出和错误,但仍然运行

当我单击外部时,Bootstrap 5 静态模式仍然关闭

带有外部样式表的div?

manifest <uses-feature> 不包含外部存储支持

第12章 添加对外部认证的支持