Rust GCCバックエンドの同期、あるいはマーフィーの法則をいかに検証するか

Rust GCC バックエンドと Rust リポジトリの同期に2か月、大規模リポジトリでの git subtree は振るわず、マーフィーの法則が終始現実となった。

日本語
コピー
一只玳瑁猫睡在作者并拢的两腿之间(原文用这张照片当配图)

Rust GCC バックエンドの同期、あるいは墨菲定律をどう証明するか

この記事は Rust GCC バックエンド(gccrs と混同しないこと。あれは GCC コンパイラの Rust フロントエンドだ)について、そのリポジトリを Rust のリポジトリとどう同期しているか、そしてその作業がどう崩壊し続け、完了までに2か月かかったかを書いたものだ。墨菲定律の完璧な実例である:

失敗しうることは必ず失敗する。

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 のリポジトリにしかない変更が生まれる。

つまり両方のリポジトリにそれぞれ固有の変更があり、どこかの時点でそれらをマージして両者を同期させる必要がある。

同期の手順

ここからが「楽しい」部分だ:2つのリポジトリ間で変更を同期する。我々は git subtree 機能を使っている。聞いたことがない? 無理もない、不完全な機能だからだ(大規模リポジトリでは性能が悪い)。これを修正する pull request は git リポジトリにあり、リンクはこちら。つまり git リポジトリを clone し、このブランチを 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 バージョン(まだ上流 GCC にマージされていない変更を含む我々自身の fork)を対象にしているので、Rust の GCC サブモジュールも更新する必要がある:

# Current folder is rust's repository.
cd src/gcc
git fetch
git checkout $(cat compiler/rustc_codegen_gcc/libgccjit.version)

変更をコミットし、問題なくコンパイルできることを確認して、push する。

最後にもう一つ: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 バージョンは、我々が使っている docker イメージ内のバージョンより新しい make を必要とした。そこで新しい make のソースをイメージに追加し、docker イメージにビルドし込み、これまで通り GCC をビルドする。

これで全部直った! 8月3日にマージ。ここまでで3つの問題、良いとは言えないが最悪でもない。順調だ。

だが察しがついているだろうが、話はここで終わらなかった。

副作用って知ってる?

マージした直後、CI が完全に落ちたと zulip の infra チームチャンネルで ping された。エラーメッセージは GCC に関係していそうだった。そこでこの同期の pull request を revert することにした。もちろん、github UI で revert しようとしたら:

Revert failed: Repository rule violations found

Cannot create ref due to creations being restricted.

手動で revert した:#160468。CI は復旧し、いつも通りになった。ただ同期はまだ完了しておらず、あの同期の pull request でなぜ CI が通ったのかは全く分からないままだった。調べてみると、ビルドされた GCC で一部の機能が有効になっておらず、一部のテストが失敗していた。最大の疑問は、あのとき同期の CI はなぜ通ったのか、だ。これで CI キャッシュのバグが1つ見つかった。キャッシュの問題を直すと、残ったバグは1つだけになった:GCC のビルドの仕方が間違っている。

面白いことに、GCC をコンパイルすると、システム上で利用可能なツールに応じて自身を設定する。リンカが retain に対応していなければ、この機能は無効になる。最近 GCC バックエンドに externally implementable items のサポートを追加した……これが retain を使っている(興味があれば、GCC バックエンドにおけるこの機能の PR は gcc#88、gcc#89、cg_gcc#921、cg_gcc#962 だ)。すると Rust リポジトリの関連テストが GCC バックエンドで ignore を外され、そして失敗した。

最初の作業は、新しいバージョンの 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日にマージ。

そろそろ subtree 同期(rustc_codegen_gcc から Rust)を再挑戦する頃合いだ。今回は #162499 で。CI が落ちた。原因は run-make のいくつかのテスト(Rust 側のテストスイート)。まあ、これは自分の責任だ。前回の修正と今回の同期の間に、LLVM 以外の codegen バックエンドで run-make のテストが動かなくなる Rust bootstrap のバグを直した。詳細は #162482。

それを踏まえ、GCC バックエンド側で修正されるまでこれらのテストを無視する commit を追加した。

CI をもう一度回す……また落ちた!今度は、その間に rustc_codegen_gcc で GCC のバージョンを上げたため、より新しい make が必要になったからだ。ではそうしよう。make のバージョンアップが何も壊さないことを確認するため、この変更だけを含む #163031 を開いた。

問題なさそうだ。では、この同期をマージしてみよう!

9月22日、#162499 がマージされ、同期はついに完了した!

次はどうするか

今回の道のりは本当についていなかった。だがその過程で Rust の CI を改善し、キャッシュの問題を見つけ、いくつかのテストスイートのバグと制約を直した。全体として、疲れたが収穫は大きかった。

とはいえ、この同期はこのやり方に問題が多すぎることも露呈した。幸い、いくつかの subtree はすでに 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)

出典: guillaume-gomez.fr← ホームへ戻る