开发者“疯狂”整活:用纯 C 语言,从头编写一个 Rust 编译器!

开发者“疯狂”整活:用纯 C 语言,从头编写一个 Rust 编译器!_第1张图片

近日,一个项目在 HN 上引起了许多开发者的注意——一名富有创新精神的开发者正在尝试使用 C 语言来编写 Rust 编译器。这位开发者表示:为了引导 Rust 发展,无论付出什么代价都值得。

原文链接:https://notgull.net/announcing-dozer/

作者 | John Nunley       翻译 | 郑丽媛

出品 | CSDN(ID:CSDNnews)

细心的 Rust 爱好者可能已经注意到,我最近不太活跃。导致这种情况的原因有很多:最近我经历了一系列非常糟糕的事情,包括一位亲人的离世让我感到极其意外,同时我在工作中承担了更多责任,不再有很多时间和精力去贡献开源项目了。亦或许,我也失去了当初在大学时代、那种足以让我全身心投入开源世界的热情。

除此之外,还有另一个原因:我正忙于一个占据了我大部分业余时间的项目。这个项目是我在开源领域中创建的最大型的一个,如果我最后能完成它,那它一定会成为我的巅峰之作。

我正在用纯 C 语言编写一个 Rust 编译器:不用 C++,不用 flex 或 yacc,甚至不用 Makefile——仅仅用纯 C 语言。这个项目叫做 Dozer。

开发者“疯狂”整活:用纯 C 语言,从头编写一个 Rust 编译器!_第2张图片

(CSDN 付费下载自视觉中国)

de0f266acb3a825268aa21d007893e29.png

为什么要这么做?

想要理解我为何走上这条“疯狂”之路,你首先需要了解 Bootstrapping(引导法)以及它的重要性(所谓引导法,这是一种在编程中常见的技术,意为通过已有的基本代码或资源来构建更复杂的系统或工具)。

假设你用 Rust 写了一些代码,为了运行这些代码,你需要先编译它们。编译器是一种程序,它会解析你的代码,验证其正确性,然后将其转换为 CPU 可以理解的机器代码。

对于 Rust 来说,主要的编译器是 rustc——也就是你运行 cargo build 时所调用的底层程序。不得不说,rustc 是一个很棒的软件,甚至可以说是开源社区的瑰宝,其代码质量可以媲美 Linux 内核和 Quake III 源代码。

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

开发者“疯狂”整活:用纯 C 语言,从头编写一个 Rust 编译器!_第3张图片

这样来看,rustc 是一个用 Rust 编写的程序,其目的是为了编译 Rust 代码。但请仔细想想,如果 rustc 是用 Rust 编写的,而我们又需要用 rustc 来编译 Rust 代码,这意味着我们需要用 rustc 来编译 rustc……?

对于一般用户来说,这其实没什么问题,因为我们可以直接从网上下载 rustc 并使用它。但有个问题:第一个 rustc 是谁编译的,总得先有“鸡”才有“蛋”吧?这到底是从哪里开始的?

其实,这个问题并不复杂:每个新 rustc 版本都是由前一个版本的 rustc 编译出来的。也就是说,rustc 1.80.0 版本是用 rustc 1.79.0 版本编译的,rustc 1.79.0 版本又是由 rustc 1.78.0 版本编译的……以此类推,一直可以追溯到 rustc 0.7 版本。而那时,编译器是用 OCaml 写的,因此只需要一个 OCaml 编译器就能得到一个完整的 rustc 程序。

好了,问题解决了,我们已经搞清楚如何从头开始创建 rustc。但是,要让这一切都正常工作,我们仍需要一个 OCaml 编译器的版本。所以说,OCaml 编译器又是用什么语言编写的呢?

开发者“疯狂”整活:用纯 C 语言,从头编写一个 Rust 编译器!_第4张图片

额……没事儿!有一个项目能成功用 Guile 编译 OCaml 编译器,而 Guile 是 Scheme 的众多变体之一,Scheme 又是 Lisp 的众多变体之一。另外,Guile 的解释器是用 C 编写的。

