1 ポイント 投稿者 GN⁺ 2025-04-14 | 1件のコメント | WhatsAppで共有
  • rustc_codegen_clr は Rust コードを C と .NET 向けに生成するプロジェクトで、テスト通過率の改善とともに Rust Week 発表 を控えている
  • Rust core テストの通過率は2か月前の 92% から 95.9% まで上昇し、残る約65件のテストも原因が互いに似ているように見える状態だ
  • 最近の修正は 128ビット整数イントリンシック、checked arithmetic、subslicing に集中しており、.NET 側でも Rust core テストの 96.3% を実行できるようになった
  • より多くの C コンパイラを対象にするため、C99 または ANSI C に近い出力と標準 POSIX API の使用を目標としており、一部の ANSI C コンパイラでは非常に単純な Rust プログラムの実行に成功している
  • 大規模な C コード生成でファイルサイズを減らす最適化と内部 IR のリファクタリングも進行しており、古いまたは特殊なプラットフォームへ Rust の実行範囲を広げる基盤になっている

Rust Week 発表とプロジェクトの進捗状況

  • rustc_codegen_clr プロジェクトの発表がオランダの Utrecht で開催される Rust Week に予定されている
  • 発表は初心者にも入りやすくしつつ、Rust-to-C コンパイルという高度なテーマも扱うバランスを目指している
  • この数か月のあいだに、テスト修正、C コンパイラ互換性、性能改善、内部リファクタリングが並行して進められた

Rust core テスト通過率の上昇

  • Rust core テストの通過率は2か月前の 92% から 95.9% に増加した
  • まだ約 65件のテスト が残っているが、原因が互いに似ているように見えるため、残る修正の性質は比較的絞り込まれている
  • .NET 側も同じ修正の影響を受け、Rust core テストの 96.3% を実行できるようになった

128ビット整数と checked arithmetic の修正

  • 改善のかなりの部分は 128ビット イントリンシック、checked arithmetic、subslicing の修正から来ている
  • C の popcount イントリンシックには __builtin_popcount__builtin_popcountl__builtin_popcountll の3種類がある
    • __builtin_popcountll は名前に反して __int128_t ではなく unsigned long long に対して動作する
    • x86_64 Linux と GCC では unsigned longunsigned long long はどちらも64ビットである
  • 既存実装では bit counting イントリンシックで 128ビット整数 を暗黙に64ビットへ切り詰めて計算してしまい、誤った結果を出していた
  • 128ビット popcount は下位64ビットと上位64ビットの set bit 数をそれぞれ数えて足し合わせる方法でエミュレーションしている
  • 128ビット乗算の overflow check については効率的な方法が見つからず、単純な方法で処理している
    • b が 0 ではなく (a * b) / b == a なら overflow はないと判定する
    • 画期的な方法ではないが、いくつかのテストを追加で通すには十分だった

Subslicing バグと fallback イントリンシック

  • subslicing のバグは sizeof の欠落により、slice のデータポインタを 要素単位 ではなくバイト単位で offset していた問題だった
  • このバグは slice の先頭ではなく末尾から subslicing したときだけ壊れており、主に pattern matching で使われる形だったため長く見つからなかった
  • byte slice と string slice では byte と UTF-8 code unit のサイズが1バイトなので個別テストは通っていたが、Rust compiler のテストスイート全体で露呈した
  • 一部のイントリンシックは直接実装せず、Rust compiler の fallback 実装 を利用できる
  • carrying_mul_add は入力の2倍の大きさを持つ整数で乗算を行う必要がある
    • 64ビットまでは処理できるが、128ビット入力には256ビット整数が必要になる
    • LLVM は256ビット整数をサポートしているが、C と .NET はサポートしていない
  • Rust compiler の fallback 実装は128ビット整数で256ビットの乗算と加算を行うため、64ビット整数だけで128ビット演算をエミュレートする際の参考になる

より多くの C コンパイラのサポート

  • プロジェクトはより多くの 特殊な C コンパイラ で Rust コードが動作する可能性を高めようとしている
  • Rust コードが Game Boy で実行されたり、move 命令だけでコンパイルされたり、Temple OS の Holly C で動作したりする事例があった
  • 入手できない proprietary C compiler を直接サポートすることはできないが、obscure C compiler の対応範囲を広げれば、そのような環境で Rust コードが動く可能性は高まる
  • 多くのプラットフォームは文書不足やアクセス性の低さのためにサポートされていないことがある
  • Git の一部を Rust で書く議論に関連して、NonStop のような proprietary プラットフォームは Rust、LLVM、GCC をサポートしておらず、Git サポートの低下や終了につながる恐れがある
  • Rust を C にコンパイルすれば、理論上は C がある場所ならどこでも Rust を実行できる
    • すべてのプラットフォームで問題を回避できるかは、まだ確実ではない
    • 対象プラットフォーム向けコンパイラを合法的に入手するにはサーバー購入が必要で、予算を大きく超えていた
    • そのようなプラットフォームで近いうちに Rust が動く可能性は低そうだ

