后浪笔记一零二四

用纯C语言编写一个Rust编译器。不依赖C++,不用flex或yacc,甚至没有Makefile。唯有纯粹的C。

它的名字叫Dozer。

在Rust的世界里,主编译器是rustc。

然而,rustc本身也是一个程序。因此它需要一个编译器,将其源代码编译为机器码。那么问题来了:rustc是用什么语言编写的?

rustc是一个用Rust编写的程序,专为编译Rust代码而生。但请稍作思考:如果rustc用Rust编写,而编译Rust代码又需要rustc,这意味着你必须用rustc来编译rustc本身。

但第一个rustc是谁编译的?总得先有鸡才有蛋吧?这一切究竟从何开始?

其实答案相当简单。每个新版本的rustc都是用前一个版本的rustc编译的。比如rustc 1.80.0由rustc 1.79.0编译,而1.79.0又由1.78.0编译,如此递归回溯,直到最早的0.7版编译器。那时的编译器是用OCaml编写的,因此只需一个OCaml编译器就能获得完整的rustc程序。

不过还有个小问题:要让这一切成立,我们仍需一个OCaml编译器。那么OCaml编译器又是用什么语言编写的?

有个项目能用Guile(Scheme的众多变体之一,而Scheme又是Lisp的分支)成功编译OCaml编译器。更妙的是,Guile的解释器是用C语言写的。

于是,如同所有终极问题的归宿,我们回到了C语言。用GCC编译即可万事大吉。但GCC本身是用……C++编写的?!

GCC在5.0版本之前一直用C编写,况且用C实现的C编译器并不罕见,例如TinyCC——它不仅用C实现,还集成了汇编和链接功能。

……可这仍未解答核心谜题:第一个C编译器是用什么写的?汇编语言吗?那么第一个汇编器又用什么编写?

1. 递减法则

这里我们要介绍​​可自举构建(Bootstrappable Builds)​​项目。在我看来,这是开源社区中最迷人的项目之一,本质上堪称代码炼金术。

他们的Linux引导流程始于一个仅512字节的二进制种子。这个种子包含的可能是你能想象的最简编译器:它接收十六进制数字,输出对应的原始字节。以下是用该编译器编译的"源代码"片段示例

31 C0           # xor ax, ax
8E D8           # mov ds, ax
8E C0           # mov es, ax
8E D0           # mov ss, ax
BC 00 77        # mov sp, 0x7700
FC              # cld ; clear direction flag
88 16 15 7C     # mov [boot_drive], dl

这个编译器先编译出一个极简的操作系统、一个简陋的shell,以及一个略微高级些的编译器。随后,新生成的编译器又会编译出更高级的编译器。如此迭代数次后,最终得到的产物已初具汇编代码的雏形。

DEFINE cmp_ebx,edx 39D3
DEFINE je 0F84
DEFINE sub_ebx, 81EB

:loop_options
    cmp_ebx,edx                         # Check if we are done
    je %loop_options_done               # We are done
    sub_ebx, %2                         # --options

他们先使用汇编代码构建出一个极简的C语言子集。接着用这个子集编译出稍高级的C编译器,迭代几次后就能编译TinyCC。此后便能逐步引导出yacc、基础coreutils、Bash、autotools,最终抵达GCC和Linux的完整生态。

整个过程可以从这里查阅: https://github.com/fosslinux/live-bootstrap/blob/master/parts.rst

目前Rust在这条引导链中出现得极晚。开发者依赖mrustc(用C++编写的Rust替代实现)来编译rustc 1.56版本,再基于此编译现代Rust代码。问题在于:当C++被引入引导链时,整个引导过程已接近尾声。这意味着在C++出现前的任何阶段,你都无缘使用Rust。

如果能有一个直接从C语言引导的Rust编译器就太理想了——确切地说,是一个仅依赖TinyCC、且不预设任何系统工具的Rust编译器。这就是Dozer项目的使命。

2. 计划

这段代码完全基于标准C编写,没有任何扩展,目前TinyCC和cproc都能顺利编译。我选用QBE作为后端,整个系统除了C编译器和一个最基本的shell实现外,不依赖任何其他工具。

这篇博客就不详细描述编写编译器的原始体验了。但目前的进展是:词法分析器已经完成,语法解析器也完成了大部分。宏和模块扩展功能我打算能拖就拖,类型检查暂时只支持i32,代码生成还有点粗糙。不过好歹算是迈出了第一步。

现在我已经能成功编译这段代码:

fn rust_main() -> i32 {
    (2 - 1) * 6 + 3
}

那么,接下来该往哪里走?我的计划是这样的:

  • 首先逐步完善Dozer,直到它能编译一些使用基础libc的示例程序,然后是libcore,最终目标是rustc。
    • 需要说明的是,我打算编译rustc的Cranelift后端————这部分完全用Rust编写。由于我们假设系统还没有C++环境,所以无法编译LLVM。
  • 接着要开发一个类似cargo的工具,能够使用Dozer来编译Rust包。
  • 同时需要找出rustc中哪些源代码是自动生成的并将其剔除,因为根据Bootstrappable项目的规则,自动生成的代码是不被允许的。
  • 然后建立一套流程:先用Dozer编译出rustc和cargo,再用我们编译的版本重新编译官方版本的rustc/cargo。

本文发表于 0001-01-01,最后修改于 0001-01-01。

本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。


上一篇 « 下一篇 »

赞赏支持

请我吃鸡腿 =^_^=

i ysf

云闪付

i wechat

微信

推荐阅读

Big Image