同步Rust GCC后端,或如何验证墨菲定律
Rust GCC 后端与 Rust 仓库同步耗时两个月,git subtree 在大型仓库上表现不佳,墨菲定律全程应验。
中文
复制

同步 Rust GCC 后端,或曰如何验证墨菲定律
这篇博客讲的是 Rust GCC 后端(别和 gccrs 搞混,那是 GCC 编译器的 Rust 前端),我们如何把它的仓库与 Rust 的仓库同步,以及整个过程如何一路崩坏,以至于我们花了两个月才终于做成。墨菲定律的绝佳例证:
凡是可能出错的事,就一定会出错。
GCC 后端是怎么开发的
Rust GCC 后端在自己的仓库里开发:https://github.com/rust-lang/rustc_codegen_gcc/。这样我们可以随便折腾,不用担心弄坏 Rust 的 CI。也就是说,这些改动目前只存在于 GCC 后端仓库里。
由于它是 Rust 编译器的代码生成后端(以下简称为 codegen),它的源码也存在于 Rust 编译器仓库中:https://github.com/rust-lang/rust/tree/main/compiler/rustc_codegen_gcc。这个目录的内容和我们的仓库完全一致……大多数时候是这样。当编译器加入新特性、做清理等等时,有时需要在各个 codegen 中同步体现:LLVM、cranelift 和 GCC。于是就会出现只存在于 Rust 仓库中的改动。
所以两个仓库各有各的独有改动,我们得在某个时间点把它们合并起来,让两边保持同步。
同步是怎么做的
接下来是“有趣”的部分:在两个仓库之间同步改动。我们用的是 git subtree 功能。没听说过?不奇怪,这是个不完整的功能(在大型仓库上表现不佳)。修复它的 pull request 可以在 git 仓库里看到,链接在此。所以你需要克隆 git 仓库,checkout 到这个分支,构建这个 git 版本,并在本地某个地方放好,需要时拿来用。具体操作说明在这里。
打好补丁的 git 准备好之后,下一步就是把 Rust 仓库的改动合并进 rustc_codegen_gcc 仓库:
# Current folder is rust's repository.
git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name
cd ../rustc_codegen_gcc
git checkout master
git pull
git checkout sync_branch_name
git merge master
Rust 的改动合并进 GCC 后端之后,我们就可以把 rustc_codegen_gcc 的改动送回 Rust:
# Current folder is rust's repository.
git pull origin master
git checkout -b subtree-update_cg_gcc_YYYY-MM-DD
git-subtree pull --prefix=compiler/rustc_codegen_gcc/ https://github.com/rust-lang/rustc_codegen_gcc.git master
git push
由于 rustc_codegen_gcc 针对的是某个特定的 GCC 版本(我们自己的 fork,包含尚未合并的上游 GCC 改动),我们还需要更新 Rust 的 GCC 子模块:
# Current folder is rust's repository.
cd src/gcc
git fetch
git checkout $(cat compiler/rustc_codegen_gcc/libgccjit.version)
提交改动,确认能正常编译,然后就可以推送了。
最后一件事:Rust 仓库的 pull request 合并之后,要把 merge commit 再合回 rustc_codegen_repository:
# Current folder is rust's repository.
git-subtree push -P compiler/rustc_codegen_gcc/ ../rustc_codegen_gcc/ sync_branch_name
终于搞定了!
接下来聊聊最后一次同步,以及我为什么会提到墨菲定律。
到目前为止还算顺利
2026 年 7 月 24 日,我提交了 #159844。遇到的第一个问题是许可证问题。我们复制了一个用来判断某些 CPU 支持哪些特性的文件。经过(法律层面的)讨论,这个问题在 7 月 31 日解决。
与此同时,我们还遇到了 CI 镜像的问题,因为我们用的新 GCC 版本需要一些「更新」的软件包。为了减少 CI 因网络导致的偶发错误,Rust 基础设施提供了一个镜像,通常比直接从外部源下载可靠得多(地址不变,流量也小得多)。
我们更新 GCC 版本时,Rust 的 CI 会把它完整重新构建一遍,供开发 GCC 后端的 Rust 开发者下载。而新的 GCC 版本需要的 make 比我们所用 docker 镜像里的版本更新。于是:把新的 make 源码加到镜像里,构建进 docker 镜像,然后像之前一样构建 GCC。
这样全部修好了!8 月 3 日合并。到目前为止三个问题,不算好,但也不算最糟。还算顺利。
不过你可能已经猜到了,事情并没有到此为止。
听说过副作用吗?
刚合并,我就在 zulip 的 infra 团队频道被 ping 了,因为 CI 彻底挂了,报错信息看起来和 GCC 有关。于是我们决定回滚这次同步的 pull request。当然,用 github UI 回滚的时候:
Revert failed: Repository rule violations found
Cannot create ref due to creations being restricted.
于是手动回滚:#160468,CI 恢复正常,一切照旧。只是同步仍然没完成,而且我们完全不知道那次同步的 pull request 为什么 CI 会通过。调查之后发现,构建出来的 GCC 没有启用某些特性,导致部分测试失败。最大的疑问是:那同步的 CI 当时为什么能过?这倒让我们发现了 CI 缓存里的一个 bug。缓存问题修好之后,只剩一个 bug:GCC 的构建方式不对。
有意思的是,编译 GCC 时,它会根据系统上可用的工具自行配置。如果你的链接器不支持 retain,这个功能就会被禁用。最近我们在 GCC 后端中加入了对外部可实现项(externally implementable items)的支持……它用到了 retain(如果你感兴趣,GCC 后端中这个功能的 PR 是 gcc#88、gcc#89、cg_gcc#921 和 cg_gcc#962)。于是 Rust 仓库中相关的测试对 GCC 后端取消了忽略,然后失败了。
所以第一件事就是安装更新版本的 binutils,修好 CI 里 GCC 的构建。我在 8 月 12 日的 #161006 中做了这件事。试了很多次之后终于跑通,8 月 17 日合并。
最后一个阻塞项清掉了!哦等等,它把 miri 的构建搞坏了。回滚!
与其在 docker 里用它替换已安装的 binutils,为什么不只让 GCC 构建使用它,把影响范围限制住?试试看。在 #161243 中完成,8 月 21 日合并。
……但 miri 又坏了!好在这次只是时机不巧:其实是另一个不相关的 PR 更新了 cc crate,恰好在我们之后合并。虚惊一场。
这之后过了太久,我们需要再次把 Rust 仓库的改动同步到 rustc_codegen_gcc 仓库,于是开了 cg_gcc#959。然后……失败了。Rust bootstrap 中的一处改动破坏了 sysroot 的处理(简单说,就是 rustc 该去哪里找库),它再也找不到 GCC 的库了。9 月 3 日在 #162152 中修复。
该再试一次 Rust 到 rustc_codegen_gcc 的同步了:cg_gcc#972,也失败了。这次的问题是,rustc_codegen_gcc 中必须使用和 Rust 相同的 LLVM 版本,因为有些测试要用 LLVM 工具(比如汇编测试用 FileCheck)。
于是在更新 LLVM 以及我们用来构建 rustc_codegen_gcc 的 rust 版本之后,我们又开了一次同步:cg_gcc#974。这次成了!\o/ 9 月 8 日合并。
是时候再试一次子树同步(rustc_codegen_gcc 到 Rust)了,这次用 #162499。CI 挂了,原因是 run-make 的一些测试(Rust 里的一个测试套件)。行吧,这个锅我背。在上一次修复和这次同步之间,我修了 Rust bootstrap 里的一个 bug,它导致 LLVM 之外的其它 codegen 后端跑不了 run-make 测试,见 #162482。
考虑到这一点,我加了一个 commit,在 GCC 后端这边修好之前先忽略这些测试。
再跑一次 CI……又挂了!这次是因为这期间我们在 rustc_codegen_gcc 里升级了 GCC 版本,它要求更新的 make 版本。那就这样吧!为了确认升级 make 版本不会搞坏东西,我开了 #163031,只包含这一处改动。
看起来没问题。那好,是时候试着合并这次同步了!
9 月 22 日,#162499 被合并,同步终于完成了!
接下来
这一趟运气实在不怎么样!但一路下来,我们改进了 Rust 的 CI,发现了缓存问题,修了一些测试套件的 bug 和局限。总的来说,虽然累,但收获不小。
不过这次同步也暴露出这个流程问题太多,我们该换个做法了。好在已经有一些子树迁移到了 josh,所以我们打算很快也对 rustc_codegen_gcc 做同样的事。
最后的话
猫睡得太沉,没能带来好运。
发布于 2026/09/22 17:00,作者 @GuillaumeGomez
[上一篇文章
crates.io 前端及其问题](https://blog.guillaume-gomez.fr/articles/2026-07-13+crates.io+front-end+and+its+issue%28s%29)