本文主要介绍了使用 GCC 引导 Rust 编译器的相关内容,包括引导过程的三个阶段、遇到的三个小问题及解决方法等,具体如下:
- Bootstrapping Rust with GCC:介绍了使用
cg_gcc引导 Rust 编译器的任务,引导过程分为三个阶段,第一阶段使用现有的基于 LLVM 的 Rust 编译器构建rustc和基于 GCC 的代码生成器;第二阶段使用基于 GCC 的代码生成器重新构建 Rust 编译器;第三阶段进行健全性检查,使用stage2再次构建编译器。如果stage1和stage2生成的可执行文件相同,则它们的行为相同,意味着用 GCC 构建的 Rust 编译器与用 LLVM 构建的编译器大致等效。 The 3 little buglets:
- Giving the compiler a lobotomy:通过“给编译器做脑叶切除手术”的调试技术,找出了一些编译器错误,如
libgcccjit显示错误,抱怨 IR 问题,然后中止构建并给出有问题的 crate 名称。通过修改源代码中的函数,如使用if语句处理不支持的 128 位匹配、删除内联引起问题的代码、禁用优化等,解决了一些问题。 - Go inlinine yourself:第一个遇到的问题是在递归函数上使用
#[inline(always)]属性,这会导致编译器无法完全内联函数,因为函数会一直包含一个未内联的自身调用。实际上,#[inline(always)]并不保证函数总是被内联,它只是一个建议。为了解决这个问题,可以将#[inline(always)]视为普通的#[inline],或者使用更复杂的方法来检测和处理递归函数。 - Breaking the cycle of inling:问题的根源在于要求 GCC 做一些不可能的事情,即无条件内联递归函数。最简单的解决方案是将所有使用
#[inline(always)]的情况视为普通的#[inline],但这会导致性能下降。另一种方法是检查函数是否递归调用自身,但这种方法忽略了间接递归函数,并且实现起来比较复杂。
- Giving the compiler a lobotomy:通过“给编译器做脑叶切除手术”的调试技术,找出了一些编译器错误,如
- Attribute based check:可以通过检查函数是否调用了另一个带有相同
#[inline(always)]属性的函数来防止递归内联引起的问题。这种方法简单易懂且易于实现,只需要获取函数的 MIR 并检查其中的块和终止器即可。 - 128 bit switch:另一个阻止 GCC 引导
rustc的问题是 128 位SwitchInt终止器的实现不正确,导致libgccjit报错。长期的解决方案是向libgccjit添加一个新函数,以直接获取 128 位常量,但这并不容易。一种简单的解决方法是将 128 位开关拆分为两个 64 位开关,但这会引入不必要的分支和复杂性。另一种方法是使用if语句代替开关,利用编译器的优化来实现 128 位开关。 - Diagnosing a segfault:编译器崩溃可能是由于无效的内存访问、数据损坏、堆栈溢出或不正确的对齐等原因引起的。通过切换优化级别和使用核心转储等方法,可以帮助诊断问题。在本文的案例中,发现是由于对齐问题导致指针解引用错误,在处理 128 位整数时,
libgccjit跳过了对齐设置,导致代码生成错误。
最后,作者表示还有很多工作要做,如使用rustlantis进行模糊测试、在 ARM 上引导、ABI 错误和 GCC 错误等,并推荐读者查看 GSoC zulip 频道以获取最新的进展信息。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。