自举来源于自己提着鞋带把自己提起来, 编译器的自举就是用X语言自己开发的编译器来编译X语言本身。
今天看一篇文章提到了 “Git 自举”,它指的是 Git 实现了自我托管,也就是 Git 可以托管 Git 自身的源码。
这个概念很简单,但是想了解一下具体实现原理是什么,于是搜了一下 “自举”,相关的大多是 “编译器自举” 的内容,我也顺便看了一下,发现没有多少有意义的参考内容。但其实也是有那么一两句介绍让我思考之后总算明白了编译器自举是怎么回事。
首先需要知道两个东西:
- 编译器是用来编译程序的,而其实它就是在编译程序们所使用的语言的语法语义啥的,那么简单来讲编译器就是用来编译语言的。
- 编译器本身也是用某一种编程语言写出来的,并且编译器也是需要先编译出来的。
其实通过上边两个必备事项,你应该看得出来,这里边已经形成了一个死结:编译器需要先被编译出来,不然它就没法编译其它东西(包括它自己),那这个编译器要怎么编译出来?
假设你现在写了一个高级语言 Xlang,那这个语言需要用什么编译器来编译呢?当然是使用编译 Xlang 的编译器,问题是现在并没有可以编译 Xlang 的编译器。
其实要实现这个编译器,并不一定要使用 Xlang 语言,可以先用其它已有语言(BB)来实现一个可以编译 Xlang 语言的编译器。而自举就是基于这个逻辑:
- 1、Xlang 语言的语法语义什么的定义好之后,
- 2、使用 Xlang 语言写一个 “通用 Xlang 编译器”,
- 3、然后用 BB 语言写一个编译器 BBX,
- 4、用 BBX 把 “通用 Xlang 编译器” 编译出来,(你可以直接理解这个 BB 语言就是最底层的那种直接跟机器打交道的,反正它写的编译器就是可以编译 Xlang 语言)
- 5、这样,一个 Xlang 语言写的 Xlang 编译器 AXL 就出现了。
- 6、它本身是可以编译 Xlang 语言的,当然也就可以编译 Xlang 写的编译器 —— 也就是它自己,实现编译器自举,所以后边用 Xlang 去开发就不需要再用到 BB 语言和 BBX 了。
这个思想其实有点像两数互换位置的算法,必须借用一个第三者来作中间态,不然无法完成。
而至于为什么不一直沿用 BBX 语言作为 Xlang 语言的通用编译器呢?这跟工程(比如迭代自举的过程中实际上相当于在审视自己语言的优劣……)和性能、效率等多方面因素有关。不展开了。
同样的逻辑回到 Git 自举上,可能意思就只是最开始实现 Git 需要使用其它版本控制工具来管理,等到 Git 功能开发完成,可以托管代码了,这时候把 Git 本身的源码托管到 Git 上。
这么看来,这个所谓的 “Git 自举”,跟编译器自举的意思差得有点儿远,因为它完全不必要形成一个环 —— 也完全没有结存在 —— 因为 Git 的开发其实也可以不用版本控制。
其实编译器的自举其实是说自己的编译器可以自行编译自己的编译器。实现方法就是这个编译器的作者用这个语言的一些特性来编写编译器并在该编译器中支持这些自己使用到的特性。
首先,第一个编译器肯定是用别的语言写的(不论是C还是Go还是Lisp还是Python),后面的版本才能谈及自举。
至于先有鸡还是先有蛋,我可以举个这样的不太恰当的例子:
比如我写了一个可以自举的C编译器叫作mycc,不论是编译器本身的执行效率还是生成的代码的质量都远远好于gcc(本故事纯属虚构),但我用的都是标准的C写的,那么我可以就直接用gcc编译mycc的源码,得到一份可以生成高质量代码但本身执行效率低下的mycc,然后当然如果我再用这个生成的mycc编译mycc的源码得到新的一份mycc,新的这份不光会产生和原来那份同等高质量的代码,而且还能拥有比先前版本更高的执行效率(因为前一份是gcc的编译产物,后一份是mycc的编译产物,而mycc生成的代码质量要远好于gcc的)。故事虽然是虚构的,但是道理差不多就是这么个道理。这也就是为什么如果从源码编译安装新版本的gcc的话,往往会“编译——安装”两到三遍的原因。