カスタムターゲットを作成する
あなたのプラットフォームでカスタムのターゲットトリプルが利用できない場合は、rustc に対して
ターゲットを記述するカスタムターゲットファイルを作成しなければなりません。
rustc に未知のターゲット向けには core ライブラリをビルドする必要があり、そのためには
nightly コンパイラを使用しなければならないことに注意してください。
ターゲットトリプルを決める
多くのターゲットには、それらを記述するために使われる既知のトリプルがすでにあり、通常は ARCH-VENDOR-SYS-ABI の形式です。LLVM が使用するものと同じトリプルを使うことを目指す べきですが、LLVM が知らない追加情報を Rust に指定する必要がある場合は、それと異なることもあります。 トリプルは技術的には人間が使うためのものにすぎませんが、特に将来的にそのターゲットがアップストリームに 取り込まれる場合は、一意で説明的であることが重要です。
ARCH 部分は、32 ビット ARM の場合を除いて、通常は単にアーキテクチャ名です。たとえば、
そのようなプロセッサにはおそらく x86_64 を使うことになるでしょうが、ARM では正確なアーキテクチャ
バージョンを指定します。典型的な値としては armv7、armv5te、thumbv7neon などがあります。参考として
組み込みターゲットの名前を見てみてください。
VENDOR 部分は省略可能で、製造元を表します。このフィールドを省略することは
unknown を使うのと同じです。
SYS 部分は、使用される OS を表します。デスクトッププラットフォームでは、典型的な値として
win32、linux、darwin があります。ベアメタルでの使用には none を使います。
ABI 部分は、プロセスがどのように起動するかを表します。ベアメタルには eabi が使われ、
glibc には gnu、musl には musl などが使われます。
ターゲットトリプルが決まったら、そのトリプル名に .json 拡張子を付けたファイルを作成します。
たとえば、armv7a-none-eabi を記述するファイルのファイル名は
armv7a-none-eabi.json になります。
ターゲットファイルの内容を埋める
ターゲットファイルは有効な JSON でなければなりません。その内容は 2 か所で説明されています:
Target ではすべてのフィールドが必須で、TargetOptions ではすべてのフィールドが省略可能です。
すべてのアンダースコアはハイフンに置き換えられます。
推奨される方法は、あなたのターゲットシステムに似た組み込みターゲットの仕様を土台として
ターゲットファイルを作成し、その後でターゲットシステムの特性に合わせて調整することです。そのためには、
rustc +nightly -Z unstable-options --print target-spec-json --target $SOME_SIMILAR_TARGET コマンドを使い、
コンパイラにすでに組み込まれているターゲットを指定します。
その出力は、ほぼそのままファイルにコピーできます。まずは次のような修正から始めます:
"is-builtin": trueを削除するllvm-targetを LLVM が想定するトリプル で埋める- パニック戦略を決める。ベアメタル実装ではおそらく
"panic-strategy": "abort"を使います。panic 発生時にabortしないと決めた場合は、プロジェクトごとに Cargo に そう指示しない限り、eh_personality 関数を定義しなければなりません。 - アトミック操作を設定する。あなたのターゲットを表す最初の選択肢を選んでください:
- シングルコアのプロセッサで、スレッドがなく、割り込みがなく、または複数のことが
並行して起こる手段が一切ない: WASM(現時点)などのようにそれが事実だと 確信 できるなら、
"singlethread": trueを設定できます。これにより、LLVM はすべてのアトミック 操作を単一スレッド版に変換するよう設定されます。このオプションを誤って使うと、 スレッドや割り込みを使用している場合に UB を引き起こす可能性があります。 - ネイティブのアトミック操作がある:
max-atomic-widthを、ターゲットがアトミックに操作できる 最大の型のビット幅に設定します。たとえば、多くの ARM コアは 32 ビットのアトミック操作を持ちます。そうした場合は"max-atomic-width": 32を設定できます。 - ネイティブのアトミック操作はないが、自分でエミュレートできる:
max-atomic-widthを、 128 までの範囲でエミュレートできる最大ビット数に設定し、そのうえで LLVM が期待するすべての atomic および sync 関数を#[no_mangle] unsafe extern "C"として実装します。これらの関数は GCC によって標準化されているため、GCC のドキュメントに補足があるかもしれません。関数が欠けているとリンカーエラーになります。一方、 実装が正しくない関数は UB を引き起こす可能性があります。たとえば、シングルコア・シングルスレッドだが割り込みのある プロセッサなら、これらの関数を、割り込みを無効化し、通常の操作を行い、その後で再度有効化するように 実装できます。 - ネイティブのアトミック操作がない: コード内で同期を手動で保証するために、unsafe な作業をいくらか
行う必要があります。
"max-atomic-width": 0を設定しなければなりません。
- シングルコアのプロセッサで、スレッドがなく、割り込みがなく、または複数のことが
並行して起こる手段が一切ない: WASM(現時点)などのようにそれが事実だと 確信 できるなら、
- 既存のツールチェーンと統合する場合はリンカーを変更する。たとえば、カスタムビルドの GCC を使う
ツールチェーンを使っているなら、
"linker-flavor": "gcc"を設定し、linkerには リンカーのコマンド名を設定します。追加のリンカー引数が必要な場合は、pre-link-argsとpost-link-argsを次のように使います:
リンカーの種類が"pre-link-args": { "gcc": [ "-Wl,--as-needed", "-Wl,-z,noexecstack", "-m64" ] }, "post-link-args": { "gcc": [ "-Wl,--allow-multiple-definition", "-Wl,--start-group,-lc,-lm,-lgcc,-lstdc++,-lsupc++,--end-group" ] }link-args内のキーになっていることを確認してください。 - LLVM の機能を設定する。利用可能な機能とその説明を一覧するには、ARCH をベースアーキテクチャ
(ARM の場合はバージョンを含まない)として
llc -march=ARCH -mattr=helpを実行します。ターゲットで 厳格なメモリアラインメントアクセスが必要な場合(例:armv5te)は、必ずstrict-alignを 有効にしてください。機能を有効にするには、その前にプラスを付けます。同様に、 機能を無効にするには、その前にマイナスを付けます。機能は次のようにカンマ区切りにします:"features": "+soft-float,+neon"。ただし、指定したトリプルと CPU に基づいて LLVM が ターゲットについて十分に理解している場合は、これが不要なこともあります。 - わかっているなら、LLVM が使う CPU を設定する。これにより CPU 固有の最適化と
機能が有効になります。前の手順のコマンド出力の先頭には、既知の CPU の一覧があります。
特定の CPU をターゲットにすることがわかっているなら、JSON ターゲットファイルの
cpuフィールドに それを設定できます。
ターゲットファイルを使用する
ターゲット仕様ファイルを用意したら、そのパス、または名前(つまり .json を除いたもの)で参照できます。現在のディレクトリまたは $RUST_TARGET_PATH 内にある場合は、その名前で参照できます。
rustc で読み取れることを確認してください:
❱ rustc --print cfg --target foo.json # または、現在のディレクトリにある場合は foo だけでも可
debug_assertions
target_arch="arm"
target_endian="little"
target_env=""
target_feature="mclass"
target_feature="v7"
target_has_atomic="16"
target_has_atomic="32"
target_has_atomic="8"
target_has_atomic="cas"
target_has_atomic="ptr"
target_os="none"
target_pointer_width="32"
target_vendor=""
これで、ついにこれを使えるようになります! 多くの資料では xargo や cargo-xbuild が推奨されてきました。
しかし、その後継である cargo の build-std 機能は、最近かなり多くの改善が加えられており、
他の選択肢とすぐに同等の機能を備えるようになりました。そのため、このガイドではこの
選択肢のみを扱います。
まずは最小限の no_std プログラム から始めましょう。次に、
cargo build -Z build-std=core --target foo.json を実行します。パスの参照方法については、
先ほどのルールに従ってください。うまくいけば、ターゲットディレクトリ内にバイナリが
生成されているはずです。
必要に応じて、常にそのターゲットを使うよう cargo を設定することもできます。最小の no_std
プログラム に関するページの末尾にある推奨事項を参照してください。ただし、
そのオプションは不安定であるため、現時点では -Z build-std=core フラグを使用する必要があります。
追加の組み込みクレートをビルドする
cargo の build-std 機能を使う場合、どのクレートをコンパイル対象に含めるかを選べます。デフォルトでは、
-Z build-std のみを渡した場合、std、core、alloc がコンパイルされます。ただし、ベアメタル向けに
コンパイルする際には std を除外したいこともあるでしょう。その場合は、build-std の後に必要なクレートを
指定してください。たとえば、core と alloc を含めるには、-Z build-std=core,alloc を渡します。
トラブルシューティング
language item required, but not found: eh_personality
ターゲットファイルに "panic-strategy": "abort" を追加するか、eh_personality 関数を定義してください。
あるいは、Cargo にこれを無視するよう指示することもできます。
undefined reference to __sync_val_compare_and_swap_#
Rust はターゲットにアトミック命令があると考えていますが、LLVM はそう考えていません。
アトミックの設定 に関する手順に戻ってください。max-atomic-width の数値を
小さくする必要があります。詳細は #58500 を参照してください。
could not find sync in alloc
上のケースと同様に、Rust はアトミックがあると認識していません。自分で実装するか、 アトミック命令があることを Rust に伝える必要があります。
multiple definition of __(something)
Rust プログラムを別の言語でビルドされたコードとリンクしており、その別の言語側に Rust も生成する コンパイラ組み込みが含まれている可能性があります。これを修正するには、複数定義を許可するよう リンカに指示する必要があります。gcc を使用している場合は、次のように追加できます:
"post-link-args": {
"gcc": [
"-Wl,--allow-multiple-definition"
]
}
error adding symbols: file format not recognized
cargo の build-std 機能に切り替え、コンパイラを更新してください。これは、内部 Rust オブジェクトを
外部リンカに渡そうとした一部のコンパイラビルドで入り込んだバグでした。