LWN:深入讨论GCC Rust前端!

关注了就能看到更多这么棒的文章哦~

A deeper look into the GCC Rust front-end

By Jonathan Corbet
October 10, 2022
Cauldron
DeepL assisted translation
https://lwn.net/Articles/909887/

Philip Herron 和 Arthur Cohen 在 2022 年的 Kangrejos 会议上介绍了 Rust 语言的 GCC 前端 "gccrs" 的最新情况。不到两周后,David Faust 加入进来,他们在 2022 年的 GNU Tools Cauldron 会议上又做了一次报告。不过这一次,他们是在和 GCC 开发者交谈,并相应地调整了他们的演讲重点。于是,对于实现 Rust 编译器会碰到的困难进行了有意思的探讨。

Herron 首先说,他一开始就发现这个任务太难了;Rust 语言实在太不稳定了,很难给它开发编译器。所以他放弃了一段时间。不过,他不断收到人们询问何时能完成这个工作,所以他最终又重新启动了这个项目。自 2015 年以来,该语言已基本稳定,所以任务也变得更容易了一些。

gccrs 项目有一些要求,而不仅仅是简单地能编译 Rust 代码就够了。这项工作开发完成之后需要合入 GCC mainline。它应该尽可能多地重用 GNU toolchain。我们也在努力使 gccrs 代码能尽可能容易地 backport 到旧版本的 GCC 中。最后,应该支持 Rust 代码的高级功能,如 link-time optimization (LTO)。

要实现这些目标,第一步是为该语言创建一个 parser (解析器),然后开始实现 Rust 的数据结构。然后是特质(traits)和泛型(generics);他说,这些功能很复杂,但它们也是编程语言支持工作的核心部分。接下来是控制流(control flow),尤其是 match 表达式;之后是 macro 扩展。他说,const generic 现在正在进行中,而关于 intrinsic 以及 built-in 的工作则刚刚开始。在 borrow checking 方面还没有开始做任何工作,因为没有它也可以生成合法的(valid) Rust 代码,所以这个工作可以在以后进行。运行 Rust test suite 的工作也正在进行中。

另一项正在进行的工作是能完成 libcore 库的编译。这个库不仅有许多重要的函数(function),而且还定义了许多语言的底层功能(low-level feature)。没有它,Herron 的说法是:"你就做不了什么"。目前的工作是针对旧版本的 libcore 做的,正在 "逐步接近完成"。

A look inside gccrs

Rust 前端与 GCC 中其他很多前端有一个不同之处,就是它使用了一种特殊的抽象语法树结构(abstract syntax tree structure)。它需要支持如 macro 扩展和 name 解析等功能。这个语法树是对 Rust 程序的一种较高层次的内部功能的展示;在编译的时候,function 和 method 之间没有区别,所有的 macro 都已经被扩展。这个语法树会被用于进行类型检查和 error 验证;在完成这些之后,它就可以被翻译之后交给 GCC 中间层(mid-layer)。

Cohen 这时接过来谈起了 macro 扩展。Rust 的 macro 与 C 或 C++支持的 macro 有很大差异。它们有类型化的参数(typed argument),可以包括语句(statement)和表达式(expression),有关于可见性的描述符(visibility modifier),等等。Rust 的 macro 可以使用重复和递归,所以导致的结果用他的说法就是:"很酷但很抽象"。他们支持 Kleene 操作符,其规范要求 follow-set ambiguity 的限制,他说"这听起来很可怕,而且确实真是那么可怕"。

他用一个 macro 作为一个(相对)简单的例子来说明,这个 macro 只是计算其参数的总和:

