LLVMビットコードへのコンパイル

GraalVMは、LLVMビットコードにコンパイルできるC/C++、Rustなどの言語を実行できます。最初のステップとして、LLVMコンパイラのフロントエンド(例:CおよびC++の場合はclang、Rustの場合はrustなど)を使用して、プログラムをLLVMビットコードにコンパイルする必要があります。

ファイル形式 #

GraalVM LLVMランタイムはプレーンなビットコードファイルを実行できますが、推奨される形式は、埋め込みビットコードを持つネイティブ実行ファイルです。実行ファイル形式はLinuxとmacOSで異なります。LinuxはデフォルトでELFファイルを使用します。ビットコードは.llvmbcというセクションに格納されます。macOSプラットフォームはMach-Oファイルを使用します。ビットコードは__LLVMセグメントの__bundleセクションにあります。

埋め込みビットコードを持つネイティブ実行ファイルを使用すると、プレーンなビットコードファイルに比べて2つの利点があります。まず、ネイティブプロジェクトのビルドシステム(例:Makefile)は、結果が実行ファイルであることを期待します。出力形式を変更する代わりにビットコードを埋め込むことで、既存のプロジェクトとの互換性が向上します。第二に、実行ファイルではライブラリ依存関係を指定できますが、LLVMビットコードではこれは不可能です。GraalVM LLVMランタイムはこの情報を活用して、依存関係を検索してロードします。

C/C++コンパイル用LLVMツールチェーン #

C/C++を埋め込みビットコード付きの実行ファイルにコンパイルすることを簡素化するために、LLVMランタイムには事前にビルドされたLLVMツールチェーンが付属しています。ツールチェーンには、C用のclangやC++用のclang++などのコンパイラが含まれていますが、リンカー(ld)や、静的ライブラリ作成用のアーカイバ(ar)など、ネイティブプロジェクトのビルドに必要な他のツールも含まれています。

  1. lli--print-toolchain-path引数を使用して、ツールチェーンの場所を取得します。
     ./path/to/bin/lli --print-toolchain-path
    
  2. LLVM_TOOLCHAIN環境変数を設定します。
     export LLVM_TOOLCHAIN=$(./path/to/bin/lli --print-toolchain-path)
    
  3. 次に、ツールチェーンパスの内容を確認して、使用可能なツールのリストを表示します。
     ls $LLVM_TOOLCHAIN
    

これらのツールは、ネイティブコンパイルの場合と同様に使用します。たとえば、このCコードをhello.cという名前のファイルに保存します。

#include <stdio.h>

int main() {
    printf("Hello from GraalVM!\n");
    return 0;
}

次に、次のようにしてhello.cを埋め込みLLVMビットコード付きの実行ファイルにコンパイルできます。

$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プログラムを実行するには、GraalVMにRust標準ライブラリの場所を知らせる必要があります。

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オプションはすべてのチェックを無効にします。GraalVMはその後、ビットコードを実行しようとしますが、予期せぬ方法でランダムに失敗する可能性があります。

お問い合わせ