この記事は Rust Advent Calendar 2022 14日目の記事です。
記事を書いているうちに、GCC Rust パッチセット第1弾がメインラインにマージされていたので、タイトルも含め、いくつか修正&追記しておきました。(2022/12/15)
はじめに
2022年12月11日、ついに Rust support 第1弾がマージされた Linux Kernel 6.1 が正式リリースされました。今回は、長いカーネルシンボル名のサポート、Rust コードのビルドルールやスクリプト、Rustカーネルアロケータやprintk周りのサポート、Rustで書かれたカーネルモジュールのサポートなど、最小限の基盤的なコードがマージされました。
既に Rust support 第2弾が提案されており、Rust での Linux カーネルの開発が現実的なものとなり、低レイヤ好きの Rustecean の活躍の場が大きく拡がります、たぶんね。
それと同じくらいに期待しているのが、GCC Front-End for Rust (GCC Rust; コマンド名 gccrs
) の GCC(Gnu Compiler Collection)へのマージの方針が GCC Steering Committee によって承認され、パッチセット第1弾が GCC のメインラインへマージされたというニュースです。
将来的に Rust-for-Linux が LLVM ベースの clang + rustc だけではなく、GCC ベースの gcc + gccrs でもビルドできる可能性があり、OSS の安全保障上からも(ちょっとおおげさ?)重要な営みでしょう。また、バックエンドが LLVM から GCC になることで、GCC がサポートする豊富な CPU アーキテクチャへの対応が可能となるなどのメリットが期待できます。
GCC Rust は C++ で記述されており、まだまだ実験的なコンパイラで、今後多くの変更が加えられるでしょうし、実際の Rust プログラムをコンパイルするには時期尚早というところです。ですが、Rust も Swift も Go も、言語処理系で開発して楽しい部分は残ってないよね、とお嘆きのあなた、いかがでしょうか?
ということで、ちょっとだけ触ってみました。
GCC Rust のビルド
開発環境としては Ubuntu がおすすめです。22.04 LTS で良いと思います。まずは、ビルドするためのパッケージをインストールしましょう。基本的には GNU C Compiler をビルドするために必要なパッケージ群となります。
$ sudo apt install build-essential libgmp3-dev libmpfr-dev libmpc-dev flex bison autogen dejagnu
次に、レポジトリをクローンします。GCC のような大規模なプロジェクトでフルクローンするとストレージを圧迫するので、パーシャルクローン(ブロブレスクローン)でやってみます。
$ git clone --filter=blob:none https://github.com/Rust-GCC/gccrs.git
GCCのメインラインからビルドする場合には、https://gcc.gnu.org/git/gcc.git
や https://github.com/gcc-mirror/gcc.git
からクローンして下さい。
ビルドはソースツリーの外で行いましょう。
$ mkdir gccrs-build
$ cd gccrs-build
$ ../gccrs/configure --prefix=$HOME/gccrs-install --disable-bootstrap --enable-multilib --enable-languages=rust
$ make -j6
-j6
のところはマシンのコア数やメモリサイズに合わせて適当に。make check-rust
でテストがだいたい通れば OK です。インストールして、パスを通しましょう。
$ make install
$ export PATH="$HOME/gccrs-install/bin:$PATH"
まずは、次のようなプログラムで、動作を確認してみましょう。
fn main() {}
起動します。
$ gccrs -O2 test.rs -o test
rust1: fatal error: gccrs is not yet able to compile Rust code properly. Most of the errors produced will be gccrs' fault and not the crate you are trying to compile. Because of this, please reports issues to us directly instead of opening issues on said crate's repository.
Our github repository: https://github.com/rust-gcc/gccrs
Our bugzilla tracker: https://gcc.gnu.org/bugzilla/buglist.cgi?bug_status=__open__&component=rust&product=gcc
If you understand this, and understand that the binaries produced might not behave accordingly, you may attempt to use gccrs in an experimental manner by passing the following flag:
`-frust-incomplete-and-experimental-compiler-do-not-use`
or by defining the following environment variable (any value will do)
GCCRS_INCOMPLETE_AND_EXPERIMENTAL_COMPILER_DO_NOT_USE
Forcargo-gccrs, this means passing
GCCRS_EXTRA_FLAGS="-frust-incomplete-and-experimental-compiler-do-not-use"
as an environment variable.
compilation terminated.
おやおや、怒られてしまいました。不具合の報告先とリスクを理解してから、
$ export GCCRS_INCOMPLETE_AND_EXPERIMENTAL_COMPILER_DO_NOT_USE=
としてから使って下さい、ということだそうです。このおまじないで、Rust プログラムが普通にコンパイルできるようになります。
cargo-gccrs のインストール
Rust 単独のプロジェクトであれば、やはり cargo
を使いたくなります。cargo-gccrs
を使うと、rustc
の代わりに gccrs
で build
, run
, test
ができるようになります。
つまり、
$ cargo gccrs run --release
とすることで、gccrs を使って、最適化フラグをオンにして、パッケージをビルドしてランすることができるようになります。
なお、最新の gccrs
へ追従していなかったので、パッチをプルリクしたら、早速マージしていただけました。これをインストールするために、GitHub からインストールします。cargo-gccrs
は Rust で書かれていて、現時点では rustc
を使わないとビルドできません。
$ cargo install --git https://github.com/Rust-GCC/cargo-gccrs cargo-gccrs
Rust プログラムをコンパイル
GCC Rust には std ライブラリや core ライブラリがまだ同梱されておらず、no_std, no_core の状態となりますので、Cライブラリの力を借りつつ、core ライブラリ相当も書いてやる必要があります。main()
は i32
を返すようですので、いつものコードはこんな感じになります。
mod core;
extern "C" {
fn write(fd: usize, buf: *const u8, len: usize);
}
fn main() -> i32 {
unsafe {
let msg = "Hello, World!\n";
let buf = msg.as_ptr();
let len = msg.len();
write(1, buf, len);
}
0
}
no_core な環境で基本データ型 str
のメソッドさえも定義されていないので、これらのメソッドを GCC Rust のテストプログラム1から拝借して書いてみます。とりあえず必要になるのは as_ptr()
と len()
だけになりますが、それを定義するためにはファットポインタの定義などが必要になったり、...、結局、下記のようになりました。
mod mem {
extern "rust-intrinsic" {
#[rustc_const_stable(feature = "const_transmute")]
fn transmute<T, U>(_: T) -> U;
}
}
struct FatPtr<T> {
data: *const T,
len: usize,
}
pub union Repr<T> {
rust: *const [T],
rust_mut: *mut [T],
raw: FatPtr<T>,
}
impl<T> [T] {
pub const fn len(&self) -> usize {
unsafe { Repr { rust: self }.raw.len }
}
}
impl str {
pub const fn as_ptr(&self) -> *const u8 {
self as *const str as *const u8
}
pub const fn as_bytes(&self) -> &[u8] {
unsafe { mem::transmute(self) }
}
pub const fn len(&self) -> usize {
self.as_bytes().len()
}
}
あとは、Cargo.toml
をさらっと書いて、run
してみます。
$ cargo gccrs run
Compiling hello v0.1.0 (/home/xxx/Misc/hello)
src/core.rs:8:1: warning: struct is never constructed: ‘FatPtr’
8 | struct FatPtr<T> {
| ^
Finished dev [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/hello`
Hello, World!
ちょっと warning が出ていますが、無事に動きました。
GCC Rust の現状
こちらを見ると、monthly と weekly のプロジェクトの進捗レポート2をみることができます。
2022年12月12日の weekly レポートをちらっと覗いてみましょう。
マイルストン | 先週 | 今週 | 差分 | 開始日 | 終了日 | 目標 |
---|---|---|---|---|---|---|
Data Structures 1 - Core | 100% | 100% | - | 2020/11/30 | 2021/1/27 | 2021/1/29 |
Control Flow 1 - Core | 100% | 100% | - | 2021/1/28 | 2021/2/10 | 2021/2/26 |
Data Structures 2 - Generics | 100% | 100% | - | 2021/2/11 | 2021/3/14 | 2021/5/28 |
Data Structures 3 - Traits | 100% | 100% | - | 2021/5/20 | 2021/9/17 | 2021/8/27 |
Control Flow 2 - Pattern Matching | 100% | 100% | - | 2021/9/20 | 2021/12/9 | 2021/11/29 |
Macros and cfg expansion | 100% | 100% | - | 2021/12/1 | 2022/3/31 | 2022/3/28 |
Imports and Visibility | 100% | 100% | - | 2022/3/29 | 2022/7/13 | 2022/5/27 |
Const Generics | 100% | 100% | - | 2022/5/30 | 2022/10/10 | 2022/10/17 |
Initial upstream patches | 100% | 100% | - | 2022/10/10 | 2022/11/13 | 2022/11/13 |
Upstream initial patchset | 78% | 79% | +1% | 2022/11/13 | - | 2022/12/19 |
Final set of upstream patches | 20% | 21% | +1% | 2022/11/16 | - | 2023/4/30 |
Intrinsics and builtins | 18% | 18% | - | 2022/9/6 | - | TBD |
Borrow checking | 0% | 0% | - | TBD | - | TBD |
Const Generics 2 | 0% | 0% | - | TBD | - | TBD |
Rust-for-Linux compilation | 0% | 0% | - | TBD | - | TBD |
このところは、GCC 13 のマージウィンドウに向けたパッチセットの開発に注力しているようですが、メインラインにマージされたようですので、Upstream initial patchset のマイルストンは次週は完了フラグとなると思われます。個人的に気になる Rust-for-Linux compilation というマイルストンもありますね。Borrow checking がこれからということで、コンパイラ本体の開発に大きなマイルストンが残っていますし、core や std のライブラリの開発という大きな山も残っています。
おわりに
GCC 用の Rust フロントエンドについて、軽く紹介してみました。実用にはまだまだですが、逆にコントリビュートの余地が広く残されていますので、「我こそは」という方、ぜひ、よろしくお願いします。人によっては、クリスマスから17連休という噂もあり...
なお、GCC にコントリビュートする場合には、まず初めにこちらのドキュメントをしっかりと読んでから。
補足: macOS でのビルド
macOS Big Sur on x86_64 でビルドするためには、Homebrew などでビルドに必要なパッケージをインストールしてから、いくつか引数を追加して configure
を起動する必要があります。macOS の clang++
では GCC Rust のコンパイルに失敗するので、g++-12
が必要です。下記の例では、Homebrew のインストール先を /usr/local
としていますので、それぞれの環境に合わせて修正してみてください。
$ brew update
$ brew install gcc-12 gmp mpfr libmpc autogen dejagnu
$ CC=gcc-12 CXX=g++-12 ../gccrs/configure \
--build=x86_64-apple-darwin20 \
--prefix=$HOME/Work/gccrs-install \
--disable-bootstrap \
--enable-languages=rust \
--enable-multilib \
--with-native-system-header-dir=/usr/include \
--with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \
--with-gmp=/usr/local \
--with-mpfr=/usr/local \
--with-mpc=/usr/local
...
...
$ make -j6
...
...
$ make install
...
...
ちなみに、AArch64 (Arm64) on Darwin のブランチがまだマージされていないため、aarch64-apple-darwin21 は not supported と言われてしまいます。