現在の C 出力戦略

  • 現在の計画は、できる限り 標準準拠の C99 または ANSI C に近いコードを生成することだ
  • thread-local 初期化のために一部の threading support が必要なため、標準 POSIX API のみを使う方向としている
  • 特定のイントリンシック向け fallback 実装を独自に持っており、その一覧を段階的に増やしている
  • 一部の ANSI C コンパイラでは非常に単純な Rust プログラムの実行に成功している
  • 現時点では対応が難しいプラットフォームでも、必要になったとき比較的容易に追加できる状態を目指している

小さな性能改善とファイルサイズ削減

  • 整数 literal の出力では、2^32 未満の整数は hexadecimal より decimal のほうが同じか短いことを反映した
    • 2550xFF より1バイト短い
    • 655360xFFFF より短い
    • 0x prefix があるため、2^32 までは hexadecimal 表記のほうが小さくならない
  • Rust compiler 全体を C に変換する極端なケースでは 最大 1GB の C source file を生成したことがあり、わずかな割合のファイルサイズ削減でも影響がある
  • debug info 挿入に使う #line directive もより賢くなった
    • source file name は変わるときだけ含める
    • debug info 使用時のファイルサイズを大きく減らせる

内部リファクタリングと IR 整理

  • rustc_codegen_clr 内部の一部機能を別 crate に分離し、incremental build の速度を高めた
  • よりメモリ効率の高い interned IR への移行も進行中だ
  • 既存 IR には C にうまく対応しない奇妙な rvalue/lvalue があり、dynamically sized type のような Rust 機能で問題が大きくなっていた
  • 例として MyStr のような custom dynamically sized type では、&self.s は単純に見えても実際には fat pointer metadata を扱う必要がある
    • C99 の compound literal を使えば struct FatPtr_str を1行で作れる
    • ANSI C では一時変数に datameta をそれぞれ入れて返す必要がある
  • Rust と MIR の1行が複数行の C に展開されるため、old IR では temporary local と sub-statement を持つ内部スコープ方式で処理していた
  • 新方式は setup 段階がより複雑だが、IR 全体をより単純にし、最後に残った厄介なケースを解決できれば old IR の該当機能を削除できる

次の作業

  • プロジェクト作業期間は約 1.5年 になり、バグが減るほど残ったバグを見つけるのにより多くの時間が必要になっている
  • 「Rust panics under the hood」第2弾を執筆中で、Rust の panic 過程を段階的に解きほぐす予定だ
  • panic message 生成の説明だけですでに10分分あり、記事を2本に分けることも検討している
  • Rust 向けの小さく正確な メモリプロファイラ も開発中だ
    • コード規模は約 2K LOC
    • 日程は厳しいが、数週間以内に関連する記事を書きたいとしている

