サポートライブラリ¶
概要¶
このドキュメントでは、ソースコードの lib/Support
と include/llvm/Support
にあるLLVMのサポートライブラリの詳細について説明します。このライブラリの目的は、LLVMが必要とする少数のオペレーティングシステムサービスにおいて、オペレーティングシステム間の違いからLLVMを保護することです。LLVMの大部分は、標準C++の移植性機能を使用して記述されています。しかし、いくつかの領域では、システム依存の機能が必要であり、サポートライブラリはこれらのシステムコールをラップする役割を果たします。
LLVMのオペレーティングシステムインターフェースの使用を一元化することにより、LLVMツールチェーンとランタイムライブラリを新しいプラットフォームに簡単に移植することができます。なぜなら(理論的には) lib/Support
だけを移植すればよいからです。このライブラリはまた、LLVMの残りの部分を #ifdef の使用や特定のオペレーティングシステムの特殊なケースから解放します。このような使用は、 include/llvm/Support
で提供されるインターフェースへの単純な呼び出しに置き換えられます。
サポートライブラリは、完全なオペレーティングシステムラッパー(Adaptive Communications Environment(ACE)やApache Portable Runtime(APR)など)ではなく、LLVMをサポートするために必要な機能のみを提供することに注意してください。
サポートライブラリは、元々はシステムライブラリと呼ばれており、eXtensible Programming System (XPS) から生まれた同様の作業に基づいて設計を考案した Reid Spencer によって書かれました。何人かの人々がこの取り組みに協力しました。特に、Win32ポートではJeff CohenとHenrik Bachが貢献しました。
LLVMの移植性を維持する¶
LLVMの移植性を維持するために、LLVM開発者はサポートライブラリに関連する一連の移植性ルールを遵守する必要があります。これらのルールを遵守することで、サポートライブラリがLLVMをオペレーティングシステムインターフェースの違いから効率的に保護するという目標を達成するのに役立ちます。以下のセクションでは、この目的を達成するために必要なルールを定義します。
システムヘッダーをインクルードしない¶
lib/Support
を除いて、LLVMソースコードはシステムヘッダーを直接 #include
するべきではありません。lib/Support
の開発中に、LLVMからそのような #includes
をすべて削除するように注意が払われています。具体的には、「unistd.h
」、「windows.h
」、「stdio.h
」、および「string.h
」のようなヘッダーファイルは、lib/Support
の実装以外でLLVMソースコードにインクルードすることが禁止されています。
システム依存の機能を取得するには、 include/llvm/Support
にある既存のシステムへのインターフェースを使用する必要があります。適切なインターフェースがない場合は、 include/llvm/Support
に追加し、サポートされているすべてのプラットフォームで lib/Support
に実装する必要があります。
システムヘッダーを公開しない¶
サポートライブラリは、LLVMを**すべて**のシステムヘッダーから保護する必要があります。システムレベルの機能を取得するために、LLVMソースは #include "llvm/Support/Thing.h"
をインクルードするだけで、それ以外は何も必要ありません。これは、 Thing.h
がシステムヘッダーファイルを公開できないことを意味します。これは、LLVMが誤ってシステム固有の機能を使用することを防ぎ、 lib/Support
インターフェースを介してのみ使用できるようにします。
標準Cヘッダーを使用する¶
**標準** Cヘッダー(「c」で始まるもの)は、 lib/Support
インターフェースを介して公開することができます。これらのヘッダーとそれらが宣言するものは、プラットフォームに依存しないと見なされます。LLVMソースファイルは、それらを直接インクルードするか、 lib/Support
インターフェースを介してインクルードすることができます。
標準C++ヘッダーを使用する¶
標準C++ライブラリと標準テンプレートライブラリの**標準** C++ヘッダーは、 lib/Support
インターフェースを介して公開することができます。これらのヘッダーとそれらが宣言するものは、プラットフォームに依存しないと見なされます。LLVMソースファイルは、それらをインクルードするか、 lib/Support
インターフェースを介してインクルードすることができます。
高レベルインターフェース¶
lib/Support
のインターフェースで指定されたエントリポイントは、LLVMが必要とする reasonably 高レベルのタスクを完了することを目的とする必要があります。私たちは、単に各オペレーティングシステムコールをラップするだけではありません。LLVMによって常に一緒に使用される複数のオペレーティングシステムコールをラップすることが望ましいです。
たとえば、プログラムを実行し、それが完了するのを待ち、その結果コードを返すために必要なものを考えてみましょう。Unixでは、これには getenv
、 fork
、 execve
、 wait
などのオペレーティングシステムコールが必要です。lib/Support
が提供する正しいものは、たとえば ExecuteProgramAndWait
などの関数であり、この機能を完全に実装します。私たちが望まないのは、関係するオペレーティングシステムコールのラッパーです。
オペレーティングシステムコールとサポートライブラリのインターフェースの間に1対1の関係があっては**なりません**。そのようなインターフェース関数は疑わしいとされます。
未使用の機能がない¶
lib/Support
のインターフェースで指定されているが、LLVMで実際に使用されていない機能があってはなりません。ここでは汎用オペレーティングシステムラッパーを作成しているのではなく、LLVMのニーズを満たすのに十分なだけを作成しています。そして、LLVMはそれほど多くを必要としません。この設計目標は、 lib/Support
インターフェースを小さく理解しやすいものにし、その実際の使用と採用を促進することを目的としています。
重複した実装がない¶
特定のプラットフォームの関数の実装は、厳密に1回だけ記述する必要があります。これは、複数のオペレーティングシステムが同じ実装を共有できる場合、関数の実装を複数のオペレーティングシステムに適用できる必要があることを意味します。このルールは、特定のクラスのオペレーティングシステム(Unix、Win32など)でサポートされているオペレーティングシステムのセットに適用されます。
仮想メソッドがない¶
サポートライブラリインターフェースは、LLVMによって非常に頻繁に呼び出される可能性があります。これらの呼び出しをできるだけ効率的にするために、仮想メソッドの使用はお勧めしません。実装の違いに継承を使用する必要はありません。複雑さが増すだけです。#include
メカニズムで十分です。
公開された関数がない¶
システムライブラリによって定義された関数(つまり、 lib/Support
によって定義されていない関数)は、その関数のヘッダーファイルが公開されていない場合でも、 lib/Support
インターフェースを介して公開してはなりません。これは、システム固有の機能の不注意な使用を防ぎます。
たとえば、 stat
システムコールは、提供するデータにバリエーションがあることで有名です。lib/Support
は stat
を宣言してはならず、宣言することも許可しません。代わりに、ファイルとディレクトリに関する情報を検出するための独自のインターフェースを提供する必要があります。これらのインターフェースは stat
を使用して実装できますが、それは厳密に実装の詳細です。サポートライブラリによって提供されるインターフェースは、すべてのプラットフォーム( stat
がないプラットフォームも含む)で実装する必要があります。
公開されたデータがない¶
システムライブラリによって定義されたデータ(つまり、 lib/Support
によって定義されていないデータ)は、その関数のヘッダーファイルが公開されていない場合でも、 lib/Support
インターフェースを介して公開してはなりません。関数と同様に、これはすべてのプラットフォームに存在しない可能性のあるデータの不注意な使用を防ぎます。
ソフトエラーを最小限に抑える¶
オペレーティングシステムのインターフェースは、一般的に、発生しうるあらゆる小さな事象に対してエラー結果を提供します。ほとんどの場合、これらのエラー結果は、正常/良好/ソフトエラーと異常/不良/ハードエラーの2つのグループに分類できます。「ファイルが見つかりません」、「権限が不足しています」などのエラーは単なる情報提供ですが、「容量不足」、「ディスクセクタ不良」、「システムコールが中断されました」などのエラーはより深刻です。前者のグループを「ソフト」エラー、後者のグループを「ハード」エラーと呼びます。
lib/Support
は、常にソフトエラーを最小限に抑えるよう努めなければなりません。これは、ソフトエラーの最小化がインターフェースの粒度と性質に影響を与える可能性があるため、設計要件です。一般的に、ソフトエラーを throw したい場合は、インターフェースの粒度を見直す必要があります。それは、おそらく低レベルすぎるものを実装しようとしているためです。経験則として、ハードエラーに直面した場合を除き、失敗「しない」インターフェース関数を提供することです。
簡単な例として、「OpenFileForWriting
」関数を追加したいとします。多くのオペレーティングシステムでは、ファイルが存在しない場合、ファイルを開こうとするとエラーが発生します。しかし、lib/Support
は、ソフトエラーであるため、エラーが発生しても単に throw するべきではありません。問題は、インターフェース関数 OpenFileForWriting
が低レベルすぎることです。これは OpenOrCreateFileForWriting
であるべきです。ソフトエラー「ファイルが存在しない」の場合、この関数はファイルを作成してから書き込み用に開きます。
この設計原則は、LLVM の他の部分全体にソフトエラー処理が伝播するのを回避するため、lib/Support
で維持する必要があります。ハードエラーは、一般的に LLVM ツールの終了を引き起こすため、throw することをためらわないでください。
経験則
ソフトエラーではなく、ハードエラーのみを throw する。
ソフトエラーを throw したくなった場合は、インターフェースを再考する。
LLVM の他の部分が処理する必要がないように、最も一般的な正常/良好/ソフトエラー状態を内部的に処理する。
throw 指定子¶
lib/Support
インターフェース関数は、C++ の throw()
指定子を使用して宣言することはできません。この要件により、コンパイラがインターフェース関数に追加の例外処理コードを挿入しないことが保証されます。これはパフォーマンスに関する考慮事項です:lib/Support
関数は多くの呼び出しチェーンの最下部にあり、そのため頻繁に呼び出される可能性があります。可能な限り効率的にする必要があります。ただし、システムライブラリのルーチンは実際に例外を throw するべきではありません。
コード構成¶
Support Library インターフェースの実装は、オペレーティングシステムの一般的なクラスによって分離されています。現在、Unix と Win32 クラスのみが定義されていますが、他のオペレーティングシステムの分類のために追加される可能性があります。どの実装をコンパイルするかを区別するために、lib/Support
のコードは LLVM_ON_UNIX
と _WIN32
#defines
を使用します。lib/Support
の各ソースファイルは、汎用(オペレーティングシステムに依存しない)機能を実装した後、#if defined(LLVM_ON_XYZ)
ディレクティブのセットを使用して正しい実装を含める必要があります。たとえば、lib/Support/Path.cpp
がある場合、そのファイルには次のような記述があるはずです。
#if defined(LLVM_ON_UNIX)
#include "Unix/Path.inc"
#endif
#if defined(_WIN32)
#include "Windows/Path.inc"
#endif
lib/Support/Unix/Path.inc
の実装は、すべての Unix バリアントを処理する必要があります。lib/Support/Windows/Path.inc
の実装は、すべての Windows バリアントを処理する必要があります。これは、実装を提供するオペレーティングシステムの基本クラスを迅速にインクルードします。特定のプラットフォームの詳細は、#ifdef
を使用して決定する必要があります。
一貫したセマンティクス¶
lib/Support
インターフェースの実装は、プラットフォームによって大きく異なる場合があります。インターフェース関数の最終結果が同じであれば、問題ありません。たとえば、ディレクトリを作成する関数は、すべてのオペレーティングシステムで非常に簡単です。一方、System V IPC はすべてのプラットフォームでサポートされているわけではありません。System V IPC を「サポート」する代わりに、lib/Support
はプロセス間通信の基本概念へのインターフェースを提供する必要があります。実装では、System V IPC が使用可能な場合は System V IPC を使用するか、名前付きパイプを使用するか、または特定のオペレーティングシステムで効果的にジョブを実行できるものを使用する可能性があります。いずれの場合も、インターフェースと実装は意味的に一貫している必要があります。