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に追加できます:

$GRAALVM_HOME/bin/gu install llvm-toolchain

前述のコマンドでは、GraalVM CommunityのユーザーのGitHubカタログからLLVMツールチェーンがインストールされます。GraalVM Enterpriseのユーザーの場合、手動インストールが必要です。

ツールチェーンの場所を取得するには、lli--print-toolchain-path引数を使用します:

export LLVM_TOOLCHAIN=$($GRAALVM_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で実行できます:

$GRAALVM_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-muslx86_64-unknown-linux-gnuは実際には異なり、ビットコードは別のCライブラリ用にコンパイルされます。--llvm.verifyBitcode=falseオプションを指定すると、どのような場合でもすべてのチェックが無効になり、予期せずランダムに失敗する可能性があります。