于是,这一切最终都指向了 C 语言。我们只需用 GCC 来编译它,一切就能顺利进行。那么我们只需要编译 GCC,而 GCC 是用……C++编写的?!

这个说法有点不准确。GCC 直到第 5 版之前都是用 C 语言编写的,这世上也并不缺少用 C 编写的 C 编译器……但这仍然没有回答我们的问题。第一个 C 编译器是用什么写的?汇编语言?那么第一个汇编器又是用什么写的呢?

e8975f18484fb30109eaa5732d744ec8.png

原理介绍

这就是我要介绍 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。

我这样讲,可能还是没法完全体现出这个过程的魅力,但这真的很引人入胜。总之,从“一个小到足以手动分析的二进制文件”开始,一步步到 Linux、GCC,再到基本上所有其他的东西,你基本上都经历过了。不过,我们还是从 TinyCC 开始再来一次吧。

目前,Rust 在这个过程中出现得非常晚。他们使用 mrustc,这是一种用 C++ 编写的 Rust 替代实现,可以编译 rustc 1.56 版本。在此基础上,他们再编译现代 Rust 代码。

这里的主要问题是,到引入 C++ 时,引导过程基本上已经结束了。因此,如果你想在引入 C++ 之前的任何时候使用 Rust,那是不可能的。

所以,对我来说,如果有一个 Rust 编译器能够从 C 开始引导,那就太好了。具体来说,就是一个可以从 TinyCC 开始引导的 Rust 编译器,同时假设系统中还没有可能有用的工具——这个编译器就是 Dozer。

8d429b77cf4e4a702991c1431c375469.png

未来计划

过去两个月中,我一直在忙于 Dozer 项目:把我那本就少得可怜的空闲时间,用来编写一种我有点讨厌的语言。

这个项目没有使用任何扩展功能,目前 TinyCC 和 cproc 都能顺利编译。我使用 QBE 作为后端。除此之外,我假设系统上没有其他工具,只有一个 C 编译器和一些非常基础的 shell 实现,再无其他。

在本文中,我不会深入探讨编写一个编译器的原始体验。但到目前为止,我已经完成了词法分析器,还完成了相当大一部分的语法解析器。宏/模块扩展我会尽量推迟,类型检查目前只支持 i32,而代码生成还稍显粗糙——但这已经是一个不错的开始了。

目前,我已可以成功编译以下代码:

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

那么,接下来怎么办呢?这是我的计划:

(1)慢慢推进 Dozer,直到它能够编译一些使用 libc 的基本示例代码,然后再编译 libcore,最后到 rustc。(顺便提一下,我计划编译 rustc 的 Cranelift 后端,这部分完全是用 Rust 编写的。由于我们假定还没有 C++,所以无法编译 LLVM。)

(2)创建一个等同于 cargo 的工具,可以用 Dozer 来编译 Rust 包。

(3)找出 rustc 中那些自动生成的代码源文件,并将它们剔除。根据 Bootstrappable 项目的规则,不允许使用自动生成的代码。

(4)创建一个可以用来编译 rustc 和 cargo 的过程,然后使用我们编译的 rustc/cargo 版本重新编译标准版本的 rustc/cargo。

毫无疑问,这是我迄今为止创建的最困难的项目,我也很怀疑自己到底能否完成它。但你知道吗?尝试过却失败了,总比从未尝试过要好。

推荐阅读:

▶国产GPU独角兽回应解散传闻;哪吒汽车员工称被违法解除劳动合同;传英特尔将剥离资产削减成本 | 极客头条

▶上6休3上3休2……这烧脑的调休安排,国内外AI都算不明白,集体大“翻车”!

▶2000名应届生收到offer两年后仍未入职,被迫参加无薪培训,印度IT巨头被指“剥削”!

74b762add472e0931b010355758580e9.gif

开发者“疯狂”整活:用纯 C 语言,从头编写一个 Rust 编译器!_第5张图片

你可能感兴趣的:(rust,开发语言,后端)