ネイティブ実行に関する制限事項と相違点
LLVMコードは、GraalVM CommunityまたはEnterpriseエディションのデフォルト構成で解釈またはコンパイルする場合と、管理対象環境(GraalVM Enterpriseで--llvm.managed
オプションで有効化されます)で同じコードが解釈またはコンパイルする場合とで、特性が異なります。LLVMビットコード形式のプログラムを直接実行するために使用されるlli
インタプリタの動作は、ネイティブ・モードと管理対象モードで異なります。相違点は、安全性の保証と言語間の相互運用性にあります。
ノート: LLVMビットコードの管理対象実行モードは、GraalVM Enterpriseでのみ使用できます。
デフォルト構成では、言語間の相互運用性には、デバッグ情報を有効にして(-g
)ビットコードをコンパイルし、LLVMビットコードに対して-mem2reg
最適化を実行する必要があります(少なくとも-O1
を使用してコンパイルするか、opt
ツールを明示的に使用します)。GraalVM Enterpriseの管理対象環境ではネイティブ・コードがポリグロット・プログラムに参加し、サポートされている他の言語との間でデータを受渡しできるため、これらの要件には、この環境で対応できます。セキュリティに関しては、管理対象環境でのネイティブ・コードの実行は、安全機能を追加して渡されます(不正なポインタ・アクセスの捕捉や境界外の配列へのアクセスなど)。
GraalVMのエディションに応じて、ネイティブ実行には特定の制限事項と相違点があります。それぞれについて考えます。
GraalVM Communityでのネイティブ実行に関する制限事項と相違点
GraalVM Community Edition環境でLLVMインタプリタを使用すると、多言語のコンテキストでLLVMビットコードを実行できます。汎用のLLVMランタイムである必要がありますが、ユーザーが認識しておく必要がある一定の基本的な制限事項または実装上の制限事項(あるいはその両方)があります。
LLVMビットコードがGraalVM CommunityでLLVMインタプリタを使用して実行される場合、ネイティブ実行(つまり、ネイティブ・コードにコンパイルされたビットコード)には次の制限事項と相違点があります:
- GraalVM LLVMインタプリタは、ビットコードがx86_64アーキテクチャをターゲットとして生成されたことを前提とします。
- ビットコードは、clangバージョン7を使用してC/C++コードをコンパイルした結果である必要があり、Rustなどの他のコンパイラや言語には、サポートされていない特定の要件がある場合があります。
- サポートされていない機能 - 次の関数はコールできません:
clone()
fork()
vfork()
setjmp()
、sigsetjmp()
、longjmp()
、siglongjmp()
exec()
関数ファミリの関数- pthread関数
- LLVMインタプリタで実行されるコードは、JVMが同じプロセスで実行されていることを認識する必要があるため、fork、brk、sbrk、futex、mmap、rt_sigaction、rt_sigprocmaskなどの多くのsyscallが想定どおりに機能しないか、JVMがクラッシュする可能性があります。
- ネイティブ・コード・ライブラリを介して、サポートされていないsyscallまたはサポートされていない機能(前述)をコールすると、予期しない副次的影響やクラッシュが発生する可能性があります。
- スレッド・ローカル変数
- ビットコードのスレッド・ローカル変数は、ネイティブ・コードのスレッド・ローカル変数と互換性がありません。
- メモリー・レイアウトに依存できない
- スレッド・ローカル変数へのポインタは、特定の場所(FSセグメントなど)には格納されません。
- メモリー内のグローバルの順序は異なる可能性があるため、相対的な場所に関する想定はできません。
- ポインタ演算を使用してスタック・フレームを検査または変更することはできません(戻りアドレスの上書きなど)。
- スタックの移動は、Truffle APIを使用した場合にのみ可能です。
- コードとデータは厳密に分離されているため、関数ポインタまたはコードへのポインタでの読取り、書込みおよびポインタ演算によって、未定義の動作が発生します。
- シグナル・ハンドラ
- シグナル・ハンドラのインストールはサポートされていません。
- スタック
- デフォルトのスタック・サイズは、オペレーティング・システムによってではなく、オプション
--llvm.stackSize
によって設定されます。
- デフォルトのスタック・サイズは、オペレーティング・システムによってではなく、オプション
- 動的リンク
- LLVMビットコード動的リンカーとの対話はサポートされておらず、たとえば、dlsym/dlopenはネイティブ・ライブラリにのみ使用できます。
- ネイティブ・ライブラリとLLVMビットコード・ライブラリが混在している場合、動的リンクの順序は未定義です。
- ネイティブ・ライブラリは、ビットコード・ライブラリからシンボルをインポートできません。
- x86_64インライン・アセンブリはサポートされていません。
- Cの仕様に従った未定義の動作
- ほとんどのCコンパイラは未定義の動作をCPUのセマンティクスにマップしますが、GraalVM LLVMインタプリタは、この未定義の動作の一部をJavaまたはその他のセマンティクスにマップする場合があります。たとえば、符号付き整数のオーバーフロー(算術オーバーフローのJavaセマンティクスにマップされる)、ゼロによる整数の除算(ArithmeticExceptionをスローする)、サイズ超過のシフト量(Javaの動作にマップされる)などです。
- 浮動小数点演算
- 一部の浮動小数点演算および数学関数では、(低い精度で演算を実行するかわりに)より精密な演算が使用され、その結果が低い精度にキャストされます。
- 丸めモードFE_TONEARESTのみがサポートされています。
- 浮動小数点例外はサポートされていません。
- NFIの制限事項(実際のネイティブ関数のコール)
- 構造体、複素数またはfp80値は、値渡し引数または値渡し戻り値としてサポートされていません。
- 同じ制限事項が、ネイティブ・コードから解釈済のLLVMビットコードに戻されるコールに適用されます。
- ポリグロット相互運用性(他のGraalVM言語の値の操作)の制限事項
- 外部オブジェクトはネイティブ・メモリーの場所に格納できません。ネイティブ・メモリーの場所は、次のとおりです:
- グローバル(ポインタ値を1つのみ保持するグローバルの特定のケースを除く)。
- mallocされたメモリー(c++ newなど)。
- スタック(自動変数のエスケープなど)。
- 外部オブジェクトはネイティブ・メモリーの場所に格納できません。ネイティブ・メモリーの場所は、次のとおりです:
- LLVM命令セットのサポート(LLVM 7.0.1に基づく)
- ほとんど使用されない一連のビットコード命令は使用できません(va_arg、catchpad、cleanuppad、catchswitch、catchret、cleanupret、fneg、callbr)。
- サポートが制限されている命令:
- atomicrmw (sub、add、and、nand、or、xor、xchgのみをサポート)。
- 値の抽出および値の挿入(単一インデックス付けオペランドのみをサポート)。
- cast (特定のほとんど使用されない種類はサポートされない)。
- loadおよびstore命令のアトミック順序およびアドレス空間属性は無視されます。
- 値 - アセンブリ定数はサポートされていません(モジュール・レベルのアセンブリおよびアセンブリ文字列)。
- 型:
- 128ビット浮動小数点型(fp128およびppc_fp128)、x86_mmx、半精度浮動小数点(fp16)およびサポートされていないプリミティブ型のベクトルはサポートされていません。
- fp80のサポートは制限されています(すべての組込み関数がfp80でサポートされているわけではなく、一部の組込み関数または命令は暗黙的にfp64にフォールバックする可能性があります)。
- LLVM 7.0.1に基づいた、ほとんど使用されないか試験段階の組込み関数のうち、実装上の制限のため、またはスコープ外であるためにサポートされないものが多数あります。
- 試験段階の組込み関数:
llvm.experimental.*
、llvm.launder.invariant.group
、llvm.strip.invariant.group
。 - トランポリン組込み関数:
llvm.init.trampoline
、llvm.adjust.trampoline
。 - 一般的な組込み関数:
llvm.var.annotation
、llvm.ptr.annotation
、llvm.annotation
、llvm.codeview.annotation
、llvm.trap
、llvm.debugtrap
、llvm.stackprotector
、llvm.stackguard
、llvm.ssa_copy
、llvm.type.test
、llvm.type.checked.load
、llvm.load.relative
、llvm.sideeffect
。 - 特殊化された算術組込み関数:
llvm.canonicalize
、llvm.fmuladd
。 - 標準cライブラリ組込み関数:
llvm.fma
、llvm.trunc
、llvm.nearbyint
、llvm.round
。 - コード・ジェネレータ組込み関数:
llvm.returnaddress
、llvm.addressofreturnaddress
、llvm.frameaddress
、llvm.localescape
、llvm.localrecover
、llvm.read_register
、llvm.write_register
、llvm.stacksave
、llvm.stackrestore
、llvm.get.dynamic.area.offset
、llvm.prefetch
、llvm.pcmarker
、llvm.readcyclecounter
、llvm.clear_cache
、llvm.instrprof*
、llvm.thread.pointer
。 - 正確なgc組込み関数:
llvm.gcroot
、llvm.gcread
、llvm.gcwrite
。 - 要素単位のアトミック・メモリー組込み関数:
llvm.*.element.unordered.atomic
。 - マスクされたベクトル組込み関数:
llvm.masked.*
。 - ビット操作組込み関数:
llvm.bitreverse
、llvm.fshl
、llvm.fshr
。
- 試験段階の組込み関数:
GraalVM Enterpriseでの管理対象実行に関する制限事項と相違点
LLVMビットコードの管理対象実行は、GraalVM Enterprise Editionの機能であり、--llvm.managed
コマンドライン・オプションを使用して有効にすることができます。管理対象モードでは、GraalVM LLVMランタイムによって、管理対象外メモリーへのアクセスと、ネイティブ・コードおよびオペレーティング・システム機能への無制御のコールが防止されます。割当ては管理対象Javaヒープ内で実行され、周囲のシステムへのアクセスは適切な言語APIおよびJava APIコールを介してルーティングされます。
GraalVMでのデフォルトのネイティブLLVM実行のすべての制限事項が管理対象実行に適用されますが、次の相違点および変更点があります:
- プラットフォーム非依存
- ビットコードは、実際の基礎となるオペレーティング・システムに関係なく、すべてのプラットフォームで提供されているmusl libcライブラリを使用して、一般的な
linux_x86_64
ターゲット用にコンパイルされる必要があります。
- ビットコードは、実際の基礎となるオペレーティング・システムに関係なく、すべてのプラットフォームで提供されているmusl libcライブラリを使用して、一般的な
- C++
- 管理対象モードでのC++には、GraalVM 20.1以降が必要です。
- ネイティブ・メモリーおよびコード
- ネイティブ関数をコールできません。このため、提供されているmusl libcおよびGraalVM LLVMインタフェースで提供される機能のみを使用できます。
- ネイティブ・ライブラリをロードできません。
- ネイティブ・メモリー・アクセスはできません。
- システム・コール
- サポートが制限されているシステム・コールは、read、readv、write、writev、open、close、dup、dup2、lseek、stat、fstat、lstat、chmod、fchmod、ioctl、fcntl、unlink、rmdir、utimensat、uname、set_tid_address、gettid、getppid、getpid、getcwd、exit、exit_group、clock_gettimeおよびarch_prctlです。
- この機能は、一般的な端末IO、プロセス制御およびファイル・システム操作に限定されます。
- 一部のsyscall (chown、lchown、fchown、brk、rt_sigaction、sigprocmask、futexなど)は、noopとして実装されているか、使用できないというエラー警告を返します(あるいはその両方)。
- musl libc
- musl libcライブラリの動作は、一部のケースで、より一般的なglibcと異なります。
- スタック
- スタック・ポインタに直接アクセスできません。
- スタックは連続しておらず、スタック割当ての範囲外のメモリーへのアクセス(たとえば、ポインタ演算を使用した隣接するスタック値へのアクセス)はできません。
- 管理対象ヒープへのポインタ
- 管理対象ポインタの一部の読取りはできません。
- 管理対象ポインタの一部を(たとえば、ポインタのタグ付けにビットを使用して)上書きし、その後で破棄された管理対象ポインタを間接参照することはできません。
- Cポインタ演算での未定義の動作が適用されます。
- 複雑なポインタ演算(ポインタの乗算など)では、管理対象ポインタをi64値に変換できます。i64値はポインタ比較で使用できますが、間接参照できません。
- 浮動小数点演算
- 80ビット浮動小数点では、64ビット浮動小数点精度のみが使用されます。
- 動的リンク
- LLVMビットコード動的リンカーとの対話はサポートされておらず、たとえば、dlsym/dlopenは使用できません。これにより、ネイティブ・コードをロードできません。