1件のコメント

 
GN⁺ 2025-04-14
Hacker News の意見
  • 代替 Rust フロントエンドやバックエンドのどれか一つでも、alpha、hppa、m68k、sh4 で Rust をブートストラップできるようにしてくれるのを、いまだに待っている
    もともと rustc_codegen_gcc プロジェクトがこれを約束していたが、結局果たせなかった
    • その4つのプラットフォームを実際に全部使っているのか、それとも完全なプラットフォーム対応と見なすための任意の基準なのか気になる
    • m68k-unknown-linux-gnu は Rust の Tier-3 ターゲットとしてマージされていなかった? [0]
      [0] https://github.com/rust-lang/compiler-team/issues/458
    • その目標は諦めたのか? 最後に聞いた時点ではまだ開発中だった
    • Rust はまだ OpenBSD on x86_64 すらまともにサポートしていない
  • fractalfir は本当に才能のある人だ。Rust Reddit でよく見かける。自分はコンパイラに詳しいほうではないが、ほかの人たちは彼の成果物をかなり気に入っているようだ
    • かなり若い人だと認識している。今後に明るい未来があることを願う
  • 「std の大半の構成要素は .NET では約95%、C では80%動作する」という内容なのに、HN のタイトルでは 95% の通過率 が .NET にだけ該当する点が抜けている
    GCC/Clang 側は「80%」にすぎない
    • README が古いままだった。その数字は今年初め時点のもので、今は次のとおり
      | .NET Core tests | 1764 | 48 | 20 | 96.29% |
      | C Core tests | 1712 | 71 | 8 | 95.59% |
  • これで試せそうな面白い用途はたくさんある。真っ先に思い浮かぶのは、Python のようなほかの言語との相互運用性の改善だ
    • PyO3 を使えば相互運用性はすでに素晴らしい。ただし、人々が Rust 部分をソースからビルドしようとして、Rust コンパイラのインストールを面倒がる場合は例外だ
      このハックは Rust コンパイラのバックエンドだ。バックエンドはプラットフォーム固有の命令を入力として受け取るため、生成された単純ではない C コードは移植可能ではないはずだ
      ユーザーは事前生成されたプラットフォーム別ソースを受け取るか、Rust コンパイラとこのバックエンドをインストールして自分で生成する必要がある
    • Rust の extern "c" 関数ではすでにできないことで、これが新たにもたらす利点は何?
  • LLVM IR から C に行くのか? それとも Rust AST から C に行くのか?
    • プロジェクトの README で答えを見つけた
      「私の .NET IR 表現は C にうまくマッピングでき、そのおかげで2〜3千行のコードで Rust を C にコンパイルするサポートを追加できた。コードベースのほぼ全体を再利用しており、C と .NET 専用のコードはコンパイルの最後の段階にだけ存在する」
    • rustc のバックエンド、つまり LLVM、GCC、Cranelift バックエンドの代替だ
      最初は .NET バックエンドとして始まったが、そのアプローチが C コード生成も容易にサポートできると分かって追加したものだ。rustc が渡してくるものを独自の**中間表現(IR)**に変換して処理する
  • Nim から C へのコンパイラはテスト通過率が100%だ
  • ところで、Rust らしい保証も維持されるのか?
    • 変換自体にバグがなければ、できない理由はない。静的保証は、通常の Rust コンパイラでコンパイルされる Rust コードを変換するという前提で、すでに検査済みで存在している
      境界チェックのような動的保証も C ランタイムで問題なく実装できる
    • できない理由があるのか?
  • とても素晴らしい。C から Rust へ行く側も素晴らしいものになりそうだ
    • 非自明なプログラムなら、全体を大きな unsafe ブロック一つに入れる必要があるだろう
      C は Rust でコンパイルできるようにするために明示的に知る必要がある情報をすべて渡してはくれない
    • Mark Russinovich が最近、英国の Rust カンファレンスで Microsoft 内部の大規模な C→Rust 変換 の試みについて言及した発表をしていた
      https://www.youtube.com/watch?v=1VgptLwP588
    • そういうツールはすでにある。問題は unsafe ブロックを多用し、たいてい結果のコードが Rust らしくないことだ
      たとえば、グローバル変数ベースの状態機械を、名前付き enum のようなものを使ったより Rust らしい状態機械に変えるのは非常に難しいだろう
      十分に強力な AI の助けがあれば可能かもしれないが、AI はまだ意図したことを実際にやり遂げるにはかなり不足していて、まだ準備ができているとは言い難い。C と Rust のコードベース全体をコンテキストウィンドウに入れられるだけのメモリも必要になるはずで、コードが一定規模を超えると、すぐに非常に高価なハードウェアが必要になるだろう。そうでなければ、多くのコード支援 LLM のように、互いに互換性のないコードを独立に生成してしまう
      それでも C プロジェクトを Rust で拡張したり、少しずつ書き直したりしたいなら、https://c2rust.com/ はすぐに使える
    • https://github.com/immunant/c2rust を見ればよい
    • これによってどんな利点を期待しているのか?
  • Rust を C に? なぜわざわざそんなことをするのか。普通に C を使えばいい。Rust を理解できるなら、C もきっと理解して上手に使えるはずだ
    • Rust の利点と、gcc/C コンパイラ全般の幅広い互換性とアーキテクチャ対応を同時に得るためだ
      Rust はパッケージ管理、簡素化された統合ビルド/テストツール、はるかに少ないレガシーな負担、人々が実際に好む高水準機能と構文を備えた現代的な言語だ
      C もクリーンだが、複雑なコードベースは C の速度を保ちながら堅牢な抽象化を作れる現代的な言語の恩恵を受ける。もちろん借用チェッカーとメモリ安全性も欠かせない
    • rustc がサポートしていないプラットフォームで Rust の利点を得るためだ。かなり直感的だ
    • ここには「できるからやってみる」という性格もかなりありそうだが、Rust がサポートしていないプラットフォームが多いからでもある
      十分に標準的な C コードを生成する Rust→C トランスレータがあれば、そのギャップを埋められる