FaultMapsと暗黙的なチェック¶
動機¶
マネージド言語ランタイムによって生成されたコードは、安全のために必要なチェックを含んでいることがありますが、実際には失敗することはありません。そのような場合、失敗した場合に大幅にコストが高くなるとしても、失敗しない場合のコストを低くすることが有利です。この非対称性は、そのような安全チェックを、チェックが失敗した場合に確実にフォルトが発生するようにできる操作に折り畳み、シグナルハンドラーを使用してそのようなフォルトから回復することによって活用できます。
たとえば、Javaでは、オブジェクトから読み書きする前に、オブジェクトのヌルチェックが必要です。オブジェクトがnull
の場合、NullPointerException
がスローされ、通常の処理が中断されます。しかし実際には、動作の良いJavaプログラムでは、null
ポインタの逆参照は非常にまれであり、通常、ヌルチェックは同じメモリ位置を操作する近くのメモリ操作に折り畳むことができます。
Fault Mapセクション¶
LLVMによって生成された暗黙的なチェックに関する情報は、特別な「フォルトマップ」セクションに格納されます。Darwinでは、このセクションの名前は__llvm_faultmaps
です。
このセクションの形式は次のとおりです。
Header {
uint8 : Fault Map Version (current version is 1)
uint8 : Reserved (expected to be 0)
uint16 : Reserved (expected to be 0)
}
uint32 : NumFunctions
FunctionInfo[NumFunctions] {
uint64 : FunctionAddress
uint32 : NumFaultingPCs
uint32 : Reserved (expected to be 0)
FunctionFaultInfo[NumFaultingPCs] {
uint32 : FaultKind
uint32 : FaultingPCOffset
uint32 : HandlerPCOffset
}
}
FailtKindは、予期されるフォルトの理由を記述します。現在、3種類のフォルトがサポートされています。
FaultMaps::FaultingLoad
- メモリからのロードによるフォルト。
FaultMaps::FaultingLoadStore
- 命令のロードとストアによるフォルト。
FaultMaps::FaultingStore
- メモリへのストアによるフォルト。
ImplicitNullChecks
パス¶
ImplicitNullChecks
パスは、ポインタがnull
かどうかをチェックするための明示的な制御フロー(例:以下)を、
%ptr = call i32* @get_ptr()
%ptr_is_null = icmp i32* %ptr, null
br i1 %ptr_is_null, label %is_null, label %not_null, !make.implicit !0
not_null:
%t = load i32, i32* %ptr
br label %do_something_with_t
is_null:
call void @HFC()
unreachable
!0 = !{}
ヌルチェックされているポインタを介したロードまたはストア命令に暗黙的に制御フローを変換します。
%ptr = call i32* @get_ptr()
%t = load i32, i32* %ptr ;; handler-pc = label %is_null
br label %do_something_with_t
is_null:
call void @HFC()
unreachable
この変換は、LLVM IRレベルではなくMachineInstr
レベルで行われます(したがって、上記の例は代表的なものであり、文字通りのものではありません)。ImplicitNullChecks
パスは、-enable-implicit-null-checks
がllc
に渡された場合、コード生成中に実行されます。
ImplicitNullChecks
パスは、必要に応じて上記で説明した__llvm_faultmaps
セクションにエントリを追加します。
make.implicit
メタデータ¶
ヌルチェックを暗黙的にすることは積極的な最適化であり、多くのメモリ操作がそのためにフォルトを起こしてしまうと、パフォーマンスの低下につながる可能性があります。言語ランタイムは、アプリケーションが定常状態に達した後、無視できる数の暗黙的なヌルチェックだけが実際にフォルトを起こすようにする必要があります。これを行う標準的な方法は、コードパッチまたは再コンパイルを介して、失敗した暗黙的なヌルチェックを明示的なヌルチェックに修復することです。したがって、明示的なヌルチェックを暗黙的なヌルチェックに変換することが有利であるためには、2つの要件を満たす必要があります。
ポインタが実際にnullである場合(つまり、「失敗」の場合)は非常にまれです。
失敗したパスは、アプリケーションが繰り返しページフォルトを起こさないように、暗黙的なヌルチェックを明示的なヌルチェックに修復します。
フロントエンドは、(1)と(2)を満たすブランチを!make.implicit
メタデータノードを使用してマークする必要があります(メタデータノードの実際のコンテンツは無視されます)。!make.implicit
メタデータでマークされたブランチのみが、暗黙的なヌルチェックへの変換の候補とみなされます。
(プロファイリングデータを使用して(1)に対処することはできますが、(2)に対処するには、ブランチプロファイルには存在しない情報が必要です。)