この記事はsoftdeviceのみんなでゆるく記事を書く。の16日目の記事です(遅刻)
先日おもむろにRustでメガドライブの開発をしてみたくなり調べたところ、現在Rustは標準でメガドライブの命令セットM68000向けのビルドに対応していませんでした。
このような場合RustからLLVMの中間コードを出力し、M68000向けのバックエンドでバイナリを出力するのが正当なやりかたのような気もするのですが、ふとRustのコードをCへと変換することができればSDCCなどの既存のCコンパイラを使うことができるのではと思ってRustからCへのトランスパイルについて調べてみました。
調べた限りではLLVM-CBEとmrustcを使う二通りの方法があり、それぞれを試してみました。
変換するRustのコード
単純な出力をするだけのコードをCに変換してみます。
cd ~
cargo new rust-to-c
// ~/rust-to-c/src/main.rs
fn main() {
println!("Rust to C!!");
}
LLVM-CBE
https://github.com/JuliaComputing/llvm-cbe
LLVMのCバックエンド。
以前のLLVMにはデフォルトでC/C++のバックエンドがあったのですが、バージョン3.9から廃止されてしまいました。現在はLLVM-CBE (C backend)を使うことでLLVMの中間コードをCに変換することができるようです。
以下の環境で試しました。
- macOS 10.14.2
- clang 7.0.0
- rustc 1.32.0 nightly
LLVMのビルド
cd ~
git clone https://github.com/llvm-mirror/llvm
cd llvm
git checkout release/16.0
mkdir build
cd build
cmake ..
cmake --build . -j1
LLVM-CBEのビルド
# llvm
cd project
git clone https://github.com/JuliaComputing/llvm-cbe
cd ../build
cmake ..
cmake --build . -j1
LLVM-IR生成
LLVMの中間コードであるLLVM-IRを生成します。
cargoから生成できそうなのですが、使い方がいまいちよく分からなかったので今回はrustcを使っています。
cd ~/rust-to-c
rustc --emit=llvm-ir -C opt-level=3 src/main.rs
ls # main.ll
Cへ変換
~/llvm/build/bin/llvm-cbe -o main.c main.ll
元は単純なコードですが300行ほどのCのコードが生成されました。
このソースをそのままclangでコンパイルするとエラーが出て怒られたので手動で修正しました。
ちなみにrustcで.llに変換する際opt-level=3を指定したらエラーの数が減ったので指定してます。
- fatal error: 'APInt-C.h' file not found
→ 該当行をコメントアウト - error: first parameter of 'main' (argument count) must be of type 'int'...
→uint32_t main(uint32_t, uint8_t**)
をint main(int, char**)
に修正 - error: use of undeclared identifier 'tmp__7'; did you mean 'tmp__6'?
→ Rustのmain関数の引数処理っぽくて今回のコードでは消しても問題なさそうなのでコメントアウト
実行ファイル生成
# dylibのパスは環境によって異なります
clang -dynamic-linker ~/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/libstd-021c609a52903041.dylib -rpath ~/.rustup/toolchains/nightly-x86_64-apple-darwin/lib/ main.c
./a.out # Rust to C!!
実行できるファイルは生成できましたが、力技で修正してるのでいまいちな感じです。
mrustc
https://github.com/thepowersgang/mrustc
mrustcはRustのコンパイラをC++で再実装したもので、ソースのコンパイル過程で一度RustのコードをCに変換します。記事執筆時では、対応しているRustのバージョンは1.19.0でした。
Macだとビルドできなかったので以下の環境で確認しています。
ビルドに結構メモリを使うので仮想環境の場合多めにメモリを割り当てておいた方が良いです。
- Vagrant bento/ubuntu-16.04
- g++ 5.4
- rustc 1.19.0 stable
mrustcのビルド
sudo apt install g++ make patch libz-dev curl cmake pkg-config
cd ~
git clone https://github.com/thepowersgang/mrustc.git
cd mrustc
make RUSTCSRC
make -f minicargo.mk
make -C run_rustc
Cへ変換
minicargoというツールでクレートをビルドできそうなのですがライブラリに対してしか動かないっぽかったので、mrustcで変換しました。
# ~/rust-to-c
~/mrustc/bin/mrustc -L ~/mrustc/output/ ~/rust-to-c/src/main.rs
ls # main main.c
まとめ
それで肝心のM68000向けのバイナリ生成はというと、、no_stdで試してみたのですがSDCCではコンパイルエラーが出てうまくコンパイル出来ませんでした。
成功した方がいたらやり方教えてください。
調べていたらRustでGBAのプログラミングをしている方がいたりして、今までCやアセンブリでしか書けなかったような分野をRustのようなモダンな言語で開発できることに夢を感じました。
懲りずに次はLLVMで真面目にやってみようと思います。