LoginSignup
6

More than 5 years have passed since last update.

Rust on i586

Posted at

はじめに

Rustの公式32bitバイナリはi686向けになっています。今時ほとんどのx86/32bitはi686なので妥当な選択ではありますが、ごくまれにi586(世代的には初代Pentium~MMX Pentiumあたり)上でRustを動かしたいことがあります。i586上で公式バイナリのrustcなどをそのまま動かしてもillegal hardware instructionとなり実行できません。このエラーについてはこちらで報告されていて、i586向けの独自ビルドを公開している方がいますので、基本的にはそれを使えば問題ありません。
だた、公開先がdropboxということと、stable(1.5.0)しかないということで今回は自分でビルドしてみることにしました。また、ビルドスクリプトも公開し、CircleCIでビルドしてGitHubにリリースするようにしました。

※ちなみにRustの公式パッケージについてはこちらで議論されているようで、i586についても言及があります。そのうち公式にi586バイナリが提供されるようになるかもしれません。

リンク

動作確認環境

CPU: AMD Geode LX 800
Distribution: Arch Linux

使い方

適当なところに展開してパスを通せばいいのですが、multirustを使うと便利です。ただし、i586上で普通にインストールを試みるとunknown CPU typeといわれてしまいますので、以下のようにCPUチェックをごまかします。

curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sed 's/i486/i586/g' | sh

インストール中にエラーが発生して終了しますが無視して問題ありません。multirustコマンドが使えるようになったら以下のようにtoolchainを登録します。--link-localで指定するのはi586版を展開したディレクトリ(rustcバイナリのパスが$path-to-rust-i586-root/bin/rustcとなるように設定)です。

multirust update nightly-i586 --link-local $path-to-rust-i586-root

これでtoolchainとしてnightly-i586(この名前は適当につけて下さい)が使えるようになるので、あとは普通にdefault/override等で指定すれば使えるようになります。

ビルドスクリプト解説

-marchを変えてコンパイルするだけかと思いきやいろいろと小細工が必要だったので一応解説を。CircleCIでビルドするのでメインはcircle.ymlのtest/overrideあたり。

circle.yml
machine:
    environment:
        CFLAGS: -m32 -march=i586
        CXXFLAGS: -m32 -march=i586
        LD_LIBRARY_PATH: /usr/local/lib

dependencies:
    post:
        - sudo apt-get update
        - sudo apt-get install gcc-multilib g++-multilib cmake curl:i386 lib32z1
        - sudo ln -s /lib/i386-linux-gnu/libcrypto.so.1.0.0 /usr/lib32/libcrypto.so
        - sudo ln -s /lib/i386-linux-gnu/libssl.so.1.0.0 /usr/lib32/libssl.so
        - sudo ln -s /usr/lib/i386-linux-gnu/libcurl.so.4 /usr/lib32/libcurl.so
        - sudo ln -s /usr/lib32/libz.so.1 /usr/lib32/libz.so

test:
    override:
        - ./env.sh nightly
        - ./setup_rust.sh
        - make -C rust rustc-stage1
        - make -C rust
        - sudo make -C rust install
        - ./setup_cargo.sh
        - make -C cargo
        - ./clean.sh

この後も続きますが、基本的にenv.shの引数を変えて同じことをしてるだけです。このあたりの重複はなんとかしたいのですがうまい書き方があれば教えてください。(後述する1コマンド2時間制限があるので、単純にスクリプトにまとめるのはうまくいきません)
また、-march指定は環境変数経由で行います。(-marchを指定する箇所はここと、src/i586_unknown_linux_gnu.rsの2か所です)

小細工その1

setup_rust.shの中でソースの展開後、いくつかのファイルを追加・上書きしています。

setup_rust.sh
#!/bin/sh

git clone https://github.com/rust-lang/rust.git

cd rust
git checkout `cat ../branch`

cp ../src/i586-unknown-linux-gnu.mk ./mk/cfg
cp ../src/i586_unknown_linux_gnu.rs ./src/librustc_back/target
echo ./src/librustc_back/target/mod.rs | xargs sed -i.bak -e 's/i686_unknown_linux_gnu/i586_unknown_linux_gnu/g'

cp ../src/get-snapshot.py ./src/etc
echo ./src/etc/snapshot.py | xargs sed -i.bak -e 's/i686/i586/g'

./configure --target=i586-unknown-linux-gnu --host=i586-unknown-linux-gnu --build=i586-unknown-linux-gnu

cd ..

i586-unknown-linux-gnu.mki586_unknown_linux_gnu.rsがrustのtarget追加用ファイルです。また、src/librustc_back/target/mod.rsにtarget一覧があるので、そこのi686-unknown-linux-gnui586-unknown-linux-gnuに変更します。snapshot.py/get-snapshot.pyは後述します。

小細工その2

続いてスナップショット関連です。RustコンパイラはRustで書かれているのでコンパイルするにはRustコンパイラが必要です。ここで必要なRustコンパイラはビルドプロセスの途中でダウンロードされていて、これを行っているのがget-snapshot.pyです。また、その中でダウンロードするバイナリのURLなどを決めているのがsnapshot.pyになります。snapshot.pyにはi686用の記述があるのでi586に変更します。さらに、get-snapshot.pyではrustcの置き換えを行います。

get-snapshot.py
    unpack_snapshot(triple, dl_path)
    os.rename("/home/ubuntu/rust-i586/rust/i586-unknown-linux-gnu/stage0/bin/rustc", "/home/ubuntu/rust-i586/rust/i586-unknown-linux-gnu/stage0/bin/rustc.org")
    shutil.copy("/home/ubuntu/rust-i586/src/rustc", "/home/ubuntu/rust-i586/rust/i586-unknown-linux-gnu/stage0/bin")