macro_rules! add {
      ($e:expr) => { $e };
($e:expr, $($es:expr).*) => { $e + add!($($es).*) };

这个 macro 的调用可以如此简单:

add!(1);  // Yields 1

但它也可以很复杂:

add!(1, add!(2, 3), five(), b, 2 + 4);

甚至可以比这个更加复杂。他说,Rust 的 macro 能够创建复杂的特定领域语言(domain-specific languages)。这是一个很好的功能,但它也意味着 "Rust" 实际上是几种语言的组合,而且所有这些语言都必须要被实现,才能算真正完成了 Rust 编译器。

接着 Herron 回来谈到了类型系统(type system),以及为什么 type system 导致创建了一个独立的 internal representation (IR)。Rust 的类型系统有很多复杂的功能,并不是所有的功能都有很好的文档;他不得不花了相当多的时间去挖掘 rustc 的代码来弄清楚这一切。他说,这些特性中的第一项是 name shadowing,这个功能允许(甚至鼓励)频繁地重新声明一些拥有相同名字的变量;shadowing 机制 "适用于 Rust",但对许多其他语言来说就完全不适用了。

一个更棘手的方面是类型推理(type inference);Rust 允许声明既没有类型(type)也没有初始化(initializer)的变量,并期望编译器最终会自己判断出来。他举了一段代码作为例子:

let a;      // No type or initializer
a = 123;      // a is some type of integer

let b: u32 = 3; // b is a 32-bit integer
let c = a + b;  // 32-bit math all around now

他说,gccrs 的 internal representatioh 会让这种类型推理生效。

然后,还有一个有趣的概念,那就是 Rust 的 never type。在 Rust 中,这样的代码是有效的:

let a = return;     // a = !

这个 return statement 做了人们所期望的事情,但是这个 state 也有一个结果,就是把从来没有的类型(用"!" 代表)分配给了变量 a。一个更现实的例子可能是在一个 match 表达式中有一个 return statement。写这样的代码无论在何时都是合法的:

let b = a + 1;    // b = ! + integer
let a = 123;  // ! can be coerced to other types

never type 是一个尚未稳定的功能。Cohen 跳出来说,没有人会写上面那样的代码,但 never type 确实可以用来实现一些有趣的工作。

Herron 提到的其他挑战还包括 struct field 的 automatic dereferencing,这是另一个没有文档描述的行为,他花了一些时间才搞清楚。monomorphization (单态)也需要一些工作,以及不少特殊情况处理。Cohen 提到了 Rust 的(某种程度上)面向对象的特性和它们所需要的额外检查。尤其是可见性方面很有意思;pub 会使一个对象对所有 link 了它的整个二进制文件都是可见的,而 pub(crate)将可见性限制在了当前的 crate,pub(super) 使它对父模块(parent module)可见。他说,要管理好所有这些,都是很困难的。当然,Rust 编译器还必须实现 unsafe,从而关闭掉编译器中的很多检查。

Generating code

Faust 简单谈了一下代码生成(code-generation)阶段的挑战。这项工作的主要内容是将 gccrs 的内部表述(IR)翻译成 GCC 后端所使用的树状结构(tree structure)。有些结构,如 if 和 loop,都是相对简单的。但是还有很多不简单的结构。

他特别提到了 match 表达式,他将其描述为 "对许多 steroid 进行的 switch 语句,甚至可能是非法的"。最简单的情况可以直接用 switch 语句来映射,但 match 的全部意义在于它会是在简单情况下使用。例如,涉及 tuple 的 match,就必须每次尝试匹配一个元素,这是 GCC 的 IR 之前设计时没有支持的行为。Arm guards(本质上是一个额外的 if,来控制一个特定的 match 是否发生)也使事情变得复杂,因为在执行 guard 表达式之前,match 所设置的变量必须先被 bind 好。

Gccrs 现在有一个很不错的 const evaluation 的 module;它是由一个 Google Summer of Code 的学生从 C++ evaluator 中衍生开发来的。rustc 的开发者最近不得不更新他们的编译器来修复一个 const-evaluation 的 bug,但是,开发者们很高兴地发现 gccrs 已经在正确处理这种情况了。

所以,Herron 继续说,gccrs 什么时候能准备好?它现在基本上可以编译 libcore,能基本工作了。还有其他的一些核心库,包括 liballoc 等,目前还没有完成编译,但应该更容易了,他说。另一方面,Cohen 说,实现 procedural macro 的代码将更加困难;它会迫使编译器成为一个 server,把 token 发送给另一个 libproc 可执行文件。这意味着需要在编译器的 front-end 部分实现一个 remote procedure call server。

然后,Herron 提到还有 borrow checking,这是该语言的固有特性。如果没有 borrow checking,gccrs 就不是一个真正的 Rust 编译器,而它目前确实就还没有。计划使用 Polonius(当前是针对 rustc 开发的)来避免重复进行相同的开发。

Herron 补充提到,他一直在与 Rust-for-Linux 的开发者讨论编译内核代码的问题。Rust 的版本管理是基于 "edition" 的概念,这是确保兼容性的一个核心机制。但是,由于内核代码使用了大量暂时还不是稳定功能的特性,其中一些特性还 "没有明确地看到可以" 后续稳定下来,所以现在不能依靠 edition 保证。Herron 说,在没有语言标准(language standard)的情况下,很难创建一个有价值的编译器。gccrs 的开发者正在努力将 kernel module 添加到他们的 test case 中,但还是需要不少时间才能正确地支持内核相关的开发工作。在会议结束时,Mark Wielaard 问道,在使用 unstable feature 方面,内核是否是唯一的一个项目?答案是 "每个人都在使用 unstable feature"。

[感谢 LWN 的订阅者支持编者参加这次活动。]

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

format,png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值