プログラミング
EurydiceによるRustの読みやすいCへのコンパイル
Compiling Rust to readable C with Eurydice (lwn.net)
要約
Eurydiceは、Rustコードを構造を保ったまま読みやすいCコードに変換する新しいプロジェクトです。これは、既存の検証ツールがCを要求する高信頼性ソフトウェア開発において特に有用です。このツールは、Rustのコンパイラ実装の多様化に貢献し、Rustコンパイラが存在しない環境への移行を容易にします。
全文翻訳
Daroc Alden著 2026年1月30日
数年前、Rustコードをコンパイルする唯一の方法は、LLVMをバックエンドとしてrustcコンパイラを使用することでした。それ以来、Mutabah's Rust Compiler (mrustc)、GCCのRustサポート (gccrs)、rust_codegen_gcc、Craneliftを含むいくつかのプロジェクトが、Rustのコンパイラ実装の多様化において大きな進歩を遂げてきました。この中で最も新しいプロジェクトであるEurydiceは、より野心的な目標を持っています。それは、RustコードをクリーンなCコードに変換することです。これは、既存の検証およびコンプライアンスツールがCを期待する高信頼性ソフトウェアにおいて特に有用です。そのようなツールがRustで動作するように更新されるまで、Eurydiceはこれらのプロジェクトへのよりスムーズな移行、およびCコンパイラは存在するが動作するRustコンパイラがない環境への足がかりを提供できます。例えば、Eurydiceは、RustからCへのポスト量子暗号ルーチンのコンパイルに使用されてきました。
Eurydiceは2023年に開始され、MITライセンスの下にあるコードとApache-2.0ライセンスの下にあるコードが含まれています。これは、Rustコードに形式検証ツールを適用することに関連するいくつかの異なるツールを開発するAeneasプロジェクトの一部です。様々なAeneasプロジェクトは、フランスの国立コンピュータサイエンス研究機関であるInriaとMicrosoftに雇用されている人々のグループによって維持されていますが、外部からの貢献も受け入れています。
Eurydiceは、多くのコンパイラと同様の一般的な構造に従います。Rustプログラムを受け取り、それを中間表現 (IR) に変換し、一連のパスでIRを変更し、それを低レベル言語(この場合はC)のコードとして出力します。Eurydiceの最も多作な貢献者であるJonathan Protzenkoは、プロジェクトのアプローチを説明するブログ記事を書いています。しかし、他のコンパイラとは異なり、Eurydiceはコードの全体的な構造を維持することに重点を置き、Rustには存在するがCには存在しない構造を除去します。例えば、最大公約数 (gcd) を使用して2つの数値の最小公倍数 (lcm) を計算するこのRust関数を考えてみましょう。
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a%b)
}
}
fn lcm(a: u64, b: u64) -> u64 {
(a * b) / gcd(a, b)
}
Eurydiceがこれらの関数をCにコンパイルする方法は次のとおりです。
uint64_t example_gcd(uint64_t a, uint64_t b) {
uint64_t uu____0;
if (b == 0ULL) {
uu____0 = a;
} else {
uu____0 = example_gcd(b, a % b);
}
return uu____0;
}
uint64_t example_lcm(uint64_t a, uint64_t b) {
uint64_t uu____0 = a * b;
return uu____0 / example_gcd(a, b);
}
このCコードが「読みやすい」と見なされるかどうかは、おそらく個人の好みの問題です。しかし、コードの構造は維持されています。元のコードの評価順序さえも、順序を定義するために必要な追加の一時変数 (example_lcm() の uu____0) を追加することによって維持されています。(Rustは、乗算がオーバーフローしてパニックを引き起こした場合、example_gcd() の呼び出しによる副作用が発生する前にそれが起こることを保証しますが、Cは乗算が別々のステートメントで実行される場合にのみそれを保証します。)
同じ関数をrustcでコンパイルすると、ビット操作で絡み合ったループのペアになります。これはマシンコード出力には適していますが、読みやすさははるかに劣ります。
もちろん、すべてのRustプログラムが忠実にCに表現できるわけではありません。例えば、範囲ではなくイテレータを使用するforループは、イテレータの状態を管理するためにEurydiceのサポートコードを呼び出すwhileループにコンパイルする必要があります。さらに重要なのは、Cにはジェネリクスの概念がないため、Rustコードは変換中にモジュール化される必要があります。これにより、型のみが異なる関数の実装がいくつか生成される可能性があります。多くの場合、より慣用的なCのアプローチは、マクロまたはvoid *引数を使用することです。動的サイズ型の実装も課題を提示します。Rustでは、フィールドが固定サイズを持たない構造を定義できます。Cの柔軟な配列メンバーのように:
struct DynamicallySized<U: ?Sized> {
header: usize,
my_data: U, // コンパイラはUのサイズを知らない
}
しかし、その構造がジェネリックであり、型のジェネリックユーザーの1つが柔軟なサイズフィールドに既知のサイズの型を与えた場合、コンパイラはその知識を利用して、適切な場所で境界チェックを省略できます。
let foo: DynamicallySized<[u8; 4]> = ...; // 配列サイズが4であることがわかっているため、境界チェックは発行されません。
let bar = foo.my_data[2];
この種の分離、つまりコードの一部は型のサイズを知っている可能性があり、一部は知らない可能性があるという事実は、形式検証の可能性との相互作用のため、Cで維持することが重要な意味論的詳細です。EurydiceがDynamicallySizedを柔軟な配列メンバーをすべてに使用するようにコンパイルした場合、Cコードの分析では、Rustには必要なかった「欠落している」境界チェックが指摘される可能性があります。逆に、Eurydiceが追加の境界チェックを追加した場合、Rustソースに現れない完全に未使用であるべき追加のエラーパスを製造する必要があります。そのため、Eurydiceは2つの異なる型を出力します。柔軟な配列メンバーを持つ動的サイズ型のバージョンと、既知の長さの配列メンバーを持つバージョンです。2つの表現間の変換は実行時にはノープですが、技術的にはCの厳密なエイリアス規則に違反します。したがって、ProtzenkoはEurydice生成コードを-fno-strict-aliasingでコンパイルすることを推奨しています。
関連ツール
より抽象的な言語を、コードの構造を維持する方法でCにコンパイルするというこのアプローチは新しいものではありません。Eurydiceの基盤となっているKaRaMeLプロジェクトは、F*プログラミング言語についても同様のことを行っています。F*は、暗号ライブラリを開発するために使用される依存型関数型プログラミング言語です。証明可能な正しいF*プログラムを同等のCにコンパイルすることで、パフォーマンスが重要なプログラムでそれらのライブラリを使用できるようになります。
残念ながら、Eurydiceは現在、小さな例を超える規模には対応していません。Rustコードの独自のパーサーと型チェッカーを実装する代わりに、Eurydiceは別のAeneasツールであるCharonを使用して、rustcから解析およびプリプロセスされたプログラムを抽出します。私が様々なRustパッケージでCharonをテストしたところ、const genericsのようなより最近のRust機能によって定期的に失敗しました。Charonが機能する場合、rustcの中間レベル中間表現 (MIR) をJSONとしてダンプし、コンパイルを理解するために必要なコンパイラフラグも提供します。EurydiceはこのJSON表現を読み取り、それをKaRaMeLの中間表現に変換します。その後、KaRaMeLコードに対して一連の小さなパスを使用して、Rust固有の詳細をいくつか削除してから、KaRaMeLがF*に使用するのと同じコード生成ロジックに引き渡します。
現在の形式では、Eurydiceは複雑なRust機能を回避する、小さく自己完結型のプログラムで最もよく機能します。しかし、そのニッチな分野ではうまく機能します。生成されたコードは、Eurydiceが追加の中間変数を発行したり、より複雑な機能を実装するためにいくつかのグルーコードを必要とする場所を除いて、元のRustコードと同じ構造を維持します。一方で、小さく自己完結型のコードは手で書き直すのが最も簡単なため、Eurydiceを導入するのは、元のRustコードが更新され、それらを同期させるための自動ソリューションが必要な場合にのみ価値があるでしょう。いずれにせよ、Eurydiceは、Rustコードを折りたたんで、スピンして、切り刻んで、より多くの環境に適合させるための、急速に拡大している方法のコレクションの中で最も新しいツールにすぎません。
[トピックの提案をしてくれたHenri Sivonenに感謝します。]
LWNサイトは現在高いスクレイパー負荷の下にあるため、匿名ユーザーのコメント表示は抑制されています。人間であれば、以下のボタンをクリックすることでこのステップをスキップできます。
注:LWNアカウントにログインすることで、将来このステップを回避できます。