unpack_snapshotまでがオリジナルの記述で、ダウンロードしてきたRustコンパイラを展開しています。その後の2行が追記分で、展開されたrustcrustc.orgにリネームし、rustcのラッパースクリプトを置きます。

#!/bin/sh

args=`echo "$*" | sed s/--target=i586-unknown-linux-gnu/--target=i686-unknown-linux-gnu/g`
$(cd $(dirname $0) && pwd )/rustc.org $args -L i586-unknown-linux-gnu/stage0/lib/rustlib/i586-unknown-linux-gnu/lib

わざわざrustcを置き換えるのはダウンロードしてきたrustcがi686用であるためです。最初のsetup_rush.sh内で./configure --target=i586-unknown-linux-gnuを指定すると、このrustcに渡されるtargetもi586-unknown-linux-gnuになりますが、i686用ではその指定は無効です。そのためラッパースクリプトでtargetをi686に置き換えています。また、rustlibへのパスもi686用しか通っていないのでi586用のパスを指定しておきます。

小細工その3

CircleCIは無料版でもビルド時間は1500分/月ということでビルドに4~5時間かかっても問題ないと思っていたのですが、1コマンドあたり2時間の制限があるそうです。(参考
当初リポジトリのクローンからmakeまで1つのスクリプトで行ったところ、この2時間制限に引っかかってしまい、やむなく分割しました。しかしmakeだけでも2時間近く(nightlyの場合で1時間50分台でした)かかってしまい、makeも分割したほうが安全そう、ということで以下のように2段階に分けています。今のところ前半で1時間、後半で45分程度なので大丈夫そうですが、これでも足りなくなるとLLVMのコンパイルも分ける必要がありそうです。

circle.yml
        - make -C rust rustc-stage1
        - make -C rust

小細工その4

最後はcargoのビルドです。

setup_cargo.sh
#!/bin/sh

git clone https://github.com/rust-lang/cargo.git

cd cargo
git submodule update --init
python -B src/etc/install-deps.py
./configure --target=i586-unknown-linux-gnu --build=i586-unknown-linux-gnu --host=i586-unknown-linux-gnu --local-rust-root=/usr/local
make
mkdir ../mods
cp -r ~/.cargo/registry/src/github.com-*/* ../mods/
mkdir .cargo
grep -lr target.i686-unknown-linux-gnu.dependencies `pwd`/../mods | awk 'BEGIN{print "paths = ["}//{out=$0;gsub("Cargo.toml","",$out);print "\""$out"\","}END{print "]"}' > .cargo/config
grep -lr target.i686-unknown-linux-gnu.dependencies ../mods | xargs sed -i.bak -e 's/target.i686-unknown-linux-gnu.dependencies/target.i586-unknown-linux-gnu.dependencies/g'
grep -lr "curl-sys = { path = \"curl-sys\", version = \"0.1.0\" }" ../mods | xargs sed -i.bak -e 's/curl-sys = { path = \"curl-sys\", version = \"0.1.0\" }/curl-sys = \"0.1.0\"/g'
make
grep -lr rustc-link-search=native=/usr/lib/x86_64-linux-gnu ./ | xargs sed -i.bak -e 's/rustc-link-search=native=/usr/lib/x86_64-linux-gnu/rustc-link-search=native=/usr/lib32/g'
cd ..

結構長いですが、ポイントはこの2行です。

grep -lr target.i686-unknown-linux-gnu.dependencies `pwd`/../mods | awk 'BEGIN{print "paths = ["}//{out=$0;gsub("Cargo.toml","",$out);print "\""$out"\","}END{print "]"}' > .cargo/config
grep -lr target.i686-unknown-linux-gnu.dependencies ../mods | xargs sed -i.bak -e 's/target.i686-unknown-linux-gnu.dependencies/target.i586-unknown-linux-gnu.dependencies/g'

cargoのビルドはcargoを使って行われるのですが、依存しているライブラリのCargo.toml[target.i686-unknown-linux-gnu.dependencies]という記述がある場合があります。これは特定のプラットフォームでのみ依存関係を記述する場合の指定ですが、今回の場合プラットフォームはi586-unknown-linux-gnuなので先ほどの記述には該当せず、依存ライブラリが見つからないことになってしまいます。
そのためこのCargo.tomlは書き換える必要があるのですが、単に~/.cargo/registry/src/github.com-...以下にあるCargo.tomlを書き換えてもビルドを走らせると元に戻ってしまいます。
そこで

mkdir ../mods
cp -r ~/.cargo/registry/src/github.com-*/* ../mods/

のように、依存ライブラリのディレクトリをコピーして

grep -lr target.i686-unknown-linux-gnu.dependencies `pwd`/../mods | awk 'BEGIN{print "paths = ["}//{out=$0;gsub("Cargo.toml","",$out);print "\""$out"\","}END{print "]"}' > .cargo/config

cargoが~/.cargo/registry/src/github.com-...ではなくコピー先を見に行くように.cargo/configを生成します。
.cargo/configpaths = ["dir1","dir2"]と書くとそのパスを優先して見に行くようになります)

grep -lr target.i686-unknown-linux-gnu.dependencies ../mods | xargs sed -i.bak -e 's/target.i686-unknown-linux-gnu.dependencies/target.i586-unknown-linux-gnu.dependencies/g'

このコピーに対してCargo.tomlを書き換えると、書き換えたCargo.tomlを維持したままビルドすることができます。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6