フォーム読み込み中
クロスコンパイルとは、コンパイラが動作している以外のプラットフォーム向けに、実行ファイルを生成することです。
クロスコンパイラ基盤の1つにLLVMがあります。LLVMの優れた特性の一つとして、クロスアーキテクチャ開発の簡素化があげられます。LLVMがクロスプラットフォーム開発の効率化をどのように実現しているかを本記事では探っていきます。
具体的には、x86アーキテクチャ上で、LLVMのフレームワークを使用して、ARM向け命令セットアーキテクチャ(ISA)に準拠したオブジェクトファイルを生成します。そして、得られたARM用オブジェクトファイルを、実際のARMサーバに転送し、実行できるかを確かめます。
LLVMは任意のプログラミング言語で書かれたソースファイルを、任意の命令セットアーキテクチャ(ISA:Instruction Set Architecture)にコンパイルすることができるコンパイラ基盤です。
プログラミング言語とはCやC++、Rust、Go、Swiftなどです。
ISAとは、代表的なところを挙げると、IntelやAMDで採用されているx86や、他にはARM、RISC-V、Power、MIPS、SPARCなどです。
下図を見てもらうとわかりやすいでしょう。コンパイルのフローが3フェーズで設計されています。
各プログラミング言語、またそのコンパイラは、ハードウェアアーキテクチャを考慮する必要がありません。LLVM IRの中間表現へ出力することだけを意識すればよくなります。
バックエンドは、プログラムのソースコードを意識する必要がありません。IRをアーキテクチャごとに変換する責任だけを担います。
IRのインターフェースが存在しなければ、コンパイラは、フロントエンド×バックエンドの乗算数だけ作らなくてはいけません。
LLVMの概要は理解いただけたかと思います。
何をしようとしていたかを思い出します。LLVM基盤のコマンドを打ち、あるアーキテクチャ上で、異なるアーキテクチャ環境向けの、アセンブリ、オブジェクトファイルを作れるのか、それをターゲットの環境上で本当に動かせるのか、を確かめます。
今回は、x86環境でARM向けアセンブリファイルやオブジェクトファイルを作成し、それをARMの実機へコピーして動かしてみます。
ARMではなく、RISC-V向けのファイル作成などももちろん可能です。RISC-Vだと実機探しに手間かかりそうなため、ARMを選択しました。シミュレータはすぐに利用できます。
まずは、適当なプログラムをC言語で作成します。
(hello.c)
#include <stdio.h>
int main() {
puts("Hello World");
return 0;
}
■ x86上で普通にビルド
このプログラムファイルを、まずはx86上で一気通貫でビルドし、実行ファイルを作成します。
$ uname -a
-snip-
x86_64 x86_64 x86_64 GNU/Linux
$ clang hello.c -o hello
$ ./hello
Hello World
}
■ x86上で、LLVMのコンパイラ基盤を通し、ステップ実行
続いても、同じソースコードから、同じオブジェクトファイルを作ります。ただし、LLVMの3つのフェーズ単位で実施していきます。
# CのソースコードをLLVM形式のIRバイトコードへ変換
$ clang -c hello.c -emit-llvm -o hello.bc
# IRバイトコードをoptimizerを通し、最適化されたIRバイトコードを出力(
# 最適化処理は引数で複数指定できます(本手順ないのコードは適当な最適化)
$ opt --instcount hello.bc -o optimized_hello.bc
$ mv optimized_hello.bc hello.bc
# IRバイトコードをオブジェクトファイルへ変換
$ llc hello.bc -filetype=obj -o hello.o
# IRバイトコードを、アセンブルファイルを通じ、オブジェクトファイルへ変換することも当然できます(上コードと同じ結果)
$ llc hello.bc -filetype=asm -o hello.s
$ as -o hello.o hello.s
# hello.oをリンクし、実行ファイルを作成(作成ファイル名:hello)
$ gcc hello.o -o hello
# 実行
$ ./hello
Hello World
gccを利用したのは、オブジェクトファイルの作成と、リンカ作業を行うためです。オブジェクトファイルを指定しているので(アセンブリファイルでも同)、gccでコンパイル、ビルドを行ったわけではありません。
gccを使わない場合は、lldを利用することになります。リンクさせるオブジェクトファイルが環境に依存し、またその数も多いため、簡易的な手順としています。
(コマンド例)
$ lld -lc hello.o xxx.o yyy.o zzz.o
■ x86上で、LLVMのコンパイラ基盤を通し、ARMオブジェクトを作る
いよいよ、ARM用のオブジェクトファイルを作りましょう。繰り返しになりますが、ここはx86環境です。
先ほどの手順との相違点は2つです。フロントエンドのclangで、ターゲットとなるアーキテクチャとしてarm64を指定します。IRは、x86と同じ内容です。
llcでmarchオプションでaarch64を指定します。
$ clang -emit-llvm -target arm64 -c hello.c -o hello.bc
$ opt --instcount hello.bc -o optimized_hello.bc
$ mv optimized_hello.bc hello.bc
$ llc hello.bc -filetype=obj -march=aarch64 -o hello.o
ARMアーキテクチャのアセンブリファイルができたように見えます。ELF(Executable and Linking Format)ヘッダをのぞいてみましょう。
$ readelf -h hello.o
ELF Header:
-snip-
Class: ELF64
OS/ABI: UNIX - System V
Machine: AArch64
-snip-
ELFは正しそうです。あとは本当に動くのかが心配ですね。この環境では動かせません。
クラウドでARMアーキテクチャのVMを立ててそこで試験してみましょう。私は、AWSでARMベースで設計されたGravitonプロセッサのインスタンスを立ち上げました。Azure、Google CloudなどのクラウドでもARMアーキテクチャサーバがあるため、それらでも問題ありません。ターゲットをarm64にしているので、本手順に従う場合は、AArch64の64ビットアドレス空間をサポートするインスタンスをご利用ください。
起動させたインスタンスへ、hello.oをscpなどでコピーします。
ここからがARMアーキテクチャのサーバ上での操作です。といっても、オブジェクトファイルまでは作っているので、リンクして実行ファイルをアウトプットするだけです。ここでもgccをリンカ目的で使います。
$ uname -a
-snip-
aarch64 aarch64 aarch64 GNU/Linux
$ gcc hello.o -o hello
$ ./hello
Hello World
動きました!
LLVMのモジューラ化の恩恵と、それに伴う高いポータビリティ性を体感するため、異なるアーキテクチャのサーバ間でファイルの生成と実行を行いました。この試験によって、異なるアーキテクチャ間でもコードの変換や最適化が円滑に行えることが確認できました。LLVMが注目されている理由も実感できたのではないでしょうか。
LLVMのバックエンドでは任意のアセンブリファイルを出力できます。ISAの調査や設計等にも有効に使えそうですね。
ソフトバンクはAWS アドバンストティアサービスパートナーです。「はじめてのAWS導入」から大規模なサービス基盤や基幹システムの構築まで、お客さまのご要望にあわせて最適なAWS環境の導入を支援します。
Microsoft Azureは、Microsoftが提供するパブリッククラウドプラットフォームです。コンピューティングからデータ保存、アプリケーションなどのリソースを、必要な時に必要な量だけ従量課金で利用することができます。
Google サービスを支える、信頼性に富んだクラウドサービスです。お客さまのニーズにあわせて利用可能なコンピューティングサービスに始まり、データから価値を導き出す情報分析や、最先端の機械学習技術が搭載されています。
条件に該当するページがございません