LLVMビットコードへのコンパイル
GraalVMは、C/C++、Rust、およびLLVMビットコードにコンパイル可能なその他の言語を実行できます。最初のステップとして、LLVMコンパイラのフロントエンド(たとえば、CおよびC++の場合はclang
、Rustプログラミング言語の場合はrust
など)を使用して、プログラムをLLVMビットコードにコンパイルする必要があります。
ファイル形式
GraalVM LLVMランタイムはプレーンなビットコード・ファイルを実行できますが、推奨される形式はビットコードが埋め込まれたネイティブ実行可能ファイルです。実行可能ファイルの形式は、LinuxとmacOSで異なります。Linuxでは、デフォルトでELFファイルが使用されます。ビットコードは、.llvmbc
というセクションに格納されます。macOSプラットフォームでは、Mach-Oファイルが使用されます。ビットコードは、__LLVM
セグメントの__bundle
セクションにあります。
ビットコードが埋め込まれたネイティブ実行可能ファイルを使用することには、プレーンなビットコード・ファイルの場合と比較して2つの利点があります。第1に、Makefile
などのネイティブ・プロジェクト用のシステムをビルドし、結果は実行可能ファイルになると想定します。出力形式を変更するかわりにビットコードを埋め込むと、既存のプロジェクトとの互換性が向上します。第2に、実行可能ファイルでは、LLVMビットコードでは不可能なライブラリ依存性の指定が可能です。GraalVM LLVMランタイムは、この情報を使用して依存性を検出し、ロードします。
C/C++をコンパイルするためのLLVMツールチェーン
ビットコードが埋め込まれた実行可能ファイルへのC/C++のコンパイルを簡略化するために、GraalVMには事前にビルドされたLLVMツールチェーンが用意されています。ツールチェーンには、C用のclang
やC++用のclang++
などのコンパイラが含まれていますが、静的ライブラリを作成するためのリンカー(ld
)やアーカイバ(ar
)などのネイティブ・プロジェクトのビルドに必要なその他のツールも含まれています。
LLVMツールチェーンは、必要に応じて、GraalVMアップデータ・ツールを使用してGraalVMに追加できます:
$JAVA_HOME/bin/gu install llvm-toolchain
ツールチェーンの場所を取得するには、lli
の--print-toolchain-path
引数を使用します:
export LLVM_TOOLCHAIN=$($JAVA_HOME/bin/lli --print-toolchain-path)
使用可能なツールのリストは、ツールチェーン・パスの内容を参照してください:
ls $LLVM_TOOLCHAIN
ネイティブ・コンパイルの場合と同様にそれらのツールを使用します。たとえば、次のCコードをhello.c
という名前のファイルに保存します:
#include <stdio.h>
int main() {
printf("Hello from GraalVM!\n");
return 0;
}
その後、次のように、LLVMビットコードが埋め込まれた実行可能ファイルにhello.c
をコンパイルできます:
$LLVM_TOOLCHAIN/clang hello.c -o hello
生成された実行可能ファイルhello
は、lli
を使用してGraalVMで実行できます:
$JAVA_HOME/bin/lli hello
外部ライブラリ依存性
ビットコード・ファイルが外部ライブラリに依存している場合、GraalVMはバイナリ・ヘッダーから依存性を自動的に取得します。たとえば:
#include <unistd.h>
#include <ncurses.h>
int main() {
initscr();
printw("Hello, Curses!");
refresh();
sleep(1);
endwin();
return 0;
}
次のhello-curses.cファイルは、次のようにコンパイルおよび実行できます:
$LLVM_TOOLCHAIN/clang hello-curses.c -lncurses -o hello-curses
lli hello-curses
C++の実行
C++コードを実行するには、GraalVM LLVMランタイムに、LLVMプロジェクトのlibc++
標準ライブラリが必要です。GraalVMに付属のLLVMツールチェーンは、libc++
に対して自動的にリンクします。たとえば、次のコードをhello-c++.cppファイルとして保存します:
#include <iostream>
int main() {
std::cout << "Hello, C++ World!" << std::endl;
}
GraalVMに付属のclang++
を使用してコンパイルし、実行します:
$LLVM_TOOLCHAIN/clang++ hello-c++.cpp -o hello-c++
lli hello-c++
Hello, C++ World!
Rustの実行
GraalVMにバンドルされているLLVMツールチェーンには、Rustコンパイラは付属していません。Rustをインストールするには、コマンド・プロンプトで次を実行し、画面の指示に従います:
curl https://sh.rustup.rs -sSf | sh
このRustコードの例をhello-rust.rsファイルに保存します:
fn main() {
println!("Hello Rust!");
}
次に、これを--emit=llvm-bc
フラグを使用してビットコードにコンパイルできます:
rustc --emit=llvm-bc hello-rust.rs
Rustプログラムを実行するには、Rust標準ライブラリの場所をGraalVMに指定する必要があります:
lli --lib $(rustc --print sysroot)/lib/libstd-* hello-rust.bc
Hello Rust!
RustコンパイラはGraalVMに付属のLLVMツールチェーンを使用していないため、ローカルRustのインストールによっては、次のいずれかのようなエラーが発生する可能性があります:
Mismatching target triple (expected x86_64-unknown-linux-gnu, got x86_64-pc-linux-gnu)
Mismatching target triple (expected x86_64-apple-macosx10.11.0, got x86_64-apple-darwin)
これは、RustコンパイラがGraalVMに付属のLLVMツールチェーンとは別のターゲット・トリプルを使用したことを示しています。この具体的な事例では、違いはLinuxディストリビューションまたはMacOSバージョン間でネーミング規則が異なることのみで、実際に違いはありません。その場合、エラーは無視しても問題ありません:
lli --experimental-options --llvm.verifyBitcode=false --lib $(rustc --print sysroot)/lib/libstd-* hello-rust.bc
このオプションは、ターゲット・トリプルに実際に互換性があること、つまりアーキテクチャ、オペレーティング・システムおよびCライブラリがすべて一致していることを手動で検証した後にのみ使用する必要があります。たとえば、x86_64-unknown-linux-musl
とx86_64-unknown-linux-gnu
は実際には異なり、ビットコードは別のCライブラリ用にコンパイルされます。--llvm.verifyBitcode=false
オプションを指定すると、どのような場合でもすべてのチェックが無効になり、予期せずランダムに失敗する可能性があります。